From d4bd99b9b0088e855048d454d1fda5e29dfb4f47 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 26 Jun 2024 16:16:27 +0800 Subject: [PATCH 01/13] fix --- templates/devtest/gitea-ui.tmpl | 11 ----------- templates/devtest/toast.tmpl | 14 ++++++++++++++ web_src/js/modules/toast.js | 18 ++++++++++++++++-- 3 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 templates/devtest/toast.tmpl diff --git a/templates/devtest/gitea-ui.tmpl b/templates/devtest/gitea-ui.tmpl index ea293fd3b41b4..06d0e36569a15 100644 --- a/templates/devtest/gitea-ui.tmpl +++ b/templates/devtest/gitea-ui.tmpl @@ -182,15 +182,6 @@ -
-

Toast

-
- - - -
-
-

ComboMarkdownEditor

ps: no JS code attached, so just a layout
@@ -201,7 +192,5 @@
- -
{{template "base/footer" .}} diff --git a/templates/devtest/toast.tmpl b/templates/devtest/toast.tmpl new file mode 100644 index 0000000000000..a1af5636d0733 --- /dev/null +++ b/templates/devtest/toast.tmpl @@ -0,0 +1,14 @@ +{{template "base/head" .}} + +
+

Toast

+
+ + + +
+
+ + + +{{template "base/footer" .}} diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index d12d203718bae..5b7ed566b1d5c 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -21,12 +21,24 @@ const levels = { }; // See https://github.com/apvarun/toastify-js#api for options -function showToast(message, level, {gravity, position, duration, useHtmlBody, ...other} = {}) { +function showToast(message, level, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other} = {}) { + const body = useHtmlBody ? String(message) : htmlEscape(message); + const key = `${level}-${body}`; + + // prevent showing duplicate toasts with same level and message, hide all existing toasts with same key + if (preventDuplicates) { + const toastElements = document.querySelectorAll(`.toastify[data-toast-unique-key="${CSS.escape(key)}"]`); + for (const el of toastElements) { + el.remove(); // "hideToast" only removes the toast after an unchangeable delay, so we need to remove it immediately to make the "reposition" work with new toasts + el._toastInst?.hideToast(); + } + } + const {icon, background, duration: levelDuration} = levels[level ?? 'info']; const toast = Toastify({ text: `
${svg(icon)}
-
${useHtmlBody ? message : htmlEscape(message)}
+
${body}
`, escapeMarkup: false, @@ -39,6 +51,8 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, .. toast.showToast(); toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast()); + toast.toastElement.setAttribute('data-toast-unique-key', key); + toast.toastElement._toastInst = toast; return toast; } From f6034b286567165583de743fa8f3451f432da733 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 26 Jun 2024 18:33:08 +0800 Subject: [PATCH 02/13] show duplicate number --- web_src/css/modules/animations.css | 4 ++-- web_src/css/modules/toast.css | 15 +++++++++++++-- web_src/js/modules/toast.js | 19 +++++++++++++------ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css index a86c9234aa335..be265bb0b4c78 100644 --- a/web_src/css/modules/animations.css +++ b/web_src/css/modules/animations.css @@ -97,7 +97,7 @@ code.language-math.is-loading::after { transform: scale(1); } 50% { - transform: scale(1.8); + transform: scale(1.5); } 100% { transform: scale(1); @@ -105,7 +105,7 @@ code.language-math.is-loading::after { } .pulse { - animation: pulse 2s linear; + animation: pulse 200ms linear; } .ui.modal, diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css index 2a9f78e01756c..ae4b4e3a15680 100644 --- a/web_src/css/modules/toast.css +++ b/web_src/css/modules/toast.css @@ -29,12 +29,23 @@ background: transparent; border: none; display: flex; - width: 30px; - height: 30px; + min-width: 30px; + min-height: 30px; justify-content: center; align-items: center; } +.toast-duplicate-number::before { + content: '('; +} +.toast-duplicate-number { + display: inline-flex; + margin: 0 2px; +} +.toast-duplicate-number::after { + content: ')'; +} + .toast-close:hover { background: var(--color-hover); } diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index 5b7ed566b1d5c..497422daaba09 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -1,6 +1,7 @@ import {htmlEscape} from 'escape-goat'; import {svg} from '../svg.js'; -import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown +import Toastify from 'toastify-js'; +import {showElem} from "../utils/dom.js"; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown const levels = { info: { @@ -27,17 +28,23 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr // prevent showing duplicate toasts with same level and message, hide all existing toasts with same key if (preventDuplicates) { - const toastElements = document.querySelectorAll(`.toastify[data-toast-unique-key="${CSS.escape(key)}"]`); - for (const el of toastElements) { - el.remove(); // "hideToast" only removes the toast after an unchangeable delay, so we need to remove it immediately to make the "reposition" work with new toasts - el._toastInst?.hideToast(); + const toastEl = document.querySelector(`.toastify[data-toast-unique-key="${CSS.escape(key)}"]`); + if (toastEl) { + const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number'); + showElem(toastDupNumEl); + toastDupNumEl.textContent = String(Number(toastDupNumEl.textContent) + 1); + toastDupNumEl.classList.remove('pulse'); + requestAnimationFrame(() => { + toastDupNumEl.classList.add('pulse'); + }); + return; } } const {icon, background, duration: levelDuration} = levels[level ?? 'info']; const toast = Toastify({ text: ` -
${svg(icon)}
+
${svg(icon)}1
${body}
`, From 019d8e6c57eeeab7d0572322bb6a1b7f66bcb65b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 26 Jun 2024 18:38:19 +0800 Subject: [PATCH 03/13] fix lint --- web_src/js/modules/toast.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index 497422daaba09..c0eb72f1ddd5b 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -1,7 +1,7 @@ import {htmlEscape} from 'escape-goat'; import {svg} from '../svg.js'; import Toastify from 'toastify-js'; -import {showElem} from "../utils/dom.js"; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown +import {showElem} from '../utils/dom.js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown const levels = { info: { From 27e5ed96b64f0d5a9adbd103bd4ef26b25b39bb9 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 26 Jun 2024 18:39:28 +0800 Subject: [PATCH 04/13] fix import comment --- web_src/js/modules/toast.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index c0eb72f1ddd5b..9027f7bf7b4e8 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -1,7 +1,7 @@ import {htmlEscape} from 'escape-goat'; import {svg} from '../svg.js'; -import Toastify from 'toastify-js'; -import {showElem} from '../utils/dom.js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown +import {showElem} from '../utils/dom.js'; +import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown const levels = { info: { From 4c81b7cff4e7c6b1cc5864bd16763e39ac619e9f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 26 Jun 2024 18:44:16 +0800 Subject: [PATCH 05/13] fix lint --- web_src/css/modules/toast.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css index ae4b4e3a15680..9112eebf89fdd 100644 --- a/web_src/css/modules/toast.css +++ b/web_src/css/modules/toast.css @@ -36,14 +36,14 @@ } .toast-duplicate-number::before { - content: '('; + content: "("; } .toast-duplicate-number { display: inline-flex; margin: 0 2px; } .toast-duplicate-number::after { - content: ')'; + content: ")"; } .toast-close:hover { From 41e387c68937dd884ace0678014bfcfcae4d73e0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 26 Jun 2024 18:46:57 +0800 Subject: [PATCH 06/13] fix comment --- web_src/js/modules/toast.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index 9027f7bf7b4e8..e9c9c1ea1de52 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -26,7 +26,7 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr const body = useHtmlBody ? String(message) : htmlEscape(message); const key = `${level}-${body}`; - // prevent showing duplicate toasts with same level and message, hide all existing toasts with same key + // prevent showing duplicate toasts with same level and message, and give a visual feedback for end users if (preventDuplicates) { const toastEl = document.querySelector(`.toastify[data-toast-unique-key="${CSS.escape(key)}"]`); if (toastEl) { @@ -34,9 +34,7 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr showElem(toastDupNumEl); toastDupNumEl.textContent = String(Number(toastDupNumEl.textContent) + 1); toastDupNumEl.classList.remove('pulse'); - requestAnimationFrame(() => { - toastDupNumEl.classList.add('pulse'); - }); + requestAnimationFrame(() => toastDupNumEl.classList.add('pulse')); return; } } From eac3e9959e89b6debf78d2b2e33910f912565747 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 26 Jun 2024 19:12:52 +0800 Subject: [PATCH 07/13] remove unnecessary prop --- web_src/js/modules/toast.js | 1 - 1 file changed, 1 deletion(-) diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index e9c9c1ea1de52..c26e7e46c3557 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -57,7 +57,6 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr toast.showToast(); toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast()); toast.toastElement.setAttribute('data-toast-unique-key', key); - toast.toastElement._toastInst = toast; return toast; } From 0b5a4bc30ea44a73b4b59694269860a34377d203 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Jun 2024 02:50:26 +0800 Subject: [PATCH 08/13] Update web_src/css/modules/toast.css Co-authored-by: silverwind --- web_src/css/modules/toast.css | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css index 9112eebf89fdd..a8260d637b719 100644 --- a/web_src/css/modules/toast.css +++ b/web_src/css/modules/toast.css @@ -41,6 +41,7 @@ .toast-duplicate-number { display: inline-flex; margin: 0 2px; + user-select: none; } .toast-duplicate-number::after { content: ")"; From 6a9ed8c6861b42c77afadf7142faa511423b12d7 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Jun 2024 02:58:34 +0800 Subject: [PATCH 09/13] fix ani --- web_src/js/modules/toast.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index c26e7e46c3557..cf01c1514df76 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -33,8 +33,11 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number'); showElem(toastDupNumEl); toastDupNumEl.textContent = String(Number(toastDupNumEl.textContent) + 1); - toastDupNumEl.classList.remove('pulse'); - requestAnimationFrame(() => toastDupNumEl.classList.add('pulse')); + toastDupNumEl.classList.add('pulse'); + if (!toastDupNumEl.getAttribute('data-animation-event-attached')) { + toastDupNumEl.addEventListener('animationend', () => toastDupNumEl.classList.remove('pulse')); + toastDupNumEl.setAttribute('data-animation-event-attached', 'true'); + } return; } } From 9872c90b258f2fd6e490805d76b5e06d2d7b18c7 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Jun 2024 03:07:31 +0800 Subject: [PATCH 10/13] test buttons --- templates/devtest/toast.tmpl | 7 ++++--- web_src/css/modules/toast.css | 4 ++++ web_src/js/standalone/devtest.js | 21 ++++++++++++--------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/templates/devtest/toast.tmpl b/templates/devtest/toast.tmpl index a1af5636d0733..003fc223a12ce 100644 --- a/templates/devtest/toast.tmpl +++ b/templates/devtest/toast.tmpl @@ -3,9 +3,10 @@

Toast

- - - + + + +
diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css index a8260d637b719..46bcc12433375 100644 --- a/web_src/css/modules/toast.css +++ b/web_src/css/modules/toast.css @@ -35,6 +35,10 @@ align-items: center; } +.toast-icon svg { + width: 30px; +} + .toast-duplicate-number::before { content: "("; } diff --git a/web_src/js/standalone/devtest.js b/web_src/js/standalone/devtest.js index 8dbba554acb80..d3e3e13a8790b 100644 --- a/web_src/js/standalone/devtest.js +++ b/web_src/js/standalone/devtest.js @@ -1,11 +1,14 @@ import {showInfoToast, showWarningToast, showErrorToast} from '../modules/toast.js'; -document.querySelector('#info-toast').addEventListener('click', () => { - showInfoToast('success 😀'); -}); -document.querySelector('#warning-toast').addEventListener('click', () => { - showWarningToast('warning 😐'); -}); -document.querySelector('#error-toast').addEventListener('click', () => { - showErrorToast('error 🙁'); -}); +function initDevtestToast() { + const levelMap = {info: showInfoToast, warning: showWarningToast, error: showErrorToast}; + for (const el of document.querySelectorAll('.toast-test-button')) { + el.addEventListener('click', () => { + const level = el.getAttribute('data-toast-level'); + const message = el.getAttribute('data-toast-message'); + levelMap[level](message); + }); + } +} + +initDevtestToast(); From 0bc60e25368d5a5f4f7f780bac46c6617ee5ed5b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Jun 2024 17:26:35 +0800 Subject: [PATCH 11/13] improve styles --- templates/devtest/toast.tmpl | 2 +- web_src/css/modules/toast.css | 22 ++++++++++------------ web_src/js/modules/toast.js | 6 +++--- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/templates/devtest/toast.tmpl b/templates/devtest/toast.tmpl index 003fc223a12ce..412f23964a2b5 100644 --- a/templates/devtest/toast.tmpl +++ b/templates/devtest/toast.tmpl @@ -6,7 +6,7 @@ - + diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css index 46bcc12433375..1145f3b1b58be 100644 --- a/web_src/css/modules/toast.css +++ b/web_src/css/modules/toast.css @@ -22,29 +22,27 @@ overflow-wrap: anywhere; } -.toast-close, -.toast-icon { - color: currentcolor; +.toast-close { border-radius: var(--border-radius); - background: transparent; - border: none; - display: flex; - min-width: 30px; - min-height: 30px; + width: 30px; + height: 30px; justify-content: center; - align-items: center; } -.toast-icon svg { +.toast-icon { + display: inline-flex; width: 30px; + height: 30px; + align-items: center; + justify-content: center; } .toast-duplicate-number::before { content: "("; } .toast-duplicate-number { - display: inline-flex; - margin: 0 2px; + display: inline-block; + margin-right: 5px; user-select: none; } .toast-duplicate-number::after { diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index cf01c1514df76..874f1d599aa0f 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -45,9 +45,9 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr const {icon, background, duration: levelDuration} = levels[level ?? 'info']; const toast = Toastify({ text: ` -
${svg(icon)}1
-
${body}
- +
${svg(icon)}
+
1${body}
+ `, escapeMarkup: false, gravity: gravity ?? 'top', From 91d3062ec490694fb35cfb8dfcad1e8b20965d4c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Jun 2024 21:15:46 +0800 Subject: [PATCH 12/13] fix another incorrect animation --- web_src/js/features/repo-diff.js | 8 ++------ web_src/js/modules/toast.js | 8 ++------ web_src/js/utils/dom.js | 11 +++++++++++ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index cd01232a7e533..6ef6ccd462fe8 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -7,7 +7,7 @@ import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js'; import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js'; import {initImageDiff} from './imagediff.js'; import {showErrorToast} from '../modules/toast.js'; -import {submitEventSubmitter, queryElemSiblings, hideElem, showElem} from '../utils/dom.js'; +import {submitEventSubmitter, queryElemSiblings, hideElem, showElem, animateOnce} from '../utils/dom.js'; import {POST, GET} from '../modules/fetch.js'; const {pageData, i18n} = window.config; @@ -26,11 +26,7 @@ function initRepoDiffReviewButton() { const num = parseInt(counter.getAttribute('data-pending-comment-number')) + 1 || 1; counter.setAttribute('data-pending-comment-number', num); counter.textContent = num; - - reviewBox.classList.remove('pulse'); - requestAnimationFrame(() => { - reviewBox.classList.add('pulse'); - }); + animateOnce(reviewBox, 'pulse'); }); }); } diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index 874f1d599aa0f..cb02586598eb3 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -1,6 +1,6 @@ import {htmlEscape} from 'escape-goat'; import {svg} from '../svg.js'; -import {showElem} from '../utils/dom.js'; +import {animateOnce, showElem} from '../utils/dom.js'; import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown const levels = { @@ -33,11 +33,7 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number'); showElem(toastDupNumEl); toastDupNumEl.textContent = String(Number(toastDupNumEl.textContent) + 1); - toastDupNumEl.classList.add('pulse'); - if (!toastDupNumEl.getAttribute('data-animation-event-attached')) { - toastDupNumEl.addEventListener('animationend', () => toastDupNumEl.classList.remove('pulse')); - toastDupNumEl.setAttribute('data-animation-event-attached', 'true'); - } + animateOnce(toastDupNumEl, 'pulse'); return; } } diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js index 6a38968899f9c..9bdb2332362a8 100644 --- a/web_src/js/utils/dom.js +++ b/web_src/js/utils/dom.js @@ -306,3 +306,14 @@ export function createElementFromAttrs(tagName, attrs) { } return el; } + +export function animateOnce(el, animationClassName) { + return new Promise((resolve) => { + el.addEventListener('animationend', function onAnimationEnd() { + el.classList.remove(animationClassName); + el.removeEventListener('animationend', onAnimationEnd); + resolve(); + }, {once: true}); + el.classList.add(animationClassName); + }); +} From 136f480e92a5b2e5949d4b0363f13757db5d3882 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Jun 2024 21:30:25 +0800 Subject: [PATCH 13/13] fix animation --- web_src/css/modules/animations.css | 8 +++++--- web_src/js/features/repo-diff.js | 2 +- web_src/js/modules/toast.js | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css index be265bb0b4c78..481e997d4fcd0 100644 --- a/web_src/css/modules/animations.css +++ b/web_src/css/modules/animations.css @@ -92,7 +92,8 @@ code.language-math.is-loading::after { } } -@keyframes pulse { +/* 1p5 means 1-point-5. it can't use "pulse" here, otherwise the animation is not right (maybe due to some conflicts */ +@keyframes pulse-1p5 { 0% { transform: scale(1); } @@ -104,8 +105,9 @@ code.language-math.is-loading::after { } } -.pulse { - animation: pulse 200ms linear; +/* pulse animation for scale(1.5) in 200ms */ +.pulse-1p5-200 { + animation: pulse-1p5 200ms linear; } .ui.modal, diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js index 6ef6ccd462fe8..279f6da757299 100644 --- a/web_src/js/features/repo-diff.js +++ b/web_src/js/features/repo-diff.js @@ -26,7 +26,7 @@ function initRepoDiffReviewButton() { const num = parseInt(counter.getAttribute('data-pending-comment-number')) + 1 || 1; counter.setAttribute('data-pending-comment-number', num); counter.textContent = num; - animateOnce(reviewBox, 'pulse'); + animateOnce(reviewBox, 'pulse-1p5-200'); }); }); } diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js index cb02586598eb3..627e24a1ff953 100644 --- a/web_src/js/modules/toast.js +++ b/web_src/js/modules/toast.js @@ -33,7 +33,7 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number'); showElem(toastDupNumEl); toastDupNumEl.textContent = String(Number(toastDupNumEl.textContent) + 1); - animateOnce(toastDupNumEl, 'pulse'); + animateOnce(toastDupNumEl, 'pulse-1p5-200'); return; } }