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 --- .../generateReleaseInfoContributionsLine.js | 1 + src/content/dependencies/linkContribution.js | 79 ++++++++--- src/static/client3.js | 150 +++++++++++++++++++++ src/static/site5.css | 42 ++++++ .../snapshot/generateAlbumReleaseInfo.js.test.cjs | 16 +-- .../snapshot/generateTrackReleaseInfo.js.test.cjs | 2 +- .../test/snapshot/linkContribution.js.test.cjs | 144 ++++++++++++++++---- test/snapshot/linkContribution.js | 37 ++++- 8 files changed, 411 insertions(+), 60 deletions(-) diff --git a/src/content/dependencies/generateReleaseInfoContributionsLine.js b/src/content/dependencies/generateReleaseInfoContributionsLine.js index 1fa8dcca..2e6c4709 100644 --- a/src/content/dependencies/generateReleaseInfoContributionsLine.js +++ b/src/content/dependencies/generateReleaseInfoContributionsLine.js @@ -35,6 +35,7 @@ export default { link.slots({ showContribution: slots.showContribution, showIcons: slots.showIcons, + iconMode: 'tooltip', }))), }); }, diff --git a/src/content/dependencies/linkContribution.js b/src/content/dependencies/linkContribution.js index 8e42f247..5bc398de 100644 --- a/src/content/dependencies/linkContribution.js +++ b/src/content/dependencies/linkContribution.js @@ -20,7 +20,6 @@ export default { if (!empty(contribution.who.urls)) { relations.artistIcons = contribution.who.urls - .slice(0, 4) .map(url => relation('linkExternalAsIcon', url)); } @@ -37,37 +36,79 @@ export default { showContribution: {type: 'boolean', default: false}, showIcons: {type: 'boolean', default: false}, preventWrapping: {type: 'boolean', default: true}, + + iconMode: { + validate: v => v.is('inline', 'tooltip'), + default: 'inline' + }, }, generate(data, relations, slots, {html, language}) { - const hasContributionPart = !!(slots.showContribution && data.what); - const hasExternalPart = !!(slots.showIcons && relations.artistIcons); - - const externalLinks = hasExternalPart && - html.tag('span', - {[html.noEdgeWhitespace]: true, class: 'icons'}, - language.formatUnitList(relations.artistIcons)); + const hasContribution = !!(slots.showContribution && data.what); + const hasExternalIcons = !!(slots.showIcons && relations.artistIcons); const parts = ['misc.artistLink']; const options = {artist: relations.artistLink}; - if (hasContributionPart) { + if (hasContribution) { parts.push('withContribution'); options.contrib = data.what; } - if (hasExternalPart) { + if (hasExternalIcons && slots.iconMode === 'inline') { parts.push('withExternalLinks'); - options.links = externalLinks; + options.links = + html.tag('span', + { + [html.noEdgeWhitespace]: true, + class: ['icons', 'icons-inline'], + }, + language.formatUnitList( + relations.artistIcons + .slice(0, 4))); } - const content = language.formatString(parts.join('.'), options); + let content = language.formatString(parts.join('.'), options); - return ( - (parts.length > 1 && slots.preventWrapping - ? html.tag('span', - {[html.noEdgeWhitespace]: true, class: 'nowrap'}, - content) - : content)); - }, + if (hasExternalIcons && slots.iconMode === 'tooltip') { + content = [ + content, + html.tag('span', + { + [html.noEdgeWhitespace]: true, + class: ['icons', 'icons-tooltip'], + inert: true, + }, + html.tag('span', + { + [html.noEdgeWhitespace]: true, + [html.joinChildren]: '', + class: 'icons-tooltip-content', + }, + relations.artistIcons)), + ]; + } + + if (hasContribution || hasExternalIcons) { + content = + html.tag('span', { + [html.noEdgeWhitespace]: true, + [html.joinChildren]: '', + + class: [ + 'contribution', + + hasExternalIcons && + slots.iconMode === 'tooltip' && + 'has-tooltip', + + parts.length > 1 && + slots.preventWrapping && + 'nowrap', + ], + }, content); + } + + return content; + } }; 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; diff --git a/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs index 9702cad8..3335a2eb 100644 --- a/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs +++ b/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs @@ -7,18 +7,18 @@ 'use strict' exports[`test/snapshot/generateAlbumReleaseInfo.js > TAP > generateAlbumReleaseInfo (snapshot) > basic behavior 1`] = `

- By Toby Fox (music probably) and Tensei (hot jams) ( - - Bandcamp - - - ). + By Toby Fox (music probably) and Tensei (hot jams) + + Bandcamp + + + .
Cover art by Hanni Brosh.
- Wallpaper art by Hanni Brosh and Niklink (edits). + Wallpaper art by Hanni Brosh and Niklink (edits).
- Banner art by Hanni Brosh and Niklink (edits). + Banner art by Hanni Brosh and Niklink (edits).
Released 3/14/2011.
diff --git a/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs b/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs index 2add28ed..3d988dce 100644 --- a/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs +++ b/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs @@ -25,7 +25,7 @@ exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseI

By Toby Fox.
- Cover art by Alpaca (🔥). + Cover art by Alpaca (🔥).

This wiki doesn't have any listening links for Suspicious Track.

` diff --git a/tap-snapshots/test/snapshot/linkContribution.js.test.cjs b/tap-snapshots/test/snapshot/linkContribution.js.test.cjs index 75b9d273..4cf3aa3f 100644 --- a/tap-snapshots/test/snapshot/linkContribution.js.test.cjs +++ b/tap-snapshots/test/snapshot/linkContribution.js.test.cjs @@ -5,8 +5,8 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links 1`] = ` -Lorem Ipsum Lover ( +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links (inline) 1`] = ` +Lorem Ipsum Lover ( External (loremipsum.io) @@ -29,6 +29,50 @@ exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > ) ` +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links (tooltip) 1`] = ` +Lorem Ipsum Lover + + External (loremipsum.io) + + + + + External (loremipsum.io) + + + + + External (loremipsum.io) + + + + + External (loremipsum.io) + + + + + External (loremipsum.io) + + + + + External (loremipsum.io) + + + + + External (loremipsum.io) + + + + + External (loremipsum.io) + + + +` + exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > no accents 1`] = ` Clark Powell Grounder & Scratch @@ -36,41 +80,41 @@ exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > ` exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > no preventWrapping 1`] = ` -Clark Powell ( - - SoundCloud - - - ) -Grounder & Scratch (Snooping) -Toby Fox (Arrangement) ( - - Bandcamp - - - , - - External (toby.fox) - - - ) +Clark Powell ( + + SoundCloud + + + ) +Grounder & Scratch (Snooping) +Toby Fox (Arrangement) ( + + Bandcamp + + + , + + External (toby.fox) + + + ) ` exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showContribution 1`] = ` Clark Powell -Grounder & Scratch (Snooping) -Toby Fox (Arrangement) +Grounder & Scratch (Snooping) +Toby Fox (Arrangement) ` -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showIcons 1`] = ` -Clark Powell ( +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showIcons (inline) 1`] = ` +Clark Powell ( SoundCloud ) Grounder & Scratch -Toby Fox ( +Toby Fox ( Bandcamp @@ -83,15 +127,36 @@ exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > ) ` -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showIcons 1`] = ` -Clark Powell ( +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showIcons (tooltip) 1`] = ` +Clark Powell + + SoundCloud + + + +Grounder & Scratch (Snooping) +Toby Fox (Arrangement) + + Bandcamp + + + + + External (toby.fox) + + + +` + +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showIcons (inline) 1`] = ` +Clark Powell ( SoundCloud ) -Grounder & Scratch (Snooping) -Toby Fox (Arrangement) ( +Grounder & Scratch (Snooping) +Toby Fox (Arrangement) ( Bandcamp @@ -103,3 +168,24 @@ exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > ) ` + +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showIcons (tooltip) 1`] = ` +Clark Powell + + SoundCloud + + + +Grounder & Scratch (Snooping) +Toby Fox (Arrangement) + + Bandcamp + + + + + External (toby.fox) + + + +` diff --git a/test/snapshot/linkContribution.js b/test/snapshot/linkContribution.js index ad5fb416..ebd3be58 100644 --- a/test/snapshot/linkContribution.js +++ b/test/snapshot/linkContribution.js @@ -33,22 +33,36 @@ testContentFunctions(t, 'linkContribution (snapshot)', async (t, evaluate) => { slots, }); - quickSnapshot('showContribution & showIcons', { + quickSnapshot('showContribution & showIcons (inline)', { showContribution: true, showIcons: true, + iconMode: 'inline', + }); + + quickSnapshot('showContribution & showIcons (tooltip)', { + showContribution: true, + showIcons: true, + iconMode: 'tooltip', }); quickSnapshot('only showContribution', { showContribution: true, }); - quickSnapshot('only showIcons', { + quickSnapshot('only showIcons (inline)', { + showIcons: true, + iconMode: 'inline', + }); + + quickSnapshot('only showIcons (tooltip)', { + showContribution: true, showIcons: true, + iconMode: 'tooltip', }); quickSnapshot('no accents', {}); - evaluate.snapshot('loads of links', { + evaluate.snapshot('loads of links (inline)', { name: 'linkContribution', args: [ {who: {name: 'Lorem Ipsum Lover', directory: 'lorem-ipsum-lover', urls: [ @@ -65,6 +79,23 @@ testContentFunctions(t, 'linkContribution (snapshot)', async (t, evaluate) => { slots: {showIcons: true}, }); + evaluate.snapshot('loads of links (tooltip)', { + name: 'linkContribution', + args: [ + {who: {name: 'Lorem Ipsum Lover', directory: 'lorem-ipsum-lover', urls: [ + 'https://loremipsum.io', + 'https://loremipsum.io/generator/', + 'https://loremipsum.io/#meaning', + 'https://loremipsum.io/#usage-and-examples', + 'https://loremipsum.io/#controversy', + 'https://loremipsum.io/#when-to-use-lorem-ipsum', + 'https://loremipsum.io/#lorem-ipsum-all-the-things', + 'https://loremipsum.io/#original-source', + ]}, what: null}, + ], + slots: {showIcons: true, iconMode: 'tooltip'}, + }); + quickSnapshot('no preventWrapping', { showContribution: true, showIcons: true, -- cgit 1.3.0-6-gf8a5