This commit is contained in:
bytedream 2025-05-13 13:49:27 +03:30 committed by GitHub
commit 1b0316c2cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 281 additions and 112 deletions

View File

@ -1331,7 +1331,9 @@ editor.upload_file = Upload File
editor.edit_file = Edit File
editor.preview_changes = Preview Changes
editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface.
editor.cannot_edit_too_large_file = The file is too large to be edited.
editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface.
editor.file_not_editable_hint = But you can still rename or move it.
editor.edit_this_file = Edit File
editor.this_file_locked = File is locked
editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file.

View File

@ -145,10 +145,6 @@ func editFile(ctx *context.Context, isNewFile bool) {
}
blob := entry.Blob()
if blob.Size() >= setting.UI.MaxDisplayFileSize {
ctx.NotFound(err)
return
}
buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
if err != nil {
@ -162,22 +158,37 @@ func editFile(ctx *context.Context, isNewFile bool) {
defer dataRc.Close()
ctx.Data["FileSize"] = blob.Size()
// Only some file types are editable online as text.
if !fInfo.st.IsRepresentableAsText() || fInfo.isLFSFile {
ctx.NotFound(nil)
return
if fInfo.isLFSFile {
lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
if err != nil {
ctx.ServerError("GetTreePathLock", err)
return
}
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
ctx.NotFound(nil)
return
}
}
d, _ := io.ReadAll(dataRc)
ctx.Data["FileSize"] = fInfo.fileSize
buf = append(buf, d...)
if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
log.Error("ToUTF8: %v", err)
ctx.Data["FileContent"] = string(buf)
// Only some file types are editable online as text.
if fInfo.isLFSFile {
ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
} else if !fInfo.st.IsRepresentableAsText() {
ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
} else if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_too_large_file")
} else {
ctx.Data["FileContent"] = content
d, _ := io.ReadAll(dataRc)
buf = append(buf, d...)
if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil {
log.Error("ToUTF8: %v", err)
ctx.Data["FileContent"] = string(buf)
} else {
ctx.Data["FileContent"] = content
}
}
} else {
// Append filename from query, or empty string to allow username the new file.
@ -280,6 +291,10 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
operation := "update"
if isNewFile {
operation = "create"
} else if !form.Content.Has() && ctx.Repo.TreePath != form.TreePath {
// The form content only has data if file is representable as text, is not too large and not in lfs. If it doesn't
// have data, the only possible operation is a rename
operation = "rename"
}
if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{
@ -292,7 +307,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
Operation: operation,
FromTreePath: ctx.Repo.TreePath,
TreePath: form.TreePath,
ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")),
ContentReader: strings.NewReader(strings.ReplaceAll(form.Content.Value(), "\r", "")),
},
},
Signoff: form.Signoff,

View File

@ -99,7 +99,7 @@ func NewDiffPatchPost(ctx *context.Context) {
OldBranch: ctx.Repo.BranchName,
NewBranch: branchName,
Message: message,
Content: strings.ReplaceAll(form.Content, "\r", ""),
Content: strings.ReplaceAll(form.Content.Value(), "\r", ""),
Author: gitCommitter,
Committer: gitCommitter,
})

View File

@ -140,13 +140,6 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
}
// Assume file is not editable first.
if fInfo.isLFSFile {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
} else if !isRepresentableAsText {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
}
// read all needed attributes which will be used later
// there should be no performance different between reading 2 or 4 here
attrsMap, err := attribute.CheckAttributes(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, attribute.CheckAttributeOpts{
@ -243,21 +236,6 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["FileContent"] = fileContent
ctx.Data["LineEscapeStatus"] = statuses
}
if !fInfo.isLFSFile {
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
ctx.Data["CanEditFile"] = false
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
} else {
ctx.Data["CanEditFile"] = true
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
}
} else if !ctx.Repo.RefFullName.IsBranch() {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
}
}
case fInfo.st.IsPDF():
ctx.Data["IsPDFFile"] = true
@ -309,15 +287,21 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) {
if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
ctx.Data["CanEditFile"] = false
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
ctx.Data["CanDeleteFile"] = false
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
} else {
ctx.Data["CanEditFile"] = true
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
ctx.Data["CanDeleteFile"] = true
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
}
} else if !ctx.Repo.RefFullName.IsBranch() {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
}
}

View File

