diff options
Diffstat (limited to 'web_src')
-rw-r--r-- | web_src/js/features/admin/common.js | 2 | ||||
-rw-r--r-- | web_src/js/features/formatting.js | 31 | ||||
-rw-r--r-- | web_src/js/index.js | 5 | ||||
-rw-r--r-- | web_src/js/modules/tippy.js | 75 | ||||
-rw-r--r-- | web_src/js/webcomponents/README.md | 6 | ||||
-rw-r--r-- | web_src/js/webcomponents/webcomponents.js | 1 |
6 files changed, 51 insertions, 69 deletions
diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js index be5aa876a5..8f895152c7 100644 --- a/web_src/js/features/admin/common.js +++ b/web_src/js/features/admin/common.js @@ -178,7 +178,7 @@ export function initAdminCommon() { // Attach view detail modals $('.view-detail').on('click', function () { $detailModal.find('.content pre').text($(this).parents('tr').find('.notice-description').text()); - $detailModal.find('.sub.header').text($(this).parents('tr').find('.notice-created-time').text()); + $detailModal.find('.sub.header').text($(this).parents('tr').find('relative-time').attr('title')); $detailModal.modal('show'); return false; }); diff --git a/web_src/js/features/formatting.js b/web_src/js/features/formatting.js deleted file mode 100644 index 5590ba44d1..0000000000 --- a/web_src/js/features/formatting.js +++ /dev/null @@ -1,31 +0,0 @@ -const {lang} = document.documentElement; -const dateFormatter = new Intl.DateTimeFormat(lang, {year: 'numeric', month: 'long', day: 'numeric'}); -const shortDateFormatter = new Intl.DateTimeFormat(lang, {year: 'numeric', month: 'short', day: 'numeric'}); -const dateTimeFormatter = new Intl.DateTimeFormat(lang, {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric'}); - -export function initFormattingReplacements() { - // for each <time></time> tag, if it has the data-format attribute, format - // the text according to the user's chosen locale and formatter. - formatAllTimeElements(); -} - -function formatAllTimeElements() { - const timeElements = document.querySelectorAll('time[data-format]'); - for (const timeElement of timeElements) { - const formatter = getFormatter(timeElement.dataset.format); - timeElement.textContent = formatter.format(new Date(timeElement.dateTime)); - } -} - -function getFormatter(format) { - switch (format) { - case 'date': - return dateFormatter; - case 'short-date': - return shortDateFormatter; - case 'date-time': - return dateTimeFormatter; - default: - throw new Error('Unknown format'); - } -} diff --git a/web_src/js/index.js b/web_src/js/index.js index 878afb10d7..f7cbb24e85 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -74,7 +74,6 @@ import {initRepoBranchButton} from './features/repo-branch.js'; import {initCommonOrganization} from './features/common-organization.js'; import {initRepoWikiForm} from './features/repo-wiki.js'; import {initRepoCommentForm, initRepository} from './features/repo-legacy.js'; -import {initFormattingReplacements} from './features/formatting.js'; import {initCopyContent} from './features/copycontent.js'; import {initCaptcha} from './features/captcha.js'; import {initRepositoryActionView} from './components/RepoActionView.vue'; @@ -83,10 +82,6 @@ import {initGiteaFomantic} from './modules/fomantic.js'; import {onDomReady} from './utils/dom.js'; import {initRepoIssueList} from './features/repo-issue-list.js'; -// Run time-critical code as soon as possible. This is safe to do because this -// script appears at the end of <body> and rendered HTML is accessible at that point. -// TODO: replace them with CustomElements -initFormattingReplacements(); // Init Gitea's Fomantic settings initGiteaFomantic(); diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js index 0d57af4f0f..6cec95d766 100644 --- a/web_src/js/modules/tippy.js +++ b/web_src/js/modules/tippy.js @@ -6,7 +6,7 @@ export function createTippy(target, opts = {}) { animation: false, allowHTML: false, hideOnClick: false, - interactiveBorder: 30, + interactiveBorder: 20, ignoreAttributes: true, maxWidth: 500, // increase over default 350px arrow: `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`, @@ -36,6 +36,8 @@ export function createTippy(target, opts = {}) { * @returns {null|tippy} */ function attachTooltip(target, content = null) { + switchTitleToTooltip(target); + content = content ?? target.getAttribute('data-tooltip-content'); if (!content) return null; @@ -55,6 +57,18 @@ function attachTooltip(target, content = null) { return target._tippy; } +function switchTitleToTooltip(target) { + const title = target.getAttribute('title'); + if (title) { + target.setAttribute('data-tooltip-content', title); + target.setAttribute('aria-label', title); + // keep the attribute, in case there are some other "[title]" selectors + // and to prevent infinite loop with <relative-time> which will re-add + // title if it is absent + target.setAttribute('title', ''); + } +} + /** * Creating tooltip tippy instance is expensive, so we only create it when the user hovers over the element * According to https://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevent-event-order , mouseover event is fired before mouseenter event @@ -67,48 +81,57 @@ function lazyTooltipOnMouseHover(e) { attachTooltip(this); } -/** - * Activate the tooltip for all children elements - * And if the element has no aria-label, use the tooltip content as aria-label - * @param target {HTMLElement} - */ -function attachChildrenLazyTooltip(target) { - for (const el of target.querySelectorAll('[data-tooltip-content]')) { - el.addEventListener('mouseover', lazyTooltipOnMouseHover, true); +// Activate the tooltip for current element. +// If the element has no aria-label, use the tooltip content as aria-label. +function attachLazyTooltip(el) { + el.addEventListener('mouseover', lazyTooltipOnMouseHover, {capture: true}); - // meanwhile, if the element has no aria-label, use the tooltip content as aria-label - if (!el.hasAttribute('aria-label')) { - const content = target.getAttribute('data-tooltip-content'); - if (content) { - el.setAttribute('aria-label', content); - } + // meanwhile, if the element has no aria-label, use the tooltip content as aria-label + if (!el.hasAttribute('aria-label')) { + const content = el.getAttribute('data-tooltip-content'); + if (content) { + el.setAttribute('aria-label', content); } } } +// Activate the tooltip for all children elements. +function attachChildrenLazyTooltip(target) { + for (const el of target.querySelectorAll('[data-tooltip-content]')) { + attachLazyTooltip(el); + } +} + +const elementNodeTypes = new Set([Node.ELEMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE]); + export function initGlobalTooltips() { - // use MutationObserver to detect new elements added to the DOM, or attributes changed - const observer = new MutationObserver((mutationList) => { - for (const mutation of mutationList) { + // use MutationObserver to detect new "data-tooltip-content" elements added to the DOM, or attributes changed + const observerConnect = (observer) => observer.observe(document, { + subtree: true, + childList: true, + attributeFilter: ['data-tooltip-content', 'title'] + }); + const observer = new MutationObserver((mutationList, observer) => { + const pending = observer.takeRecords(); + observer.disconnect(); + for (const mutation of [...mutationList, ...pending]) { if (mutation.type === 'childList') { // mainly for Vue components and AJAX rendered elements for (const el of mutation.addedNodes) { - // handle all "tooltip" elements in added nodes which have 'querySelectorAll' method, skip non-related nodes (eg: "#text") - if ('querySelectorAll' in el) { + if (elementNodeTypes.has(el.nodeType)) { attachChildrenLazyTooltip(el); + if (el.hasAttribute('data-tooltip-content')) { + attachLazyTooltip(el); + } } } } else if (mutation.type === 'attributes') { - // sync the tooltip content if the attributes change attachTooltip(mutation.target); } } + observerConnect(observer); }); - observer.observe(document, { - subtree: true, - childList: true, - attributeFilter: ['data-tooltip-content'], - }); + observerConnect(observer); attachChildrenLazyTooltip(document.documentElement); } diff --git a/web_src/js/webcomponents/README.md b/web_src/js/webcomponents/README.md index 2b586a63d2..0fde507310 100644 --- a/web_src/js/webcomponents/README.md +++ b/web_src/js/webcomponents/README.md @@ -10,9 +10,3 @@ https://developer.mozilla.org/en-US/docs/Web/Web_Components so they should have their own dependencies and should be very light, then they won't affect the page loading time too much. * If the component is not a public one, it's suggested to have its own `Gitea` or `gitea-` prefix to avoid conflicts. - -# TODO - -There are still some components that are not migrated to web components yet: - -* `<time data-format>` diff --git a/web_src/js/webcomponents/webcomponents.js b/web_src/js/webcomponents/webcomponents.js index 5c4afb1eec..7e8135aa00 100644 --- a/web_src/js/webcomponents/webcomponents.js +++ b/web_src/js/webcomponents/webcomponents.js @@ -1,3 +1,4 @@ import '@webcomponents/custom-elements'; // polyfill for some browsers like Pale Moon +import '@github/relative-time-element'; import './GiteaLocaleNumber.js'; import './GiteaOriginUrl.js'; |