« get me outta code hell

content, css: vertical tooltips + basic external parsing - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-11-23 09:16:23 -0400
committer(quasar) nebula <qznebula@protonmail.com>2023-11-24 13:45:10 -0400
commit0202375db8ccd03d98ed6c2ffbb800b67c026639 (patch)
treea7bdecbeeaeb7ba43333cce69ac9ecd47d768a36 /src
parenta1d50400b858e40471bc1bb78408d69d39907c5f (diff)
content, css: vertical tooltips + basic external parsing
Diffstat (limited to 'src')
-rw-r--r--src/content/dependencies/linkContribution.js14
-rw-r--r--src/content/dependencies/linkExternalAsIcon.js265
-rw-r--r--src/static/site5.css36
-rw-r--r--src/strings-default.yaml24
4 files changed, 272 insertions, 67 deletions
diff --git a/src/content/dependencies/linkContribution.js b/src/content/dependencies/linkContribution.js
index 5bc398de..ef61c766 100644
--- a/src/content/dependencies/linkContribution.js
+++ b/src/content/dependencies/linkContribution.js
@@ -1,15 +1,8 @@
 import {empty} from '#sugar';
 
 export default {
-  contentDependencies: [
-    'linkArtist',
-    'linkExternalAsIcon',
-  ],
-
-  extraDependencies: [
-    'html',
-    'language',
-  ],
+  contentDependencies: ['linkArtist', 'linkExternalAsIcon'],
+  extraDependencies: ['html', 'language'],
 
   relations(relation, contribution) {
     const relations = {};
@@ -85,7 +78,8 @@ export default {
               [html.joinChildren]: '',
               class: 'icons-tooltip-content',
             },
-            relations.artistIcons)),
+            relations.artistIcons
+              .map(icon => icon.slot('withText', true)))),
       ];
     }
 