@ -10,6 +10,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
project_model "code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/context"
@ -689,7 +690,7 @@ func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.E
// EditRepoFileForm form for changing repository file
type EditRepoFileForm struct {
TreePath string `binding:"Required;MaxSize(500)"`
Content string
Content optional.Option[string]
CommitSummary string `binding:"MaxSize(100)"`
CommitMessage string
CommitChoice string `binding:"Required;MaxSize(50)"`

View File

@ -246,7 +246,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
contentStore := lfs.NewContentStore()
for _, file := range opts.Files {
switch file.Operation {
case "create", "update":
case "create", "update", "rename":
if err := CreateOrUpdateFile(ctx, t, file, contentStore, repo.ID, hasOldBranch); err != nil {
return nil, err
}
@ -488,31 +488,32 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
}
}
treeObjectContentReader := file.ContentReader
var lfsMetaObject *git_model.LFSMetaObject
if setting.LFS.StartServer && hasOldBranch {
// Check there is no way this can return multiple infos
attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{
Attributes: []string{attribute.Filter},
Filenames: []string{file.Options.treePath},
})
var oldEntry *git.TreeEntry
// Assume that the file.ContentReader of a pure rename operation is invalid. Use the file content how it's present in
// git instead
if file.Operation == "rename" {
lastCommitID, err := t.GetLastCommit(ctx)
if err != nil {
return err
}
commit, err := t.GetCommit(lastCommitID)
if err != nil {
return err
}
if attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs" {
// OK so we are supposed to LFS this data!
pointer, err := lfs.GeneratePointer(treeObjectContentReader)
if err != nil {
return err
}
lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: repoID}
treeObjectContentReader = strings.NewReader(pointer.StringContent())
if oldEntry, err = commit.GetTreeEntryByPath(file.Options.fromTreePath); err != nil {
return err
}
}
// Add the object to the database
objectHash, err := t.HashObject(ctx, treeObjectContentReader)
var objectHash string
var lfsPointer *lfs.Pointer
switch file.Operation {
case "create", "update":
objectHash, lfsPointer, err = createOrUpdateFileHash(ctx, t, file, hasOldBranch)
case "rename":
objectHash, lfsPointer, err = renameFileHash(ctx, t, oldEntry, file)
}
if err != nil {
return err
}
@ -528,9 +529,9 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
}
}
if lfsMetaObject != nil {
if lfsPointer != nil {
// We have an LFS object - create it
lfsMetaObject, err = git_model.NewLFSMetaObject(ctx, lfsMetaObject.RepositoryID, lfsMetaObject.Pointer)
lfsMetaObject, err := git_model.NewLFSMetaObject(ctx, repoID, *lfsPointer)
if err != nil {
return err
}
@ -539,11 +540,20 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
return err
}
if !exist {
_, err := file.ContentReader.Seek(0, io.SeekStart)
if err != nil {
return err
var lfsContentReader io.Reader
if file.Operation != "rename" {
if _, err := file.ContentReader.Seek(0, io.SeekStart); err != nil {
return err
}
lfsContentReader = file.ContentReader
} else {
if lfsContentReader, err = oldEntry.Blob().DataAsync(); err != nil {
return err
}
defer lfsContentReader.(io.ReadCloser).Close()
}
if err := contentStore.Put(lfsMetaObject.Pointer, file.ContentReader); err != nil {
if err := contentStore.Put(lfsMetaObject.Pointer, lfsContentReader); err != nil {
if _, err2 := git_model.RemoveLFSMetaObjectByOid(ctx, repoID, lfsMetaObject.Oid); err2 != nil {
return fmt.Errorf("unable to remove failed inserted LFS object %s: %v (Prev Error: %w)", lfsMetaObject.Oid, err2, err)
}
@ -555,6 +565,99 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file
return nil
}
func createOrUpdateFileHash(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile, hasOldBranch bool) (string, *lfs.Pointer, error) {
treeObjectContentReader := file.ContentReader
var lfsPointer *lfs.Pointer
if setting.LFS.StartServer && hasOldBranch {
// Check there is no way this can return multiple infos
attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{
Attributes: []string{attribute.Filter},
Filenames: []string{file.Options.treePath},
})
if err != nil {
return "", nil, err
}
if attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs" {
// OK so we are supposed to LFS this data!
pointer, err := lfs.GeneratePointer(treeObjectContentReader)
if err != nil {
return "", nil, err
}
lfsPointer = &pointer
treeObjectContentReader = strings.NewReader(pointer.StringContent())
}
}
// Add the object to the database
objectHash, err := t.HashObject(ctx, treeObjectContentReader)
if err != nil {
return "", nil, err
}
return objectHash, lfsPointer, nil
}
func renameFileHash(ctx context.Context, t *TemporaryUploadRepository, oldEntry *git.TreeEntry, file *ChangeRepoFile) (string, *lfs.Pointer, error) {
if setting.LFS.StartServer {
attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{
Attributes: []string{attribute.Filter},
Filenames: []string{file.Options.treePath, file.Options.fromTreePath},
})
if err != nil {
return "", nil, err
}
oldIsLfs := attributesMap[file.Options.fromTreePath] != nil && attributesMap[file.Options.fromTreePath].Get(attribute.Filter).ToString().Value() == "lfs"
newIsLfs := attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs"
// If the old and new paths are both in lfs or both not in lfs, the object hash of the old file can be used directly
// as the object doesn't change
if oldIsLfs == newIsLfs {
return oldEntry.ID.String(), nil, nil
}
oldEntryReader, err := oldEntry.Blob().DataAsync()
if err != nil {
return "", nil, err
}
defer oldEntryReader.Close()
var treeObjectContentReader io.Reader
var lfsPointer *lfs.Pointer
// If the old path is in lfs but the new isn't, read the content from lfs and add it as normal git object
// If the new path is in lfs but the old isn't, read the content from the git object and generate a lfs
// pointer of it
if oldIsLfs {
pointer, err := lfs.ReadPointer(oldEntryReader)
if err != nil {
return "", nil, err
}
treeObjectContentReader, err = lfs.ReadMetaObject(pointer)
if err != nil {
return "", nil, err
}
defer treeObjectContentReader.(io.ReadCloser).Close()
} else {
pointer, err := lfs.GeneratePointer(oldEntryReader)
if err != nil {
return "", nil, err
}
treeObjectContentReader = strings.NewReader(pointer.StringContent())
lfsPointer = &pointer
}
// Add the object to the database
objectID, err := t.HashObject(ctx, treeObjectContentReader)
if err != nil {
return "", nil, err
}
return objectID, lfsPointer, nil
}
return oldEntry.ID.String(), nil, nil
}
// VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch
func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName string, treePaths []string) error {
protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName)

