diff --git a/src/content/dependencies/generateAlbumChronologyLinks.js b/src/content/dependencies/generateAlbumChronologyLinks.js deleted file mode 100644 index 3dd7a18e1..000000000 --- a/src/content/dependencies/generateAlbumChronologyLinks.js +++ /dev/null @@ -1,53 +0,0 @@ -import {sortAlbumsTracksChronologically} from '#sort'; - -import getChronologyRelations from '../util/getChronologyRelations.js'; - -export default { - contentDependencies: [ - 'generateChronologyLinks', - 'linkAlbum', - 'linkArtist', - 'linkTrack', - ], - - relations: (relation, album) => ({ - chronologyLinks: - relation('generateChronologyLinks'), - - coverArtistChronologyContributions: - getChronologyRelations(album, { - contributions: album.coverArtistContribs ?? [], - - linkArtist: artist => relation('linkArtist', artist), - - linkThing: trackOrAlbum => - (trackOrAlbum.album - ? relation('linkTrack', trackOrAlbum) - : relation('linkAlbum', trackOrAlbum)), - - getThings(artist) { - const getDate = thing => thing.coverArtDate ?? thing.date; - - const things = - ([ - artist.albumCoverArtistContributions, - artist.trackCoverArtistContributions, - ]).flat() - .map(({thing}) => thing) - .filter(getDate); - - return sortAlbumsTracksChronologically(things, {getDate}); - }, - }), - }), - - generate: (relations) => - relations.chronologyLinks.slots({ - chronologyInfoSets: [ - { - headingString: 'misc.chronology.heading.coverArt', - contributions: relations.coverArtistChronologyContributions, - }, - ], - }), -} diff --git a/src/content/dependencies/generateAlbumInfoPage.js b/src/content/dependencies/generateAlbumInfoPage.js index 3af312bb4..1bffe2d03 100644 --- a/src/content/dependencies/generateAlbumInfoPage.js +++ b/src/content/dependencies/generateAlbumInfoPage.js @@ -10,7 +10,6 @@ export default { 'generateAlbumSocialEmbed', 'generateAlbumStyleRules', 'generateAlbumTrackList', - 'generateAlbumChronologyLinks', 'generateCommentarySection', 'generateContentHeading', 'generatePageLayout', @@ -33,9 +32,6 @@ export default { albumNavAccent: relation('generateAlbumNavAccent', album, null), - chronologyLinks: - relation('generateAlbumChronologyLinks', album), - secondaryNav: relation('generateAlbumSecondaryNav', album), @@ -196,9 +192,6 @@ export default { }, ], - navContent: - relations.chronologyLinks, - banner: relations.banner ?? null, bannerPosition: 'top', diff --git a/src/content/dependencies/generateAlbumReleaseInfo.js b/src/content/dependencies/generateAlbumReleaseInfo.js index e4c184c01..28227f450 100644 --- a/src/content/dependencies/generateAlbumReleaseInfo.js +++ b/src/content/dependencies/generateAlbumReleaseInfo.js @@ -67,17 +67,25 @@ export default { {[html.joinChildren]: html.tag('br')}, [ - relations.artistContributionsLine - .slots({stringKey: capsule + '.by'}), + relations.artistContributionsLine.slots({ + stringKey: capsule + '.by', + chronologyKind: 'album', + }), - relations.coverArtistContributionsLine - .slots({stringKey: capsule + '.coverArtBy'}), + relations.coverArtistContributionsLine.slots({ + stringKey: capsule + '.coverArtBy', + chronologyKind: 'coverArt', + }), - relations.wallpaperArtistContributionsLine - .slots({stringKey: capsule + '.wallpaperArtBy'}), + relations.wallpaperArtistContributionsLine.slots({ + stringKey: capsule + '.wallpaperArtBy', + chronologyKind: 'wallpaperArt', + }), - relations.bannerArtistContributionsLine - .slots({stringKey: capsule + '.bannerArtBy'}), + relations.bannerArtistContributionsLine.slots({ + stringKey: capsule + '.bannerArtBy', + chronologyKind: 'bannerArt', + }), language.$(capsule, 'released', { [language.onlyIfOptions]: ['date'], diff --git a/src/content/dependencies/generateChronologyLinks.js b/src/content/dependencies/generateChronologyLinks.js deleted file mode 100644 index 7f24ded78..000000000 --- a/src/content/dependencies/generateChronologyLinks.js +++ /dev/null @@ -1,112 +0,0 @@ -import {accumulateSum, empty} from '#sugar'; - -export default { - extraDependencies: ['html', 'language'], - - slots: { - allowCollapsing: { - type: 'boolean', - default: true, - }, - - showOnly: { - type: 'boolean', - default: false, - }, - - chronologyInfoSets: { - validate: v => - v.strictArrayOf( - v.validateProperties({ - headingString: v.isString, - contributions: v.strictArrayOf(v.validateProperties({ - index: v.isCountingNumber, - only: v.isBoolean, - artistDirectory: v.isDirectory, - artistLink: v.isHTML, - previousLink: v.isHTML, - nextLink: v.isHTML, - })), - })), - } - }, - - generate(slots, {html, language}) { - if (empty(slots.chronologyInfoSets)) { - return html.blank(); - } - - let infoSets = slots.chronologyInfoSets; - - if (!slots.showOnly) { - infoSets = infoSets - .map(({contributions, ...entry}) => ({ - ...entry, - contributions: - contributions - .filter(({only}) => !only), - })) - .filter(({contributions}) => !empty(contributions)); - } - - const totalContributionCount = - accumulateSum( - infoSets, - ({contributions}) => contributions.length); - - if (totalContributionCount === 0) { - return html.blank(); - } - - if (slots.allowCollapsing && totalContributionCount > 8) { - return html.tag('div', {class: 'chronology'}, - language.$('misc.chronology.seeArtistPages')); - } - - return html.tags( - infoSets.map(({ - headingString, - contributions, - }) => - contributions.map(({ - index, - artistLink, - previousLink, - nextLink, - only, - }) => { - const heading = - html.tag('span', {class: 'heading'}, - language.$(headingString, { - index: - (only - ? language.formatString('misc.chronology.heading.onlyIndex') - : language.formatIndex(index)), - - artist: artistLink, - })); - - const navigation = - !only && - html.tag('span', {class: 'buttons'}, - language.formatUnitList([ - previousLink?.slots({ - tooltipStyle: 'browser', - color: false, - content: language.$('misc.nav.previous'), - }), - - nextLink?.slots({ - tooltipStyle: 'browser', - color: false, - content: language.$('misc.nav.next'), - }), - ].filter(Boolean))); - - return html.tag('div', {class: 'chronology'}, - (navigation - ? language.$('misc.chronology.withNavigation', {heading, navigation}) - : heading)); - }))); - }, -}; diff --git a/src/content/dependencies/generateChronologyLinksScopeSwitcher.js b/src/content/dependencies/generateChronologyLinksScopeSwitcher.js deleted file mode 100644 index 4a1b67a74..000000000 --- a/src/content/dependencies/generateChronologyLinksScopeSwitcher.js +++ /dev/null @@ -1,68 +0,0 @@ -import {stitchArrays} from '#sugar'; - -export default { - extraDependencies: ['html', 'language'], - - slots: { - scopes: { - validate: v => v.strictArrayOf(v.isStringNonEmpty), - }, - - contents: { - validate: v => v.strictArrayOf(v.isHTML), - }, - - open: { - type: 'boolean', - default: true, - }, - }, - - generate(slots, {html, language}) { - // TODO: Manual [html.onlyIfContent]-alike here is a bit unfortunate. - // We can't use a normal [html.onlyIfContent] because the summary counts - // as content - we'd need to encode that we want to exclude it from the - // content check (for the
element), somehow. - if (slots.contents.every(content => html.isBlank(content))) { - return html.blank(); - } - - const summary = - html.tag('summary', - {class: 'underline-white'}, - - html.tag('span', - language.encapsulate('trackPage.nav.chronology.scope', capsule => - language.$(capsule, 'title', { - scope: - slots.scopes.map((scope, index) => - html.tag('a', {class: 'switcher-link'}, - {href: '#'}, - - (index === 0 - ? {style: 'display: inline'} - : {style: 'display: none'}), - - language.$(capsule, scope))), - })))); - - const scopeContents = - stitchArrays({ - scope: slots.scopes, - content: slots.contents, - }).map(({scope, content}, index) => - html.tag('div', {class: 'scope-' + scope}, - (index === 0 - ? {style: 'display: block'} - : {style: 'display: none'}), - - content)); - - return ( - html.tag('details', {class: 'scoped-chronology-switcher'}, - slots.open && - {open: true}, - - [summary, scopeContents])); - }, -}; diff --git a/src/content/dependencies/generateContributionList.js b/src/content/dependencies/generateContributionList.js index 0c4ef87a0..8e8c5020d 100644 --- a/src/content/dependencies/generateContributionList.js +++ b/src/content/dependencies/generateContributionList.js @@ -8,7 +8,11 @@ export default { .map(contrib => relation('linkContribution', contrib)), }), - generate: (relations, {html}) => + slots: { + chronologyKind: {type: 'string'}, + }, + + generate: (relations, slots, {html}) => html.tag('ul', {[html.onlyIfContent]: true}, @@ -16,9 +20,10 @@ export default { .map(contributionLink => html.tag('li', contributionLink.slots({ - showIcons: true, + showExternalLinks: true, showContribution: true, + showChronology: true, preventWrapping: false, - iconMode: 'tooltip', + chronologyKind: slots.chronologyKind, })))), }; diff --git a/src/content/dependencies/generateContributionTooltip.js b/src/content/dependencies/generateContributionTooltip.js new file mode 100644 index 000000000..3a31014d7 --- /dev/null +++ b/src/content/dependencies/generateContributionTooltip.js @@ -0,0 +1,48 @@ +export default { + contentDependencies: [ + 'generateContributionTooltipChronologySection', + 'generateContributionTooltipExternalLinkSection', + 'generateTooltip', + ], + + extraDependencies: ['html'], + + relations: (relation, contribution) => ({ + tooltip: + relation('generateTooltip'), + + externalLinkSection: + relation('generateContributionTooltipExternalLinkSection', contribution), + + chronologySection: + relation('generateContributionTooltipChronologySection', contribution), + }), + + slots: { + showExternalLinks: {type: 'boolean'}, + showChronology: {type: 'boolean'}, + + chronologyKind: {type: 'string'}, + }, + + generate: (relations, slots, {html}) => + relations.tooltip.slots({ + attributes: + {class: 'contribution-tooltip'}, + + contentAttributes: { + [html.joinChildren]: + html.tag('span', {class: 'tooltip-divider'}), + }, + + content: [ + slots.showExternalLinks && + relations.externalLinkSection, + + slots.showChronology && + relations.chronologySection.slots({ + kind: slots.chronologyKind, + }), + ], + }), +}; diff --git a/src/content/dependencies/generateContributionTooltipChronologySection.js b/src/content/dependencies/generateContributionTooltipChronologySection.js new file mode 100644 index 000000000..78c9051c3 --- /dev/null +++ b/src/content/dependencies/generateContributionTooltipChronologySection.js @@ -0,0 +1,117 @@ +export default { + contentDependencies: ['linkAnythingMan'], + extraDependencies: ['html', 'language'], + + query(contribution) { + let previous = contribution; + while (previous && previous.thing === contribution.thing) { + previous = previous.previousBySameArtist; + } + + let next = contribution; + while (next && next.thing === contribution.thing) { + next = next.nextBySameArtist; + } + + return {previous, next}; + }, + + relations: (relation, query, _contribution) => ({ + previousLink: + (query.previous + ? relation('linkAnythingMan', query.previous.thing) + : null), + + nextLink: + (query.next + ? relation('linkAnythingMan', query.next.thing) + : null), + }), + + data: (query, _contribution) => ({ + previousName: + (query.previous + ? query.previous.thing.name + : null), + + nextName: + (query.next + ? query.next.thing.name + : null), + }), + + slots: { + kind: { + validate: v => + v.is( + 'album', + 'bannerArt', + 'coverArt', + 'flash', + 'track', + 'trackArt', + 'trackContribution', + 'wallpaperArt'), + }, + }, + + generate: (data, relations, slots, {html, language}) => + language.encapsulate('misc.artistLink.chronology', capsule => + html.tags([ + html.tags([ + relations.previousLink?.slots({ + attributes: {class: 'chronology-link'}, + content: [ + html.tag('span', {class: 'chronology-symbol'}, + language.$(capsule, 'previous.symbol')), + + html.tag('span', {class: 'chronology-text'}, + language.sanitize(data.previousName)), + ], + }), + + html.tag('span', {class: 'chronology-info'}, + {[html.onlyIfSiblings]: true}, + + language.encapsulate(capsule, 'previous.info', workingCapsule => { + const workingOptions = {}; + + if (slots.kind) { + workingCapsule += '.withKind'; + workingOptions.kind = + language.$(capsule, 'kind', slots.kind); + } + + return language.$(workingCapsule, workingOptions); + })), + ]), + + html.tags([ + relations.nextLink?.slots({ + attributes: {class: 'chronology-link'}, + content: [ + html.tag('span', {class: 'chronology-symbol'}, + language.$(capsule, 'next.symbol')), + + html.tag('span', {class: 'chronology-text'}, + language.sanitize(data.nextName)), + ], + }), + + html.tag('span', {class: 'chronology-info'}, + {[html.onlyIfSiblings]: true}, + + language.encapsulate(capsule, 'next.info', workingCapsule => { + const workingOptions = {}; + + if (slots.kind) { + workingCapsule += '.withKind'; + workingOptions.kind = + language.$(capsule, 'kind', slots.kind); + } + + return language.$(workingCapsule, workingOptions); + })) + ]), + ])), +}; diff --git a/src/content/dependencies/generateContributionTooltipExternalLinkSection.js b/src/content/dependencies/generateContributionTooltipExternalLinkSection.js new file mode 100644 index 000000000..d43420984 --- /dev/null +++ b/src/content/dependencies/generateContributionTooltipExternalLinkSection.js @@ -0,0 +1,68 @@ +import {stitchArrays} from '#sugar'; + +export default { + contentDependencies: [ + 'generateExternalHandle', + 'generateExternalIcon', + 'generateExternalPlatform', + ], + + extraDependencies: ['html', 'language'], + + relations: (relation, contribution) => ({ + icons: + contribution.artist.urls + .map(url => relation('generateExternalIcon', url)), + + handles: + contribution.artist.urls + .map(url => relation('generateExternalHandle', url)), + + platforms: + contribution.artist.urls + .map(url => relation('generateExternalPlatform', url)), + }), + + data: (contribution) => ({ + urls: contribution.artist.urls, + }), + + generate: (data, relations, {html, language}) => + language.encapsulate('misc.artistLink', capsule => + html.tags( + stitchArrays({ + icon: relations.icons, + handle: relations.handles, + platform: relations.platforms, + url: data.urls, + }).map(({icon, handle, platform, url}) => { + for (const template of [icon, handle, platform]) { + template.setSlot('context', 'artist'); + } + + return [ + html.tag('a', {class: 'external-link'}, + {href: url}, + + [ + icon, + + html.tag('span', {class: 'external-handle'}, + (html.isBlank(handle) + ? platform + : handle)), + ]), + + html.tag('span', {class: 'external-platform'}, + // This is a pretty ridiculous hack, but we currently + // don't have a way of telling formatExternalLink to *not* + // use the fallback string, which just formats the URL as + // its host/domain... so is technically detectable. + ((html.resolve(platform, {normalize: 'string'}) === + (new URL(url)).host) + + ? language.$(capsule, 'noExternalLinkPlatformName') + : platform)), + ]; + }))), +}; diff --git a/src/content/dependencies/generateExternalHandle.js b/src/content/dependencies/generateExternalHandle.js new file mode 100644 index 000000000..8c0368a4a --- /dev/null +++ b/src/content/dependencies/generateExternalHandle.js @@ -0,0 +1,20 @@ +import {isExternalLinkContext} from '#external-links'; + +export default { + extraDependencies: ['html', 'language'], + + data: (url) => ({url}), + + slots: { + context: { + validate: () => isExternalLinkContext, + default: 'generic', + }, + }, + + generate: (data, slots, {language}) => + language.formatExternalLink(data.url, { + style: 'handle', + context: slots.context, + }), +}; diff --git a/src/content/dependencies/generateExternalIcon.js b/src/content/dependencies/generateExternalIcon.js new file mode 100644 index 000000000..637af6587 --- /dev/null +++ b/src/content/dependencies/generateExternalIcon.js @@ -0,0 +1,26 @@ +import {isExternalLinkContext} from '#external-links'; + +export default { + extraDependencies: ['html', 'language', 'to'], + + data: (url) => ({url}), + + slots: { + context: { + validate: () => isExternalLinkContext, + default: 'generic', + }, + }, + + generate: (data, slots, {html, language, to}) => + html.tag('span', {class: 'external-icon'}, + html.tag('svg', + html.tag('use', { + href: + to('staticMisc.icon', + language.formatExternalLink(data.url, { + style: 'icon-id', + context: slots.context, + })), + }))), +}; diff --git a/src/content/dependencies/generateExternalPlatform.js b/src/content/dependencies/generateExternalPlatform.js new file mode 100644 index 000000000..c4f63ecf9 --- /dev/null +++ b/src/content/dependencies/generateExternalPlatform.js @@ -0,0 +1,20 @@ +import {isExternalLinkContext} from '#external-links'; + +export default { + extraDependencies: ['html', 'language'], + + data: (url) => ({url}), + + slots: { + context: { + validate: () => isExternalLinkContext, + default: 'generic', + }, + }, + + generate: (data, slots, {language}) => + language.formatExternalLink(data.url, { + style: 'platform', + context: slots.context, + }), +}; diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js index 96337d831..d06f0c01d 100644 --- a/src/content/dependencies/generateFlashInfoPage.js +++ b/src/content/dependencies/generateFlashInfoPage.js @@ -147,7 +147,9 @@ export default { title: language.$('releaseInfo.contributors'), }), - relations.contributorContributionList, + relations.contributorContributionList.slots({ + chronologyKind: 'flash', + }), ]), relations.artistCommentarySection, diff --git a/src/content/dependencies/generateReleaseInfoContributionsLine.js b/src/content/dependencies/generateReleaseInfoContributionsLine.js index 2e6c47091..3e96ed448 100644 --- a/src/content/dependencies/generateReleaseInfoContributionsLine.js +++ b/src/content/dependencies/generateReleaseInfoContributionsLine.js @@ -17,10 +17,12 @@ export default { }, slots: { - stringKey: {type: 'string'}, - showContribution: {type: 'boolean', default: true}, - showIcons: {type: 'boolean', default: true}, + showExternalLinks: {type: 'boolean', default: true}, + showChronology: {type: 'boolean', default: true}, + + stringKey: {type: 'string'}, + chronologyKind: {type: 'string'}, }, generate(relations, slots, {html, language}) { @@ -34,8 +36,9 @@ export default { relations.contributionLinks.map(link => link.slots({ showContribution: slots.showContribution, - showIcons: slots.showIcons, - iconMode: 'tooltip', + showExternalLinks: slots.showExternalLinks, + showChronology: slots.showChronology, + chronologyKind: slots.chronologyKind, }))), }); }, diff --git a/src/content/dependencies/generateTooltip.js b/src/content/dependencies/generateTooltip.js index 81f74aec1..8314d33ce 100644 --- a/src/content/dependencies/generateTooltip.js +++ b/src/content/dependencies/generateTooltip.js @@ -21,10 +21,13 @@ export default { generate: (slots, {html}) => html.tag('span', {class: 'tooltip'}, {[html.noEdgeWhitespace]: true}, + {[html.onlyIfContent]: true}, slots.attributes, html.tag('span', {class: 'tooltip-content'}, {[html.noEdgeWhitespace]: true}, + {[html.onlyIfContent]: true}, slots.contentAttributes, + slots.content)), }; diff --git a/src/content/dependencies/generateTrackChronologyLinks.js b/src/content/dependencies/generateTrackChronologyLinks.js deleted file mode 100644 index f9ad62999..000000000 --- a/src/content/dependencies/generateTrackChronologyLinks.js +++ /dev/null @@ -1,177 +0,0 @@ -import {sortAlbumsTracksChronologically} from '#sort'; -import {accumulateSum, stitchArrays} from '#sugar'; - -import getChronologyRelations from '../util/getChronologyRelations.js'; - -export default { - contentDependencies: [ - 'generateChronologyLinks', - 'generateChronologyLinksScopeSwitcher', - 'linkAlbum', - 'linkArtist', - 'linkTrack', - ], - - relations(relation, track) { - function getScopedRelations(album) { - const albumFilter = - (album - ? track => track.album === album - : () => true); - - return { - chronologyLinks: - relation('generateChronologyLinks'), - - artistChronologyContributions: - getChronologyRelations(track, { - contributions: [ - ...track.artistContribs ?? [], - ...track.contributorContribs ?? [], - ], - - linkArtist: artist => relation('linkArtist', artist), - linkThing: track => relation('linkTrack', track), - - getThings(artist) { - const getDate = thing => thing.date; - - const things = - ([ - artist.trackArtistContributions, - artist.trackContributorContributions, - ]).flat() - .map(({thing}) => thing) - .filter(getDate) - .filter(albumFilter); - - return sortAlbumsTracksChronologically(things, {getDate}); - }, - }), - - coverArtistChronologyContributions: - getChronologyRelations(track, { - contributions: track.coverArtistContribs ?? [], - - linkArtist: artist => relation('linkArtist', artist), - - linkThing: trackOrAlbum => - (trackOrAlbum.album - ? relation('linkTrack', trackOrAlbum) - : relation('linkAlbum', trackOrAlbum)), - - getThings(artist) { - const getDate = thing => thing.coverArtDate ?? thing.date; - - // Album artwork isn't part of cover artist chronology scoped to - // even the same album - we use this list to show "nth track art". - const applicableContributions = - (album - ? artist.trackCoverArtistContributions - : ([ - artist.albumCoverArtistContributions, - artist.trackCoverArtistContributions, - ]).flat()); - - const things = - applicableContributions - .map(({thing}) => thing) - .filter(getDate) - .filter(albumFilter); - - return sortAlbumsTracksChronologically(things, {getDate}); - }, - }), - }; - } - - const relations = {}; - - relations.scopeSwitcher = - relation('generateChronologyLinksScopeSwitcher'); - - relations.wiki = - getScopedRelations(null); - - relations.album = - getScopedRelations(track.album); - - for (const setKey of [ - 'artistChronologyContributions', - 'coverArtistChronologyContributions', - ]) { - const wikiSet = relations.wiki[setKey]; - const albumSet = relations.album[setKey]; - - const wikiArtistDirectories = - wikiSet - .map(({artistDirectory}) => artistDirectory); - - albumSet.sort((a, b) => - (a.only === b.only && a.index === b.index - ? (wikiArtistDirectories.indexOf(a.artistDirectory) - - wikiArtistDirectories.indexOf(b.artistDirectory)) - : 0)); - } - - return relations; - }, - - generate(relations) { - function slotScopedRelations({content, artworkHeadingString}) { - return content.chronologyLinks.slots({ - showOnly: true, - allowCollapsing: false, - - chronologyInfoSets: [ - { - headingString: 'misc.chronology.heading.track', - contributions: content.artistChronologyContributions, - }, - { - headingString: `misc.chronology.heading.${artworkHeadingString}`, - contributions: content.coverArtistChronologyContributions, - }, - ], - }); - } - - const scopes = [ - 'wiki', - 'album', - ]; - - const contents = [ - relations.wiki, - relations.album, - ]; - - const artworkHeadingStrings = [ - 'coverArt', - 'trackArt', - ]; - - const totalContributionCount = - Math.max(... - contents.map(content => - accumulateSum([ - content.artistChronologyContributions, - content.coverArtistChronologyContributions, - ], contributions => contributions.length))); - - relations.scopeSwitcher.setSlots({ - scopes, - - open: - totalContributionCount <= 5, - - contents: - stitchArrays({ - content: contents, - artworkHeadingString: artworkHeadingStrings, - }).map(slotScopedRelations), - }); - - return relations.scopeSwitcher; - }, -}; diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js index 7337b987d..64ed0cb45 100644 --- a/src/content/dependencies/generateTrackInfoPage.js +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -10,7 +10,6 @@ export default { 'generateContributionList', 'generatePageLayout', 'generateTrackAdditionalNamesBox', - 'generateTrackChronologyLinks', 'generateTrackCoverArtwork', 'generateTrackInfoPageFeaturedByFlashesList', 'generateTrackInfoPageOtherReleasesList', @@ -49,9 +48,6 @@ export default { albumNavAccent: relation('generateAlbumNavAccent', track.album, track), - chronologyLinks: - relation('generateTrackChronologyLinks', track), - secondaryNav: relation('generateAlbumSecondaryNav', track.album), @@ -217,7 +213,9 @@ export default { title: language.$('releaseInfo.contributors'), }), - relations.contributorContributionList, + relations.contributorContributionList.slots({ + chronologyKind: 'trackContribution', + }), ]), html.tags([ @@ -395,9 +393,6 @@ export default { showExtraLinks: false, }), - navContent: - relations.chronologyLinks, - secondaryNav: relations.secondaryNav .slot('mode', 'track'), diff --git a/src/content/dependencies/generateTrackList.js b/src/content/dependencies/generateTrackList.js index bc3b6035f..7c3b11c15 100644 --- a/src/content/dependencies/generateTrackList.js +++ b/src/content/dependencies/generateTrackList.js @@ -17,12 +17,7 @@ export default { .map(contrib => relation('linkContribution', contrib))), }), - slots: { - showContribution: {type: 'boolean', default: false}, - showIcons: {type: 'boolean', default: false}, - }, - - generate: (relations, slots, {html, language}) => + generate: (relations, {html, language}) => html.tag('ul', {[html.onlyIfContent]: true}, @@ -42,12 +37,7 @@ export default { html.metatag('chunkwrap', {split: ','}, language.$(itemCapsule, 'withArtists.by', { artists: - language.formatConjunctionList( - contributionLinks.map(link => - link.slots({ - showContribution: slots.showContribution, - showIcons: slots.showIcons, - }))), + language.formatConjunctionList(contributionLinks), }))); } diff --git a/src/content/dependencies/generateTrackReleaseInfo.js b/src/content/dependencies/generateTrackReleaseInfo.js index e234dd5d6..8a0810466 100644 --- a/src/content/dependencies/generateTrackReleaseInfo.js +++ b/src/content/dependencies/generateTrackReleaseInfo.js @@ -54,11 +54,15 @@ export default { {[html.joinChildren]: html.tag('br')}, [ - relations.artistContributionLinks - .slots({stringKey: capsule + '.by'}), + relations.artistContributionLinks.slots({ + stringKey: capsule + '.by', + chronologyKind: 'track', + }), - relations.coverArtistContributionsLine - ?.slots({stringKey: capsule + '.coverArtBy'}), + relations.coverArtistContributionsLine?.slots({ + stringKey: capsule + '.coverArtBy', + chronologyKind: 'trackArt', + }), language.$(capsule, 'released', { [language.onlyIfOptions]: ['date'], diff --git a/src/content/dependencies/linkAnythingMan.js b/src/content/dependencies/linkAnythingMan.js new file mode 100644 index 000000000..d46974039 --- /dev/null +++ b/src/content/dependencies/linkAnythingMan.js @@ -0,0 +1,25 @@ +export default { + contentDependencies: [ + 'linkAlbum', + 'linkFlash', + 'linkTrack', + ], + + query: (thing) => ({ + referenceType: thing.constructor[Symbol.for('Thing.referenceType')], + }), + + relations: (relation, query, thing) => ({ + link: + (query.referenceType === 'album' + ? relation('linkAlbum', thing) + : query.referenceType === 'flash' + ? relation('linkFlash', thing) + : query.referenceType === 'track' + ? relation('linkTrack', thing) + : null), + }), + + generate: (relations) => + relations.link, +}; diff --git a/src/content/dependencies/linkContribution.js b/src/content/dependencies/linkContribution.js index 1a51c3878..26f0b2d74 100644 --- a/src/content/dependencies/linkContribution.js +++ b/src/content/dependencies/linkContribution.js @@ -1,145 +1,78 @@ -import {empty, stitchArrays} from '#sugar'; - export default { contentDependencies: [ + 'generateContributionTooltip', 'generateTextWithTooltip', - 'generateTooltip', 'linkArtist', - 'linkExternalAsIcon', ], extraDependencies: ['html', 'language'], - relations(relation, contribution) { - const relations = {}; - - relations.artistLink = - relation('linkArtist', contribution.artist); + relations: (relation, contribution) => ({ + artistLink: + relation('linkArtist', contribution.artist), - relations.textWithTooltip = - relation('generateTextWithTooltip'); + textWithTooltip: + relation('generateTextWithTooltip'), - relations.tooltip = - relation('generateTooltip'); + tooltip: + relation('generateContributionTooltip', contribution), + }), - if (!empty(contribution.artist.urls)) { - relations.artistIcons = - contribution.artist.urls - .map(url => relation('linkExternalAsIcon', url)); - } - - return relations; - }, - - data(contribution) { - return { - contribution: contribution.annotation, - urls: contribution.artist.urls, - }; - }, + data: (contribution) => ({ + contribution: contribution.annotation, + urls: contribution.artist.urls, + }), slots: { showContribution: {type: 'boolean', default: false}, - showIcons: {type: 'boolean', default: false}, - preventWrapping: {type: 'boolean', default: true}, + showExternalLinks: {type: 'boolean', default: false}, + showChronology: {type: 'boolean', default: false}, - iconMode: { - validate: v => v.is('inline', 'tooltip'), - default: 'inline' - }, + preventWrapping: {type: 'boolean', default: true}, + chronologyKind: {type: 'string'}, }, - generate(data, relations, slots, {html, language}) { - const hasContribution = !!(slots.showContribution && data.contribution); - const hasExternalIcons = !!(slots.showIcons && relations.artistIcons); - - const parts = ['misc.artistLink']; - const options = {}; - - options.artist = - (hasExternalIcons && slots.iconMode === 'tooltip' - ? relations.textWithTooltip.slots({ - customInteractionCue: true, - - text: - relations.artistLink.slots({ - attributes: {class: 'text-with-tooltip-interaction-cue'}, - }), - - tooltip: - relations.tooltip.slots({ - attributes: - {class: ['icons', 'icons-tooltip']}, - - contentAttributes: - {[html.joinChildren]: ''}, - - content: - stitchArrays({ - icon: relations.artistIcons, - url: data.urls, - }).map(({icon, url}) => { - icon.setSlots({ - context: 'artist', - withText: true, - }); - - let platformText = - language.formatExternalLink(url, { - context: 'artist', - style: 'platform', - }); - - // This is a pretty ridiculous hack, but we currently - // don't have a way of telling formatExternalLink to *not* - // use the fallback string, which just formats the URL as - // its host/domain... so is technically detectable. - if (platformText.toString() === (new URL(url)).host) { - platformText = - language.$('misc.artistLink.noExternalLinkPlatformName'); - } - - const platformSpan = - html.tag('span', {class: 'icon-platform'}, - platformText); - - return [icon, platformSpan]; - }), - }), - }) - : relations.artistLink); - - if (hasContribution) { - parts.push('withContribution'); - options.contrib = data.contribution; - } - - if (hasExternalIcons && slots.iconMode === 'inline') { - parts.push('withExternalLinks'); - options.links = - html.tag('span', {class: ['icons', 'icons-inline']}, - {[html.noEdgeWhitespace]: true}, - language.formatUnitList( - relations.artistIcons - .slice(0, 4) - .map(icon => icon.slot('context', 'artist')))); - } - - const contributionPart = - language.formatString(...parts, options); - - if (!hasContribution && !hasExternalIcons) { - return contributionPart; - } - - return ( - html.tag('span', {class: 'contribution'}, - {[html.noEdgeWhitespace]: true}, - - parts.length > 1 && - slots.preventWrapping && - {class: 'nowrap'}, - - contributionPart)); - }, + generate: (data, relations, slots, {html, language}) => + html.tag('span', {class: 'contribution'}, + {[html.noEdgeWhitespace]: true}, + + slots.preventWrapping && + {class: 'nowrap'}, + + language.encapsulate('misc.artistLink', workingCapsule => { + const workingOptions = {}; + + relations.tooltip.setSlots({ + showExternalLinks: slots.showExternalLinks, + showChronology: slots.showChronology, + chronologyKind: slots.chronologyKind, + }); + + workingOptions.artist = + (html.isBlank(relations.tooltip) + ? relations.artistLink + : relations.textWithTooltip.slots({ + customInteractionCue: true, + + text: + relations.artistLink.slots({ + attributes: {class: 'text-with-tooltip-interaction-cue'}, + }), + + tooltip: + relations.tooltip.slots({ + showExternalLinks: slots.showExternalLinks, + showChronology: slots.showChronology, + chronologyKind: slots.chronologyKind, + }), + })); + + if (slots.showContribution && data.contribution) { + workingCapsule += '.withContribution'; + workingOptions.contrib = + data.contribution; + } + + return language.formatString(workingCapsule, workingOptions); + })), }; diff --git a/src/content/dependencies/linkExternalAsIcon.js b/src/content/dependencies/linkExternalAsIcon.js deleted file mode 100644 index e2ce4b3ce..000000000 --- a/src/content/dependencies/linkExternalAsIcon.js +++ /dev/null @@ -1,51 +0,0 @@ -import {isExternalLinkContext} from '#external-links'; - -export default { - extraDependencies: ['html', 'language', 'to'], - - 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, slots, {html, language, to}) { - const format = style => - language.formatExternalLink(data.url, {style, context: slots.context}); - - const platformText = format('platform'); - const handleText = format('handle'); - const iconId = format('icon-id'); - - return html.tag('a', {class: 'icon'}, - {href: data.url}, - - slots.withText && - {class: 'has-text'}, - - [ - html.tag('svg', [ - !slots.withText && - html.tag('title', platformText), - - html.tag('use', { - href: to('staticMisc.icon', iconId), - }), - ]), - - slots.withText && - html.tag('span', {class: 'icon-text'}, - (html.isBlank(handleText) - ? platformText - : handleText)), - ]); - }, -}; diff --git a/src/content/util/getChronologyRelations.js b/src/content/util/getChronologyRelations.js deleted file mode 100644 index c601a990f..000000000 --- a/src/content/util/getChronologyRelations.js +++ /dev/null @@ -1,57 +0,0 @@ -export default function getChronologyRelations(thing, { - contributions, - linkArtist, - linkThing, - getThings, -}) { - // One call to getChronologyRelations is considered "lumping" together all - // contributions as carrying equivalent meaning (for example, "artist" - // contributions and "contributor" contributions are bunched together in - // one call to getChronologyRelations, while "cover artist" contributions - // are a separate call). getChronologyRelations prevents duplicates that - // carry the same meaning by only using the first instance of each artist - // in the contributions array passed to it. It's expected that the string - // identifying which kind of contribution ("track" or "cover art") is - // shared and applied to all contributions, as providing them together - // in one call to getChronologyRelations implies they carry the same - // meaning. - - const artistsSoFar = new Set(); - - contributions = contributions.filter(({artist}) => { - if (artistsSoFar.has(artist)) { - return false; - } else { - artistsSoFar.add(artist); - return true; - } - }); - - return contributions.map(({artist}) => { - const things = Array.from(new Set(getThings(artist))); - - // Don't show a line if this contribution isn't part of the artist's - // chronology at all (usually because this thing isn't dated). - const index = things.indexOf(thing); - if (index === -1) { - return; - } - - const previous = things[index - 1]; - const next = things[index + 1]; - - return { - index: index + 1, - artistDirectory: artist.directory, - only: !(previous || next), - - artistLink: linkArtist(artist), - previousLink: previous ? linkThing(previous) : null, - nextLink: next ? linkThing(next) : null, - }; - }).filter(Boolean) - .sort((a, b) => - (a.only === b.only ? b.index - a.index - : a.only ? +1 - : -1)) -} diff --git a/src/data/composite/data/excludeFromList.js b/src/data/composite/data/excludeFromList.js index d798dcdcf..2a3e818eb 100644 --- a/src/data/composite/data/excludeFromList.js +++ b/src/data/composite/data/excludeFromList.js @@ -5,11 +5,6 @@ // See also: // - fillMissingListItems // -// More list utilities: -// - withFilteredList, withMappedList, withSortedList -// - withFlattenedList, withUnflattenedList -// - withPropertyFromList, withPropertiesFromList -// import {input, templateCompositeFrom} from '#composite'; import {empty} from '#sugar'; diff --git a/src/data/composite/data/fillMissingListItems.js b/src/data/composite/data/fillMissingListItems.js index 4f818a791..356b1119e 100644 --- a/src/data/composite/data/fillMissingListItems.js +++ b/src/data/composite/data/fillMissingListItems.js @@ -4,11 +4,6 @@ // See also: // - excludeFromList // -// More list utilities: -// - withFilteredList, withMappedList, withSortedList -// - withFlattenedList, withUnflattenedList -// - withPropertyFromList, withPropertiesFromList -// import {input, templateCompositeFrom} from '#composite'; diff --git a/src/data/composite/data/index.js b/src/data/composite/data/index.js index 0a47c43c4..c80bb3503 100644 --- a/src/data/composite/data/index.js +++ b/src/data/composite/data/index.js @@ -3,16 +3,32 @@ // Entries here may depend on entries in #composite/control-flow. // +// Utilities which act on generic objects + +export {default as withPropertiesFromObject} from './withPropertiesFromObject.js'; +export {default as withPropertyFromObject} from './withPropertyFromObject.js'; + +// Utilities which act on generic lists + export {default as excludeFromList} from './excludeFromList.js'; + export {default as fillMissingListItems} from './fillMissingListItems.js'; +export {default as withUniqueItemsOnly} from './withUniqueItemsOnly.js'; + export {default as withFilteredList} from './withFilteredList.js'; -export {default as withFlattenedList} from './withFlattenedList.js'; export {default as withMappedList} from './withMappedList.js'; -export {default as withPropertiesFromList} from './withPropertiesFromList.js'; -export {default as withPropertiesFromObject} from './withPropertiesFromObject.js'; -export {default as withPropertyFromList} from './withPropertyFromList.js'; -export {default as withPropertyFromObject} from './withPropertyFromObject.js'; export {default as withSortedList} from './withSortedList.js'; -export {default as withSum} from './withSum.js'; + +export {default as withPropertyFromList} from './withPropertyFromList.js'; +export {default as withPropertiesFromList} from './withPropertiesFromList.js'; + +export {default as withFlattenedList} from './withFlattenedList.js'; export {default as withUnflattenedList} from './withUnflattenedList.js'; -export {default as withUniqueItemsOnly} from './withUniqueItemsOnly.js'; + +export {default as withIndexInList} from './withIndexInList.js'; +export {default as withNearbyItemFromList} from './withNearbyItemFromList.js'; + +// Utilities which act on slightly more particular data forms +// (probably, containers of particular kinds of values) + +export {default as withSum} from './withSum.js'; diff --git a/src/data/composite/data/withFilteredList.js b/src/data/composite/data/withFilteredList.js index 82e569033..60fe66f41 100644 --- a/src/data/composite/data/withFilteredList.js +++ b/src/data/composite/data/withFilteredList.js @@ -16,12 +16,6 @@ // - withMappedList // - withSortedList // -// More list utilities: -// - excludeFromList -// - fillMissingListItems -// - withFlattenedList, withUnflattenedList -// - withPropertyFromList, withPropertiesFromList -// import {input, templateCompositeFrom} from '#composite'; diff --git a/src/data/composite/data/withFlattenedList.js b/src/data/composite/data/withFlattenedList.js index edfa34038..31b1a742b 100644 --- a/src/data/composite/data/withFlattenedList.js +++ b/src/data/composite/data/withFlattenedList.js @@ -5,12 +5,6 @@ // See also: // - withUnflattenedList // -// More list utilities: -// - excludeFromList -// - fillMissingListItems -// - withFilteredList, withMappedList, withSortedList -// - withPropertyFromList, withPropertiesFromList -// import {input, templateCompositeFrom} from '#composite'; diff --git a/src/data/composite/data/withIndexInList.js b/src/data/composite/data/withIndexInList.js new file mode 100644 index 000000000..b1af20333 --- /dev/null +++ b/src/data/composite/data/withIndexInList.js @@ -0,0 +1,38 @@ +// Gets the index of the provided item in the provided list. Note that this +// will output -1 if the item is not found, and this may be detected using +// any availability check with type: 'index'. If the list includes the item +// twice, the output index will be of the first match. +// +// Both the list and item must be provided. +// +// See also: +// - withNearbyItemFromList +// - exitWithoutDependency +// - raiseOutputWithoutDependency +// + +import {input, templateCompositeFrom} from '#composite'; + +export default templateCompositeFrom({ + annotation: `withIndexInList`, + + inputs: { + list: input({acceptsNull: false, type: 'array'}), + item: input({acceptsNull: false}), + }, + + outputs: ['#index'], + + steps: () => [ + { + dependencies: [input('list'), input('item')], + compute: (continuation, { + [input('list')]: list, + [input('item')]: item, + }) => continuation({ + ['#index']: + list.indexOf(item), + }), + }, + ], +}); diff --git a/src/data/composite/data/withMappedList.js b/src/data/composite/data/withMappedList.js index e0a700b26..0bc63a927 100644 --- a/src/data/composite/data/withMappedList.js +++ b/src/data/composite/data/withMappedList.js @@ -5,12 +5,6 @@ // - withFilteredList // - withSortedList // -// More list utilities: -// - excludeFromList -// - fillMissingListItems -// - withFlattenedList, withUnflattenedList -// - withPropertyFromList, withPropertiesFromList -// import {input, templateCompositeFrom} from '#composite'; diff --git a/src/data/composite/data/withNearbyItemFromList.js b/src/data/composite/data/withNearbyItemFromList.js new file mode 100644 index 000000000..83a8cc21e --- /dev/null +++ b/src/data/composite/data/withNearbyItemFromList.js @@ -0,0 +1,73 @@ +// Gets a nearby (typically adjacent) item in a list, meaning the item which is +// placed at a particular offset compared to the provided item. This is null if +// the provided list doesn't include the provided item at all, and also if the +// offset would read past either end of the list - except if configured: +// +// - If the 'wrap' input is provided (as true), the offset will loop around +// and continue from the opposing end. +// +// - If the 'valuePastEdge' input is provided, that value will be output +// instead of null. +// +// Both the list and item must be provided. +// +// See also: +// - withIndexInList +// + +import {input, templateCompositeFrom} from '#composite'; +import {atOffset} from '#sugar'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; + +import withIndexInList from './withIndexInList.js'; + +export default templateCompositeFrom({ + annotation: `withNearbyItemFromList`, + + inputs: { + list: input({acceptsNull: false, type: 'array'}), + item: input({acceptsNull: false}), + + offset: input({type: 'number'}), + wrap: input({type: 'boolean', defaultValue: false}), + }, + + outputs: ['#nearbyItem'], + + steps: () => [ + withIndexInList({ + list: input('list'), + item: input('item'), + }), + + raiseOutputWithoutDependency({ + dependency: '#index', + mode: input.value('index'), + + output: input.value({ + ['#nearbyItem']: + null, + }), + }), + + { + dependencies: [ + input('list'), + input('offset'), + input('wrap'), + '#index', + ], + + compute: (continuation, { + [input('list')]: list, + [input('offset')]: offset, + [input('wrap')]: wrap, + ['#index']: index, + }) => continuation({ + ['#nearbyItem']: + atOffset(list, index, offset, {wrap}), + }), + }, + ], +}); diff --git a/src/data/composite/data/withPropertiesFromList.js b/src/data/composite/data/withPropertiesFromList.js index 08907babd..fb4134bc3 100644 --- a/src/data/composite/data/withPropertiesFromList.js +++ b/src/data/composite/data/withPropertiesFromList.js @@ -8,12 +8,6 @@ // - withPropertiesFromObject // - withPropertyFromList // -// More list utilities: -// - excludeFromList -// - fillMissingListItems -// - withFilteredList, withMappedList, withSortedList -// - withFlattenedList, withUnflattenedList -// import {input, templateCompositeFrom} from '#composite'; import {isString, validateArrayItems} from '#validators'; diff --git a/src/data/composite/data/withPropertyFromList.js b/src/data/composite/data/withPropertyFromList.js index a2c66d77f..65ebf77b3 100644 --- a/src/data/composite/data/withPropertyFromList.js +++ b/src/data/composite/data/withPropertyFromList.js @@ -9,12 +9,6 @@ // - withPropertiesFromList // - withPropertyFromObject // -// More list utilities: -// - excludeFromList -// - fillMissingListItems -// - withFilteredList, withMappedList, withSortedList -// - withFlattenedList, withUnflattenedList -// import {input, templateCompositeFrom} from '#composite'; diff --git a/src/data/composite/data/withSortedList.js b/src/data/composite/data/withSortedList.js index dd8107869..a7d217684 100644 --- a/src/data/composite/data/withSortedList.js +++ b/src/data/composite/data/withSortedList.js @@ -27,12 +27,6 @@ // - withFilteredList // - withMappedList // -// More list utilities: -// - excludeFromList -// - fillMissingListItems -// - withFlattenedList, withUnflattenedList -// - withPropertyFromList, withPropertiesFromList -// import {input, templateCompositeFrom} from '#composite'; diff --git a/src/data/composite/data/withUnflattenedList.js b/src/data/composite/data/withUnflattenedList.js index 39a666dc8..820d628aa 100644 --- a/src/data/composite/data/withUnflattenedList.js +++ b/src/data/composite/data/withUnflattenedList.js @@ -7,12 +7,6 @@ // See also: // - withFlattenedList // -// More list utilities: -// - excludeFromList -// - fillMissingListItems -// - withFilteredList, withMappedList, withSortedList -// - withPropertyFromList, withPropertiesFromList -// import {input, templateCompositeFrom} from '#composite'; import {isWholeNumber, validateArrayItems} from '#validators'; diff --git a/src/data/composite/things/contribution/index.js b/src/data/composite/things/contribution/index.js index 2c812644d..9b22be2ef 100644 --- a/src/data/composite/things/contribution/index.js +++ b/src/data/composite/things/contribution/index.js @@ -1,6 +1,7 @@ export {default as inheritFromContributionPresets} from './inheritFromContributionPresets.js'; export {default as thingPropertyMatches} from './thingPropertyMatches.js'; export {default as thingReferenceTypeMatches} from './thingReferenceTypeMatches.js'; +export {default as withContainingReverseContributionList} from './withContainingReverseContributionList.js'; export {default as withContributionArtist} from './withContributionArtist.js'; export {default as withContributionContext} from './withContributionContext.js'; export {default as withMatchingContributionPresets} from './withMatchingContributionPresets.js'; diff --git a/src/data/composite/things/contribution/withContainingReverseContributionList.js b/src/data/composite/things/contribution/withContainingReverseContributionList.js new file mode 100644 index 000000000..56704c8b9 --- /dev/null +++ b/src/data/composite/things/contribution/withContainingReverseContributionList.js @@ -0,0 +1,40 @@ +// Get the artist's contribution list containing this property. + +import {input, templateCompositeFrom} from '#composite'; + +import {raiseOutputWithoutDependency} from '#composite/control-flow'; +import {withPropertyFromObject} from '#composite/data'; + +import withContributionArtist from './withContributionArtist.js'; + +export default templateCompositeFrom({ + annotation: `withContainingReverseContributionList`, + + inputs: { + artistProperty: input({ + defaultDependency: 'artistProperty', + acceptsNull: true, + }), + }, + + outputs: ['#containingReverseContributionList'], + + steps: () => [ + raiseOutputWithoutDependency({ + dependency: input('artistProperty'), + output: input.value({ + ['#containingReverseContributionList']: + null, + }), + }), + + withContributionArtist(), + + withPropertyFromObject({ + object: '#artist', + property: input('artistProperty'), + }).outputs({ + ['#value']: '#containingReverseContributionList', + }), + ], +}); diff --git a/src/data/composite/things/contribution/withContributionArtist.js b/src/data/composite/things/contribution/withContributionArtist.js index 9e588936a..5a611c1af 100644 --- a/src/data/composite/things/contribution/withContributionArtist.js +++ b/src/data/composite/things/contribution/withContributionArtist.js @@ -5,10 +5,13 @@ import {withPropertyFromObject} from '#composite/data'; import {withResolvedReference} from '#composite/wiki-data'; export default templateCompositeFrom({ - annotation: `withOwnContributionArtist`, + annotation: `withContributionArtist`, inputs: { - ref: input({type: 'string'}), + ref: input({ + type: 'string', + defaultDependency: 'artist', + }), }, outputs: ['#artist'], diff --git a/src/data/composite/wiki-data/withRecontextualizedContributionList.js b/src/data/composite/wiki-data/withRecontextualizedContributionList.js index 06c997b5f..d2401eacd 100644 --- a/src/data/composite/wiki-data/withRecontextualizedContributionList.js +++ b/src/data/composite/wiki-data/withRecontextualizedContributionList.js @@ -1,12 +1,14 @@ // Clones all the contributions in a list, with thing and thingProperty both // updated to match the current thing. Overwrites the provided dependency. -// Doesn't do anything if the provided dependency is null. +// Optionally updates artistProperty as well. Doesn't do anything if +// the provided dependency is null. // // See also: // - withRedatedContributionList // import {input, templateCompositeFrom} from '#composite'; +import {isStringNonEmpty} from '#validators'; import {raiseOutputWithoutDependency} from '#composite/control-flow'; import {withClonedThings} from '#composite/wiki-data'; @@ -19,6 +21,11 @@ export default templateCompositeFrom({ type: 'array', acceptsNull: true, }), + + artistProperty: input({ + validate: isStringNonEmpty, + defaultValue: null, + }), }, outputs: ({ @@ -47,16 +54,25 @@ export default templateCompositeFrom({ }, { - dependencies: [input.myself(), input.thisProperty()], + dependencies: [ + input.myself(), + input.thisProperty(), + input('artistProperty'), + ], compute: (continuation, { [input.myself()]: myself, [input.thisProperty()]: thisProperty, + [input('artistProperty')]: artistProperty, }) => continuation({ - ['#assignment']: { - thing: myself, - thingProperty: thisProperty, - }, + ['#assignment']: + Object.assign( + {thing: myself}, + {thingProperty: thisProperty}, + + (artistProperty + ? {artistProperty} + : {})), }), }, diff --git a/src/data/composite/wiki-data/withResolvedContribs.js b/src/data/composite/wiki-data/withResolvedContribs.js index 23b916919..b5d7255ba 100644 --- a/src/data/composite/wiki-data/withResolvedContribs.js +++ b/src/data/composite/wiki-data/withResolvedContribs.js @@ -36,6 +36,11 @@ export default templateCompositeFrom({ validate: isStringNonEmpty, defaultValue: null, }), + + artistProperty: input({ + validate: isStringNonEmpty, + defaultValue: null, + }), }, outputs: ['#resolvedContribs'], @@ -103,12 +108,14 @@ export default templateCompositeFrom({ dependencies: [ '#details', '#thingProperty', + input('artistProperty'), input.myself(), ], compute: (continuation, { ['#details']: details, ['#thingProperty']: thingProperty, + [input('artistProperty')]: artistProperty, [input.myself()]: myself, }) => continuation({ ['#contributions']: @@ -119,6 +126,7 @@ export default templateCompositeFrom({ ...details, thing: myself, thingProperty: thingProperty, + artistProperty: artistProperty, }); return contrib; diff --git a/src/data/composite/wiki-properties/contributionList.js b/src/data/composite/wiki-properties/contributionList.js index a0e6e52b7..d9a6b417f 100644 --- a/src/data/composite/wiki-properties/contributionList.js +++ b/src/data/composite/wiki-properties/contributionList.js @@ -15,7 +15,7 @@ // import {input, templateCompositeFrom} from '#composite'; -import {isContributionList, isDate} from '#validators'; +import {isContributionList, isDate, isStringNonEmpty} from '#validators'; import {exposeConstant, exposeDependencyOrContinue} from '#composite/control-flow'; import {withResolvedContribs} from '#composite/wiki-data'; @@ -30,6 +30,11 @@ export default templateCompositeFrom({ validate: isDate, acceptsNull: true, }), + + artistProperty: input({ + validate: isStringNonEmpty, + defaultValue: null, + }), }, update: {validate: isContributionList}, @@ -38,6 +43,7 @@ export default templateCompositeFrom({ withResolvedContribs({ from: input.updateValue(), thingProperty: input.thisProperty(), + artistProperty: input('artistProperty'), date: input('date'), }), diff --git a/src/data/things/album.js b/src/data/things/album.js index ae5226ba7..a00219465 100644 --- a/src/data/things/album.js +++ b/src/data/things/album.js @@ -142,6 +142,7 @@ export class Album extends Thing { artistContribs: contributionList({ date: 'date', + artistProperty: input.value('albumArtistContributions'), }), coverArtistContribs: [ @@ -151,6 +152,7 @@ export class Album extends Thing { contributionList({ date: '#coverArtDate', + artistProperty: input.value('albumCoverArtistContributions'), }), ], @@ -158,6 +160,10 @@ export class Album extends Thing { // May be null, indicating cover art was added for tracks on the date // each track specifies, or else the track's own release date. date: 'trackArtDate', + + // This is the "correct" value, but it gets overwritten - with the same + // value - regardless. + artistProperty: input.value('trackCoverArtistContributions'), }), wallpaperArtistContribs: [ @@ -167,6 +173,7 @@ export class Album extends Thing { contributionList({ date: '#coverArtDate', + artistProperty: input.value('albumWallpaperArtistContributions'), }), ], @@ -177,6 +184,7 @@ export class Album extends Thing { contributionList({ date: '#coverArtDate', + artistProperty: input.value('albumBannerArtistContributions'), }), ], diff --git a/src/data/things/contribution.js b/src/data/things/contribution.js index 9d6a97114..79acf1e19 100644 --- a/src/data/things/contribution.js +++ b/src/data/things/contribution.js @@ -8,7 +8,7 @@ import Thing from '#thing'; import {isStringNonEmpty, isThing, validateReference} from '#validators'; import {exitWithoutDependency, exposeDependency} from '#composite/control-flow'; -import {withPropertyFromObject} from '#composite/data'; +import {withNearbyItemFromList, withPropertyFromObject} from '#composite/data'; import {withResolvedReference} from '#composite/wiki-data'; import {flag, simpleDate} from '#composite/wiki-properties'; @@ -16,6 +16,7 @@ import { inheritFromContributionPresets, thingPropertyMatches, thingReferenceTypeMatches, + withContainingReverseContributionList, withContributionArtist, withContributionContext, withMatchingContributionPresets, @@ -35,6 +36,11 @@ export class Contribution extends Thing { update: {validate: isStringNonEmpty}, }, + artistProperty: { + flags: {update: true, expose: true}, + update: {validate: isStringNonEmpty}, + }, + date: simpleDate(), artist: [ @@ -155,6 +161,46 @@ export class Contribution extends Thing { isForFlash: thingReferenceTypeMatches({ value: input.value('flash'), }), + + previousBySameArtist: [ + withContainingReverseContributionList().outputs({ + '#containingReverseContributionList': '#list', + }), + + exitWithoutDependency({ + dependency: '#list', + }), + + withNearbyItemFromList({ + list: '#list', + item: input.myself(), + offset: input.value(-1), + }), + + exposeDependency({ + dependency: '#nearbyItem', + }), + ], + + nextBySameArtist: [ + withContainingReverseContributionList().outputs({ + '#containingReverseContributionList': '#list', + }), + + exitWithoutDependency({ + dependency: '#list', + }), + + withNearbyItemFromList({ + list: '#list', + item: input.myself(), + offset: input.value(+1), + }), + + exposeDependency({ + dependency: '#nearbyItem', + }), + ], }); [inspect.custom](depth, options, inspect) { diff --git a/src/data/things/flash.js b/src/data/things/flash.js index 7d37ed81a..89e59fe7f 100644 --- a/src/data/things/flash.js +++ b/src/data/things/flash.js @@ -100,6 +100,7 @@ export class Flash extends Thing { contributorContribs: contributionList({ date: 'date', + artistProperty: input.value('flashContributorContributions'), }), featuredTracks: referenceList({ diff --git a/src/data/things/track.js b/src/data/things/track.js index 28a784f5b..4aaf364ce 100644 --- a/src/data/things/track.js +++ b/src/data/things/track.js @@ -203,6 +203,7 @@ export class Track extends Thing { withResolvedContribs({ from: input.updateValue({validate: isContributionList}), thingProperty: input.thisProperty(), + artistProperty: input.value('trackArtistContributions'), date: '#date', }).outputs({ '#resolvedContribs': '#artistContribs', @@ -219,6 +220,7 @@ export class Track extends Thing { withRecontextualizedContributionList({ list: '#album.artistContribs', + artistProperty: input.value('trackArtistContributions'), }), withRedatedContributionList({ @@ -236,6 +238,7 @@ export class Track extends Thing { contributionList({ date: '#date', + artistProperty: input.value('trackContributorContributions'), }), ], @@ -254,6 +257,7 @@ export class Track extends Thing { withResolvedContribs({ from: input.updateValue({validate: isContributionList}), thingProperty: input.thisProperty(), + artistProperty: input.value('trackCoverArtistContributions'), date: '#trackArtDate', }).outputs({ '#resolvedContribs': '#coverArtistContribs', @@ -270,6 +274,7 @@ export class Track extends Thing { withRecontextualizedContributionList({ list: '#album.trackCoverArtistContribs', + artistProperty: input.value('trackCoverArtistContributions'), }), withRedatedContributionList({ diff --git a/src/static/css/site.css b/src/static/css/site.css index dc99bcc5b..80801c852 100644 --- a/src/static/css/site.css +++ b/src/static/css/site.css @@ -817,24 +817,6 @@ a:not([href]):hover { content: "\0020/\0020"; } -#header .chronology .heading, -#header .chronology .buttons { - white-space: nowrap; -} - -#header .scoped-chronology { - display: none; -} - -#header .scoped-chronology-switcher .switcher-link { - text-decoration: underline; - text-decoration-style: dotted; -} - -#header .scoped-chronology-switcher > div { - margin-left: 20px; -} - #secondary-nav { text-align: center; } @@ -910,7 +892,7 @@ li:not(:first-child:last-child) .tooltip, 0 -2px 4px -2px var(--primary-color) inset; } -.icons-tooltip { +.contribution-tooltip { padding: 3px 6px 6px 6px; left: -34px; } @@ -931,7 +913,7 @@ li:not(:first-child:last-child) .tooltip, margin-right: -120px; } -.icons-tooltip .tooltip-content { +.contribution-tooltip .tooltip-content { padding: 6px 2px 2px 2px; -webkit-user-select: none; @@ -942,42 +924,122 @@ li:not(:first-child:last-child) .tooltip, display: grid; grid-template-columns: - [icon-start] auto [icon-end domain-start] auto [domain-end]; + [icon-start] 26px [icon-end handle-start] auto [handle-end platform-start] auto [platform-end]; +} + +.contribution-tooltip .external-link { + display: grid; + grid-column-start: icon-start; + grid-column-end: handle-end; + grid-template-columns: subgrid; + + height: 1.4em; +} + +.contribution-tooltip .chronology-link { + display: grid; + grid-column-start: icon-start; + grid-column-end: handle-end; + grid-template-columns: subgrid; + + height: 1.2em; } -.icons-tooltip .icon { +.contribution-tooltip .external-icon, +.contribution-tooltip .chronology-symbol { grid-column-start: icon-start; grid-column-end: icon-end; } -.icons-tooltip .icon-platform { +.contribution-tooltip .external-icon svg { + width: 18px; + height: 18px; + top: -0.1em; +} + +.contribution-tooltip .chronology-symbol { + text-align: center; +} + +.contribution-tooltip .external-handle, +.contribution-tooltip .chronology-text { + grid-column-start: handle-start; + grid-column-end: handle-end; + + width: max-content; + max-width: 200px; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.contribution-tooltip .external-handle { + padding-right: 8px; +} + +.contribution-tooltip .chronology-text { + padding-right: 6px; +} + +.contribution-tooltip .chronology-text, +.contribution-tooltip .chronology-info { + font-size: 0.85em; +} + +.contribution-tooltip .tooltip-divider { + grid-column-start: icon-start; + grid-column-end: platform-end; + + border-top: 1px dotted var(--primary-color); + margin-top: 3px; + margin-bottom: 4px; +} + +.contribution-tooltip .external-platform, +.contribution-tooltip .chronology-info { display: none; - grid-column-start: domain-start; - grid-column-end: domain-end; + grid-column-start: platform-start; + grid-column-end: platform-end; - --icon-platform-opacity: 0.8; - padding-right: 4px; + --external-platform-opacity: 0.8; opacity: 0.8; + padding-right: 4px; + + white-space: nowrap; } -.icons-tooltip.show-info .icon-platform { +.contribution-tooltip.show-info .external-platform, +.contribution-tooltip.show-info .chronology-info { display: inline; - animation: icon-platform 0.2s forwards linear; + animation: external-platform 0.2s forwards linear; } -@keyframes icon-platform { +@keyframes external-platform { from { opacity: 0; } to { - opacity: var(--icon-platform-opacity); + opacity: var(--external-platform-opacity); } } -.icons-tooltip .icon:hover + .icon-platform { - --icon-platform-opacity: 1; +.contribution-tooltip .external-link:hover, +.contribution-tooltip .chronology-link:hover { + filter: brightness(1.4); + text-decoration: none; +} + +.contribution-tooltip .external-link:hover .external-handle, +.contribution-tooltip .chronology-link:hover .chronology-text { + text-decoration: underline; +} + +.contribution-tooltip .external-link:hover + .external-platform, +.contribution-tooltip .chronology-link:hover + .chronology-info { + --external-platform-opacity: 1; text-decoration: underline; text-decoration-color: #ffffffaa; } @@ -993,27 +1055,15 @@ li:not(:first-child:last-child) .tooltip, padding: 3px 4.5px; } -.icons { - font-style: normal; - white-space: nowrap; -} - -.icons a:hover { - filter: brightness(1.4); -} - -.icons a { - padding: 0 3px; -} - -.icon { +.external-icon { display: inline-block; + padding: 0 3px; width: 24px; height: 1em; position: relative; } -.icon > svg { +.external-icon svg { width: 24px; height: 24px; top: -0.25em; @@ -1021,23 +1071,6 @@ li:not(:first-child:last-child) .tooltip, 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/static/js/client.js b/src/static/js/client.js index 8299b63e0..21c3911a2 100644 --- a/src/static/js/client.js +++ b/src/static/js/client.js @@ -3208,114 +3208,6 @@ clientSteps.getPageReferences.push(getAdditionalNamesBoxReferences); clientSteps.addInternalListeners.push(addAdditionalNamesBoxInternalListeners); clientSteps.addPageListeners.push(addAdditionalNamesBoxListeners); -// Scoped chronology links -------------------------------- - -const scopedChronologyLinksInfo = initInfo('scopedChronologyLinksInfo', { - switcher: null, - containers: null, - switcherLinks: null, - modes: null, - - session: { - selectedMode: 'wiki', - }, -}); - -function getScopedChronologyLinksReferences() { - const info = scopedChronologyLinksInfo; - - info.switcher = - document.querySelector('.scoped-chronology-switcher'); - - if (!info.switcher) { - return; - } - - info.containers = - Array.from(info.switcher.querySelectorAll(':scope > div')); - - info.switcherLinks = - Array.from(info.switcher.querySelectorAll('.switcher-link')); - - info.modes = - info.containers - .map(container => - Array.from(container.classList) - .find(className => className.startsWith('scope-')) - .slice('scope-'.length)); -} - -function addScopedChronologyLinksPageHandlers() { - const info = scopedChronologyLinksInfo; - const {session} = scopedChronologyLinksInfo; - - if (!info.switcher) { - return; - } - - for (const [index, { - container: currentContainer, - switcherLink: currentSwitcherLink, - }] of stitchArrays({ - container: info.containers, - switcherLink: info.switcherLinks, - }).entries()) { - const nextContainer = - atOffset(info.containers, index, +1, {wrap: true}); - - const nextSwitcherLink = - atOffset(info.switcherLinks, index, +1, {wrap: true}); - - const nextMode = - atOffset(info.modes, index, +1, {wrap: true}); - - currentSwitcherLink.addEventListener('click', domEvent => { - domEvent.preventDefault(); - - cssProp(currentContainer, 'display', 'none'); - cssProp(currentSwitcherLink, 'display', 'none'); - cssProp(nextContainer, 'display', 'block'); - cssProp(nextSwitcherLink, 'display', 'inline'); - - session.selectedMode = nextMode; - }); - } -} - -function mutateScopedChronologyLinksContent() { - const info = scopedChronologyLinksInfo; - - if (!info.switcher) { - return; - } - - const {selectedMode} = info.session; - - if (info.modes.includes(selectedMode)) { - const selectedIndex = info.modes.indexOf(selectedMode); - - for (const [index, { - container, - switcherLink, - }] of stitchArrays({ - container: info.containers, - switcherLink: info.switcherLinks, - }).entries()) { - if (index === selectedIndex) { - cssProp(container, 'display', 'block'); - cssProp(switcherLink, 'display', 'inline'); - } else { - cssProp(container, 'display', 'none'); - cssProp(switcherLink, 'display', 'none'); - } - } - } -} - -clientSteps.getPageReferences.push(getScopedChronologyLinksReferences); -clientSteps.mutatePageContent.push(mutateScopedChronologyLinksContent); -clientSteps.addPageListeners.push(addScopedChronologyLinksPageHandlers); - // Group contributions table ------------------------------ // TODO: Update to clientSteps style. @@ -3465,7 +3357,7 @@ function getArtistExternalLinkTooltipPageReferences() { const info = artistExternalLinkTooltipInfo; info.tooltips = - Array.from(document.getElementsByClassName('icons-tooltip')); + Array.from(document.getElementsByClassName('contribution-tooltip')); info.tooltipRows = info.tooltips.map(tooltip => diff --git a/src/strings-default.yaml b/src/strings-default.yaml index 8429c4cd9..26107c0b1 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -476,16 +476,33 @@ misc: # Contribution to a track, artwork, or other thing. withContribution: "{ARTIST} ({CONTRIB})" - # External links to visit the artist's own websites or profiles. - withExternalLinks: "{ARTIST} ({LINKS})" - - # Combination of above. - withContribution.withExternalLinks: "{ARTIST} ({CONTRIB}) ({LINKS})" - # Displayed in an artist's tooltip, if one of their URLs # isn't a specially detected web platform. noExternalLinkPlatformName: "Other" + chronology: + previous: + symbol: "←" + info: + _: "Previous by this artist" + withKind: "Previous {KIND} by this artist" + + next: + symbol: "→" + info: + _: "Next by this artist" + withKind: "Next {KIND} by this artist" + + kind: + album: "album" + bannerArt: "banner art" + coverArt: "cover art" + flash: "flash" + track: "track" + trackArt: "track art" + trackContribution: "track contribution" + wallpaperArt: "wallpaper art" + # chronology: # # "Chronology links" are a section that appear in the nav bar for diff --git a/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs index 4a7f35c31..14cce64e8 100644 --- a/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs +++ b/tap-snapshots/test/snapshot/generateAlbumReleaseInfo.js.test.cjs @@ -7,16 +7,17 @@ 'use strict' exports[`test/snapshot/generateAlbumReleaseInfo.js > TAP > generateAlbumReleaseInfo (snapshot) > basic behavior 1`] = `

- By Toby Fox (music probably) and Tensei - - tenseimusic - Bandcamp (hot jams). + By Toby Fox (music probably) and Tensei + + tenseimusic + + Bandcamp (hot jams).
- Cover art by Hanni Brosh. + Cover art by Hanni Brosh.
- Wallpaper art by Hanni Brosh and Niklink (edits). + Wallpaper art by Hanni Brosh and Niklink (edits).
- Banner art by Hanni Brosh and Niklink (edits). + Banner art by Hanni Brosh and Niklink (edits).
Released 3/14/2011.
diff --git a/tap-snapshots/test/snapshot/generateAlbumTrackList.js.test.cjs b/tap-snapshots/test/snapshot/generateAlbumTrackList.js.test.cjs index 091f1b9f4..10ab17c46 100644 --- a/tap-snapshots/test/snapshot/generateAlbumTrackList.js.test.cjs +++ b/tap-snapshots/test/snapshot/generateAlbumTrackList.js.test.cjs @@ -10,7 +10,7 @@ exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList

  • (0:20) Track 1
  • [mocked: generateAlbumTrackListMissingDuration - slots: {}] Track 2
  • (0:40) Track 3
  • -
  • [mocked: generateAlbumTrackListMissingDuration - slots: {}] Track 4 by Apricot, Peach, and Cerise
  • +
  • [mocked: generateAlbumTrackListMissingDuration - slots: {}] Track 4 by Apricot, Peach, and Cerise
  • ` @@ -31,7 +31,7 @@ exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList Second section: -
    +
    ` @@ -52,17 +52,17 @@ exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList Second section: -
    +
    ` @@ -83,17 +83,17 @@ exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList Second section: -
    +
    ` @@ -114,17 +114,17 @@ exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList Second section: -
    +
    ` @@ -145,16 +145,16 @@ exports[`test/snapshot/generateAlbumTrackList.js > TAP > generateAlbumTrackList Second section: -
    +
    ` diff --git a/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs b/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs index e35f93580..098fe145a 100644 --- a/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs +++ b/tap-snapshots/test/snapshot/generateTrackReleaseInfo.js.test.cjs @@ -7,7 +7,7 @@ 'use strict' exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseInfo (snapshot) > basic behavior 1`] = `

    - By Toby Fox. + By Toby Fox.
    Released 11/29/2011.
    @@ -17,13 +17,13 @@ exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseI ` exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseInfo (snapshot) > cover artist contribs, non-unique 1`] = ` -

    By Toby Fox.

    +

    By Toby Fox.

    This wiki doesn't have any listening links for Suspicious Track.

    ` exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseInfo (snapshot) > cover artist contribs, unique 1`] = `

    - By Toby Fox. + By Toby Fox.
    Cover art by Alpaca (🔥).

    @@ -31,6 +31,6 @@ exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseI ` exports[`test/snapshot/generateTrackReleaseInfo.js > TAP > generateTrackReleaseInfo (snapshot) > reduced details 1`] = ` -

    By Toby Fox.

    +

    By Toby Fox.

    This wiki doesn't have any listening links for Suspicious Track.

    ` diff --git a/tap-snapshots/test/snapshot/linkContribution.js.test.cjs b/tap-snapshots/test/snapshot/linkContribution.js.test.cjs index 92d697e72..a9ac916eb 100644 --- a/tap-snapshots/test/snapshot/linkContribution.js.test.cjs +++ b/tap-snapshots/test/snapshot/linkContribution.js.test.cjs @@ -5,159 +5,114 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links (inline) 1`] = ` -Lorem Ipsum Lover ( - - loremipsum.io - - - , - - loremipsum.io - - - , - - loremipsum.io - - - , - - loremipsum.io - - - ) -` - -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links (tooltip) 1`] = ` -Lorem Ipsum Lover - - loremipsum.io - Other - - loremipsum.io - Other - - loremipsum.io - Other - - loremipsum.io - Other - - loremipsum.io - Other - - loremipsum.io - Other - - loremipsum.io - Other - - loremipsum.io - Other +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > loads of links 1`] = ` +Lorem Ipsum Lover + + loremipsum.io + + Other + + + loremipsum.io + + Other + + + loremipsum.io + + Other + + + loremipsum.io + + Other + + + loremipsum.io + + Other + + + loremipsum.io + + Other + + + loremipsum.io + + Other + + + loremipsum.io + + Other ` exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > no accents 1`] = ` -Clark Powell -Grounder & Scratch -Toby Fox +Clark Powell +Grounder & Scratch +Toby Fox ` exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > no preventWrapping 1`] = ` -Clark Powell ( - - SoundCloud - - - ) +Clark Powell + + plazmataz + + SoundCloud Grounder & Scratch (Snooping) -Toby Fox (Arrangement) ( - - Bandcamp - - - , - - toby.fox - - - ) +Toby Fox + + tobyfox + + Bandcamp + + + toby.fox + + Other (Arrangement) ` exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showContribution 1`] = ` -Clark Powell +Clark Powell Grounder & Scratch (Snooping) Toby Fox (Arrangement) ` -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showIcons (inline) 1`] = ` -Clark Powell ( - - SoundCloud - - - ) -Grounder & Scratch -Toby Fox ( - - Bandcamp - - - , - - toby.fox - - - ) -` - -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showIcons (tooltip) 1`] = ` -Clark Powell - - plazmataz - SoundCloud -Grounder & Scratch (Snooping) -Toby Fox - - tobyfox - Bandcamp - - toby.fox - Other (Arrangement) -` - -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showIcons (inline) 1`] = ` -Clark Powell ( - - SoundCloud - - - ) -Grounder & Scratch (Snooping) -Toby Fox (Arrangement) ( - - Bandcamp - - - , - - toby.fox - - - ) +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > only showExternalLinks 1`] = ` +Clark Powell + + plazmataz + + SoundCloud +Grounder & Scratch +Toby Fox + + tobyfox + + Bandcamp + + + toby.fox + + Other ` -exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showIcons (tooltip) 1`] = ` -Clark Powell - - plazmataz - SoundCloud +exports[`test/snapshot/linkContribution.js > TAP > linkContribution (snapshot) > showContribution & showExternalLinks 1`] = ` +Clark Powell + + plazmataz + + SoundCloud Grounder & Scratch (Snooping) -Toby Fox - - tobyfox - Bandcamp - - toby.fox - Other (Arrangement) +Toby Fox + + tobyfox + + Bandcamp + + + toby.fox + + Other (Arrangement) ` diff --git a/test/snapshot/generateAlbumReleaseInfo.js b/test/snapshot/generateAlbumReleaseInfo.js index a109912f6..f41e502d7 100644 --- a/test/snapshot/generateAlbumReleaseInfo.js +++ b/test/snapshot/generateAlbumReleaseInfo.js @@ -8,22 +8,22 @@ testContentFunctions(t, 'generateAlbumReleaseInfo (snapshot)', async (t, evaluat name: 'generateAlbumReleaseInfo', args: [{ artistContribs: [ - {artist: {name: 'Toby Fox', directory: 'toby-fox', urls: null}, annotation: 'music probably'}, + {artist: {name: 'Toby Fox', directory: 'toby-fox', urls: []}, annotation: 'music probably'}, {artist: {name: 'Tensei', directory: 'tensei', urls: ['https://tenseimusic.bandcamp.com/']}, annotation: 'hot jams'}, ], coverArtistContribs: [ - {artist: {name: 'Hanni Brosh', directory: 'hb', urls: null}, annotation: null}, + {artist: {name: 'Hanni Brosh', directory: 'hb', urls: []}, annotation: null}, ], wallpaperArtistContribs: [ - {artist: {name: 'Hanni Brosh', directory: 'hb', urls: null}, annotation: null}, - {artist: {name: 'Niklink', directory: 'niklink', urls: null}, annotation: 'edits'}, + {artist: {name: 'Hanni Brosh', directory: 'hb', urls: []}, annotation: null}, + {artist: {name: 'Niklink', directory: 'niklink', urls: []}, annotation: 'edits'}, ], bannerArtistContribs: [ - {artist: {name: 'Hanni Brosh', directory: 'hb', urls: null}, annotation: null}, - {artist: {name: 'Niklink', directory: 'niklink', urls: null}, annotation: 'edits'}, + {artist: {name: 'Hanni Brosh', directory: 'hb', urls: []}, annotation: null}, + {artist: {name: 'Niklink', directory: 'niklink', urls: []}, annotation: 'edits'}, ], name: 'AlterniaBound', diff --git a/test/snapshot/generateAlbumTrackList.js b/test/snapshot/generateAlbumTrackList.js index 08b319025..a7c3f591d 100644 --- a/test/snapshot/generateAlbumTrackList.js +++ b/test/snapshot/generateAlbumTrackList.js @@ -10,13 +10,13 @@ testContentFunctions(t, 'generateAlbumTrackList (snapshot)', async (t, evaluate) }); const contribs1 = [ - {artist: {name: 'Apricot', directory: 'apricot', urls: null}}, + {artist: {name: 'Apricot', directory: 'apricot', urls: []}}, ]; const contribs2 = [ - {artist: {name: 'Apricot', directory: 'apricot', urls: null}}, + {artist: {name: 'Apricot', directory: 'apricot', urls: []}}, {artist: {name: 'Peach', directory: 'peach', urls: ['https://peach.bandcamp.com/']}}, - {artist: {name: 'Cerise', directory: 'cerise', urls: null}}, + {artist: {name: 'Cerise', directory: 'cerise', urls: []}}, ]; const color1 = '#fb07ff'; diff --git a/test/snapshot/generateTrackReleaseInfo.js b/test/snapshot/generateTrackReleaseInfo.js index 78f0fee73..931377c8c 100644 --- a/test/snapshot/generateTrackReleaseInfo.js +++ b/test/snapshot/generateTrackReleaseInfo.js @@ -4,8 +4,8 @@ import {testContentFunctions} from '#test-lib'; testContentFunctions(t, 'generateTrackReleaseInfo (snapshot)', async (t, evaluate) => { await evaluate.load(); - const artistContribs = [{artist: {name: 'Toby Fox', directory: 'toby-fox', urls: null}, annotation: null}]; - const coverArtistContribs = [{artist: {name: 'Alpaca', directory: 'alpaca', urls: null}, annotation: '🔥'}]; + const artistContribs = [{artist: {name: 'Toby Fox', directory: 'toby-fox', urls: []}, annotation: null}]; + const coverArtistContribs = [{artist: {name: 'Alpaca', directory: 'alpaca', urls: []}, annotation: '🔥'}]; evaluate.snapshot('basic behavior', { name: 'generateTrackReleaseInfo', diff --git a/test/snapshot/linkContribution.js b/test/snapshot/linkContribution.js index 1043ddc67..5844b0b96 100644 --- a/test/snapshot/linkContribution.js +++ b/test/snapshot/linkContribution.js @@ -33,53 +33,22 @@ testContentFunctions(t, 'linkContribution (snapshot)', async (t, evaluate) => { slots, }); - quickSnapshot('showContribution & showIcons (inline)', { + quickSnapshot('showContribution & showExternalLinks', { showContribution: true, - showIcons: true, - iconMode: 'inline', - }); - - quickSnapshot('showContribution & showIcons (tooltip)', { - showContribution: true, - showIcons: true, - iconMode: 'tooltip', + showExternalLinks: true, }); quickSnapshot('only showContribution', { showContribution: true, }); - quickSnapshot('only showIcons (inline)', { - showIcons: true, - iconMode: 'inline', - }); - - quickSnapshot('only showIcons (tooltip)', { - showContribution: true, - showIcons: true, - iconMode: 'tooltip', + quickSnapshot('only showExternalLinks', { + showExternalLinks: true, }); quickSnapshot('no accents', {}); - evaluate.snapshot('loads of links (inline)', { - name: 'linkContribution', - args: [ - {artist: {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', - ]}, annotation: null}, - ], - slots: {showIcons: true}, - }); - - evaluate.snapshot('loads of links (tooltip)', { + evaluate.snapshot('loads of links', { name: 'linkContribution', args: [ {artist: {name: 'Lorem Ipsum Lover', directory: 'lorem-ipsum-lover', urls: [ @@ -93,12 +62,12 @@ testContentFunctions(t, 'linkContribution (snapshot)', async (t, evaluate) => { 'https://loremipsum.io/#original-source', ]}, annotation: null}, ], - slots: {showIcons: true, iconMode: 'tooltip'}, + slots: {showExternalLinks: true}, }); quickSnapshot('no preventWrapping', { showContribution: true, - showIcons: true, + showExternalLinks: true, preventWrapping: false, }); }); diff --git a/test/unit/content/dependencies/linkContribution.js b/test/unit/content/dependencies/linkContribution.js index ab45b03af..e7a29310f 100644 --- a/test/unit/content/dependencies/linkContribution.js +++ b/test/unit/content/dependencies/linkContribution.js @@ -27,18 +27,20 @@ t.test('generateContributionLinks (unit)', async t => { await testContentFunctions(t, 'generateContributionLinks (unit 1)', async (t, evaluate) => { const slots = { showContribution: true, - showIcons: true, + showExternalLinks: true, }; await evaluate.load({ mock: evaluate.mock(mock => ({ linkArtist: { - relations: mock.function('linkArtist.relations', () => ({})) + relations: mock + .function('linkArtist.relations', () => ({})) .args([undefined, artist1]).next() .args([undefined, artist2]).next() .args([undefined, artist3]), - data: mock.function('linkArtist.data', () => ({})) + data: mock + .function('linkArtist.data', () => ({})) .args([artist1]).next() .args([artist2]).next() .args([artist3]), @@ -49,13 +51,18 @@ t.test('generateContributionLinks (unit)', async t => { .repeat(3), }, - linkExternalAsIcon: { - data: mock.function('linkExternalAsIcon.data', () => ({})) + generateExternalIcon: { + data: mock + .function('generateExternalIcon.data', () => ({})) .args([artist1.urls[0]]).next() .args([artist3.urls[0]]).next() .args([artist3.urls[1]]), - generate: mock.function('linkExternalAsIcon.generate', () => 'icon') + generate: mock + .function('generateExternalIcon.generate', () => ({ + toString: () => 'icon', + setSlot: () => {}, + })) .repeat(3), } })), @@ -75,23 +82,26 @@ t.test('generateContributionLinks (unit)', async t => { await testContentFunctions(t, 'generateContributionLinks (unit 2)', async (t, evaluate) => { const slots = { showContribution: false, - showIcons: false, + showExternalLinks: false, }; await evaluate.load({ mock: evaluate.mock(mock => ({ linkArtist: { - relations: mock.function('linkArtist.relations', () => ({})) + relations: mock + .function('linkArtist.relations', () => ({})) .args([undefined, artist1]).next() .args([undefined, artist2]).next() .args([undefined, artist3]), - data: mock.function('linkArtist.data', () => ({})) + data: mock + .function('linkArtist.data', () => ({})) .args([artist1]).next() .args([artist2]).next() .args([artist3]), - generate: mock.function(() => 'artist link') + generate: mock + .function(() => 'artist link') .repeat(3), }, @@ -99,11 +109,16 @@ t.test('generateContributionLinks (unit)', async t => { // tree is the same since whether or not the external icon links are // shown is dependent on a slot, which is undefined and arbitrary at // relations/data time (it might change on a whim at generate time). - linkExternalAsIcon: { - data: mock.function('linkExternalAsIcon.data', () => ({})) + generateExternalIcon: { + data: mock + .function('generateExternalIcon.data', () => ({})) .repeat(3), - generate: mock.function('linkExternalAsIcon.generate', () => 'icon') + generate: mock + .function('generateExternalIcon.generate', () => ({ + toString: () => 'icon', + setSlot: () => {}, + })) .repeat(3), }, })),