From fa2dd378d647766d3b4d7968408ddb35e99db99b Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Thu, 19 Dec 2024 23:58:04 +0100 Subject: [PATCH 1/7] Warn on navigation if currently editing comment or title (#32223) --- templates/repo/issue/view_content.tmpl | 4 ++-- templates/repo/issue/view_title.tmpl | 6 +++--- web_src/js/features/repo-issue-edit.ts | 2 ++ web_src/js/features/repo-issue.ts | 3 +++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 69b5a11a1499e..e15a56461c2cd 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -139,7 +139,7 @@ {{template "repo/issue/view_content/reference_issue_dialog" .}} diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index bf2ede58e487d..2c432f1fda1b2 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -26,9 +26,9 @@ {{if $canEditIssueTitle}} -
+
- +
@@ -36,7 +36,7 @@ {{ctx.Locale.Tr "repo.issues.save"}}
-
+ {{end}}
{{if .HasMerged}} diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index 9d146951bd42f..c2392f0fb73ab 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -7,6 +7,7 @@ import {attachRefIssueContextPopup} from './contextpopup.ts'; import {initCommentContent, initMarkupContent} from '../markup/content.ts'; import {triggerUploadStateChanged} from './comp/EditorUpload.ts'; import {convertHtmlToMarkdown} from '../markup/html2markdown.ts'; +import {applyAreYouSure} from '../vendor/jquery.are-you-sure.ts'; async function tryOnEditContent(e) { const clickTarget = e.target.closest('.edit-content'); @@ -86,6 +87,7 @@ async function tryOnEditContent(e) { comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); if (!comboMarkdownEditor) { editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML; + applyAreYouSure(editContentZone.firstElementChild); const saveButton = querySingleVisibleElem(editContentZone, '.ui.primary.button'); const cancelButton = querySingleVisibleElem(editContentZone, '.ui.cancel.button'); comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index 7541039786830..f41b6e210780a 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -16,6 +16,7 @@ import {GET, POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {initRepoIssueSidebar} from './repo-issue-sidebar.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; +import {applyAreYouSure} from '../vendor/jquery.are-you-sure.ts'; const {appSubUrl} = window.config; @@ -535,6 +536,8 @@ export function initRepoIssueTitleEdit() { const issueTitleEditor = document.querySelector('#issue-title-editor'); if (!issueTitleEditor) return; + applyAreYouSure(issueTitleEditor); + const issueTitleInput = issueTitleEditor.querySelector('input'); const oldTitle = issueTitleInput.getAttribute('data-old-title'); issueTitleDisplay.querySelector('#issue-title-edit-show').addEventListener('click', () => { From 74f80d471b44b5bdb4a7e000629d7f65e71b0b4c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 20 Dec 2024 22:39:21 +0800 Subject: [PATCH 2/7] fix dirty form check --- templates/repo/issue/view_content.tmpl | 4 ++-- templates/repo/issue/view_title.tmpl | 4 ++-- web_src/js/features/repo-issue-edit.ts | 11 ++++++++--- web_src/js/features/repo-issue.ts | 11 ++++++----- web_src/js/vendor/jquery.are-you-sure.ts | 7 ++++++- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index e15a56461c2cd..1a68781ecd142 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -158,8 +158,8 @@
- - + +
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index 2c432f1fda1b2..4e4c53f5ef728 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -31,8 +31,8 @@
- - +
diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index c2392f0fb73ab..6df86a827ad33 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -7,7 +7,7 @@ import {attachRefIssueContextPopup} from './contextpopup.ts'; import {initCommentContent, initMarkupContent} from '../markup/content.ts'; import {triggerUploadStateChanged} from './comp/EditorUpload.ts'; import {convertHtmlToMarkdown} from '../markup/html2markdown.ts'; -import {applyAreYouSure} from '../vendor/jquery.are-you-sure.ts'; +import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts'; async function tryOnEditContent(e) { const clickTarget = e.target.closest('.edit-content'); @@ -20,9 +20,11 @@ async function tryOnEditContent(e) { const rawContent = segment.querySelector('.raw-content'); let comboMarkdownEditor : ComboMarkdownEditor; + let form: HTMLFormElement; const cancelAndReset = (e) => { e.preventDefault(); + form.classList.add('ignore-dirty'); showElem(renderContent); hideElem(editContentZone); comboMarkdownEditor.dropzoneReloadFiles(); @@ -30,6 +32,7 @@ async function tryOnEditContent(e) { const saveAndRefresh = async (e) => { e.preventDefault(); + form.classList.remove('ignore-dirty'); renderContent.classList.add('is-loading'); showElem(renderContent); hideElem(editContentZone); @@ -49,6 +52,7 @@ async function tryOnEditContent(e) { showErrorToast(data.errorMessage); return; } + reinitializeAreYouSure(form); editContentZone.setAttribute('data-content-version', data.contentVersion); if (!data.content) { renderContent.innerHTML = document.querySelector('#no-content').innerHTML; @@ -87,14 +91,15 @@ async function tryOnEditContent(e) { comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); if (!comboMarkdownEditor) { editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML; - applyAreYouSure(editContentZone.firstElementChild); + form = editContentZone.querySelector('form'); + applyAreYouSure(form); const saveButton = querySingleVisibleElem(editContentZone, '.ui.primary.button'); const cancelButton = querySingleVisibleElem(editContentZone, '.ui.cancel.button'); comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading(); comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState); cancelButton.addEventListener('click', cancelAndReset); - saveButton.addEventListener('click', saveAndRefresh); + form.addEventListener('submit', saveAndRefresh); } // FIXME: ideally here should reload content and attachment list from backend for existing editor, to avoid losing data diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index f41b6e210780a..9282e99c4a65f 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -16,7 +16,6 @@ import {GET, POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {initRepoIssueSidebar} from './repo-issue-sidebar.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; -import {applyAreYouSure} from '../vendor/jquery.are-you-sure.ts'; const {appSubUrl} = window.config; @@ -533,14 +532,13 @@ export function initRepoIssueWipToggle() { export function initRepoIssueTitleEdit() { const issueTitleDisplay = document.querySelector('#issue-title-display'); - const issueTitleEditor = document.querySelector('#issue-title-editor'); + const issueTitleEditor = document.querySelector('#issue-title-editor'); if (!issueTitleEditor) return; - applyAreYouSure(issueTitleEditor); - const issueTitleInput = issueTitleEditor.querySelector('input'); const oldTitle = issueTitleInput.getAttribute('data-old-title'); issueTitleDisplay.querySelector('#issue-title-edit-show').addEventListener('click', () => { + issueTitleEditor.classList.remove('ignore-dirty'); hideElem(issueTitleDisplay); hideElem('#pull-desc-display'); showElem(issueTitleEditor); @@ -551,6 +549,7 @@ export function initRepoIssueTitleEdit() { issueTitleInput.focus(); }); issueTitleEditor.querySelector('.ui.cancel.button').addEventListener('click', () => { + issueTitleEditor.classList.add('ignore-dirty'); hideElem(issueTitleEditor); hideElem('#pull-desc-editor'); showElem(issueTitleDisplay); @@ -561,7 +560,8 @@ export function initRepoIssueTitleEdit() { const prTargetUpdateUrl = pullDescEditor?.getAttribute('data-target-update-url'); const editSaveButton = issueTitleEditor.querySelector('.ui.primary.button'); - editSaveButton.addEventListener('click', async () => { + issueTitleEditor.addEventListener('submit', async (e) => { + e.preventDefault(); const newTitle = issueTitleInput.value.trim(); try { if (newTitle && newTitle !== oldTitle) { @@ -580,6 +580,7 @@ export function initRepoIssueTitleEdit() { } } } + issueTitleEditor.classList.remove('dirty'); window.location.reload(); } catch (error) { console.error(error); diff --git a/web_src/js/vendor/jquery.are-you-sure.ts b/web_src/js/vendor/jquery.are-you-sure.ts index 9efe783c54775..b7f6a12442866 100644 --- a/web_src/js/vendor/jquery.are-you-sure.ts +++ b/web_src/js/vendor/jquery.are-you-sure.ts @@ -2,6 +2,7 @@ // Fork of the upstream module. The only changes are: // * use export to make it work with ES6 modules. // * the addition of `const` to make it strict mode compatible. +// * check "ignore-dirty" class, ignore forms with this class. /*! * jQuery Plugin: Are-You-Sure (Dirty Form Detection) @@ -161,7 +162,7 @@ export function initAreYouSure($) { if (!settings.silent && !window.aysUnloadSet) { window.aysUnloadSet = true; $(window).bind('beforeunload', function() { - const $dirtyForms = $("form").filter('.' + settings.dirtyClass); + const $dirtyForms = $("form:not(.ignore-dirty)").filter('.' + settings.dirtyClass); if ($dirtyForms.length == 0) { return; } @@ -199,3 +200,7 @@ export function initAreYouSure($) { export function applyAreYouSure(selectorOrEl: string|Element|$, opts = {}) { $(selectorOrEl).areYouSure(opts); } + +export function reinitializeAreYouSure(selectorOrEl: string|Element|$) { + $(selectorOrEl).trigger('reinitialize.areYouSure'); +} From f0851e497b1740aef1221562926295c978660aba Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 20 Dec 2024 22:41:41 +0800 Subject: [PATCH 3/7] modern html doesn't use `/>` --- templates/repo/issue/view_title.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index 4e4c53f5ef728..0354f6ef2292c 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -28,7 +28,7 @@ {{if $canEditIssueTitle}}
- +
From 84c914fe39fa708a9597d94d95ba3f5ab9b48342 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 20 Dec 2024 22:47:09 +0800 Subject: [PATCH 4/7] fine tune edge cases --- web_src/js/features/repo-issue-edit.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index 6df86a827ad33..fe05e007998df 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -32,7 +32,6 @@ async function tryOnEditContent(e) { const saveAndRefresh = async (e) => { e.preventDefault(); - form.classList.remove('ignore-dirty'); renderContent.classList.add('is-loading'); showElem(renderContent); hideElem(editContentZone); @@ -52,6 +51,7 @@ async function tryOnEditContent(e) { showErrorToast(data.errorMessage); return; } + form.classList.remove('ignore-dirty'); // the form is no longer dirty reinitializeAreYouSure(form); editContentZone.setAttribute('data-content-version', data.contentVersion); if (!data.content) { @@ -101,6 +101,7 @@ async function tryOnEditContent(e) { cancelButton.addEventListener('click', cancelAndReset); form.addEventListener('submit', saveAndRefresh); } + form.classList.remove('ignore-dirty'); // FIXME: ideally here should reload content and attachment list from backend for existing editor, to avoid losing data if (!comboMarkdownEditor.value()) { From 9d4f26eb3755a40b1095b7fe82b1f143e1e8f174 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 20 Dec 2024 22:51:13 +0800 Subject: [PATCH 5/7] fine tune edge cases --- web_src/js/features/repo-issue-edit.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index fe05e007998df..a04fc5e2bd784 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -100,8 +100,10 @@ async function tryOnEditContent(e) { comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState); cancelButton.addEventListener('click', cancelAndReset); form.addEventListener('submit', saveAndRefresh); + } else { + form = editContentZone.querySelector('form'); + form.classList.remove('ignore-dirty'); // the form is shown again, respect the "dirty" state } - form.classList.remove('ignore-dirty'); // FIXME: ideally here should reload content and attachment list from backend for existing editor, to avoid losing data if (!comboMarkdownEditor.value()) { From be12c62251087d0c038c0b8d9d39f0903a76b40c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 20 Dec 2024 23:05:57 +0800 Subject: [PATCH 6/7] do not check hidden form's dirty state --- web_src/js/features/repo-issue-edit.ts | 10 ++-------- web_src/js/features/repo-issue.ts | 2 -- web_src/js/vendor/jquery.are-you-sure.ts | 8 ++++---- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/web_src/js/features/repo-issue-edit.ts b/web_src/js/features/repo-issue-edit.ts index a04fc5e2bd784..cf4c223e0368e 100644 --- a/web_src/js/features/repo-issue-edit.ts +++ b/web_src/js/features/repo-issue-edit.ts @@ -20,11 +20,9 @@ async function tryOnEditContent(e) { const rawContent = segment.querySelector('.raw-content'); let comboMarkdownEditor : ComboMarkdownEditor; - let form: HTMLFormElement; const cancelAndReset = (e) => { e.preventDefault(); - form.classList.add('ignore-dirty'); showElem(renderContent); hideElem(editContentZone); comboMarkdownEditor.dropzoneReloadFiles(); @@ -51,8 +49,7 @@ async function tryOnEditContent(e) { showErrorToast(data.errorMessage); return; } - form.classList.remove('ignore-dirty'); // the form is no longer dirty - reinitializeAreYouSure(form); + reinitializeAreYouSure(editContentZone.querySelector('form')); // the form is no longer dirty editContentZone.setAttribute('data-content-version', data.contentVersion); if (!data.content) { renderContent.innerHTML = document.querySelector('#no-content').innerHTML; @@ -91,7 +88,7 @@ async function tryOnEditContent(e) { comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); if (!comboMarkdownEditor) { editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML; - form = editContentZone.querySelector('form'); + const form = editContentZone.querySelector('form'); applyAreYouSure(form); const saveButton = querySingleVisibleElem(editContentZone, '.ui.primary.button'); const cancelButton = querySingleVisibleElem(editContentZone, '.ui.cancel.button'); @@ -100,9 +97,6 @@ async function tryOnEditContent(e) { comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState); cancelButton.addEventListener('click', cancelAndReset); form.addEventListener('submit', saveAndRefresh); - } else { - form = editContentZone.querySelector('form'); - form.classList.remove('ignore-dirty'); // the form is shown again, respect the "dirty" state } // FIXME: ideally here should reload content and attachment list from backend for existing editor, to avoid losing data diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index 9282e99c4a65f..1e1bbd64f0726 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -538,7 +538,6 @@ export function initRepoIssueTitleEdit() { const issueTitleInput = issueTitleEditor.querySelector('input'); const oldTitle = issueTitleInput.getAttribute('data-old-title'); issueTitleDisplay.querySelector('#issue-title-edit-show').addEventListener('click', () => { - issueTitleEditor.classList.remove('ignore-dirty'); hideElem(issueTitleDisplay); hideElem('#pull-desc-display'); showElem(issueTitleEditor); @@ -549,7 +548,6 @@ export function initRepoIssueTitleEdit() { issueTitleInput.focus(); }); issueTitleEditor.querySelector('.ui.cancel.button').addEventListener('click', () => { - issueTitleEditor.classList.add('ignore-dirty'); hideElem(issueTitleEditor); hideElem('#pull-desc-editor'); showElem(issueTitleDisplay); diff --git a/web_src/js/vendor/jquery.are-you-sure.ts b/web_src/js/vendor/jquery.are-you-sure.ts index b7f6a12442866..ba5a1a5685cdb 100644 --- a/web_src/js/vendor/jquery.are-you-sure.ts +++ b/web_src/js/vendor/jquery.are-you-sure.ts @@ -162,10 +162,10 @@ export function initAreYouSure($) { if (!settings.silent && !window.aysUnloadSet) { window.aysUnloadSet = true; $(window).bind('beforeunload', function() { - const $dirtyForms = $("form:not(.ignore-dirty)").filter('.' + settings.dirtyClass); - if ($dirtyForms.length == 0) { - return; - } + const $forms = $("form:not(.ignore-dirty)").filter('.' + settings.dirtyClass); + const dirtyFormCount = Array.from($forms).reduce((res, form) => form.closest('.tw-hidden') ? res : res + 1, 0); + if (dirtyFormCount === 0) return; + // Prevent multiple prompts - seen on Chrome and IE if (navigator.userAgent.toLowerCase().match(/msie|chrome/)) { if (window.aysHasPrompted) { From cffeec3b1eac2cfb80f89836e8d55accbb2b2c20 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 20 Dec 2024 23:08:18 +0800 Subject: [PATCH 7/7] sync comment --- web_src/js/vendor/jquery.are-you-sure.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/vendor/jquery.are-you-sure.ts b/web_src/js/vendor/jquery.are-you-sure.ts index ba5a1a5685cdb..7f0bef8040d02 100644 --- a/web_src/js/vendor/jquery.are-you-sure.ts +++ b/web_src/js/vendor/jquery.are-you-sure.ts @@ -2,7 +2,7 @@ // Fork of the upstream module. The only changes are: // * use export to make it work with ES6 modules. // * the addition of `const` to make it strict mode compatible. -// * check "ignore-dirty" class, ignore forms with this class. +// * ignore forms with "ignore-dirty" class, ignore hidden forms (closest('.tw-hidden')) /*! * jQuery Plugin: Are-You-Sure (Dirty Form Detection)