diff options
| author | (quasar) nebula <qznebula@protonmail.com> | 2026-05-01 08:53:23 -0300 |
|---|---|---|
| committer | (quasar) nebula <qznebula@protonmail.com> | 2026-05-01 08:53:57 -0300 |
| commit | f6aad9a81fbb1b4e619355cbec316988837fb61a (patch) | |
| tree | 6281b063b7741bd4fb46b087bfabedc8a0b97c8d | |
| parent | 92d82211fdf3d653b1a5a4d2cfdad5c684e2367d (diff) | |
content, external-links: pass url entry through, handle annotation
19 files changed, 139 insertions, 86 deletions
diff --git a/src/content/dependencies/generateAlbumCommentaryPage.js b/src/content/dependencies/generateAlbumCommentaryPage.js index a380f468..d2706918 100644 --- a/src/content/dependencies/generateAlbumCommentaryPage.js +++ b/src/content/dependencies/generateAlbumCommentaryPage.js @@ -52,7 +52,7 @@ export default { relations.albumCommentaryListeningLinks = album.urls - .map(entry => relation('linkExternal', entry.url)); + .map(entry => relation('linkExternal', entry)); if (album.hasCoverArt) { relations.albumCommentaryCover = @@ -75,7 +75,7 @@ export default { relations.trackCommentaryListeningLinks = query.tracksWithCommentary .map(track => - track.urls.map(entry => relation('linkExternal', entry.url))); + track.urls.map(entry => relation('linkExternal', entry))); relations.trackCommentaryCovers = query.tracksWithCommentary diff --git a/src/content/dependencies/generateAlbumSidebarGroupBox.js b/src/content/dependencies/generateAlbumSidebarGroupBox.js index 15fedf75..9aa29cf6 100644 --- a/src/content/dependencies/generateAlbumSidebarGroupBox.js +++ b/src/content/dependencies/generateAlbumSidebarGroupBox.js @@ -39,7 +39,7 @@ export default { relations.externalLinks = group.urls - .map(entry => relation('linkExternal', entry.url)); + .map(entry => relation('linkExternal', entry)); if (group.descriptionShort) { relations.description = diff --git a/src/content/dependencies/generateArtTagGalleryPage.js b/src/content/dependencies/generateArtTagGalleryPage.js index ec0624d1..49f58a9d 100644 --- a/src/content/dependencies/generateArtTagGalleryPage.js +++ b/src/content/dependencies/generateArtTagGalleryPage.js @@ -42,7 +42,7 @@ export default { if (!empty(artTag.extraReadingURLs)) { relations.extraReadingLinks = artTag.extraReadingURLs - .map(entry => relation('linkExternal', entry.url)); + .map(entry => relation('linkExternal', entry)); } if (!empty(artTag.directAncestorArtTags)) { diff --git a/src/content/dependencies/generateArtTagInfoPage.js b/src/content/dependencies/generateArtTagInfoPage.js index db26260b..dc7ee364 100644 --- a/src/content/dependencies/generateArtTagInfoPage.js +++ b/src/content/dependencies/generateArtTagInfoPage.js @@ -50,7 +50,7 @@ export default { extraReadingLinks: artTag.extraReadingURLs - .map(entry => relation('linkExternal', entry.url)), + .map(entry => relation('linkExternal', entry)), relatedArtTagLinks: artTag.relatedArtTags diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js index 3961dc49..45b111ed 100644 --- a/src/content/dependencies/generateArtistInfoPage.js +++ b/src/content/dependencies/generateArtistInfoPage.js @@ -62,7 +62,7 @@ export default { visitLinks: artist.urls - .map(entry => relation('linkExternal', entry.url)), + .map(entry => relation('linkExternal', entry)), tracksChunkedList: relation('generateArtistInfoPageTracksChunkedList', artist), diff --git a/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js b/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js index 570a984a..4351d590 100644 --- a/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js +++ b/src/content/dependencies/generateArtistInfoPageMusicVideosChunkItem.js @@ -38,7 +38,7 @@ export default { externalLinks: query.musicVideo.urls - .map(entry => relation('linkExternal', entry.url)), + .map(entry => relation('linkExternal', entry)), }), data: (query, _artist, contribs) => ({ diff --git a/src/content/dependencies/generateContributionTooltipExternalLinkSection.js b/src/content/dependencies/generateContributionTooltipExternalLinkSection.js index cc61fab4..f6af70d2 100644 --- a/src/content/dependencies/generateContributionTooltipExternalLinkSection.js +++ b/src/content/dependencies/generateContributionTooltipExternalLinkSection.js @@ -4,15 +4,15 @@ export default { relations: (relation, contribution) => ({ icons: contribution.artist.urls - .map(entry => relation('generateExternalIcon', entry.url)), + .map(entry => relation('generateExternalIcon', entry)), handles: contribution.artist.urls - .map(entry => relation('generateExternalHandle', entry.url)), + .map(entry => relation('generateExternalHandle', entry)), platforms: contribution.artist.urls - .map(entry => relation('generateExternalPlatform', entry.url)), + .map(entry => relation('generateExternalPlatform', entry)), }), data: (contribution) => ({ diff --git a/src/content/dependencies/generateExternalHandle.js b/src/content/dependencies/generateExternalHandle.js index 8653b177..358b4305 100644 --- a/src/content/dependencies/generateExternalHandle.js +++ b/src/content/dependencies/generateExternalHandle.js @@ -1,7 +1,7 @@ import {isExternalLinkContext} from '#external-links'; export default { - data: (url) => ({url}), + data: (urlEntry) => ({urlEntry}), slots: { context: { @@ -11,7 +11,7 @@ export default { }, generate: (data, slots, {language}) => - language.formatExternalLink(data.url, { + language.formatExternalLink(data.urlEntry, { style: 'handle', context: slots.context, }), diff --git a/src/content/dependencies/generateExternalIcon.js b/src/content/dependencies/generateExternalIcon.js index 03af643e..0f3ee1b7 100644 --- a/src/content/dependencies/generateExternalIcon.js +++ b/src/content/dependencies/generateExternalIcon.js @@ -1,7 +1,7 @@ import {isExternalLinkContext} from '#external-links'; export default { - data: (url) => ({url}), + data: (urlEntry) => ({urlEntry}), slots: { context: { @@ -16,7 +16,7 @@ export default { html.tag('use', { href: to('staticMisc.icon', - language.formatExternalLink(data.url, { + language.formatExternalLink(data.urlEntry, { style: 'icon-id', context: slots.context, })), diff --git a/src/content/dependencies/generateExternalPlatform.js b/src/content/dependencies/generateExternalPlatform.js index b2822d64..98c09815 100644 --- a/src/content/dependencies/generateExternalPlatform.js +++ b/src/content/dependencies/generateExternalPlatform.js @@ -1,7 +1,7 @@ import {isExternalLinkContext} from '#external-links'; export default { - data: (url) => ({url}), + data: (urlEntry) => ({urlEntry}), slots: { context: { @@ -11,7 +11,7 @@ export default { }, generate: (data, slots, {language}) => - language.formatExternalLink(data.url, { + language.formatExternalLink(data.urlEntry, { style: 'platform', context: slots.context, }), diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js index b334412b..39590832 100644 --- a/src/content/dependencies/generateFlashInfoPage.js +++ b/src/content/dependencies/generateFlashInfoPage.js @@ -42,7 +42,7 @@ export default { externalLinks: query.urls - .map(entry => relation('linkExternal', entry.url)), + .map(entry => relation('linkExternal', entry)), artworkColumn: relation('generateFlashArtworkColumn', flash), diff --git a/src/content/dependencies/generateGroupInfoPage.js b/src/content/dependencies/generateGroupInfoPage.js index 261d2212..87d5d2f8 100644 --- a/src/content/dependencies/generateGroupInfoPage.js +++ b/src/content/dependencies/generateGroupInfoPage.js @@ -51,7 +51,7 @@ export default { visitLinks: group.urls - .map(entry => relation('linkExternal', entry.url)), + .map(entry => relation('linkExternal', entry)), description: relation('transformContent', group.description), diff --git a/src/content/dependencies/generateLyricsEntry.js b/src/content/dependencies/generateLyricsEntry.js index 0ecf319f..b1113e57 100644 --- a/src/content/dependencies/generateLyricsEntry.js +++ b/src/content/dependencies/generateLyricsEntry.js @@ -13,7 +13,7 @@ export default { sourceLinks: entry.sourceURLs - .map(url => relation('linkExternal', url)), + .map(url => relation('linkExternal', {url})), originDetails: relation('transformContent', entry.originDetails), diff --git a/src/content/dependencies/generateMusicVideo.js b/src/content/dependencies/generateMusicVideo.js index 15eb233b..8508fdb6 100644 --- a/src/content/dependencies/generateMusicVideo.js +++ b/src/content/dependencies/generateMusicVideo.js @@ -21,7 +21,7 @@ export default { watchLinks: musicVideo.urls - .map(entry => relation('linkExternal', entry.url)), + .map(entry => relation('linkExternal', entry)), }), data: (musicVideo, _thing) => ({ diff --git a/src/content/dependencies/generateReleaseInfoListenLine.js b/src/content/dependencies/generateReleaseInfoListenLine.js index cd3baaf2..54e7985f 100644 --- a/src/content/dependencies/generateReleaseInfoListenLine.js +++ b/src/content/dependencies/generateReleaseInfoListenLine.js @@ -65,7 +65,7 @@ export default { relations: (relation, query, _thing) => ({ links: query.urls - .map(entry => relation('linkExternal', entry.url)), + .map(entry => relation('linkExternal', entry)), }), data(query, thing) { diff --git a/src/content/dependencies/linkExternal.js b/src/content/dependencies/linkExternal.js index e1393455..b47b1a90 100644 --- a/src/content/dependencies/linkExternal.js +++ b/src/content/dependencies/linkExternal.js @@ -9,8 +9,12 @@ export default { wikiInfo.canonicalMediaBase, }), - data: (sprawl, url) => ({ - url, + data: (sprawl, urlEntry) => ({ + url: + urlEntry.url, + + annotation: + urlEntry.annotation, canonicalBase: sprawl.canonicalBase, @@ -70,11 +74,9 @@ export default { }, generate(data, slots, {html, language, to}) { - const {url} = data; - let urlIsValid; try { - new URL(url); + new URL(data.url); urlIsValid = true; } catch { urlIsValid = false; @@ -83,31 +85,39 @@ export default { let href; if (urlIsValid) { const {canonicalBase, canonicalMediaBase} = data; - const past = front => decodeURIComponent(url.slice(front.length)); - if (canonicalMediaBase && url.startsWith(canonicalMediaBase)) { + const past = front => decodeURIComponent(data.url.slice(front.length)); + if (canonicalMediaBase && data.url.startsWith(canonicalMediaBase)) { href = to('media.path', past(canonicalMediaBase)); - } else if (canonicalBase && url.startsWith(canonicalBase)) { + } else if (canonicalBase && data.url.startsWith(canonicalBase)) { href = to('shared.path', past(canonicalBase)); } else { - href = url; + href = data.url; } } + const urlEntry = { + url: href, + annotation: data.annotation, + }; + let formattedLink; + let formattedPlatform; if (urlIsValid) { formattedLink = - language.formatExternalLink(url, { + language.formatExternalLink(urlEntry, { style: slots.style, context: slots.context, }); + formattedPlatform = + language.formatExternalLink(urlEntry, { + style: 'platform', + context: slots.context, + }); + // Fall back to platform if nothing matched the desired style. - if (html.isBlank(formattedLink) && slots.style !== 'platform') { - formattedLink = - language.formatExternalLink(url, { - style: 'platform', - context: slots.context, - }); + if (html.isBlank(formattedLink)) { + formattedLink = formattedPlatform; } } else { formattedLink = null; @@ -119,6 +129,10 @@ export default { let linkContent; if (urlIsValid) { + // For valid URLs, the annotation (if any) is already handled + // by formatExternalLink. It is not shown at all, in up-front + // presentation, if there is custom content. + linkAttributes.set('href', href); if (html.isBlank(slots.content)) { @@ -127,17 +141,25 @@ export default { linkContent = slots.content; } } else { + // For invalid URLs, there is no automatically formatted link, + // and the annotation (if any) is rolled into presentation below. + // However, it's still not shown if there is custom content. + if (html.isBlank(slots.content)) { - linkContent = - html.tag('i', - language.$('misc.external.invalidURL.annotation')); + if (data.annotation) { + linkContent = + language.$('misc.external.withAnnotation', { + link: html.tag('i', language.$('misc.external.invalidURL.annotation')), + annotation: language.sanitize(data.annotation), + }); + } else { + linkContent = html.tag('i', language.$('misc.external.invalidURL')); + } } else { linkContent = - language.$('misc.external.invalidURL', { + language.$('misc.external.withAnnotation', { link: slots.content, - annotation: - html.tag('i', - language.$('misc.external.invalidURL.annotation')), + annotation: html.tag('i', language.$('misc.external.invalidURL.annotation')), }); } } @@ -159,9 +181,7 @@ export default { } else { titleText = language.$('misc.external.opensInNewTab', { - link: formattedLink, - annotation: - language.$('misc.external.opensInNewTab.annotation'), + platform: formattedPlatform, }); } } else if (!html.isBlank(slots.content)) { diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js index 775ccfdc..62a547e4 100644 --- a/src/content/dependencies/transformContent.js +++ b/src/content/dependencies/transformContent.js @@ -282,14 +282,14 @@ export default { nodes .filter(({type}) => type === 'external-link') .map(({data: {href}}) => - relation('linkExternal', href)), + relation('linkExternal', {url: href})), externalLinksForTooltipNodes: nodes .filter(({type}) => type === 'tooltip') .filter(({data}) => data.link) .map(({data: {link: href}}) => - relation('linkExternal', href)), + relation('linkExternal', {url: href})), images: nodes diff --git a/src/external-links.js b/src/external-links.js index fa4a832e..847970b8 100644 --- a/src/external-links.js +++ b/src/external-links.js @@ -733,10 +733,10 @@ function createEmptyResults() { return Object.fromEntries(externalLinkStyles.map(style => [style, null])); } -export function getMatchingDescriptorsForExternalLink(url, descriptors, { +export function getMatchingDescriptorsForExternalLink(urlEntry, descriptors, { context = 'generic', } = {}) { - const {domain, pathname, query} = urlParts(url); + const {domain, pathname, query} = urlParts(urlEntry.url); const compareDomain = string => { // A dot at the start of the descriptor's domain indicates @@ -760,7 +760,7 @@ export function getMatchingDescriptorsForExternalLink(url, descriptors, { const compareQuery = regex => regex.test(query.slice(1)); const compareExtractSpec = extract => - extractPartFromExternalLink(url, extract, {mode: 'test'}); + extractPartFromExternalLink(urlEntry, extract, {mode: 'test'}); const contextArray = (Array.isArray(context) @@ -813,12 +813,12 @@ export function getMatchingDescriptorsForExternalLink(url, descriptors, { return [...matchingDescriptors, fallbackDescriptor]; } -export function extractPartFromExternalLink(url, extract, { +export function extractPartFromExternalLink(urlEntry, extract, { // Set to 'test' to just see if this would extract anything. // This disables running custom transformations. mode = 'extract', } = {}) { - const {domain, pathname, query} = urlParts(url); + const {domain, pathname, query} = urlParts(urlEntry.url); let regexen = []; let tests = []; @@ -827,7 +827,7 @@ export function extractPartFromExternalLink(url, extract, { if (extract instanceof RegExp) { regexen.push(extract); - tests.push(url); + tests.push(urlEntry.url); } else { for (const [key, value] of Object.entries(extract)) { switch (key) { @@ -862,7 +862,7 @@ export function extractPartFromExternalLink(url, extract, { continue; case 'url': - tests.push(url); + tests.push(urlEntry.url); break; case 'domain': @@ -917,19 +917,19 @@ export function extractPartFromExternalLink(url, extract, { return value; } -export function extractAllCustomPartsFromExternalLink(url, custom) { +export function extractAllCustomPartsFromExternalLink(urlEntry, custom) { const customParts = {}; // All or nothing: if one part doesn't match, all results are scrapped. for (const [key, value] of Object.entries(custom)) { - customParts[key] = extractPartFromExternalLink(url, value); + customParts[key] = extractPartFromExternalLink(urlEntry, value); if (!customParts[key]) return null; } return customParts; } -export function getExternalLinkStringOfStyleFromDescriptor(url, style, descriptor, {language}) { +export function getExternalLinkStringOfStyleFromDescriptor(urlEntry, style, descriptor, {language}) { const prefix = 'misc.external'; function getDetail() { @@ -939,46 +939,75 @@ export function getExternalLinkStringOfStyleFromDescriptor(url, style, descripto if (typeof descriptor.detail === 'string') { return language.$(prefix, descriptor.platform, descriptor.detail); - } else { + } else if (descriptor.detial.substring) { const {substring, ...rest} = descriptor.detail; const opts = withEntries(rest, entries => entries .map(([key, value]) => [ key, - extractPartFromExternalLink(url, value), + extractPartFromExternalLink(urlEntry, value), ])); return language.$(prefix, descriptor.platform, substring, opts); + } else if (descriptor.detail.annotation) { + const annotation = + extractPartFromExternalLink(urlEntry, descriptor.detail.annotation); + + if (urlEntry.annotation) { + return language.$(prefix, descriptor.platform, 'withAutomaticAndCustomAnnotations', { + automatic: annotation, + custom: urlEntry.annotation, + }); + } else { + return language.$(prefix, descriptor.platform, 'withAnnotation', { + annotation, + }); + } } } switch (style) { case 'platform': { - const platform = language.$(prefix, descriptor.platform); - const domain = urlParts(url).domain; + const platformName = language.$(prefix, descriptor.platform); + const domain = urlParts(urlEntry.url).domain; + let platformPart; if (descriptor === fallbackDescriptor) { // The fallback descriptor has a "platform" which is just // the word "External". This isn't really useful when you're // looking for platform info! if (domain) { - return language.sanitize(domain.replace(/^www\./, '')); + platformPart = language.sanitize(domain.replace(/^www\./, '')); } else { - return platform; + platformPart = platformName; } } else if (descriptor.detail) { + // getDetail handles the whole string, including annotation, so just + // return its result as-is. return getDetail(); } else if (descriptor.unusualDomain && domain) { - return language.$(prefix, 'withDomain', {platform, domain}); + platformPart = language.$(prefix, 'withUnusualDomain', { + platform: platformName, + domain, + }); + } else { + platformPart = platformName; + } + + if (urlEntry.annotation) { + return language.$(prefix, 'withAnnotation', { + link: platformPart, + annotation: language.sanitize(urlEntry.annotation), + }); } else { - return platform; + return platformPart; } } case 'handle': { if (descriptor.handle) { - return extractPartFromExternalLink(url, descriptor.handle); + return extractPartFromExternalLink(urlEntry, descriptor.handle); } else { return null; } @@ -1008,12 +1037,12 @@ export function couldDescriptorSupportStyle(descriptor, style) { } } -export function getExternalLinkStringOfStyleFromDescriptors(url, style, descriptors, { +export function getExternalLinkStringOfStyleFromDescriptors(urlEntry, style, descriptors, { language, context = 'generic', }) { const matchingDescriptors = - getMatchingDescriptorsForExternalLink(url, descriptors, {context}); + getMatchingDescriptorsForExternalLink(urlEntry, descriptors, {context}); const styleFilteredDescriptors = matchingDescriptors.filter(descriptor => @@ -1021,7 +1050,7 @@ export function getExternalLinkStringOfStyleFromDescriptors(url, style, descript for (const descriptor of styleFilteredDescriptors) { const descriptorResult = - getExternalLinkStringOfStyleFromDescriptor(url, style, descriptor, {language}); + getExternalLinkStringOfStyleFromDescriptor(urlEntry, style, descriptor, {language}); if (descriptorResult) { return descriptorResult; @@ -1031,17 +1060,17 @@ export function getExternalLinkStringOfStyleFromDescriptors(url, style, descript return null; } -export function getExternalLinkStringsFromDescriptor(url, descriptor, {language}) { +export function getExternalLinkStringsFromDescriptor(urlEntry, descriptor, {language}) { return ( Object.fromEntries( externalLinkStyles.map(style => getExternalLinkStringOfStyleFromDescriptor( - url, + urlEntry, style, descriptor, {language})))); } -export function getExternalLinkStringsFromDescriptors(url, descriptors, { +export function getExternalLinkStringsFromDescriptors(urlEntry, descriptors, { language, context = 'generic', }) { @@ -1049,11 +1078,11 @@ export function getExternalLinkStringsFromDescriptors(url, descriptors, { const remainingKeys = new Set(Object.keys(results)); const matchingDescriptors = - getMatchingDescriptorsForExternalLink(url, descriptors, {context}); + getMatchingDescriptorsForExternalLink(urlEntry, descriptors, {context}); for (const descriptor of matchingDescriptors) { const descriptorResults = - getExternalLinkStringsFromDescriptor(url, descriptor, {language}); + getExternalLinkStringsFromDescriptor(urlEntry, descriptor, {language}); const descriptorKeys = new Set( diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 0745160a..458e85e7 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -676,19 +676,23 @@ misc: external: external: "External" - withDomain: - "{PLATFORM} ({DOMAIN})" + withUnusualDomain: >- + {PLATFORM} ({DOMAIN}) - withHandle: - "{PLATFORM} ({HANDLE})" + withHandle: >- + {PLATFORM} ({HANDLE}) - opensInNewTab: - _: "{LINK} ({ANNOTATION})" - annotation: "opens in new tab" + withAnnotation: >- + {LINK} ({ANNOTATION}) - invalidURL: - _: "{LINK} ({ANNOTATION})" - annotation: "invalid URL" + withAutomaticAndCustomAnnotations: >- + {LINK} ({AUTOMATIC}, {CUSTOM}) + + opensInNewTab: "{PLATFORM} (opens in new tab)" + opensInNewTab.annotation: "opens in new tab" + + invalidURL: "invalid URL" + invalidURL.annotation: "invalid URL" amazonMusic: "Amazon Music" appleMusic: "Apple Music" |