diff --git a/src/content/dependencies/linkExternalAsIcon.js b/src/content/dependencies/linkExternalAsIcon.js
index cd168992..d3ed9122 100644
--- a/src/content/dependencies/linkExternalAsIcon.js
+++ b/src/content/dependencies/linkExternalAsIcon.js
@@ -1,6 +1,202 @@
-// TODO: Define these as extra dependencies and pass them somewhere
-const BANDCAMP_DOMAINS = ['bc.s3m.us', 'music.solatrux.com'];
-const MASTODON_DOMAINS = ['types.pl'];
+import {stitchArrays} from '#sugar';
+
+const fallbackDescriptor = {
+  icon: 'globe',
+  string: 'external',
+
+  normal: 'domain',
+  compact: 'domain',
+};
+
+// TODO: Define all this stuff in data!
+const externalSpec = [
+  {
+    matchDomain: 'bandcamp.com',
+
+    icon: 'bandcamp',
+    string: 'bandcamp',
+
+    compact: 'handle',
+
+    handle: {domain: /^[^.]*/},
+  },
+
+  {
+    matchDomains: ['bc.s3m.us', 'music.solatrux.com'],
+
+    icon: 'bandcamp',
+    string: 'bandcamp',
+
+    normal: 'domain',
+    compact: 'domain',
+  },
+
+  {
+    matchDomains: ['types.pl'],
+
+    icon: 'mastodon',
+    string: 'mastodon',
+
+    compact: 'domain',
+  },
+
+  {
+    matchDomains: ['youtube.com', 'youtu.be'],
+
+    icon: 'youtube',
+    string: 'youtube',
+
+    compact: 'handle',
+
+    handle: {
+      pathname: /^(@.*?)\/?$/,
+    },
+  },
+
+  {
+    matchDomain: 'soundcloud.com',
+
+    icon: 'soundcloud',
+    string: 'soundcloud',
+
+    compact: 'handle',
+
+    handle: /[^/]*\/?$/,
+  },
+
+  {
+    matchDomain: 'tumblr.com',
+
+    icon: 'tumblr',
+    string: 'tumblr',
+
+    compact: 'handle',
+
+    handle: {domain: /^[^.]*/},
+  },
+
+  {
+    matchDomain: 'twitter.com',
+
+    icon: 'twitter',
+    string: 'twitter',
+
+    compact: 'handle',
+
+    handle: {
+      prefix: '@',
+      pathname: /^@?.*\/?$/,
+    },
+  },
+
+  {
+    matchDomain: 'deviantart.com',
+
+    icon: 'deviantart',
+    string: 'deviantart',
+  },
+
+  {
+    matchDomain: 'instagram.com',
+
+    icon: 'instagram',
+    string: 'instagram',
+  },
+
+  {
+    matchDomain: 'newgrounds.com',
+
+    icon: 'newgrounds',
+    string: 'newgrounds',
+  },
+];
+
+function determineLinkText(url, descriptor, {language}) {
+  const prefix = 'misc.external';
+
+  const {
+    hostname: domain,
+    pathname,
+  } = new URL(url);
+
+  let normal = null;
+  let compact = null;
+
+  const place = language.$(prefix, descriptor.string);
+
+  if (descriptor.normal === 'domain') {
+    normal = language.$(prefix, 'withDomain', {place, domain});
+  }
+
+  if (descriptor.compact === 'domain') {
+    compact = domain.replace(/^www\./, '');
+  }
+
+  let handle = null;
+
+  if (descriptor.handle) {
+    let regexen = [];
+    let tests = [];
+
+    let handlePrefix = '';
+
+    if (descriptor.handle instanceof RegExp) {
+      regexen.push(descriptor.handle);
+      tests.push(url);
+    } else {
+      for (const [key, value] of Object.entries(descriptor.handle)) {
+        switch (key) {
+          case 'prefix':
+            handlePrefix = value;
+            continue;
+
+          case 'url':
+            tests.push(url);
+            break;
+
+          case 'domain':
+          case 'hostname':
+            tests.push(domain);
+            break;
+
+          case 'path':
+          case 'pathname':
+            tests.push(pathname.slice(1));
+            break;
+
+          default:
+            tests.push('');
+            break;
+        }
+
+        regexen.push(value);
+      }
+    }
+
+    for (const {regex, test} of stitchArrays({
+      regex: regexen,
+      test: tests,
+    })) {
+      const match = test.match(regex);
+      if (match) {
+        handle = handlePrefix + (match[1] ?? match[0]);
+        break;
+      }
+    }
+  }
+
+  if (descriptor.compact === 'handle') {
+    compact = handle;
+  }
+
+  if (normal === 'handle' && handle) {
+    normal = language.$(prefix, 'withHandle', {place, handle});
+  }
+
+  normal ??= language.$(prefix, descriptor.string);
+
+  return {normal, compact};
+}
 
 export default {
   extraDependencies: ['html', 'language', 'to'],
@@ -9,38 +205,39 @@ export default {
     return {url};
   },
 
-  generate(data, {html, language, to}) {
-    const domain = new URL(data.url).hostname;
-    const [id, msg] = (
-      domain.includes('bandcamp.com')
-        ? ['bandcamp', language.$('misc.external.bandcamp')]
-      : BANDCAMP_DOMAINS.includes(domain)
-        ? ['bandcamp', language.$('misc.external.bandcamp.domain', {domain})]
-      : MASTODON_DOMAINS.includes(domain)
-        ? ['mastodon', language.$('misc.external.mastodon.domain', {domain})]
-      : domain.includes('youtu')
-        ? ['youtube', language.$('misc.external.youtube')]
-      : domain.includes('soundcloud')
-        ? ['soundcloud', language.$('misc.external.soundcloud')]
-      : domain.includes('tumblr.com')
-        ? ['tumblr', language.$('misc.external.tumblr')]
-      : domain.includes('twitter.com')
-        ? ['twitter', language.$('misc.external.twitter')]
-      : domain.includes('deviantart.com')
-        ? ['deviantart', language.$('misc.external.deviantart')]
-      : domain.includes('instagram.com')
-        ? ['instagram', language.$('misc.external.bandcamp')]
-      : domain.includes('newgrounds.com')
-        ? ['newgrounds', language.$('misc.external.newgrounds')]
-        : ['globe', language.$('misc.external.domain', {domain})]);
+  slots: {
+    withText: {type: 'boolean'},
+  },
+
+  generate(data, slots, {html, language, to}) {
+    const {hostname: domain} = new URL(data.url);
+
+    const descriptor =
+      externalSpec.find(({matchDomain, matchDomains}) => {
+        const compare = d => domain.includes(d);
+        if (matchDomain && compare(matchDomain)) return true;
+        if (matchDomains && matchDomains.some(compare)) return true;
+        return false;
+      }) ?? fallbackDescriptor;
+
+    const {normal: normalText, compact: compactText} =
+      determineLinkText(data.url, descriptor, {language});
 
     return html.tag('a',
-      {href: data.url, class: 'icon'},
-      html.tag('svg', [
-        html.tag('title', msg),
-        html.tag('use', {
-          href: to('shared.staticIcon', id),
-        }),
-      ]));
+      {href: data.url, class: ['icon', slots.withText && 'has-text']},
+      [
+        html.tag('svg', [
+          !slots.withText &&
+            html.tag('title', normalText),
+
+          html.tag('use', {
+            href: to('shared.staticIcon', descriptor.icon),
+          }),
+        ]),
+
+        slots.withText &&
+          html.tag('span', {class: 'icon-text'},
+            compactText ?? normalText),
+      ]);
   },
 };
