diff options
Diffstat (limited to 'src/content')
15 files changed, 223 insertions, 256 deletions
diff --git a/src/content/dependencies/generateAdditionalNamesBox.js b/src/content/dependencies/generateAdditionalNamesBox.js new file mode 100644 index 00000000..f7fa3b00 --- /dev/null +++ b/src/content/dependencies/generateAdditionalNamesBox.js @@ -0,0 +1,48 @@ +import {stitchArrays} from '#sugar'; + +export default { + contentDependencies: ['transformContent'], + extraDependencies: ['html', 'language'], + + relations: (relation, additionalNames) => ({ + names: + additionalNames.map(({name}) => + relation('transformContent', name)), + + annotations: + additionalNames.map(({annotation}) => + (annotation + ? relation('transformContent', annotation) + : null)), + }), + + generate: (relations, {html, language}) => { + const names = + relations.names.map(name => + html.tag('span', {class: 'additional-name'}, + name.slot('mode', 'inline'))); + + const annotations = + relations.annotations.map(annotation => + (annotation + ? html.tag('span', {class: 'annotation'}, + language.$('misc.additionalNames.item.annotation', { + annotation: + annotation.slot('mode', 'inline'), + })) + : null)); + + return html.tag('div', {id: 'additional-names-box'}, [ + html.tag('p', + language.$('misc.additionalNames.title')), + + html.tag('ul', + stitchArrays({name: names, annotation: annotations}) + .map(({name, annotation}) => + html.tag('li', + (annotation + ? language.$('misc.additionalNames.item.withAnnotation', {name, annotation}) + : language.$('misc.additionalNames.item', {name}))))), + ]); + }, +}; diff --git a/src/content/dependencies/generateAlbumReleaseInfo.js b/src/content/dependencies/generateAlbumReleaseInfo.js index d6405283..dd5baab9 100644 --- a/src/content/dependencies/generateAlbumReleaseInfo.js +++ b/src/content/dependencies/generateAlbumReleaseInfo.js @@ -94,7 +94,11 @@ export default { links: language.formatDisjunctionList( relations.externalLinks - .map(link => link.slot('mode', 'album'))), + .map(link => + link.slots({ + context: 'album', + style: 'normal', + }))), })), ]); }, diff --git a/src/content/dependencies/generateAlbumSidebarGroupBox.js b/src/content/dependencies/generateAlbumSidebarGroupBox.js index 331ddaba..f3705450 100644 --- a/src/content/dependencies/generateAlbumSidebarGroupBox.js +++ b/src/content/dependencies/generateAlbumSidebarGroupBox.js @@ -66,7 +66,10 @@ export default { !empty(relations.externalLinks) && html.tag('p', language.$('releaseInfo.visitOn', { - links: language.formatDisjunctionList(relations.externalLinks), + links: + language.formatDisjunctionList( + relations.externalLinks + .map(link => link.slot('context', 'group'))), })), slots.mode === 'album' && diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js index 03bc0af5..be9f9b86 100644 --- a/src/content/dependencies/generateArtistInfoPage.js +++ b/src/content/dependencies/generateArtistInfoPage.js @@ -161,7 +161,13 @@ export default { sec.visit && html.tag('p', language.$('releaseInfo.visitOn', { - links: language.formatDisjunctionList(sec.visit.externalLinks), + links: + language.formatDisjunctionList( + sec.visit.externalLinks.map(link => + link.slots({ + context: 'artist', + mode: 'platform', + }))), })), sec.artworks?.artistGalleryLink && diff --git a/src/content/dependencies/generateContributionList.js b/src/content/dependencies/generateContributionList.js index 731cfba5..6401e65e 100644 --- a/src/content/dependencies/generateContributionList.js +++ b/src/content/dependencies/generateContributionList.js @@ -16,5 +16,6 @@ export default { showIcons: true, showContribution: true, preventWrapping: false, + iconMode: 'tooltip', })))), }; diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js index 09c6b37c..c60f9696 100644 --- a/src/content/dependencies/generateFlashInfoPage.js +++ b/src/content/dependencies/generateFlashInfoPage.js @@ -133,7 +133,7 @@ export default { links: language.formatDisjunctionList( relations.externalLinks - .map(link => link.slot('mode', 'flash'))), + .map(link => link.slot('context', 'flash'))), })), sec.featuredTracks && [ diff --git a/src/content/dependencies/generateGroupInfoPage.js b/src/content/dependencies/generateGroupInfoPage.js index 0583755e..05df33fb 100644 --- a/src/content/dependencies/generateGroupInfoPage.js +++ b/src/content/dependencies/generateGroupInfoPage.js @@ -107,7 +107,10 @@ export default { sec.info.visitLinks && html.tag('p', language.$('releaseInfo.visitOn', { - links: language.formatDisjunctionList(sec.info.visitLinks), + links: + language.formatDisjunctionList( + sec.info.visitLinks + .map(link => link.slot('context', 'group'))), })), html.tag('blockquote', diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js index 95551f3e..7dee8cc3 100644 --- a/src/content/dependencies/generatePageLayout.js +++ b/src/content/dependencies/generatePageLayout.js @@ -108,6 +108,8 @@ export default { title: {type: 'html'}, showWikiNameInTitle: {type: 'boolean', default: true}, + additionalNames: {type: 'html'}, + cover: {type: 'html'}, socialEmbed: {type: 'html'}, @@ -222,22 +224,25 @@ export default { const colors = getColors(slots.color ?? data.wikiColor); const hasSocialEmbed = !html.isBlank(slots.socialEmbed); - let titleHTML = null; - - if (!html.isBlank(slots.title)) { - switch (slots.headingMode) { - case 'sticky': - titleHTML = - relations.stickyHeadingContainer.slots({ - title: slots.title, - cover: slots.cover, - }); - break; - case 'static': - titleHTML = html.tag('h1', slots.title); - break; - } - } + const titleContentsHTML = + (html.isBlank(slots.title) + ? null + : html.isBlank(slots.additionalNames) + ? language.sanitize(slots.title) + : html.tag('a', { + href: '#additional-names-box', + title: language.$('misc.additionalNames.tooltip').toString(), + }, language.sanitize(slots.title))); + + const titleHTML = + (html.isBlank(slots.title) + ? null + : slots.headingMode === 'sticky' + ? relations.stickyHeadingContainer.slots({ + title: titleContentsHTML, + cover: slots.cover, + }) + : html.tag('h1', titleContentsHTML)); let footerContent = slots.footerContent; @@ -254,6 +259,7 @@ export default { titleHTML, slots.cover, + slots.additionalNames, html.tag('div', { 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/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js index 200cf054..2848b15c 100644 --- a/src/content/dependencies/generateTrackInfoPage.js +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -6,6 +6,7 @@ import getChronologyRelations from '../util/getChronologyRelations.js'; export default { contentDependencies: [ 'generateAdditionalFilesShortcut', + 'generateAdditionalNamesBox', 'generateAlbumAdditionalFilesList', 'generateAlbumNavAccent', 'generateAlbumSidebar', @@ -107,6 +108,11 @@ export default { list: relation('generateAlbumAdditionalFilesList', album, additionalFiles), }); + if (!empty(track.additionalNames)) { + relations.additionalNamesBox = + relation('generateAdditionalNamesBox', track.additionalNames); + } + if (track.hasUniqueCoverArt || album.hasCoverArt) { relations.cover = relation('generateTrackCoverArtwork', track); @@ -296,6 +302,8 @@ export default { title: language.$('trackPage.title', {track: data.name}), headingMode: 'sticky', + additionalNames: relations.additionalNamesBox ?? null, + color: data.color, styleRules: [relations.albumStyleRules], diff --git a/src/content/dependencies/generateTrackReleaseInfo.js b/src/content/dependencies/generateTrackReleaseInfo.js index 9a7478ca..c347dbce 100644 --- a/src/content/dependencies/generateTrackReleaseInfo.js +++ b/src/content/dependencies/generateTrackReleaseInfo.js @@ -77,7 +77,10 @@ export default { html.tag('p', (relations.externalLinks ? language.$('releaseInfo.listenOn', { - links: language.formatDisjunctionList(relations.externalLinks), + links: + language.formatDisjunctionList( + relations.externalLinks + .map(link => link.slot('context', 'track'))), }) : language.$('releaseInfo.listenOn.noLinks', { name: html.tag('i', data.name), diff --git a/src/content/dependencies/linkContribution.js b/src/content/dependencies/linkContribution.js index 8e42f247..790afa4f 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 = {}; @@ -20,7 +13,6 @@ export default { if (!empty(contribution.who.urls)) { relations.artistIcons = contribution.who.urls - .slice(0, 4) .map(url => relation('linkExternalAsIcon', url)); } @@ -37,37 +29,81 @@ 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) + .map(icon => icon.slot('context', 'artist')))); } - 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 + .map(icon => icon.slots({context: 'artist', withText: true})))), + ]; + } + + 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/content/dependencies/linkExternal.js b/src/content/dependencies/linkExternal.js index 5de612e2..0a079614 100644 --- a/src/content/dependencies/linkExternal.js +++ b/src/content/dependencies/linkExternal.js @@ -1,140 +1,30 @@ -// 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 {isExternalLinkContext, isExternalLinkStyle} from '#external-links'; export default { extraDependencies: ['html', 'language', 'wikiData'], - sprawl: ({wikiInfo}) => ({wikiInfo}), - - data(sprawl, url) { - const data = {url}; - - const {canonicalBase} = sprawl.wikiInfo; - if (canonicalBase) { - const {hostname: canonicalDomain} = new URL(canonicalBase); - Object.assign(data, {canonicalDomain}); - } - - return data; - }, + data: (url) => ({url}), slots: { - mode: { - validate: v => v.is('generic', 'album', 'flash'), + style: { + // This awkward syntax is because the slot descriptor validator can't + // differentiate between a function that returns a validator (the usual + // syntax) and a function that is itself a validator. + validate: () => isExternalLinkStyle, + default: 'normal', + }, + + context: { + validate: () => isExternalLinkContext, default: 'generic', }, }, - generate(data, slots, {html, language}) { - let isLocal; - let domain; - let pathname; - - try { - const url = new URL(data.url); - domain = url.hostname; - pathname = url.pathname; - } catch (error) { - // No support for relative local URLs yet, sorry! (I.e, local URLs must - // be absolute relative to the domain name in order to work.) - isLocal = true; - domain = null; - pathname = null; - } - - // isLocal also applies for URLs which match the 'Canonical Base' under - // wiki-info.yaml, if present. - if (data.canonicalDomain && domain === data.canonicalDomain) { - isLocal = true; - } - - const link = html.tag('a', - { - href: data.url, - class: 'nowrap', - }, - - // truly unhinged indentation here - isLocal - ? language.$('misc.external.local') - - : domain.includes('bandcamp.com') - ? language.$('misc.external.bandcamp') - - : BANDCAMP_DOMAINS.includes(domain) - ? language.$('misc.external.bandcamp.domain', {domain}) - - : MASTODON_DOMAINS.includes(domain) - ? language.$('misc.external.mastodon.domain', {domain}) - - : domain.includes('youtu') - ? slots.mode === 'album' - ? data.url.includes('list=') - ? language.$('misc.external.youtube.playlist') - : language.$('misc.external.youtube.fullAlbum') - : language.$('misc.external.youtube') - - : domain.includes('soundcloud') - ? language.$('misc.external.soundcloud') - - : domain.includes('tumblr.com') - ? language.$('misc.external.tumblr') - - : domain.includes('twitter.com') - ? language.$('misc.external.twitter') - - : domain.includes('deviantart.com') - ? language.$('misc.external.deviantart') - - : domain.includes('wikipedia.org') - ? language.$('misc.external.wikipedia') - - : domain.includes('poetryfoundation.org') - ? language.$('misc.external.poetryFoundation') - - : domain.includes('instagram.com') - ? language.$('misc.external.instagram') - - : domain.includes('patreon.com') - ? language.$('misc.external.patreon') - - : domain.includes('spotify.com') - ? language.$('misc.external.spotify') - - : domain.includes('newgrounds.com') - ? language.$('misc.external.newgrounds') - - : domain); - - switch (slots.mode) { - case 'flash': { - const wrap = content => - html.tag('span', {class: 'nowrap'}, content); - - if (domain.includes('homestuck.com')) { - const match = pathname.match(/\/story\/(.*)\/?/); - if (match) { - if (isNaN(Number(match[1]))) { - return wrap(language.$('misc.external.flash.homestuck.secret', {link})); - } else { - return wrap(language.$('misc.external.flash.homestuck.page', { - link, - page: match[1], - })); - } - } - } else if (domain.includes('bgreco.net')) { - return wrap(language.$('misc.external.flash.bgreco', {link})); - } else if (domain.includes('youtu')) { - return wrap(language.$('misc.external.flash.youtube', {link})); - } - - return link; - } - - default: - return link; - } - } + generate: (data, slots, {html, language}) => + html.tag('a', + {href: data.url, class: 'nowrap'}, + language.formatExternalLink(data.url, { + style: slots.style, + context: slots.context, + })), }; diff --git a/src/content/dependencies/linkExternalAsIcon.js b/src/content/dependencies/linkExternalAsIcon.js index cd168992..357c835c 100644 --- a/src/content/dependencies/linkExternalAsIcon.js +++ b/src/content/dependencies/linkExternalAsIcon.js @@ -1,46 +1,45 @@ -// 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 {isExternalLinkContext} from '#external-links'; export default { extraDependencies: ['html', 'language', 'to'], - data(url) { - return {url}; + data: (url) => ({url}), + + slots: { + context: { + // This awkward syntax is because the slot descriptor validator can't + // differentiate between a function that returns a validator (the usual + // syntax) and a function that is itself a validator. + validate: () => isExternalLinkContext, + default: 'generic', + }, + + withText: {type: 'boolean'}, }, - 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})]); + generate(data, slots, {html, language, to}) { + const format = style => + language.formatExternalLink(data.url, {style, context: slots.context}); + + const normalText = format('normal'); + const compactText = format('compact'); + const iconId = format('icon-id'); 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', iconId), + }), + ]), + + slots.withText && + html.tag('span', {class: 'icon-text'}, + compactText ?? normalText), + ]); }, }; diff --git a/src/content/dependencies/linkExternalFlash.js b/src/content/dependencies/linkExternalFlash.js deleted file mode 100644 index 65158ff8..00000000 --- a/src/content/dependencies/linkExternalFlash.js +++ /dev/null @@ -1,41 +0,0 @@ -// Note: This function is seriously hard-coded for HSMusic, with custom -// presentation of links to Homestuck flashes hosted various places. - -export default { - contentDependencies: ['linkExternal'], - extraDependencies: ['html', 'language'], - - relations(relation, url) { - return { - link: relation('linkExternal', url), - }; - }, - - data(url, flash) { - return { - url, - page: flash.page, - }; - }, - - generate(data, relations, {html, language}) { - const {link} = relations; - const {url, page} = data; - - return html.tag('span', - {class: 'nowrap'}, - - url.includes('homestuck.com') - ? isNaN(Number(page)) - ? language.$('misc.external.flash.homestuck.secret', {link}) - : language.$('misc.external.flash.homestuck.page', {link, page}) - - : url.includes('bgreco.net') - ? language.$('misc.external.flash.bgreco', {link}) - - : url.includes('youtu') - ? language.$('misc.external.flash.youtube', {link}) - - : link); - }, -}; |