« get me outta code hell

content, test: linkContribution: tooltip icons - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-07-21 20:06:32 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-11-24 13:45:08 -0400
commitc11edada828dc734cce6988e5819630a73326085 (patch)
tree2754223a452ea3d6bc71cf12e6787f7c1ad40c37
parenta6dedb0b17f514e408919240c060072df2b179dd (diff)
content, test: linkContribution: tooltip icons
-rw-r--r--src/content/dependencies/generateReleaseInfoContributionsLine.js1
-rw-r--r--src/content/dependencies/linkContribution.js79
-rw-r--r--src/static/client3.js150
-rw-r--r--src/static/site5.css42
-rw-r--r--tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs16
-rw-r--r--tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs2
-rw-r--r--tap-snapshots/test/snapshot/linkContribution.js.test.cjs144
-rw-r--r--test/snapshot/linkContribution.js37
8 files changed, 411 insertions, 60 deletions
diff --git a/src/content/dependencies/generateReleaseInfoContributionsLine.js b/src/content/dependencies/generateReleaseInfoContributionsLine.js
index 1fa8dcc..2e6c470 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 8e42f24..5bc398d 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 8372a26..091d1fc 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 bb83fe6..0669679 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 9702cad..3335a2e 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`] = `
 <p>
-    By <span class="nowrap"><a href="artist/toby-fox/">Toby Fox</a> (music probably)</span> and <span class="nowrap"><a href="artist/tensei/">Tensei</a> (hot jams) (<span class="icons"><a href="https://tenseimusic.bandcamp.com/" class="icon">
-                <svg>
-                    <title>Bandcamp</title>
-                    <use href="static/icons.svg#icon-bandcamp"></use>
-                </svg>
-            </a></span>)</span>.
+    By <span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a> (music probably)</span> and <span class="contribution has-tooltip nowrap"><a href="artist/tensei/">Tensei</a> (hot jams)<span class="icons icons-tooltip" inert><span class="icons-tooltip-content"><a href="https://tenseimusic.bandcamp.com/" class="icon">
+                    <svg>
+                        <title>Bandcamp</title>
+                        <use href="static/icons.svg#icon-bandcamp"></use>
+                    </svg>
+                </a></span></span></span>.
     <br>
     Cover art by <a href="artist/hb/">Hanni Brosh</a>.
     <br>
-    Wallpaper art by <a href="artist/hb/">Hanni Brosh</a> and <span class="nowrap"><a href="artist/niklink/">Niklink</a> (edits)</span>.
+    Wallpaper art by <a href="artist/hb/">Hanni Brosh</a> and <span class="contribution nowrap"><a href="artist/niklink/">Niklink</a> (edits)</span>.
     <br>
-    Banner art by <a href="artist/hb/">Hanni Brosh</a> and <span class="nowrap"><a href="artist/niklink/">Niklink</a> (edits)</span>.
+    Banner art by <a href="artist/hb/">Hanni Brosh</a> and <span class="contribution nowrap"><a href="artist/niklink/">Niklink</a> (edits)</span>.
     <br>
     Released 3/14/2011.
     <br>
diff --git a/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs b/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs
index 2add28e..3d988dc 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
 <p>
     By <a href="artist/toby-fox/">Toby Fox</a>.
     <br>
-    Cover art by <span class="nowrap"><a href="artist/alpaca/">Alpaca</a> (&#x1F525;)</span>.
+    Cover art by <span class="contribution nowrap"><a href="artist/alpaca/">Alpaca</a> (&#x1F525;)</span>.
 </p>
 <p>This wiki doesn't have any listening links for <i>Suspicious Track</i>.</p>
 `
diff --git a/tap-snapshots/test/snapshot/linkContribution.js.test.cjs b/tap-snapshots/test/snapshot/linkContribution.js.test.cjs
index 75b9d27..4cf3aa3 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`] = `
-<span class="nowrap"><a href="artist/lorem-ipsum-lover/">Lorem Ipsum Lover</a> (<span class="icons"><a href="https://loremipsum.io" class="icon">
+exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links (inline) 1`] = `
+<span class="contribution nowrap"><a href="artist/lorem-ipsum-lover/">Lorem Ipsum Lover</a> (<span class="icons icons-inline"><a href="https://loremipsum.io" class="icon">
             <svg>
                 <title>External (loremipsum.io)</title>
                 <use href="static/icons.svg#icon-globe"></use>
@@ -29,6 +29,50 @@ exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) >
         </a></span>)</span>
 `
 
+exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links (tooltip) 1`] = `
+<span class="contribution has-tooltip"><a href="artist/lorem-ipsum-lover/">Lorem Ipsum Lover</a><span class="icons icons-tooltip" inert><span class="icons-tooltip-content"><a href="https://loremipsum.io" class="icon">
+                <svg>
+                    <title>External (loremipsum.io)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a><a href="https://loremipsum.io/generator/" class="icon">
+                <svg>
+                    <title>External (loremipsum.io)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a><a href="https://loremipsum.io/#meaning" class="icon">
+                <svg>
+                    <title>External (loremipsum.io)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a><a href="https://loremipsum.io/#usage-and-examples" class="icon">
+                <svg>
+                    <title>External (loremipsum.io)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a><a href="https://loremipsum.io/#controversy" class="icon">
+                <svg>
+                    <title>External (loremipsum.io)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a><a href="https://loremipsum.io/#when-to-use-lorem-ipsum" class="icon">
+                <svg>
+                    <title>External (loremipsum.io)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a><a href="https://loremipsum.io/#lorem-ipsum-all-the-things" class="icon">
+                <svg>
+                    <title>External (loremipsum.io)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a><a href="https://loremipsum.io/#original-source" class="icon">
+                <svg>
+                    <title>External (loremipsum.io)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a></span></span></span>
+`
+
 exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > no accents 1`] = `
 <a href="artist/clark-powell/">Clark Powell</a>
 <a href="artist/the-big-baddies/">Grounder &amp; Scratch</a>
@@ -36,41 +80,41 @@ exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) >
 `
 
 exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > no preventWrapping 1`] = `
-<a href="artist/clark-powell/">Clark Powell</a> (<span class="icons"><a href="https://soundcloud.com/plazmataz" class="icon">
-        <svg>
-            <title>SoundCloud</title>
-            <use href="static/icons.svg#icon-soundcloud"></use>
-        </svg>
-    </a></span>)
-<a href="artist/the-big-baddies/">Grounder &amp; Scratch</a> (Snooping)
-<a href="artist/toby-fox/">Toby Fox</a> (Arrangement) (<span class="icons"><a href="https://tobyfox.bandcamp.com/" class="icon">
-        <svg>
-            <title>Bandcamp</title>
-            <use href="static/icons.svg#icon-bandcamp"></use>
-        </svg>
-    </a>, <a href="https://toby.fox/" class="icon">
-        <svg>
-            <title>External (toby.fox)</title>
-            <use href="static/icons.svg#icon-globe"></use>
-        </svg>
-    </a></span>)
+<span class="contribution"><a href="artist/clark-powell/">Clark Powell</a> (<span class="icons icons-inline"><a href="https://soundcloud.com/plazmataz" class="icon">
+            <svg>
+                <title>SoundCloud</title>
+                <use href="static/icons.svg#icon-soundcloud"></use>
+            </svg>
+        </a></span>)</span>
+<span class="contribution"><a href="artist/the-big-baddies/">Grounder &amp; Scratch</a> (Snooping)</span>
+<span class="contribution"><a href="artist/toby-fox/">Toby Fox</a> (Arrangement) (<span class="icons icons-inline"><a href="https://tobyfox.bandcamp.com/" class="icon">
+            <svg>
+                <title>Bandcamp</title>
+                <use href="static/icons.svg#icon-bandcamp"></use>
+            </svg>
+        </a>, <a href="https://toby.fox/" class="icon">
+            <svg>
+                <title>External (toby.fox)</title>
+                <use href="static/icons.svg#icon-globe"></use>
+            </svg>
+        </a></span>)</span>
 `
 
 exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showContribution 1`] = `
 <a href="artist/clark-powell/">Clark Powell</a>
-<span class="nowrap"><a href="artist/the-big-baddies/">Grounder &amp; Scratch</a> (Snooping)</span>
-<span class="nowrap"><a href="artist/toby-fox/">Toby Fox</a> (Arrangement)</span>
+<span class="contribution nowrap"><a href="artist/the-big-baddies/">Grounder &amp; Scratch</a> (Snooping)</span>
+<span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a> (Arrangement)</span>
 `
 
-exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showIcons 1`] = `
-<span class="nowrap"><a href="artist/clark-powell/">Clark Powell</a> (<span class="icons"><a href="https://soundcloud.com/plazmataz" class="icon">
+exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showIcons (inline) 1`] = `
+<span class="contribution nowrap"><a href="artist/clark-powell/">Clark Powell</a> (<span class="icons icons-inline"><a href="https://soundcloud.com/plazmataz" class="icon">
             <svg>
                 <title>SoundCloud</title>
                 <use href="static/icons.svg#icon-soundcloud"></use>
             </svg>
         </a></span>)</span>
 <a href="artist/the-big-baddies/">Grounder &amp; Scratch</a>
-<span class="nowrap"><a href="artist/toby-fox/">Toby Fox</a> (<span class="icons"><a href="https://tobyfox.bandcamp.com/" class="icon">
+<span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a> (<span class="icons icons-inline"><a href="https://tobyfox.bandcamp.com/" class="icon">
             <svg>
                 <title>Bandcamp</title>
                 <use href="static/icons.svg#icon-bandcamp"></use>
@@ -83,15 +127,36 @@ exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) >
         </a></span>)</span>
 `
 
-exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showIcons 1`] = `
-<span class="nowrap"><a href="artist/clark-powell/">Clark Powell</a> (<span class="icons"><a href="https://soundcloud.com/plazmataz" class="icon">
+exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showIcons (tooltip) 1`] = `
+<span class="contribution has-tooltip"><a href="artist/clark-powell/">Clark Powell</a><span class="icons icons-tooltip" inert><span class="icons-tooltip-content"><a href="https://soundcloud.com/plazmataz" class="icon">
+                <svg>
+                    <title>SoundCloud</title>
+                    <use href="static/icons.svg#icon-soundcloud"></use>
+                </svg>
+            </a></span></span></span>
+<span class="contribution nowrap"><a href="artist/the-big-baddies/">Grounder &amp; Scratch</a> (Snooping)</span>
+<span class="contribution has-tooltip nowrap"><a href="artist/toby-fox/">Toby Fox</a> (Arrangement)<span class="icons icons-tooltip" inert><span class="icons-tooltip-content"><a href="https://tobyfox.bandcamp.com/" class="icon">
+                <svg>
+                    <title>Bandcamp</title>
+                    <use href="static/icons.svg#icon-bandcamp"></use>
+                </svg>
+            </a><a href="https://toby.fox/" class="icon">
+                <svg>
+                    <title>External (toby.fox)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a></span></span></span>
+`
+
+exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showIcons (inline) 1`] = `
+<span class="contribution nowrap"><a href="artist/clark-powell/">Clark Powell</a> (<span class="icons icons-inline"><a href="https://soundcloud.com/plazmataz" class="icon">
             <svg>
                 <title>SoundCloud</title>
                 <use href="static/icons.svg#icon-soundcloud"></use>
             </svg>
         </a></span>)</span>
-<span class="nowrap"><a href="artist/the-big-baddies/">Grounder &amp; Scratch</a> (Snooping)</span>
-<span class="nowrap"><a href="artist/toby-fox/">Toby Fox</a> (Arrangement) (<span class="icons"><a href="https://tobyfox.bandcamp.com/" class="icon">
+<span class="contribution nowrap"><a href="artist/the-big-baddies/">Grounder &amp; Scratch</a> (Snooping)</span>
+<span class="contribution nowrap"><a href="artist/toby-fox/">Toby Fox</a> (Arrangement) (<span class="icons icons-inline"><a href="https://tobyfox.bandcamp.com/" class="icon">
             <svg>
                 <title>Bandcamp</title>
                 <use href="static/icons.svg#icon-bandcamp"></use>
@@ -103,3 +168,24 @@ exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) >
             </svg>
         </a></span>)</span>
 `
+
+exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showIcons (tooltip) 1`] = `
+<span class="contribution has-tooltip"><a href="artist/clark-powell/">Clark Powell</a><span class="icons icons-tooltip" inert><span class="icons-tooltip-content"><a href="https://soundcloud.com/plazmataz" class="icon">
+                <svg>
+                    <title>SoundCloud</title>
+                    <use href="static/icons.svg#icon-soundcloud"></use>
+                </svg>
+            </a></span></span></span>
+<span class="contribution nowrap"><a href="artist/the-big-baddies/">Grounder &amp; Scratch</a> (Snooping)</span>
+<span class="contribution has-tooltip nowrap"><a href="artist/toby-fox/">Toby Fox</a> (Arrangement)<span class="icons icons-tooltip" inert><span class="icons-tooltip-content"><a href="https://tobyfox.bandcamp.com/" class="icon">
+                <svg>
+                    <title>Bandcamp</title>
+                    <use href="static/icons.svg#icon-bandcamp"></use>
+                </svg>
+            </a><a href="https://toby.fox/" class="icon">
+                <svg>
+                    <title>External (toby.fox)</title>
+                    <use href="static/icons.svg#icon-globe"></use>
+                </svg>
+            </a></span></span></span>
+`
diff --git a/test/snapshot/linkContribution.js b/test/snapshot/linkContribution.js
index ad5fb41..ebd3be5 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,