View File

@ -0,0 +1,54 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package files
import (
"testing"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert"
)
func TestUpdateRename(t *testing.T) {
unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
branch := repo.DefaultBranch
temp, _ := NewTemporaryUploadRepository(repo)
_ = temp.Clone(ctx, branch, true)
_ = temp.SetDefaultIndex(ctx)
filesBeforeRename, _ := temp.LsFiles(ctx, "README.txt", "README.md")
assert.Equal(t, []string{"README.md", ""}, filesBeforeRename)
file := &ChangeRepoFile{
Operation: "rename",
FromTreePath: "README.md",
TreePath: "README.txt",
ContentReader: nil,
SHA: "",
Options: &RepoFileOptions{
fromTreePath: "README.md",
treePath: "README.txt",
executable: false,
},
}
contentStore := lfs.NewContentStore()
err := CreateOrUpdateFile(ctx, temp, file, contentStore, 1, true)
assert.NoError(t, err)
filesAfterRename, _ := temp.LsFiles(ctx, "README.txt", "README.md")
assert.Equal(t, []string{"README.txt", ""}, filesAfterRename)
}

View File

@ -148,7 +148,7 @@
<a class="item" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
{{else}}
<a class="item" rel="nofollow" href="{{$.SourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
{{if and $.Repository.CanEnableEditor $.CanEditFile (not $file.IsLFSFile) (not $file.IsBin)}}
{{if and $.Repository.CanEnableEditor $.CanEditFile}}
<a class="item" rel="nofollow" href="{{$.HeadRepoLink}}/_edit/{{PathEscapeSegments $.HeadBranchName}}/{{PathEscapeSegments $file.Name}}?return_uri={{print $.BackToLink "#diff-" $file.NameHash | QueryEscape}}">{{ctx.Locale.Tr "repo.editor.edit_this_file"}}</a>
{{end}}
{{end}}

View File

