From b3fb0c327961af3e3cb80e82465ee9f24fc9d4a6 Mon Sep 17 00:00:00 2001 From: brymut Date: Mon, 10 Nov 2025 11:45:56 +0300 Subject: [PATCH 01/30] feat(repo): Add file search functionality to repository file tree --- templates/repo/view_content.tmpl | 39 ++++--- templates/repo/view_file_tree.tmpl | 5 + web_src/js/components/ViewFileTree.vue | 150 ++++++++++++++++++++++++- 3 files changed, 171 insertions(+), 23 deletions(-) diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index 66e4fffcb9b19..0d9e97edd70a0 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -42,26 +42,6 @@ {{ctx.Locale.Tr "repo.find_file.go_to_file"}} {{end}} - {{if and .RefFullName.IsBranch (not .IsViewFile)}} - - {{end}} - {{if and $isTreePathRoot .Repository.IsTemplate}} {{ctx.Locale.Tr "repo.use_template"}} @@ -86,6 +66,25 @@
+ {{if and .RefFullName.IsBranch (not .IsViewFile)}} + + {{end}} {{if $isTreePathRoot}} {{template "repo/clone_panel" .}} diff --git a/templates/repo/view_file_tree.tmpl b/templates/repo/view_file_tree.tmpl index 8aed05f346940..844b396bd46f6 100644 --- a/templates/repo/view_file_tree.tmpl +++ b/templates/repo/view_file_tree.tmpl @@ -7,9 +7,14 @@ {{ctx.Locale.Tr "files"}}
+
+ +
+ {{/* TODO: Dynamically move components such as refSelector and createPR here */}}
diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index 1f90f9258670c..0526e360707a5 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -1,9 +1,15 @@ @@ -35,4 +128,55 @@ onMounted(async () => { gap: 1px; margin-right: .5rem; } + +.file-tree-search-results { + display: flex; + flex-direction: column; + margin: 0 0.5rem 0.5rem; + max-height: 400px; + overflow-y: auto; + background: var(--color-box-body); + border: 1px solid var(--color-secondary); + border-radius: 6px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); +} + +.file-tree-search-result-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + cursor: pointer; + transition: background-color 0.1s; + border-bottom: 1px solid var(--color-secondary); +} + +.file-tree-search-result-item:last-child { + border-bottom: none; +} + +.file-tree-search-result-item:hover, +.file-tree-search-result-item.selected { + background-color: var(--color-hover); +} + +.file-tree-search-result-path { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 14px; +} + +.search-match { + color: var(--color-red); + font-weight: var(--font-weight-semibold); +} + +.file-tree-search-no-results { + padding: 1rem; + text-align: center; + color: var(--color-text-light-2); + font-size: 14px; +} From c7120de1957ec0f26a7a5ab90b34f2c3a9525f3a Mon Sep 17 00:00:00 2001 From: brymut Date: Mon, 10 Nov 2025 18:17:11 +0300 Subject: [PATCH 02/30] feat(repo): add tree view context menu actions --- options/locale/locale_en-US.ini | 2 ++ templates/repo/view_content.tmpl | 24 ++++++++++++++++++++ web_src/css/index.css | 1 + web_src/css/repo/file-actions.css | 19 ++++++++++++++++ web_src/js/features/repo-file-actions.ts | 28 ++++++++++++++++++++++++ web_src/js/index-domready.ts | 2 ++ 6 files changed, 76 insertions(+) create mode 100644 web_src/css/repo/file-actions.css create mode 100644 web_src/js/features/repo-file-actions.ts diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b5b90b31a53cb..78391a016b9f3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1317,6 +1317,7 @@ ambiguous_character = `%[1]c [U+%04[1]X] can be confused with %[2]c [U+%04[2]X]` escape_control_characters = Escape unescape_control_characters = Unescape file_copy_permalink = Copy Permalink +center_content = Center content view_git_blame = View Git Blame video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag. audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag. @@ -1354,6 +1355,7 @@ 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. editor.fork_before_edit = You must fork this repository to make or propose changes to this file. editor.delete_this_file = Delete File +editor.delete_this_directory = Delete Directory editor.must_have_write_access = You must have write access to make or propose changes to this file. editor.file_delete_success = File "%s" has been deleted. editor.name_your_file = Name your file… diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index 0d9e97edd70a0..0716746a2401e 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -84,6 +84,30 @@ + {{end}} {{if $isTreePathRoot}} diff --git a/web_src/css/index.css b/web_src/css/index.css index 291cd04b2b95c..796497c6305b8 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -63,6 +63,7 @@ @import "./repo/issue-list.css"; @import "./repo/list-header.css"; @import "./repo/file-view.css"; +@import "./repo/file-actions.css"; @import "./repo/wiki.css"; @import "./repo/header.css"; @import "./repo/home.css"; diff --git a/web_src/css/repo/file-actions.css b/web_src/css/repo/file-actions.css new file mode 100644 index 0000000000000..c1a508aa9b851 --- /dev/null +++ b/web_src/css/repo/file-actions.css @@ -0,0 +1,19 @@ +/* Repository file actions dropdown and centered content */ +.repo-file-actions-dropdown .menu { + min-width: 200px; +} + +.repo-file-actions-dropdown .menu .item { + cursor: pointer; +} + +.repo-file-actions-dropdown .menu .divider { + margin: 0.5rem 0; +} + +/* Center content option */ +.repo-content-centered { + max-width: 980px; + margin-left: auto !important; + margin-right: auto !important; +} diff --git a/web_src/js/features/repo-file-actions.ts b/web_src/js/features/repo-file-actions.ts new file mode 100644 index 0000000000000..bd167e135ca84 --- /dev/null +++ b/web_src/js/features/repo-file-actions.ts @@ -0,0 +1,28 @@ +// Handle repository file/directory actions dropdown +export function initRepoFileActions() { + const centerContentCheckbox = document.querySelector('#center-content-checkbox'); + if (!centerContentCheckbox) return; + + // Load saved preference + const isCentered = localStorage.getItem('repo-content-centered') === 'true'; + centerContentCheckbox.checked = isCentered; + applyCenterContent(isCentered); + + // Handle checkbox change + centerContentCheckbox.addEventListener('change', () => { + const centered = centerContentCheckbox.checked; + localStorage.setItem('repo-content-centered', String(centered)); + applyCenterContent(centered); + }); +} + +function applyCenterContent(centered: boolean) { + const container = document.querySelector('.ui.container'); + if (!container) return; + + if (centered) { + container.classList.add('repo-content-centered'); + } else { + container.classList.remove('repo-content-centered'); + } +} diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index df56c85c868c6..49d7814b9656c 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -64,6 +64,7 @@ import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton} import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts'; import {callInitFunctions} from './modules/init.ts'; import {initRepoViewFileTree} from './features/repo-view-file-tree.ts'; +import {initRepoFileActions} from './features/repo-file-actions.ts'; const initStartTime = performance.now(); const initPerformanceTracer = callInitFunctions([ @@ -137,6 +138,7 @@ const initPerformanceTracer = callInitFunctions([ initRepoReleaseNew, initRepoTopicBar, initRepoViewFileTree, + initRepoFileActions, initRepoWikiForm, initRepository, initRepositoryActionView, From 34e73a1d2cea35ad6e0885f684d34b7508ceb2a8 Mon Sep 17 00:00:00 2001 From: brymut Date: Tue, 11 Nov 2025 05:34:19 +0300 Subject: [PATCH 03/30] enhance(repo): fix overflow and scrolling on search results in sidebar search --- templates/repo/view_file_tree.tmpl | 2 +- web_src/css/repo/file-actions.css | 7 +- web_src/css/repo/home.css | 2 + web_src/js/components/ViewFileTree.vue | 118 +++++++++++++++++++------ 4 files changed, 99 insertions(+), 30 deletions(-) diff --git a/templates/repo/view_file_tree.tmpl b/templates/repo/view_file_tree.tmpl index 844b396bd46f6..29302599ef508 100644 --- a/templates/repo/view_file_tree.tmpl +++ b/templates/repo/view_file_tree.tmpl @@ -12,7 +12,7 @@ {{/* TODO: Dynamically move components such as refSelector and createPR here */}} -
.menu { + margin-top: 4px !important; +} + +.ui.dropdown.repo-file-actions-dropdown > .menu { + margin-top: 4px !important; min-width: 200px; } diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index ee371f1b1c982..f0c366c7650fa 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -63,6 +63,8 @@ bottom: 0; height: 100%; overflow-y: hidden; + overflow-x: visible; + z-index: 10; } .repo-view-content { diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index 0526e360707a5..2dde7392dcee8 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -1,12 +1,14 @@ diff --git a/web_src/js/features/repo-file-actions.ts b/web_src/js/features/repo-file-actions.ts deleted file mode 100644 index bd167e135ca84..0000000000000 --- a/web_src/js/features/repo-file-actions.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Handle repository file/directory actions dropdown -export function initRepoFileActions() { - const centerContentCheckbox = document.querySelector('#center-content-checkbox'); - if (!centerContentCheckbox) return; - - // Load saved preference - const isCentered = localStorage.getItem('repo-content-centered') === 'true'; - centerContentCheckbox.checked = isCentered; - applyCenterContent(isCentered); - - // Handle checkbox change - centerContentCheckbox.addEventListener('change', () => { - const centered = centerContentCheckbox.checked; - localStorage.setItem('repo-content-centered', String(centered)); - applyCenterContent(centered); - }); -} - -function applyCenterContent(centered: boolean) { - const container = document.querySelector('.ui.container'); - if (!container) return; - - if (centered) { - container.classList.add('repo-content-centered'); - } else { - container.classList.remove('repo-content-centered'); - } -} diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index 49d7814b9656c..df56c85c868c6 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -64,7 +64,6 @@ import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton} import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts'; import {callInitFunctions} from './modules/init.ts'; import {initRepoViewFileTree} from './features/repo-view-file-tree.ts'; -import {initRepoFileActions} from './features/repo-file-actions.ts'; const initStartTime = performance.now(); const initPerformanceTracer = callInitFunctions([ @@ -138,7 +137,6 @@ const initPerformanceTracer = callInitFunctions([ initRepoReleaseNew, initRepoTopicBar, initRepoViewFileTree, - initRepoFileActions, initRepoWikiForm, initRepository, initRepositoryActionView, From 541313bc329c937efb4dc672df6a4a0d09d9303c Mon Sep 17 00:00:00 2001 From: brymut Date: Wed, 12 Nov 2025 13:08:20 +0300 Subject: [PATCH 05/30] feat(repo): Added tree view to adding new file(s) view. --- routers/web/repo/editor.go | 3 +++ routers/web/repo/editor_apply_patch.go | 1 + templates/repo/editor/edit.tmpl | 20 ++++++++++++++++---- templates/repo/editor/patch.tmpl | 20 ++++++++++++++++---- templates/repo/editor/upload.tmpl | 20 ++++++++++++++++---- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 11a20bece9d49..bf664146fa45b 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -280,6 +280,8 @@ func EditFile(ctx *context.Context) { return } + prepareHomeTreeSideBarSwitch(ctx) + // on the "New File" page, we should add an empty path field to make end users could input a new name prepareTreePathFieldsAndPaths(ctx, util.Iif(isNewFile, ctx.Repo.TreePath+"/", ctx.Repo.TreePath)) @@ -465,6 +467,7 @@ func DeleteFilePost(ctx *context.Context) { func UploadFile(ctx *context.Context) { ctx.Data["PageIsUpload"] = true + prepareHomeTreeSideBarSwitch(ctx) prepareTreePathFieldsAndPaths(ctx, ctx.Repo.TreePath) opts := prepareEditorCommitFormOptions(ctx, "_upload") if ctx.Written() { diff --git a/routers/web/repo/editor_apply_patch.go b/routers/web/repo/editor_apply_patch.go index aad7b4129c795..a18369e5a47be 100644 --- a/routers/web/repo/editor_apply_patch.go +++ b/routers/web/repo/editor_apply_patch.go @@ -14,6 +14,7 @@ import ( ) func NewDiffPatch(ctx *context.Context) { + prepareHomeTreeSideBarSwitch(ctx) prepareEditorCommitFormOptions(ctx, "_diffpatch") if ctx.Written() { return diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 0911d02e1f423..566aca09b0c32 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -1,15 +1,25 @@ {{template "base/head" .}}
{{template "repo/header" .}} -
+
{{template "base/alert" .}} -
+
+ {{template "repo/view_file_tree" .}} +
+
+ {{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
+
+ {{template "repo/editor/common_breadcrumb" .}}
{{if not .NotEditableReason}} @@ -47,7 +57,9 @@
{{end}} {{template "repo/editor/commit_form" .}} - + +
+
{{template "base/footer" .}} diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl index fa00edd92e77c..04027645b627b 100644 --- a/templates/repo/editor/patch.tmpl +++ b/templates/repo/editor/patch.tmpl @@ -1,15 +1,25 @@ {{template "base/head" .}}
{{template "repo/header" .}} -
+
{{template "base/alert" .}} -
+
+ {{template "repo/view_file_tree" .}} +
+
+ {{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
+
+
{{template "repo/editor/commit_form" .}} - + +
+
{{template "base/footer" .}} diff --git a/templates/repo/editor/upload.tmpl b/templates/repo/editor/upload.tmpl index 3e36c77b3b924..20f96eb0cba37 100644 --- a/templates/repo/editor/upload.tmpl +++ b/templates/repo/editor/upload.tmpl @@ -1,19 +1,31 @@ {{template "base/head" .}}
{{template "repo/header" .}} -
+
{{template "base/alert" .}} -
+
+
+ {{template "repo/view_file_tree" .}} +
+
+ {{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
+
+ {{template "repo/editor/common_breadcrumb" .}}
{{template "repo/upload" .}}
{{template "repo/editor/commit_form" .}} - + +
+
{{template "base/footer" .}} From 3bdb2ba275448c935a3cc244b8c1df6bf8e99e07 Mon Sep 17 00:00:00 2001 From: brymut Date: Thu, 13 Nov 2025 10:04:54 +0300 Subject: [PATCH 06/30] enhance(repo): move commit_form to modal and commit action buttons above editor. --- templates/repo/editor/commit_form.tmpl | 12 +- templates/repo/editor/edit.tmpl | 36 ++++-- templates/repo/editor/patch.tmpl | 36 ++++-- templates/repo/editor/upload.tmpl | 36 ++++-- web_src/css/index.css | 1 + web_src/css/repo/editor-commit.css | 129 ++++++++++++++++++++++ web_src/js/features/repo-editor-commit.ts | 102 +++++++++++++++++ web_src/js/index-domready.ts | 2 + 8 files changed, 326 insertions(+), 28 deletions(-) create mode 100644 web_src/css/repo/editor-commit.css create mode 100644 web_src/js/features/repo-editor-commit.ts diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl index 10872f8af7452..f39009f2183ad 100644 --- a/templates/repo/editor/commit_form.tmpl +++ b/templates/repo/editor/commit_form.tmpl @@ -1,4 +1,7 @@
+ {{ctx.AvatarUtils.Avatar .SignedUser 40 "commit-avatar"}}

@@ -80,8 +83,9 @@ {{end}}

- - {{ctx.Locale.Tr "repo.editor.cancel"}} +
+ +
diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 566aca09b0c32..21306c85aaf2d 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -14,13 +14,21 @@ > {{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
- - {{template "repo/editor/common_breadcrumb" .}} +
+
+ + {{template "repo/editor/common_breadcrumb" .}} +
+
+ {{ctx.Locale.Tr "repo.editor.cancel"}} + +
{{if not .NotEditableReason}}
@@ -56,10 +64,22 @@
{{end}} - {{template "repo/editor/commit_form" .}} + {{/* Commit form fields - inside form but hidden, will be shown in modal */}} + + {{/* Hidden dummy button for repo-editor.ts compatibility */}} +
+ + {{/* Commit Changes Modal - fields will be moved here visually */}} +
{{template "base/footer" .}} diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl index 04027645b627b..8cbb03ca0992a 100644 --- a/templates/repo/editor/patch.tmpl +++ b/templates/repo/editor/patch.tmpl @@ -14,13 +14,14 @@ > {{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
- - - {{template "repo/editor/commit_form" .}} + {{/* Commit form fields - inside form but hidden, will be shown in modal */}} + + {{/* Hidden dummy button for repo-editor.ts compatibility */}} +
+ + {{/* Commit Changes Modal - fields will be moved here visually */}} + {{template "base/footer" .}} diff --git a/templates/repo/editor/upload.tmpl b/templates/repo/editor/upload.tmpl index 20f96eb0cba37..b983f4a6516eb 100644 --- a/templates/repo/editor/upload.tmpl +++ b/templates/repo/editor/upload.tmpl @@ -11,21 +11,41 @@
{{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
- - {{template "repo/editor/common_breadcrumb" .}} +
+
+ + {{template "repo/editor/common_breadcrumb" .}} +
+
+ {{ctx.Locale.Tr "repo.editor.cancel"}} + +
{{template "repo/upload" .}}
- {{template "repo/editor/commit_form" .}} + {{/* Commit form fields - inside form but hidden, will be shown in modal */}} + + {{/* Hidden dummy button for repo-editor.ts compatibility */}} +
+ + {{/* Commit Changes Modal - fields will be moved here visually */}} + {{template "base/footer" .}} diff --git a/web_src/css/index.css b/web_src/css/index.css index 796497c6305b8..53ed8c6909701 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -64,6 +64,7 @@ @import "./repo/list-header.css"; @import "./repo/file-view.css"; @import "./repo/file-actions.css"; +@import "./repo/editor-commit.css"; @import "./repo/wiki.css"; @import "./repo/header.css"; @import "./repo/home.css"; diff --git a/web_src/css/repo/editor-commit.css b/web_src/css/repo/editor-commit.css new file mode 100644 index 0000000000000..281e7719d11c5 --- /dev/null +++ b/web_src/css/repo/editor-commit.css @@ -0,0 +1,129 @@ +/* Editor commit modal styling */ +#commit-changes-modal .content { + padding: 1.5rem; +} + +.commit-form-in-modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 10001; + max-width: 680px; + width: 90%; + max-height: 85vh; + overflow-y: auto; + background: var(--color-box-body); + border-radius: 6px; + padding: 1.5rem; + padding-top: 3rem; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); +} + +/* Close button inside the commit form */ +.commit-modal-close { + position: absolute; + top: 0.5rem; + right: 0.5rem; + cursor: pointer; + background: transparent; + border: none; + padding: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-text); + opacity: 0.6; + z-index: 10; + border-radius: 4px; + transition: all 0.2s; +} + +.commit-modal-close:hover { + opacity: 1; + background: var(--color-hover); +} + +#commit-changes-modal .commit-form-wrapper { + padding-left: 48px; + position: relative; +} + +#commit-changes-modal .commit-form-wrapper .commit-avatar { + float: left; + margin-left: -48px; +} + +#commit-changes-modal .commit-form-wrapper .commit-form { + position: relative; + padding: 15px; + border: 1px solid var(--color-secondary); + border-radius: var(--border-radius); + background-color: var(--color-box-body); +} + +#commit-changes-modal .commit-form h3 { + margin-top: 0; + margin-bottom: 1rem; +} + +#commit-changes-modal .commit-form .field { + margin-bottom: 1rem; +} + +#commit-changes-modal .commit-form input[name="commit_summary"] { + width: 100%; + padding: 10px 12px; + font-size: 14px; +} + +#commit-changes-modal .commit-form textarea[name="commit_message"] { + width: 100%; + padding: 10px 12px; + font-size: 14px; +} + +#commit-changes-modal .quick-pull-choice .field { + margin-bottom: 0.75rem; +} + +#commit-changes-modal .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input { + position: relative; + margin-left: 25px; +} + +#commit-changes-modal .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input input { + width: 240px !important; + padding-left: 26px !important; +} + +#commit-changes-modal .commit-form-wrapper .commit-form .quick-pull-choice .octicon-git-branch { + position: absolute; + top: 9px; + left: 8px; +} + +/* Arrow pointing to avatar */ +#commit-changes-modal .avatar-content-left-arrow::before, +#commit-changes-modal .avatar-content-left-arrow::after { + right: 100%; + top: 20px; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +#commit-changes-modal .avatar-content-left-arrow::before { + border-right-color: var(--color-secondary); + border-width: 9px; + margin-top: -9px; +} + +#commit-changes-modal .avatar-content-left-arrow::after { + border-right-color: var(--color-box-body); + border-width: 8px; + margin-top: -8px; +} diff --git a/web_src/js/features/repo-editor-commit.ts b/web_src/js/features/repo-editor-commit.ts new file mode 100644 index 0000000000000..82b561cc1510b --- /dev/null +++ b/web_src/js/features/repo-editor-commit.ts @@ -0,0 +1,102 @@ +import $ from 'jquery'; + +export function initRepoEditorCommit() { + const commitButton = document.querySelector('#commit-changes-button'); + const commitModal = document.querySelector('#commit-changes-modal'); + const modalCommitButton = document.querySelector('#commit-button'); + + if (!commitButton || !commitModal) return; + + const elForm = document.querySelector('.repository.editor .edit.form'); + const dirtyFileClass = 'dirty-file'; + + // Sync the top commit button state with the modal commit button state + const syncTopCommitButtonState = () => { + // Check if form has changes (using the same dirty class from repo-editor.ts) + const hasChanges = elForm?.classList.contains(dirtyFileClass); + + // Also check the modal commit button state as fallback + const modalButtonDisabled = modalCommitButton?.disabled; + + if (hasChanges || !modalButtonDisabled) { + commitButton.classList.remove('disabled'); + } else { + commitButton.classList.add('disabled'); + } + }; + + // For upload page - enable button when files are added + const dropzone = document.querySelector('.dropzone'); + if (dropzone) { + const observer = new MutationObserver(() => { + const filesContainer = dropzone.querySelector('.files'); + const hasFiles = filesContainer && filesContainer.children.length > 0; + + if (hasFiles) { + commitButton.classList.remove('disabled'); + } else { + commitButton.classList.add('disabled'); + } + }); + + const filesContainer = dropzone.querySelector('.files'); + if (filesContainer) { + observer.observe(filesContainer, {childList: true}); + } + } + + // Watch for changes in the form's dirty state + if (elForm) { + const observer = new MutationObserver(syncTopCommitButtonState); + observer.observe(elForm, {attributes: true, attributeFilter: ['class']}); + + // Initial sync + syncTopCommitButtonState(); + } + + // Also sync when modal commit button state changes + if (modalCommitButton) { + const observer = new MutationObserver(syncTopCommitButtonState); + observer.observe(modalCommitButton, {attributes: true, attributeFilter: ['disabled']}); + } + + const commitFormFields = document.querySelector('#commit-form-fields'); + + commitButton.addEventListener('click', (e) => { + e.preventDefault(); + if (!commitButton.classList.contains('disabled')) { + // Show the commit form fields (they stay in the form, just become visible) + if (commitFormFields) { + commitFormFields.style.display = 'block'; + // Position it inside the modal using CSS + commitFormFields.classList.add('commit-form-in-modal'); + } + $(commitModal).modal('show'); + } + }); + + // When modal closes, hide the form fields again + $(commitModal).modal({ + onHidden: () => { + if (commitFormFields) { + commitFormFields.style.display = 'none'; + commitFormFields.classList.remove('commit-form-in-modal'); + } + }, + }); + + // Handle close button click + const closeButton = document.querySelector('#commit-modal-close-btn'); + if (closeButton) { + closeButton.addEventListener('click', () => { + $(commitModal).modal('hide'); + }); + } + + // Handle form submission - close modal after submit + if (elForm) { + elForm.addEventListener('submit', () => { + $(commitModal).modal('hide'); + }); + } +} diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index df56c85c868c6..367a6736db002 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -34,6 +34,7 @@ import {initOrgTeam} from './features/org-team.ts'; import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.ts'; import {initRepoRelease, initRepoReleaseNew} from './features/repo-release.ts'; import {initRepoEditor} from './features/repo-editor.ts'; +import {initRepoEditorCommit} from './features/repo-editor-commit.ts'; import {initCompSearchUserBox} from './features/comp/SearchUserBox.ts'; import {initInstall} from './features/install.ts'; import {initCompWebHookEditor} from './features/comp/WebHookEditor.ts'; @@ -123,6 +124,7 @@ const initPerformanceTracer = callInitFunctions([ initRepoEllipsisButton, initRepoDiffCommitBranchesAndTags, initRepoEditor, + initRepoEditorCommit, initRepoGraphGit, initRepoIssueContentHistory, initRepoIssueList, From 15eec6f0bf720cb5fae6f1ca0aa8200ee97ced3d Mon Sep 17 00:00:00 2001 From: brymut Date: Sat, 15 Nov 2025 11:54:28 +0300 Subject: [PATCH 07/30] enhance(repo): move delete to modal, add files to directory in file view and apply patch ui improvements. --- routers/web/repo/view_file.go | 6 ++ templates/repo/editor/common_breadcrumb.tmpl | 1 - templates/repo/editor/patch.tmpl | 5 +- templates/repo/view_content.tmpl | 18 +++- templates/repo/view_file.tmpl | 73 +++++++++++++++- web_src/css/index.css | 1 + web_src/css/repo/delete-file.css | 92 ++++++++++++++++++++ web_src/js/features/repo-delete-file.ts | 42 +++++++++ web_src/js/index-domready.ts | 2 + 9 files changed, 230 insertions(+), 10 deletions(-) create mode 100644 web_src/css/repo/delete-file.css create mode 100644 web_src/js/features/repo-delete-file.ts diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index 167cd5f927f38..e90f7277946c6 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -307,5 +307,11 @@ func prepareFileViewEditorButtons(ctx *context.Context) bool { ctx.Data["EditFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.edit_this_file")) ctx.Data["CanDeleteFile"] = !isLFSLocked ctx.Data["DeleteFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.delete_this_file")) + + // Generate unique branch name for delete modal + if ctx.Doer != nil { + ctx.Data["new_branch_name"] = getUniquePatchBranchName(ctx, ctx.Doer.LowerName, ctx.Repo.Repository) + } + return true } diff --git a/templates/repo/editor/common_breadcrumb.tmpl b/templates/repo/editor/common_breadcrumb.tmpl index 8cfbe09d3eef5..e91648d6129e6 100644 --- a/templates/repo/editor/common_breadcrumb.tmpl +++ b/templates/repo/editor/common_breadcrumb.tmpl @@ -11,6 +11,5 @@ {{$v}} {{end}} {{end}} - {{ctx.Locale.Tr "repo.editor.or"}} {{ctx.Locale.Tr "repo.editor.cancel_lower"}} diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl index 8cbb03ca0992a..72c23e30507fe 100644 --- a/templates/repo/editor/patch.tmpl +++ b/templates/repo/editor/patch.tmpl @@ -22,11 +22,8 @@ {{svg "octicon-sidebar-collapse"}} diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index 1fff13ea7cf56..04a16543d624b 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -66,24 +66,33 @@
- {{if and .RefFullName.IsBranch (not .IsViewFile)}} + {{if .RefFullName.IsBranch}} + {{$addFilePath := .TreePath}} + {{if .IsViewFile}} + {{if gt (len .TreeNames) 1}} + {{$addFilePath = StringUtils.Join (slice .TreeNames 0 (Eval (len .TreeNames) "-" 1)) "/"}} + {{else}} + {{$addFilePath = ""}} + {{end}} + {{end}} + {{if not .IsViewFile}} + {{end}} {{end}} {{if $isTreePathRoot}} diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 8fce1b6f2c8fc..a02c282dc6eb7 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -74,7 +74,7 @@ {{svg "octicon-pencil"}} {{end}} {{if .CanDeleteFile}} - {{svg "octicon-trash"}} + {{svg "octicon-trash"}} {{else}} {{svg "octicon-trash"}} {{end}} @@ -148,4 +148,75 @@ {{ctx.Locale.Tr "repo.file_copy_permalink"}}
+ + {{/* Delete File Modal */}} + {{if .CanDeleteFile}} + + {{end}} diff --git a/web_src/css/index.css b/web_src/css/index.css index 53ed8c6909701..c4d3a22157520 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -65,6 +65,7 @@ @import "./repo/file-view.css"; @import "./repo/file-actions.css"; @import "./repo/editor-commit.css"; +@import "./repo/delete-file.css"; @import "./repo/wiki.css"; @import "./repo/header.css"; @import "./repo/home.css"; diff --git a/web_src/css/repo/delete-file.css b/web_src/css/repo/delete-file.css new file mode 100644 index 0000000000000..be211434ce53f --- /dev/null +++ b/web_src/css/repo/delete-file.css @@ -0,0 +1,92 @@ +/* Delete file modal styling */ +#delete-file-modal { + max-width: 680px; +} + +#delete-file-modal .content { + padding: 1.5rem; +} + +#delete-file-modal .commit-form-wrapper { + padding-left: 48px; + position: relative; +} + +#delete-file-modal .commit-form-wrapper .commit-avatar { + float: left; + margin-left: -48px; +} + +#delete-file-modal .commit-form-wrapper .commit-form { + position: relative; + padding: 15px; + border: 1px solid var(--color-secondary); + border-radius: var(--border-radius); + background-color: var(--color-box-body); +} + +#delete-file-modal .commit-form h3 { + margin-top: 0; + margin-bottom: 1rem; +} + +#delete-file-modal .commit-form .field { + margin-bottom: 1rem; +} + +#delete-file-modal .commit-form input[name="commit_summary"] { + width: 100%; + padding: 10px 12px; + font-size: 14px; +} + +#delete-file-modal .commit-form textarea[name="commit_message"] { + width: 100%; + padding: 10px 12px; + font-size: 14px; +} + +#delete-file-modal .quick-pull-choice .field { + margin-bottom: 0.75rem; +} + +#delete-file-modal .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input { + position: relative; + margin-left: 25px; +} + +#delete-file-modal .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input input { + width: 240px !important; + padding-left: 26px !important; +} + +#delete-file-modal .commit-form-wrapper .commit-form .quick-pull-choice .octicon-git-branch { + position: absolute; + top: 9px; + left: 8px; +} + +/* Arrow pointing to avatar */ +#delete-file-modal .avatar-content-left-arrow::before, +#delete-file-modal .avatar-content-left-arrow::after { + right: 100%; + top: 20px; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} + +#delete-file-modal .avatar-content-left-arrow::before { + border-right-color: var(--color-secondary); + border-width: 9px; + margin-top: -9px; +} + +#delete-file-modal .avatar-content-left-arrow::after { + border-right-color: var(--color-box-body); + border-width: 8px; + margin-top: -8px; +} diff --git a/web_src/js/features/repo-delete-file.ts b/web_src/js/features/repo-delete-file.ts new file mode 100644 index 0000000000000..08435966c686c --- /dev/null +++ b/web_src/js/features/repo-delete-file.ts @@ -0,0 +1,42 @@ +import $ from 'jquery'; + +export function initRepoDeleteFile() { + const deleteButton = document.querySelector('#delete-file-button'); + const deleteModal = document.querySelector('#delete-file-modal'); + const deleteForm = document.querySelector('#delete-file-form'); + + if (!deleteButton || !deleteModal || !deleteForm) { + return; + } + + deleteButton.addEventListener('click', (e) => { + e.preventDefault(); + $(deleteModal).modal('show'); + }); + + // Handle form submission + deleteForm.addEventListener('submit', () => { + $(deleteModal).modal('hide'); + }); + + // Handle commit choice radio buttons + const commitChoiceRadios = deleteForm.querySelectorAll('input[name="commit_choice"]'); + const newBranchNameContainer = deleteForm.querySelector('.quick-pull-branch-name'); + const newBranchNameInput = deleteForm.querySelector('input[name="new_branch_name"]'); + + for (const radio of commitChoiceRadios) { + radio.addEventListener('change', () => { + if (radio.value === 'commit-to-new-branch') { + newBranchNameContainer?.classList.remove('tw-hidden'); + if (newBranchNameInput) { + newBranchNameInput.required = true; + } + } else { + newBranchNameContainer?.classList.add('tw-hidden'); + if (newBranchNameInput) { + newBranchNameInput.required = false; + } + } + }); + } +} diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index 367a6736db002..f44a84476009b 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -35,6 +35,7 @@ import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/use import {initRepoRelease, initRepoReleaseNew} from './features/repo-release.ts'; import {initRepoEditor} from './features/repo-editor.ts'; import {initRepoEditorCommit} from './features/repo-editor-commit.ts'; +import {initRepoDeleteFile} from './features/repo-delete-file.ts'; import {initCompSearchUserBox} from './features/comp/SearchUserBox.ts'; import {initInstall} from './features/install.ts'; import {initCompWebHookEditor} from './features/comp/WebHookEditor.ts'; @@ -125,6 +126,7 @@ const initPerformanceTracer = callInitFunctions([ initRepoDiffCommitBranchesAndTags, initRepoEditor, initRepoEditorCommit, + initRepoDeleteFile, initRepoGraphGit, initRepoIssueContentHistory, initRepoIssueList, From dc7febea57ba7babafa26505722278e5462f7f36 Mon Sep 17 00:00:00 2001 From: brymut Date: Mon, 17 Nov 2025 21:01:26 +0300 Subject: [PATCH 08/30] fix: use fomanticQuery instead of jquery --- web_src/js/features/repo-delete-file.ts | 6 +++--- web_src/js/features/repo-editor-commit.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web_src/js/features/repo-delete-file.ts b/web_src/js/features/repo-delete-file.ts index 08435966c686c..b01211ad39647 100644 --- a/web_src/js/features/repo-delete-file.ts +++ b/web_src/js/features/repo-delete-file.ts @@ -1,4 +1,4 @@ -import $ from 'jquery'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; export function initRepoDeleteFile() { const deleteButton = document.querySelector('#delete-file-button'); @@ -11,12 +11,12 @@ export function initRepoDeleteFile() { deleteButton.addEventListener('click', (e) => { e.preventDefault(); - $(deleteModal).modal('show'); + fomanticQuery(deleteModal).modal('show'); }); // Handle form submission deleteForm.addEventListener('submit', () => { - $(deleteModal).modal('hide'); + fomanticQuery(deleteModal).modal('hide'); }); // Handle commit choice radio buttons diff --git a/web_src/js/features/repo-editor-commit.ts b/web_src/js/features/repo-editor-commit.ts index 82b561cc1510b..64f5018a8acc6 100644 --- a/web_src/js/features/repo-editor-commit.ts +++ b/web_src/js/features/repo-editor-commit.ts @@ -1,4 +1,4 @@ -import $ from 'jquery'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; export function initRepoEditorCommit() { const commitButton = document.querySelector('#commit-changes-button'); @@ -71,12 +71,12 @@ export function initRepoEditorCommit() { // Position it inside the modal using CSS commitFormFields.classList.add('commit-form-in-modal'); } - $(commitModal).modal('show'); + fomanticQuery(commitModal).modal('show'); } }); // When modal closes, hide the form fields again - $(commitModal).modal({ + fomanticQuery(commitModal).modal({ onHidden: () => { if (commitFormFields) { commitFormFields.style.display = 'none'; @@ -89,14 +89,14 @@ export function initRepoEditorCommit() { const closeButton = document.querySelector('#commit-modal-close-btn'); if (closeButton) { closeButton.addEventListener('click', () => { - $(commitModal).modal('hide'); + fomanticQuery(commitModal).modal('hide'); }); } // Handle form submission - close modal after submit if (elForm) { elForm.addEventListener('submit', () => { - $(commitModal).modal('hide'); + fomanticQuery(commitModal).modal('hide'); }); } } From 96bf7457743d18c087170894f88116b9337cd943 Mon Sep 17 00:00:00 2001 From: brymut Date: Tue, 18 Nov 2025 07:34:29 +0300 Subject: [PATCH 09/30] rollback modal changes --- options/locale/locale_en-US.ini | 3 - routers/web/repo/view_file.go | 6 - templates/repo/editor/commit_form.tmpl | 12 +- templates/repo/editor/common_breadcrumb.tmpl | 1 + templates/repo/editor/edit.tmpl | 36 ++---- templates/repo/editor/patch.tmpl | 39 ++---- templates/repo/editor/upload.tmpl | 36 ++---- templates/repo/view_file.tmpl | 73 +---------- web_src/css/index.css | 2 - web_src/css/repo/delete-file.css | 92 ------------- web_src/css/repo/editor-commit.css | 129 ------------------- web_src/css/repo/home.css | 2 - web_src/js/components/ViewFileTree.vue | 3 - web_src/js/features/repo-delete-file.ts | 42 ------ web_src/js/features/repo-editor-commit.ts | 102 --------------- web_src/js/index-domready.ts | 4 - 16 files changed, 32 insertions(+), 550 deletions(-) delete mode 100644 web_src/css/repo/delete-file.css delete mode 100644 web_src/css/repo/editor-commit.css delete mode 100644 web_src/js/features/repo-delete-file.ts delete mode 100644 web_src/js/features/repo-editor-commit.ts diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index cef6076225f10..6712250924361 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -111,8 +111,6 @@ copy_error = Copy failed copy_type_unsupported = This file type cannot be copied copy_filename = Copy filename -repo.more_operations = More Operations - write = Write preview = Preview loading = Loading… @@ -1319,7 +1317,6 @@ ambiguous_character = `%[1]c [U+%04[1]X] can be confused with %[2]c [U+%04[2]X]` escape_control_characters = Escape unescape_control_characters = Unescape file_copy_permalink = Copy Permalink -center_content = Center content view_git_blame = View Git Blame video_not_supported_in_browser = Your browser does not support the HTML5 'video' tag. audio_not_supported_in_browser = Your browser does not support the HTML5 'audio' tag. diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index e90f7277946c6..167cd5f927f38 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -307,11 +307,5 @@ func prepareFileViewEditorButtons(ctx *context.Context) bool { ctx.Data["EditFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.edit_this_file")) ctx.Data["CanDeleteFile"] = !isLFSLocked ctx.Data["DeleteFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.delete_this_file")) - - // Generate unique branch name for delete modal - if ctx.Doer != nil { - ctx.Data["new_branch_name"] = getUniquePatchBranchName(ctx, ctx.Doer.LowerName, ctx.Repo.Repository) - } - return true } diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl index f39009f2183ad..10872f8af7452 100644 --- a/templates/repo/editor/commit_form.tmpl +++ b/templates/repo/editor/commit_form.tmpl @@ -1,7 +1,4 @@
- {{ctx.AvatarUtils.Avatar .SignedUser 40 "commit-avatar"}}

@@ -83,9 +80,8 @@ {{end}}

-
- -
+ + {{ctx.Locale.Tr "repo.editor.cancel"}}
diff --git a/templates/repo/editor/common_breadcrumb.tmpl b/templates/repo/editor/common_breadcrumb.tmpl index e91648d6129e6..8cfbe09d3eef5 100644 --- a/templates/repo/editor/common_breadcrumb.tmpl +++ b/templates/repo/editor/common_breadcrumb.tmpl @@ -11,5 +11,6 @@ {{$v}} {{end}} {{end}} + {{ctx.Locale.Tr "repo.editor.or"}} {{ctx.Locale.Tr "repo.editor.cancel_lower"}} diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index 21306c85aaf2d..566aca09b0c32 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -14,21 +14,13 @@ > {{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
-
- - {{template "repo/editor/common_breadcrumb" .}} -
-
- {{ctx.Locale.Tr "repo.editor.cancel"}} - -
+
+ + {{template "repo/editor/common_breadcrumb" .}}
{{if not .NotEditableReason}}
@@ -64,22 +56,10 @@
{{end}} - {{/* Commit form fields - inside form but hidden, will be shown in modal */}} - - {{/* Hidden dummy button for repo-editor.ts compatibility */}} - + {{template "repo/editor/commit_form" .}} - - {{/* Commit Changes Modal - fields will be moved here visually */}} - {{template "base/footer" .}} diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl index 72c23e30507fe..cd478332a7712 100644 --- a/templates/repo/editor/patch.tmpl +++ b/templates/repo/editor/patch.tmpl @@ -14,25 +14,18 @@ > {{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
-
- -
@@ -47,22 +40,10 @@
- {{/* Commit form fields - inside form but hidden, will be shown in modal */}} - - {{/* Hidden dummy button for repo-editor.ts compatibility */}} - + {{template "repo/editor/commit_form" .}}
- - {{/* Commit Changes Modal - fields will be moved here visually */}} - {{template "base/footer" .}} diff --git a/templates/repo/editor/upload.tmpl b/templates/repo/editor/upload.tmpl index b983f4a6516eb..20f96eb0cba37 100644 --- a/templates/repo/editor/upload.tmpl +++ b/templates/repo/editor/upload.tmpl @@ -11,41 +11,21 @@
{{.CsrfTokenHtml}} {{template "repo/editor/common_top" .}} -
-
- - {{template "repo/editor/common_breadcrumb" .}} -
-
- {{ctx.Locale.Tr "repo.editor.cancel"}} - -
+
+ + {{template "repo/editor/common_breadcrumb" .}}
{{template "repo/upload" .}}
- {{/* Commit form fields - inside form but hidden, will be shown in modal */}} - - {{/* Hidden dummy button for repo-editor.ts compatibility */}} - + {{template "repo/editor/commit_form" .}}
- - {{/* Commit Changes Modal - fields will be moved here visually */}} - {{template "base/footer" .}} diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index a02c282dc6eb7..8fce1b6f2c8fc 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -74,7 +74,7 @@ {{svg "octicon-pencil"}} {{end}} {{if .CanDeleteFile}} - {{svg "octicon-trash"}} + {{svg "octicon-trash"}} {{else}} {{svg "octicon-trash"}} {{end}} @@ -148,75 +148,4 @@ {{ctx.Locale.Tr "repo.file_copy_permalink"}} - - {{/* Delete File Modal */}} - {{if .CanDeleteFile}} - - {{end}} diff --git a/web_src/css/index.css b/web_src/css/index.css index c4d3a22157520..796497c6305b8 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -64,8 +64,6 @@ @import "./repo/list-header.css"; @import "./repo/file-view.css"; @import "./repo/file-actions.css"; -@import "./repo/editor-commit.css"; -@import "./repo/delete-file.css"; @import "./repo/wiki.css"; @import "./repo/header.css"; @import "./repo/home.css"; diff --git a/web_src/css/repo/delete-file.css b/web_src/css/repo/delete-file.css deleted file mode 100644 index be211434ce53f..0000000000000 --- a/web_src/css/repo/delete-file.css +++ /dev/null @@ -1,92 +0,0 @@ -/* Delete file modal styling */ -#delete-file-modal { - max-width: 680px; -} - -#delete-file-modal .content { - padding: 1.5rem; -} - -#delete-file-modal .commit-form-wrapper { - padding-left: 48px; - position: relative; -} - -#delete-file-modal .commit-form-wrapper .commit-avatar { - float: left; - margin-left: -48px; -} - -#delete-file-modal .commit-form-wrapper .commit-form { - position: relative; - padding: 15px; - border: 1px solid var(--color-secondary); - border-radius: var(--border-radius); - background-color: var(--color-box-body); -} - -#delete-file-modal .commit-form h3 { - margin-top: 0; - margin-bottom: 1rem; -} - -#delete-file-modal .commit-form .field { - margin-bottom: 1rem; -} - -#delete-file-modal .commit-form input[name="commit_summary"] { - width: 100%; - padding: 10px 12px; - font-size: 14px; -} - -#delete-file-modal .commit-form textarea[name="commit_message"] { - width: 100%; - padding: 10px 12px; - font-size: 14px; -} - -#delete-file-modal .quick-pull-choice .field { - margin-bottom: 0.75rem; -} - -#delete-file-modal .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input { - position: relative; - margin-left: 25px; -} - -#delete-file-modal .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input input { - width: 240px !important; - padding-left: 26px !important; -} - -#delete-file-modal .commit-form-wrapper .commit-form .quick-pull-choice .octicon-git-branch { - position: absolute; - top: 9px; - left: 8px; -} - -/* Arrow pointing to avatar */ -#delete-file-modal .avatar-content-left-arrow::before, -#delete-file-modal .avatar-content-left-arrow::after { - right: 100%; - top: 20px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} - -#delete-file-modal .avatar-content-left-arrow::before { - border-right-color: var(--color-secondary); - border-width: 9px; - margin-top: -9px; -} - -#delete-file-modal .avatar-content-left-arrow::after { - border-right-color: var(--color-box-body); - border-width: 8px; - margin-top: -8px; -} diff --git a/web_src/css/repo/editor-commit.css b/web_src/css/repo/editor-commit.css deleted file mode 100644 index 281e7719d11c5..0000000000000 --- a/web_src/css/repo/editor-commit.css +++ /dev/null @@ -1,129 +0,0 @@ -/* Editor commit modal styling */ -#commit-changes-modal .content { - padding: 1.5rem; -} - -.commit-form-in-modal { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 10001; - max-width: 680px; - width: 90%; - max-height: 85vh; - overflow-y: auto; - background: var(--color-box-body); - border-radius: 6px; - padding: 1.5rem; - padding-top: 3rem; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); -} - -/* Close button inside the commit form */ -.commit-modal-close { - position: absolute; - top: 0.5rem; - right: 0.5rem; - cursor: pointer; - background: transparent; - border: none; - padding: 0.5rem; - display: flex; - align-items: center; - justify-content: center; - color: var(--color-text); - opacity: 0.6; - z-index: 10; - border-radius: 4px; - transition: all 0.2s; -} - -.commit-modal-close:hover { - opacity: 1; - background: var(--color-hover); -} - -#commit-changes-modal .commit-form-wrapper { - padding-left: 48px; - position: relative; -} - -#commit-changes-modal .commit-form-wrapper .commit-avatar { - float: left; - margin-left: -48px; -} - -#commit-changes-modal .commit-form-wrapper .commit-form { - position: relative; - padding: 15px; - border: 1px solid var(--color-secondary); - border-radius: var(--border-radius); - background-color: var(--color-box-body); -} - -#commit-changes-modal .commit-form h3 { - margin-top: 0; - margin-bottom: 1rem; -} - -#commit-changes-modal .commit-form .field { - margin-bottom: 1rem; -} - -#commit-changes-modal .commit-form input[name="commit_summary"] { - width: 100%; - padding: 10px 12px; - font-size: 14px; -} - -#commit-changes-modal .commit-form textarea[name="commit_message"] { - width: 100%; - padding: 10px 12px; - font-size: 14px; -} - -#commit-changes-modal .quick-pull-choice .field { - margin-bottom: 0.75rem; -} - -#commit-changes-modal .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input { - position: relative; - margin-left: 25px; -} - -#commit-changes-modal .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input input { - width: 240px !important; - padding-left: 26px !important; -} - -#commit-changes-modal .commit-form-wrapper .commit-form .quick-pull-choice .octicon-git-branch { - position: absolute; - top: 9px; - left: 8px; -} - -/* Arrow pointing to avatar */ -#commit-changes-modal .avatar-content-left-arrow::before, -#commit-changes-modal .avatar-content-left-arrow::after { - right: 100%; - top: 20px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; -} - -#commit-changes-modal .avatar-content-left-arrow::before { - border-right-color: var(--color-secondary); - border-width: 9px; - margin-top: -9px; -} - -#commit-changes-modal .avatar-content-left-arrow::after { - border-right-color: var(--color-box-body); - border-width: 8px; - margin-top: -8px; -} diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index f0c366c7650fa..ee371f1b1c982 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -63,8 +63,6 @@ bottom: 0; height: 100%; overflow-y: hidden; - overflow-x: visible; - z-index: 10; } .repo-view-content { diff --git a/web_src/js/components/ViewFileTree.vue b/web_src/js/components/ViewFileTree.vue index d90212fb6777a..3f5c9e9165a55 100644 --- a/web_src/js/components/ViewFileTree.vue +++ b/web_src/js/components/ViewFileTree.vue @@ -84,7 +84,6 @@ const handleClickOutside = (e: MouseEvent) => { const target = e.target as HTMLElement; const resultsEl = searchResults.value; - // Check if click is outside search input and results if (searchInputElement && !searchInputElement.contains(target) && resultsEl && !resultsEl.contains(target)) { clearSearch(); @@ -102,14 +101,12 @@ onMounted(async () => { allFiles.value = await response.json(); } - // Setup search input listener searchInputElement = document.querySelector('#file-tree-search'); if (searchInputElement) { searchInputElement.addEventListener('input', handleSearchInput); searchInputElement.addEventListener('keydown', handleKeyDown); } - // Add click outside listener document.addEventListener('click', handleClickOutside); window.addEventListener('popstate', (e) => { diff --git a/web_src/js/features/repo-delete-file.ts b/web_src/js/features/repo-delete-file.ts deleted file mode 100644 index b01211ad39647..0000000000000 --- a/web_src/js/features/repo-delete-file.ts +++ /dev/null @@ -1,42 +0,0 @@ -import {fomanticQuery} from '../modules/fomantic/base.ts'; - -export function initRepoDeleteFile() { - const deleteButton = document.querySelector('#delete-file-button'); - const deleteModal = document.querySelector('#delete-file-modal'); - const deleteForm = document.querySelector('#delete-file-form'); - - if (!deleteButton || !deleteModal || !deleteForm) { - return; - } - - deleteButton.addEventListener('click', (e) => { - e.preventDefault(); - fomanticQuery(deleteModal).modal('show'); - }); - - // Handle form submission - deleteForm.addEventListener('submit', () => { - fomanticQuery(deleteModal).modal('hide'); - }); - - // Handle commit choice radio buttons - const commitChoiceRadios = deleteForm.querySelectorAll('input[name="commit_choice"]'); - const newBranchNameContainer = deleteForm.querySelector('.quick-pull-branch-name'); - const newBranchNameInput = deleteForm.querySelector('input[name="new_branch_name"]'); - - for (const radio of commitChoiceRadios) { - radio.addEventListener('change', () => { - if (radio.value === 'commit-to-new-branch') { - newBranchNameContainer?.classList.remove('tw-hidden'); - if (newBranchNameInput) { - newBranchNameInput.required = true; - } - } else { - newBranchNameContainer?.classList.add('tw-hidden'); - if (newBranchNameInput) { - newBranchNameInput.required = false; - } - } - }); - } -} diff --git a/web_src/js/features/repo-editor-commit.ts b/web_src/js/features/repo-editor-commit.ts deleted file mode 100644 index 64f5018a8acc6..0000000000000 --- a/web_src/js/features/repo-editor-commit.ts +++ /dev/null @@ -1,102 +0,0 @@ -import {fomanticQuery} from '../modules/fomantic/base.ts'; - -export function initRepoEditorCommit() { - const commitButton = document.querySelector('#commit-changes-button'); - const commitModal = document.querySelector('#commit-changes-modal'); - const modalCommitButton = document.querySelector('#commit-button'); - - if (!commitButton || !commitModal) return; - - const elForm = document.querySelector('.repository.editor .edit.form'); - const dirtyFileClass = 'dirty-file'; - - // Sync the top commit button state with the modal commit button state - const syncTopCommitButtonState = () => { - // Check if form has changes (using the same dirty class from repo-editor.ts) - const hasChanges = elForm?.classList.contains(dirtyFileClass); - - // Also check the modal commit button state as fallback - const modalButtonDisabled = modalCommitButton?.disabled; - - if (hasChanges || !modalButtonDisabled) { - commitButton.classList.remove('disabled'); - } else { - commitButton.classList.add('disabled'); - } - }; - - // For upload page - enable button when files are added - const dropzone = document.querySelector('.dropzone'); - if (dropzone) { - const observer = new MutationObserver(() => { - const filesContainer = dropzone.querySelector('.files'); - const hasFiles = filesContainer && filesContainer.children.length > 0; - - if (hasFiles) { - commitButton.classList.remove('disabled'); - } else { - commitButton.classList.add('disabled'); - } - }); - - const filesContainer = dropzone.querySelector('.files'); - if (filesContainer) { - observer.observe(filesContainer, {childList: true}); - } - } - - // Watch for changes in the form's dirty state - if (elForm) { - const observer = new MutationObserver(syncTopCommitButtonState); - observer.observe(elForm, {attributes: true, attributeFilter: ['class']}); - - // Initial sync - syncTopCommitButtonState(); - } - - // Also sync when modal commit button state changes - if (modalCommitButton) { - const observer = new MutationObserver(syncTopCommitButtonState); - observer.observe(modalCommitButton, {attributes: true, attributeFilter: ['disabled']}); - } - - const commitFormFields = document.querySelector('#commit-form-fields'); - - commitButton.addEventListener('click', (e) => { - e.preventDefault(); - if (!commitButton.classList.contains('disabled')) { - // Show the commit form fields (they stay in the form, just become visible) - if (commitFormFields) { - commitFormFields.style.display = 'block'; - // Position it inside the modal using CSS - commitFormFields.classList.add('commit-form-in-modal'); - } - fomanticQuery(commitModal).modal('show'); - } - }); - - // When modal closes, hide the form fields again - fomanticQuery(commitModal).modal({ - onHidden: () => { - if (commitFormFields) { - commitFormFields.style.display = 'none'; - commitFormFields.classList.remove('commit-form-in-modal'); - } - }, - }); - - // Handle close button click - const closeButton = document.querySelector('#commit-modal-close-btn'); - if (closeButton) { - closeButton.addEventListener('click', () => { - fomanticQuery(commitModal).modal('hide'); - }); - } - - // Handle form submission - close modal after submit - if (elForm) { - elForm.addEventListener('submit', () => { - fomanticQuery(commitModal).modal('hide'); - }); - } -} diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index f44a84476009b..df56c85c868c6 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -34,8 +34,6 @@ import {initOrgTeam} from './features/org-team.ts'; import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.ts'; import {initRepoRelease, initRepoReleaseNew} from './features/repo-release.ts'; import {initRepoEditor} from './features/repo-editor.ts'; -import {initRepoEditorCommit} from './features/repo-editor-commit.ts'; -import {initRepoDeleteFile} from './features/repo-delete-file.ts'; import {initCompSearchUserBox} from './features/comp/SearchUserBox.ts'; import {initInstall} from './features/install.ts'; import {initCompWebHookEditor} from './features/comp/WebHookEditor.ts'; @@ -125,8 +123,6 @@ const initPerformanceTracer = callInitFunctions([ initRepoEllipsisButton, initRepoDiffCommitBranchesAndTags, initRepoEditor, - initRepoEditorCommit, - initRepoDeleteFile, initRepoGraphGit, initRepoIssueContentHistory, initRepoIssueList, From 1ed8c772acf3f450bcb3121feb4d2c312c109c93 Mon Sep 17 00:00:00 2001 From: brymut Date: Thu, 20 Nov 2025 12:48:15 +0300 Subject: [PATCH 10/30] refactor(Repo): extract directory deletion logic into a dedicated service with testing --- routers/web/repo/editor.go | 40 ++-------- services/repository/files/delete.go | 94 ++++++++++++++++++++++++ services/repository/files/delete_test.go | 48 ++++++++++++ templates/repo/editor/patch.tmpl | 62 ++++++++-------- templates/repo/view_content.tmpl | 5 +- web_src/css/repo/file-actions.css | 12 +-- web_src/js/components/ViewFileTree.vue | 5 +- 7 files changed, 184 insertions(+), 82 deletions(-) create mode 100644 services/repository/files/delete.go create mode 100644 services/repository/files/delete_test.go diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index bf664146fa45b..971d5630959d6 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -394,6 +394,10 @@ func DeleteFilePost(ctx *context.Context) { } treePath := ctx.Repo.TreePath + if treePath == "" { + ctx.JSONError(ctx.Tr("repo.editor.cannot_delete_root")) + return + } // Check if the path is a directory entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath) @@ -402,50 +406,18 @@ func DeleteFilePost(ctx *context.Context) { return } - var filesToDelete []*files_service.ChangeRepoFile var commitMessage string - if entry.IsDir() { - // Get all files in the directory recursively - tree, err := ctx.Repo.Commit.SubTree(treePath) - if err != nil { - ctx.ServerError("SubTree", err) - return - } - - entries, err := tree.ListEntriesRecursiveFast() - if err != nil { - ctx.ServerError("ListEntriesRecursiveFast", err) - return - } - - // Create delete operations for all files in the directory - for _, e := range entries { - if !e.IsDir() && !e.IsSubModule() { - filesToDelete = append(filesToDelete, &files_service.ChangeRepoFile{ - Operation: "delete", - TreePath: treePath + "/" + e.Name(), - }) - } - } - commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete_directory", treePath)) } else { - // Single file deletion - filesToDelete = []*files_service.ChangeRepoFile{ - { - Operation: "delete", - TreePath: treePath, - }, - } commitMessage = parsed.GetCommitMessage(ctx.Locale.TrString("repo.editor.delete", treePath)) } - _, err = files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ + _, err = files_service.DeleteRepoFile(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.DeleteRepoFileOptions{ LastCommitID: parsed.form.LastCommit, OldBranch: parsed.OldBranchName, NewBranch: parsed.NewBranchName, - Files: filesToDelete, + TreePath: treePath, Message: commitMessage, Signoff: parsed.form.Signoff, Author: parsed.GitCommitter, diff --git a/services/repository/files/delete.go b/services/repository/files/delete.go new file mode 100644 index 0000000000000..14bb888e2d559 --- /dev/null +++ b/services/repository/files/delete.go @@ -0,0 +1,94 @@ +package files + +import ( + "context" + "fmt" + + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/structs" +) + +type DeleteRepoFileOptions struct { + LastCommitID string + OldBranch string + NewBranch string + TreePath string + Message string + Signoff bool + Author *IdentityOptions + Committer *IdentityOptions +} + +// DeleteRepoFile deletes a file or directory in the given repository +func DeleteRepoFile(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, opts *DeleteRepoFileOptions) (*structs.FilesResponse, error) { + if opts.TreePath == "" { + return nil, fmt.Errorf("path cannot be empty") + } + + // If no branch name is set, assume the default branch + if opts.OldBranch == "" { + opts.OldBranch = repo.DefaultBranch + } + if opts.NewBranch == "" { + opts.NewBranch = opts.OldBranch + } + + gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) + if err != nil { + return nil, err + } + defer closer.Close() + + // Get commit + commit, err := gitRepo.GetBranchCommit(opts.OldBranch) + if err != nil { + return nil, err + } + + // Get entry + entry, err := commit.GetTreeEntryByPath(opts.TreePath) + if err != nil { + return nil, err + } + + var filesToDelete []*ChangeRepoFile + + if entry.IsDir() { + tree, err := commit.SubTree(opts.TreePath) + if err != nil { + return nil, err + } + + entries, err := tree.ListEntriesRecursiveFast() + if err != nil { + return nil, err + } + + for _, e := range entries { + if !e.IsDir() && !e.IsSubModule() { + filesToDelete = append(filesToDelete, &ChangeRepoFile{ + Operation: "delete", + TreePath: opts.TreePath + "/" + e.Name(), + }) + } + } + } else { + filesToDelete = append(filesToDelete, &ChangeRepoFile{ + Operation: "delete", + TreePath: opts.TreePath, + }) + } + + return ChangeRepoFiles(ctx, repo, doer, &ChangeRepoFilesOptions{ + LastCommitID: opts.LastCommitID, + OldBranch: opts.OldBranch, + NewBranch: opts.NewBranch, + Message: opts.Message, + Files: filesToDelete, + Signoff: opts.Signoff, + Author: opts.Author, + Committer: opts.Committer, + }) +} diff --git a/services/repository/files/delete_test.go b/services/repository/files/delete_test.go new file mode 100644 index 0000000000000..10c06fd560887 --- /dev/null +++ b/services/repository/files/delete_test.go @@ -0,0 +1,48 @@ +package files + +import ( + "os" + "path/filepath" + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +func TestDeleteRepoFile(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() + + // Remove hooks to avoid "gitea: no such file or directory" error + repoPath := repo_model.RepoPath(ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name) + assert.NoError(t, os.RemoveAll(filepath.Join(repoPath, "hooks"))) + + t.Run("DeleteRoot", func(t *testing.T) { + _, err := DeleteRepoFile(ctx, ctx.Repo.Repository, ctx.Doer, &DeleteRepoFileOptions{ + TreePath: "", + OldBranch: "master", + NewBranch: "master", + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "path cannot be empty") + }) + + t.Run("DeleteFile", func(t *testing.T) { + // README.md exists in repo1 + _, err := DeleteRepoFile(ctx, ctx.Repo.Repository, ctx.Doer, &DeleteRepoFileOptions{ + TreePath: "README.md", + OldBranch: "master", + NewBranch: "master", + Message: "Delete README.md", + }) + assert.NoError(t, err) + }) +} diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl index cd478332a7712..207a7ce51efb2 100644 --- a/templates/repo/editor/patch.tmpl +++ b/templates/repo/editor/patch.tmpl @@ -8,39 +8,35 @@ {{template "repo/view_file_tree" .}}
-
- {{.CsrfTokenHtml}} - {{template "repo/editor/common_top" .}} -
- - -
- - {{template "repo/editor/commit_form" .}} + + {{.CsrfTokenHtml}} + {{template "repo/editor/common_top" .}} +
+ + +
+ + {{template "repo/editor/commit_form" .}}
diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index 04a16543d624b..fa8cfe5cfd0f1 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -96,10 +96,7 @@