aboutsummaryrefslogtreecommitdiffstats
path: root/web_src
diff options
context:
space:
mode:
Diffstat (limited to 'web_src')
-rw-r--r--web_src/js/features/admin/common.js2
-rw-r--r--web_src/js/features/formatting.js31
-rw-r--r--web_src/js/index.js5
-rw-r--r--web_src/js/modules/tippy.js75
-rw-r--r--web_src/js/webcomponents/README.md6
-rw-r--r--web_src/js/webcomponents/webcomponents.js1
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';