1
1
mirror of https://github.com/go-gitea/gitea synced 2024-12-22 08:34:26 +00:00

show warning on navigation if currently editing comment or title (#32920)

This PR fixes the issue https://github.com/go-gitea/gitea/issues/32223

Make the browser to show the confirm popup, as it does with other forms.

---------

Co-authored-by: Tim Wundenberg <tim@wundenbergs.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Tim 2024-12-20 16:39:19 +01:00 committed by GitHub
parent 52b319bc00
commit 7580bd98c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 27 additions and 16 deletions

View File

@ -139,7 +139,7 @@
</div> </div>
<template id="issue-comment-editor-template"> <template id="issue-comment-editor-template">
<div class="ui form comment"> <form class="ui form comment">
<div class="field"> <div class="field">
{{template "shared/combomarkdowneditor" (dict {{template "shared/combomarkdowneditor" (dict
"CustomInit" true "CustomInit" true
@ -158,11 +158,11 @@
<div class="field"> <div class="field">
<div class="text right edit"> <div class="text right edit">
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button> <button type="button" class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.save"}}</button> <button type="submit" class="ui primary button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
</div> </div>
</div> </div>
</div> </form>
</template> </template>
{{template "repo/issue/view_content/reference_issue_dialog" .}} {{template "repo/issue/view_content/reference_issue_dialog" .}}

View File

@ -26,17 +26,17 @@
</div> </div>
</div> </div>
{{if $canEditIssueTitle}} {{if $canEditIssueTitle}}
<div class="ui form issue-title tw-hidden" id="issue-title-editor"> <form class="ui form issue-title tw-hidden" id="issue-title-editor">
<div class="ui input tw-flex-1"> <div class="ui input tw-flex-1">
<input value="{{.Issue.Title}}" data-old-title="{{.Issue.Title}}" maxlength="255" autocomplete="off"> <input name="title" value="{{.Issue.Title}}" data-old-title="{{.Issue.Title}}" maxlength="255" autocomplete="off">
</div> </div>
<div class="issue-title-buttons"> <div class="issue-title-buttons">
<button class="ui small basic cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button> <button type="button" class="ui small basic cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
<button class="ui small primary button" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/title"> <button type="submit" class="ui small primary button" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/title">
{{ctx.Locale.Tr "repo.issues.save"}} {{ctx.Locale.Tr "repo.issues.save"}}
</button> </button>
</div> </div>
</div> </form>
{{end}} {{end}}
<div class="issue-title-meta"> <div class="issue-title-meta">
{{if .HasMerged}} {{if .HasMerged}}

View File

@ -7,6 +7,7 @@ import {attachRefIssueContextPopup} from './contextpopup.ts';
import {initCommentContent, initMarkupContent} from '../markup/content.ts'; import {initCommentContent, initMarkupContent} from '../markup/content.ts';
import {triggerUploadStateChanged} from './comp/EditorUpload.ts'; import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts'; import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
async function tryOnEditContent(e) { async function tryOnEditContent(e) {
const clickTarget = e.target.closest('.edit-content'); const clickTarget = e.target.closest('.edit-content');
@ -48,6 +49,7 @@ async function tryOnEditContent(e) {
showErrorToast(data.errorMessage); showErrorToast(data.errorMessage);
return; return;
} }
reinitializeAreYouSure(editContentZone.querySelector('form')); // the form is no longer dirty
editContentZone.setAttribute('data-content-version', data.contentVersion); editContentZone.setAttribute('data-content-version', data.contentVersion);
if (!data.content) { if (!data.content) {
renderContent.innerHTML = document.querySelector('#no-content').innerHTML; renderContent.innerHTML = document.querySelector('#no-content').innerHTML;
@ -86,13 +88,15 @@ async function tryOnEditContent(e) {
comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
if (!comboMarkdownEditor) { if (!comboMarkdownEditor) {
editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML; editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML;
const form = editContentZone.querySelector('form');
applyAreYouSure(form);
const saveButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.primary.button'); const saveButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.primary.button');
const cancelButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.cancel.button'); const cancelButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.cancel.button');
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')); comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading(); const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading();
comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState); comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
cancelButton.addEventListener('click', cancelAndReset); 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 // FIXME: ideally here should reload content and attachment list from backend for existing editor, to avoid losing data

View File

@ -532,7 +532,7 @@ export function initRepoIssueWipToggle() {
export function initRepoIssueTitleEdit() { export function initRepoIssueTitleEdit() {
const issueTitleDisplay = document.querySelector('#issue-title-display'); const issueTitleDisplay = document.querySelector('#issue-title-display');
const issueTitleEditor = document.querySelector('#issue-title-editor'); const issueTitleEditor = document.querySelector<HTMLFormElement>('#issue-title-editor');
if (!issueTitleEditor) return; if (!issueTitleEditor) return;
const issueTitleInput = issueTitleEditor.querySelector('input'); const issueTitleInput = issueTitleEditor.querySelector('input');
@ -558,7 +558,8 @@ export function initRepoIssueTitleEdit() {
const prTargetUpdateUrl = pullDescEditor?.getAttribute('data-target-update-url'); const prTargetUpdateUrl = pullDescEditor?.getAttribute('data-target-update-url');
const editSaveButton = issueTitleEditor.querySelector('.ui.primary.button'); const editSaveButton = issueTitleEditor.querySelector('.ui.primary.button');
editSaveButton.addEventListener('click', async () => { issueTitleEditor.addEventListener('submit', async (e) => {
e.preventDefault();
const newTitle = issueTitleInput.value.trim(); const newTitle = issueTitleInput.value.trim();
try { try {
if (newTitle && newTitle !== oldTitle) { if (newTitle && newTitle !== oldTitle) {
@ -577,6 +578,7 @@ export function initRepoIssueTitleEdit() {
} }
} }
} }
issueTitleEditor.classList.remove('dirty');
window.location.reload(); window.location.reload();
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@ -2,6 +2,7 @@
// Fork of the upstream module. The only changes are: // Fork of the upstream module. The only changes are:
// * use export to make it work with ES6 modules. // * use export to make it work with ES6 modules.
// * the addition of `const` to make it strict mode compatible. // * the addition of `const` to make it strict mode compatible.
// * ignore forms with "ignore-dirty" class, ignore hidden forms (closest('.tw-hidden'))
/*! /*!
* jQuery Plugin: Are-You-Sure (Dirty Form Detection) * jQuery Plugin: Are-You-Sure (Dirty Form Detection)
@ -161,10 +162,10 @@ export function initAreYouSure($) {
if (!settings.silent && !window.aysUnloadSet) { if (!settings.silent && !window.aysUnloadSet) {
window.aysUnloadSet = true; window.aysUnloadSet = true;
$(window).bind('beforeunload', function() { $(window).bind('beforeunload', function() {
const $dirtyForms = $("form").filter('.' + settings.dirtyClass); const $forms = $("form:not(.ignore-dirty)").filter('.' + settings.dirtyClass);
if ($dirtyForms.length == 0) { const dirtyFormCount = Array.from($forms).reduce((res, form) => form.closest('.tw-hidden') ? res : res + 1, 0);
return; if (dirtyFormCount === 0) return;
}
// Prevent multiple prompts - seen on Chrome and IE // Prevent multiple prompts - seen on Chrome and IE
if (navigator.userAgent.toLowerCase().match(/msie|chrome/)) { if (navigator.userAgent.toLowerCase().match(/msie|chrome/)) {
if (window.aysHasPrompted) { if (window.aysHasPrompted) {
@ -199,3 +200,7 @@ export function initAreYouSure($) {
export function applyAreYouSure(selectorOrEl: string|Element|$, opts = {}) { export function applyAreYouSure(selectorOrEl: string|Element|$, opts = {}) {
$(selectorOrEl).areYouSure(opts); $(selectorOrEl).areYouSure(opts);
} }
export function reinitializeAreYouSure(selectorOrEl: string|Element|$) {
$(selectorOrEl).trigger('reinitialize.areYouSure');
}