@ -77,7 +77,7 @@
</div>
{{end}}
</div>
<button id="commit-button" type="submit" class="ui primary button">
<button id="commit-button" type="submit" class="ui primary button" {{if .PageIsEdit}}disabled{{end}}>
{{if eq .commit_choice "commit-to-new-branch"}}{{ctx.Locale.Tr "repo.editor.propose_file_change"}}{{else}}{{ctx.Locale.Tr "repo.editor.commit_changes"}}{{end}}
</button>
<a class="ui button red" href="{{if .ReturnURI}}{{.ReturnURI}}{{else}}{{$.BranchLink}}/{{PathEscapeSegments .TreePath}}{{end}}">{{ctx.Locale.Tr "repo.editor.cancel"}}</a>

View File

@ -28,31 +28,40 @@
<input type="hidden" id="tree_path" name="tree_path" value="{{.TreePath}}" required>
</div>
</div>
<div class="field">
<div class="ui top attached header">
<div class="ui compact small menu small-menu-items repo-editor-menu">
<a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
<a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
{{if not .IsNewFile}}
<a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
{{end}}
{{if not .NotEditableReason}}
<div class="field">
<div class="ui top attached header">
<div class="ui compact small menu small-menu-items repo-editor-menu">
<a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
<a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
{{if not .IsNewFile}}
<a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
{{end}}
</div>
</div>
<div class="ui bottom attached segment tw-p-0">
<div class="ui active tab tw-rounded-b" data-tab="write">
<textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
data-previewable-extensions="{{.PreviewableExtensions}}"
data-line-wrap-extensions="{{.LineWrapExtensions}}">{{.FileContent}}</textarea>
<div class="editor-loading is-loading"></div>
</div>
<div class="ui tab tw-px-4 tw-py-3" data-tab="preview">
{{ctx.Locale.Tr "loading"}}
</div>
<div class="ui tab" data-tab="diff">
<div class="tw-p-16"></div>
</div>
</div>
</div>
<div class="ui bottom attached segment tw-p-0">
<div class="ui active tab tw-rounded-b" data-tab="write">
<textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
data-previewable-extensions="{{.PreviewableExtensions}}"
data-line-wrap-extensions="{{.LineWrapExtensions}}">{{.FileContent}}</textarea>
<div class="editor-loading is-loading"></div>
</div>
<div class="ui tab tw-px-4 tw-py-3" data-tab="preview">
{{ctx.Locale.Tr "loading"}}
</div>
<div class="ui tab" data-tab="diff">
<div class="tw-p-16"></div>
{{else}}
<div class="field">
<div class="ui segment tw-text-center">
<h4 class="tw-font-semibold tw-mb-2">{{.NotEditableReason}}</h4>
<p>{{ctx.Locale.Tr "repo.editor.file_not_editable_hint"}}</p>
</div>
</div>
</div>
{{end}}
{{template "repo/editor/commit_form" .}}
</form>
</div>

View File

@ -141,38 +141,39 @@ export function initRepoEditor() {
}
});
const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form');
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
// to enable or disable the commit button
const commitButton = document.querySelector<HTMLButtonElement>('#commit-button');
const dirtyFileClass = 'dirty-file';
// Enabling the button at the start if the page has posted
if (document.querySelector<HTMLInputElement>('input[name="page_has_posted"]')?.value === 'true') {
commitButton.disabled = false;
}
// Registering a custom listener for the file path and the file content
// FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
applyAreYouSure(elForm, {
silent: true,
dirtyClass: dirtyFileClass,
fieldSelector: ':input:not(.commit-form-wrapper :input)',
change($form: any) {
const dirty = $form[0]?.classList.contains(dirtyFileClass);
commitButton.disabled = !dirty;
},
});
// on the upload page, there is no editor(textarea)
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
if (!editArea) return;
const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form');
initEditPreviewTab(elForm);
(async () => {
const editor = await createCodeEditor(editArea, filenameInput);
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
// to enable or disable the commit button
const commitButton = document.querySelector<HTMLButtonElement>('#commit-button');
const dirtyFileClass = 'dirty-file';
// Disabling the button at the start
if (document.querySelector<HTMLInputElement>('input[name="page_has_posted"]').value !== 'true') {
commitButton.disabled = true;
}
// Registering a custom listener for the file path and the file content
// FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added
applyAreYouSure(elForm, {
silent: true,
dirtyClass: dirtyFileClass,
fieldSelector: ':input:not(.commit-form-wrapper :input)',
change($form: any) {
const dirty = $form[0]?.classList.contains(dirtyFileClass);
commitButton.disabled = !dirty;
},
});
// Update the editor from query params, if available,
// only after the dirtyFileClass initialization
const params = new URLSearchParams(window.location.search);