From c11edada828dc734cce6988e5819630a73326085 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 21 Jul 2023 20:06:32 -0300 Subject: content, test: linkContribution: tooltip icons --- src/static/client3.js | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/static/site5.css | 42 ++++++++++++++ 2 files changed, 192 insertions(+) (limited to 'src/static') diff --git a/src/static/client3.js b/src/static/client3.js index 8372a268..091d1fcf 100644 --- a/src/static/client3.js +++ b/src/static/client3.js @@ -958,6 +958,8 @@ clientSteps.addPageListeners.push(addScrollListenerForStickyHeadings); // Image overlay ------------------------------------------ +// TODO: Update to clientSteps style. + function addImageOverlayClickHandlers() { const container = document.getElementById('image-overlay-container'); @@ -1245,6 +1247,8 @@ function loadImage(imageUrl, onprogress) { // Group contributions table ------------------------------ +// TODO: Update to clientSteps style. + const groupContributionsTableInfo = Array.from(document.querySelectorAll('#content dl')) .filter(dl => dl.querySelector('a.group-contributions-sort-button')) @@ -1277,6 +1281,152 @@ for (const info of groupContributionsTableInfo) { }); } +// Artist link icon tooltips ------------------------------ + +// TODO: Update to clientSteps style. + +const linkIconTooltipInfo = + Array.from(document.querySelectorAll('span.contribution.has-tooltip')) + .map(span => ({ + mainLink: span.querySelector('a'), + iconsContainer: span.querySelector('span.icons-tooltip'), + iconLinks: span.querySelectorAll('span.icons-tooltip a'), + })); + +for (const info of linkIconTooltipInfo) { + const focusElements = + [info.mainLink, ...info.iconLinks]; + + const hoverElements = + [info.mainLink, info.iconsContainer]; + + let hidden = true; + + const show = () => { + info.iconsContainer.classList.add('visible'); + info.iconsContainer.inert = false; + hidden = false; + }; + + const hide = () => { + info.iconsContainer.classList.remove('visible'); + info.iconsContainer.inert = true; + hidden = true; + }; + + const considerHiding = () => { + if (hoverElements.some(el => el.matches(':hover'))) { + return; + } + + if (focusElements.includes(document.activeElement)) { + return; + } + + if (justTouched) { + return; + } + + hide(); + }; + + // Hover (pointer) + + let hoverTimeout; + + info.mainLink.addEventListener('mouseenter', () => { + if (hidden) { + hoverTimeout = setTimeout(show, 250); + } + }); + + info.mainLink.addEventListener('mouseout', () => { + if (hidden) { + clearTimeout(hoverTimeout); + } else { + considerHiding(); + } + }); + + info.iconsContainer.addEventListener('mouseout', () => { + if (!hidden) { + considerHiding(); + } + }); + + // Focus (keyboard) + + let focusTimeout; + + info.mainLink.addEventListener('focus', () => { + focusTimeout = setTimeout(show, 750); + }); + + info.mainLink.addEventListener('blur', () => { + clearTimeout(focusTimeout); + }); + + info.iconsContainer.addEventListener('focusout', () => { + requestAnimationFrame(considerHiding); + }); + + info.mainLink.addEventListener('blur', () => { + requestAnimationFrame(considerHiding); + }); + + // Touch (finger) + + let justTouched = false; + let touchTimeout; + + info.mainLink.addEventListener('touchend', event => { + let wasTarget = false; + + for (const touch of event.changedTouches) { + if (touch.target === info.mainLink) { + wasTarget = true; + break; + } + } + + if (!wasTarget) { + return; + } + + justTouched = true; + + clearTimeout(touchTimeout); + touchTimeout = setTimeout(() => { + justTouched = false; + }, 250); + + show(); + }); + + info.mainLink.addEventListener('click', event => { + if (hidden && justTouched) { + event.preventDefault(); + event.target.focus(); + show(); + } + }); + + document.body.addEventListener('touchend', event => { + const touches = [...event.changedTouches, ...event.touches]; + for (const {clientX, clientY} of touches) { + const touchEl = document.elementFromPoint(clientX, clientY); + if (!touchEl) continue; + + for (const hoverEl of hoverElements) { + if (touchEl === hoverEl) return; + if (hoverEl.contains(touchEl)) return; + } + } + + hide(); + }); +} + // Sticky commentary sidebar ------------------------------ const albumCommentarySidebarInfo = clientInfo.albumCommentarySidebarInfo = { diff --git a/src/static/site5.css b/src/static/site5.css index bb83fe67..06696799 100644 --- a/src/static/site5.css +++ b/src/static/site5.css @@ -427,6 +427,7 @@ a { a:hover { text-decoration: underline; + text-decoration-style: solid !important; } a.current { @@ -472,11 +473,52 @@ a:not([href]):hover { white-space: nowrap; } +.contribution { + position: relative; +} + +.contribution.has-tooltip a { + text-decoration: underline; + text-decoration-style: dotted; +} + .icons { font-style: normal; white-space: nowrap; } +.icons-tooltip { + position: absolute; + z-index: 999; + left: -12px; + top: calc(1em - 2px); + padding: 4px 12px 6px 8px; +} + +.icons-tooltip:not(.visible) { + display: none; +} + +.icons-tooltip-content { + display: block; + padding: 6px 2px 2px 2px; + background: var(--bg-black-color); + border: 1px dotted var(--primary-color); + border-radius: 4px; + + -webkit-user-select: none; + user-select: none; + cursor: default; +} + +.icons a:hover { + filter: brightness(1.4); +} + +.icons a { + padding: 0 3px; +} + .icon { display: inline-block; width: 24px; -- cgit 1.3.0-6-gf8a5