diff options
Diffstat (limited to 'web_src/js')
-rw-r--r-- | web_src/js/features/common-global.js | 3 | ||||
-rw-r--r-- | web_src/js/features/comp/ComboMarkdownEditor.js | 3 | ||||
-rw-r--r-- | web_src/js/features/repo-issue-content.js | 5 | ||||
-rw-r--r-- | web_src/js/features/repo-issue-list.js | 3 | ||||
-rw-r--r-- | web_src/js/modules/toast.js | 60 | ||||
-rw-r--r-- | web_src/js/modules/toast.test.js | 17 | ||||
-rw-r--r-- | web_src/js/standalone/devtest.js | 11 |
7 files changed, 97 insertions, 5 deletions
diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index e5fd7c29fc..a99b29141d 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -9,6 +9,7 @@ import {hideElem, showElem, toggleElem} from '../utils/dom.js'; import {htmlEscape} from 'escape-goat'; import {createTippy} from '../modules/tippy.js'; import {confirmModal} from './comp/ConfirmModal.js'; +import {showErrorToast} from '../modules/toast.js'; const {appUrl, appSubUrl, csrfToken, i18n} = window.config; @@ -439,7 +440,7 @@ export function initGlobalButtons() { return; } // should never happen, otherwise there is a bug in code - alert('Nothing to hide'); + showErrorToast('Nothing to hide'); }); initGlobalShowModal(); diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js index 103e71daae..3d696be75b 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.js +++ b/web_src/js/features/comp/ComboMarkdownEditor.js @@ -8,6 +8,7 @@ import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js'; import {renderPreviewPanelContent} from '../repo-editor.js'; import {easyMDEToolbarActions} from './EasyMDEToolbarActions.js'; import {initTextExpander} from './TextExpander.js'; +import {showErrorToast} from '../../modules/toast.js'; let elementIdCounter = 0; @@ -26,7 +27,7 @@ export function validateTextareaNonEmpty($textarea) { $form[0]?.reportValidity(); } else { // The alert won't hurt users too much, because we are dropping the EasyMDE and the check only occurs in a few places. - alert('Require non-empty content'); + showErrorToast('Require non-empty content'); } return false; } diff --git a/web_src/js/features/repo-issue-content.js b/web_src/js/features/repo-issue-content.js index d66f6ad4a4..fc916aea19 100644 --- a/web_src/js/features/repo-issue-content.js +++ b/web_src/js/features/repo-issue-content.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import {svg} from '../svg.js'; +import {showErrorToast} from '../modules/toast.js'; const {appSubUrl, csrfToken} = window.config; let i18nTextEdited; @@ -39,12 +40,12 @@ function showContentHistoryDetail(issueBaseUrl, commentId, historyId, itemTitleH if (resp.ok) { $dialog.modal('hide'); } else { - alert(resp.message); + showErrorToast(resp.message); } }); } } else { // required by eslint - window.alert(`unknown option item: ${optionItem}`); + showErrorToast(`unknown option item: ${optionItem}`); } }, onHide() { diff --git a/web_src/js/features/repo-issue-list.js b/web_src/js/features/repo-issue-list.js index 4d61de0ce5..5402f958f2 100644 --- a/web_src/js/features/repo-issue-list.js +++ b/web_src/js/features/repo-issue-list.js @@ -4,6 +4,7 @@ import {toggleElem} from '../utils/dom.js'; import {htmlEscape} from 'escape-goat'; import {Sortable} from 'sortablejs'; import {confirmModal} from './comp/ConfirmModal.js'; +import {showErrorToast} from '../modules/toast.js'; function initRepoIssueListCheckboxes() { const $issueSelectAll = $('.issue-checkbox-all'); @@ -75,7 +76,7 @@ function initRepoIssueListCheckboxes() { ).then(() => { window.location.reload(); }).catch((reason) => { - window.alert(reason.responseJSON.error); + showErrorToast(reason.responseJSON.error); }); }); } diff --git a/web_src/js/modules/toast.js b/web_src/js/modules/toast.js new file mode 100644 index 0000000000..b0d02dc644 --- /dev/null +++ b/web_src/js/modules/toast.js @@ -0,0 +1,60 @@ +import {htmlEscape} from 'escape-goat'; +import {svg} from '../svg.js'; + +const levels = { + info: { + icon: 'octicon-check', + background: 'var(--color-green)', + duration: 2500, + }, + warning: { + icon: 'gitea-exclamation', + background: 'var(--color-orange)', + duration: -1, // requires dismissal to hide + }, + error: { + icon: 'gitea-exclamation', + background: 'var(--color-red)', + duration: -1, // requires dismissal to hide + }, +}; + +// See https://github.com/apvarun/toastify-js#api for options +async function showToast(message, level, {gravity, position, duration, ...other} = {}) { + if (!message) return; + + const {default: Toastify} = await import(/* webpackChunkName: 'toastify' */'toastify-js'); + const {icon, background, duration: levelDuration} = levels[level ?? 'info']; + + const toast = Toastify({ + text: ` + <div class='toast-icon'>${svg(icon)}</div> + <div class='toast-body'>${htmlEscape(message)}</div> + <button class='toast-close'>${svg('octicon-x')}</button> + `, + escapeMarkup: false, + gravity: gravity ?? 'top', + position: position ?? 'center', + duration: duration ?? levelDuration, + style: {background}, + ...other, + }); + + toast.showToast(); + + toast.toastElement.querySelector('.toast-close').addEventListener('click', () => { + toast.removeElement(toast.toastElement); + }); +} + +export async function showInfoToast(message, opts) { + return await showToast(message, 'info', opts); +} + +export async function showWarningToast(message, opts) { + return await showToast(message, 'warning', opts); +} + +export async function showErrorToast(message, opts) { + return await showToast(message, 'error', opts); +} diff --git a/web_src/js/modules/toast.test.js b/web_src/js/modules/toast.test.js new file mode 100644 index 0000000000..b691aaebb6 --- /dev/null +++ b/web_src/js/modules/toast.test.js @@ -0,0 +1,17 @@ +import {test, expect} from 'vitest'; +import {showInfoToast, showErrorToast, showWarningToast} from './toast.js'; + +test('showInfoToast', async () => { + await showInfoToast('success 😀', {duration: -1}); + expect(document.querySelector('.toastify')).toBeTruthy(); +}); + +test('showWarningToast', async () => { + await showWarningToast('warning 😐', {duration: -1}); + expect(document.querySelector('.toastify')).toBeTruthy(); +}); + +test('showErrorToast', async () => { + await showErrorToast('error 🙁', {duration: -1}); + expect(document.querySelector('.toastify')).toBeTruthy(); +}); diff --git a/web_src/js/standalone/devtest.js b/web_src/js/standalone/devtest.js new file mode 100644 index 0000000000..d0ca511c0f --- /dev/null +++ b/web_src/js/standalone/devtest.js @@ -0,0 +1,11 @@ +import {showInfoToast, showWarningToast, showErrorToast} from '../modules/toast.js'; + +document.getElementById('info-toast').addEventListener('click', () => { + showInfoToast('success 😀'); +}); +document.getElementById('warning-toast').addEventListener('click', () => { + showWarningToast('warning 😐'); +}); +document.getElementById('error-toast').addEventListener('click', () => { + showErrorToast('error 🙁'); +}); |