diff --git a/src/static/site5.css b/src/static/site5.css
index 8c2b07a1..c1dfff82 100644
--- a/src/static/site5.css
+++ b/src/static/site5.css
@@ -477,7 +477,7 @@ a:not([href]):hover {
   position: relative;
 }
 
-.contribution.has-tooltip a {
+.contribution.has-tooltip > a {
   text-decoration: underline;
   text-decoration-style: dotted;
 }
@@ -489,8 +489,8 @@ a:not([href]):hover {
 
 .icons-tooltip {
   position: absolute;
-  z-index: 1;
-  left: -12px;
+  z-index: 3;
+  left: -36px;
   top: calc(1em - 2px);
   padding: 4px 12px 6px 8px;
 }
@@ -504,14 +504,21 @@ a:not([href]):hover {
   padding: 6px 2px 2px 2px;
   background: var(--bg-black-color);
   border: 1px dotted var(--primary-color);
-  border-radius: 4px;
+  border-radius: 6px;
 
-  -webkit-backdrop-filter: blur(2px);
-          backdrop-filter: blur(2px);
+  -webkit-backdrop-filter:
+    brightness(1.5) saturate(1.4) blur(4px);
+
+          backdrop-filter:
+    brightness(1.5) saturate(1.4) blur(4px);
 
   -webkit-user-select: none;
           user-select: none;
 
+  box-shadow:
+    0 3px 4px 4px #000000aa,
+    0 -2px 4px -2px var(--primary-color) inset;
+
   cursor: default;
 }
 
@@ -538,6 +545,23 @@ a:not([href]):hover {
   fill: var(--primary-color);
 }
 
+.icon.has-text {
+  display: block;
+  width: unset;
+  height: 1.4em;
+}
+
+.icon.has-text > svg {
+  width: 18px;
+  height: 18px;
+  top: -0.1em;
+}
+
+.icon.has-text > .icon-text {
+  margin-left: 24px;
+  padding-right: 8px;
+}
+
 .rerelease,
 .other-group-accent {
   opacity: 0.7;
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index e6b8d6db..9fdf0182 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -404,19 +404,15 @@ misc:
   #   wiki - sorry!
 
   external:
+    external: "External"
 
-    # domain:
-    #   General domain when one the URL doesn't match one of the
-    #   sites below.
+    withDomain:
+      "{PLACE} ({DOMAIN})"
 
-    domain: "External ({DOMAIN})"
-
-    # local:
-    #   Files which are locally available on the wiki (under its media
-    #   directory).
+    withHandle:
+      "{PLACE} ({HANDLE})"
 
     local: "Wiki Archive (local upload)"
-
     deviantart: "DeviantArt"
     instagram: "Instagram"
     newgrounds: "Newgrounds"
@@ -427,14 +423,8 @@ misc:
     tumblr: "Tumblr"
     twitter: "Twitter"
     wikipedia: "Wikipedia"
-
-    bandcamp:
-      _: "Bandcamp"
-      domain: "Bandcamp ({DOMAIN})"
-
-    mastodon:
-      _: "Mastodon"
-      domain: "Mastodon ({DOMAIN})"
+    bandcamp: "Bandcamp"
+    mastodon: "Mastodon"
 
     youtube:
       _: "YouTube"