From 0202375db8ccd03d98ed6c2ffbb800b67c026639 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 23 Nov 2023 09:16:23 -0400 Subject: content, css: vertical tooltips + basic external parsing --- src/content/dependencies/linkContribution.js | 14 +- src/content/dependencies/linkExternalAsIcon.js | 265 +++++++++++++++++++++---- src/static/site5.css | 36 +++- src/strings-default.yaml | 24 +-- 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" -- cgit 1.3.0-6-gf8a5