From 3a5b49cf3a10702c0dae1190c9baabd8a2c2ef3b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 12 Apr 2023 13:20:32 -0300 Subject: content: stub track page, misc. other changes * generateContributionLinks replaced with linkContribution, tests still need updating * album pages respect albums without cover art * track pages without unique art inherit art tags from album (fixes #13) not heavily tested, this commit probably breaks some pages which were loading correctly before --- .../generateAlbumAdditionalFilesList.js | 4 +- src/content/dependencies/generateAlbumInfoPage.js | 37 +- .../dependencies/generateAlbumInfoPageContent.js | 88 +-- .../dependencies/generateAlbumTrackListItem.js | 13 +- src/content/dependencies/generateContentHeading.js | 2 +- .../dependencies/generateContributionLinks.js | 87 --- src/content/dependencies/generateTrackInfoPage.js | 40 ++ .../dependencies/generateTrackInfoPageContent.js | 645 +++++++++++++++++++++ src/content/dependencies/linkAlbum.js | 8 + src/content/dependencies/linkContribution.js | 74 +++ src/data/things/artist.js | 5 +- src/data/things/thing.js | 1 + src/page/index.js | 2 +- src/page/track.js | 550 +----------------- src/write/build-modes/live-dev-server.js | 5 + 15 files changed, 855 insertions(+), 706 deletions(-) delete mode 100644 src/content/dependencies/generateContributionLinks.js create mode 100644 src/content/dependencies/generateTrackInfoPage.js create mode 100644 src/content/dependencies/generateTrackInfoPageContent.js create mode 100644 src/content/dependencies/linkAlbum.js create mode 100644 src/content/dependencies/linkContribution.js (limited to 'src') diff --git a/src/content/dependencies/generateAlbumAdditionalFilesList.js b/src/content/dependencies/generateAlbumAdditionalFilesList.js index 04e6a5f1..f8fd5499 100644 --- a/src/content/dependencies/generateAlbumAdditionalFilesList.js +++ b/src/content/dependencies/generateAlbumAdditionalFilesList.js @@ -41,8 +41,8 @@ export default { }) { return relations.additionalFilesList .slots({ - additionalFileLinks: relations.additionalFileLinks, - additionalFileSizes: + fileLinks: relations.additionalFileLinks, + fileSizes: Object.fromEntries(data.fileLocations.map(file => [ file, (data.showFileSizes diff --git a/src/content/dependencies/generateAlbumInfoPage.js b/src/content/dependencies/generateAlbumInfoPage.js index 5c575cb2..e5ce193c 100644 --- a/src/content/dependencies/generateAlbumInfoPage.js +++ b/src/content/dependencies/generateAlbumInfoPage.js @@ -7,37 +7,26 @@ export default { 'generatePageLayout', ], - extraDependencies: [ - 'language', - ], + extraDependencies: ['language'], relations(relation, album) { - const relations = {}; - - relations.layout = relation('generatePageLayout'); - - relations.content = relation('generateAlbumInfoPageContent', album); - relations.socialEmbed = relation('generateAlbumSocialEmbed', album); - relations.albumStyleRules = relation('generateAlbumStyleRules', album); - relations.colorStyleRules = relation('generateColorStyleRules', album.color); - - return relations; + return { + layout: relation('generatePageLayout'), + + content: relation('generateAlbumInfoPageContent', album), + socialEmbed: relation('generateAlbumSocialEmbed', album), + albumStyleRules: relation('generateAlbumStyleRules', album), + colorStyleRules: relation('generateColorStyleRules', album.color), + }; }, data(album) { - const data = {}; - - data.name = album.name; - data.color = album.color; - - return data; + return { + name: album.name, + }; }, - generate(data, relations, { - language, - }) { - // page.themeColor = data.color; - + generate(data, relations, {language}) { return relations.layout .slots({ title: language.$('albumPage.title', {album: data.name}), diff --git a/src/content/dependencies/generateAlbumInfoPageContent.js b/src/content/dependencies/generateAlbumInfoPageContent.js index fd66f6b0..76862f9c 100644 --- a/src/content/dependencies/generateAlbumInfoPageContent.js +++ b/src/content/dependencies/generateAlbumInfoPageContent.js @@ -5,11 +5,11 @@ export default { 'generateAdditionalFilesShortcut', 'generateAlbumAdditionalFilesList', 'generateAlbumTrackList', - 'generateContributionLinks', 'generateContentHeading', 'generateCoverArtwork', 'linkAlbumCommentary', 'linkAlbumGallery', + 'linkContribution', 'linkExternal', ], @@ -22,14 +22,14 @@ export default { relations(relation, album) { const relations = {}; - relations.cover = - relation('generateCoverArtwork', album.artTags); - const contributionLinksRelation = contribs => - relation('generateContributionLinks', contribs, { - showContribution: true, - showIcons: true, - }); + contribs.map(contrib => + relation('linkContribution', contrib.who, contrib.what)); + + if (album.hasCoverArt) { + relations.cover = + relation('generateCoverArtwork', album.artTags); + } relations.artistLinks = contributionLinksRelation(album.artistContribs); @@ -43,9 +43,6 @@ export default { relations.bannerArtistLinks = contributionLinksRelation(album.bannerArtistContribs); - const contentHeadingRelation = () => - relation('generateContentHeading'); - if (album.tracks.some(t => t.hasUniqueCoverArt)) { relations.galleryLink = relation('linkAlbumGallery', album); @@ -57,10 +54,8 @@ export default { } relations.externalLinks = - (empty(album.urls) - ? null - : album.urls.map(url => - relation('linkExternal', url, {type: 'album'}))); + album.urls.map(url => + relation('linkExternal', url, {type: 'album'})); relations.trackList = relation('generateAlbumTrackList', album); @@ -69,14 +64,14 @@ export default { relation('generateAdditionalFilesShortcut', album.additionalFiles); relations.additionalFilesHeading = - contentHeadingRelation(); + relation('generateContentHeading'); relations.additionalFilesList = relation('generateAlbumAdditionalFilesList', album); } relations.artistCommentaryHeading = - contentHeadingRelation(); + relation('generateContentHeading'); return relations; }, @@ -84,19 +79,19 @@ export default { data(album) { const data = {}; - data.coverArtDirectory = album.directory; - data.coverArtFileExtension = album.coverArtFileExtension; - data.date = album.date; data.duration = accumulateSum(album.tracks, track => track.duration); data.durationApproximate = album.tracks.length > 1; - if ( - album.hasCoverArt && - album.coverArtDate && - +album.coverArtDate !== +album.date - ) { - data.coverArtDate = album.coverArtDate; + data.hasCoverArt = album.hasCoverArt; + + if (album.hasCoverArt) { + data.coverArtDirectory = album.directory; + data.coverArtFileExtension = album.coverArtFileExtension; + + if (album.coverArtDate && +album.coverArtDate !== +album.date) { + data.coverArtDate = album.coverArtDate; + } } if (!empty(album.additionalFiles)) { @@ -116,39 +111,50 @@ export default { }) { const content = {}; - content.cover = relations.cover - .slots({ - path: ['media.albumCover', data.coverArtDirectory, data.coverArtFileExtension], - alt: language.$('misc.alt.trackCover') - }); + const formatContributions = contributionLinks => + language.formatConjunctionList( + contributionLinks.map(link => + link + .slots({ + showContribution: true, + showIcons: true, + }))); + + if (data.hasCoverArt) { + content.cover = relations.cover + .slots({ + path: ['media.albumCover', data.coverArtDirectory, data.coverArtFileExtension], + alt: language.$('misc.alt.trackCover') + }); + } content.main = { headingMode: 'sticky', - content: html.tag(null, [ + content: html.tags([ html.tag('p', { [html.onlyIfContent]: true, - [html.joinChildren]: '
', + [html.joinChildren]: html.tag('br'), }, [ - relations.artistLinks && + !empty(relations.artistLinks) && language.$('releaseInfo.by', { - artists: relations.artistLinks, + artists: formatContributions(relations.artistLinks), }), - relations.coverArtistLinks && + !empty(relations.coverArtistLinks) && language.$('releaseInfo.coverArtBy', { - artists: relations.coverArtistLinks, + artists: formatContributions(relations.coverArtistLinks), }), - relations.wallpaperArtistLinks && + !empty(relations.wallpaperArtistLinks) && language.$('releaseInfo.wallpaperArtBy', { - artists: relations.wallpaperArtistLinks, + artists: formatContributions(relations.wallpaperArtistLinks), }), - relations.bannerArtistLinks && + !empty(relations.bannerArtistLinks) && language.$('releaseInfo.bannerArtBy', { - artists: relations.bannerArtistLinks, + artists: formatContributions(relations.bannerArtistLinks), }), data.date && diff --git a/src/content/dependencies/generateAlbumTrackListItem.js b/src/content/dependencies/generateAlbumTrackListItem.js index dd41ba11..fe46153d 100644 --- a/src/content/dependencies/generateAlbumTrackListItem.js +++ b/src/content/dependencies/generateAlbumTrackListItem.js @@ -2,7 +2,7 @@ import {compareArrays} from '../../util/sugar.js'; export default { contentDependencies: [ - 'generateContributionLinks', + 'linkContribution', 'linkTrack', ], @@ -16,10 +16,11 @@ export default { const relations = {}; relations.contributionLinks = - relation('generateContributionLinks', track.artistContribs, { - showContribution: false, - showIcons: false, - }); + track.artistContribs.map(({who, what}) => + relation('linkContribution', who, what, { + showContribution: false, + showIcons: false, + })); relations.trackLink = relation('linkTrack', track); @@ -67,7 +68,7 @@ export default { by: html.tag('span', {class: 'by'}, language.$('trackList.item.withArtists.by', { - artists: relations.contributionLinks, + artists: language.formatConjunctionList(relations.contributionLinks), })), }))); }, diff --git a/src/content/dependencies/generateContentHeading.js b/src/content/dependencies/generateContentHeading.js index f5e4bd00..109a32fd 100644 --- a/src/content/dependencies/generateContentHeading.js +++ b/src/content/dependencies/generateContentHeading.js @@ -19,7 +19,7 @@ export default { id: slots.id, tabindex: '0', }, - slots.content); + slots.title); }, }); } diff --git a/src/content/dependencies/generateContributionLinks.js b/src/content/dependencies/generateContributionLinks.js deleted file mode 100644 index c035c271..00000000 --- a/src/content/dependencies/generateContributionLinks.js +++ /dev/null @@ -1,87 +0,0 @@ -import {empty} from '../../util/sugar.js'; - -export default { - contentDependencies: [ - 'linkArtist', - 'linkExternalAsIcon', - ], - - extraDependencies: [ - 'html', - 'language', - ], - - relations(relation, contributions, {showIcons = false} = {}) { - const relations = {}; - - relations.artistLinks = - contributions.map(({who}) => relation('linkArtist', who)); - - if (showIcons) { - relations.artistIcons = - contributions.map(({who}) => - who.urls.map(url => - relation('linkExternalAsIcon', url))); - } - - return relations; - }, - - data(contributions, { - showContribution = false, - showIcons = false, - } = {}) { - const data = {}; - - data.contributionData = - contributions.map(({who, what}) => ({ - hasContributionPart: !!(showContribution && what), - hasExternalPart: !!(showIcons && !empty(who.urls)), - contribution: showContribution && what, - })); - - return data; - }, - - generate(data, relations, { - html, - language, - }) { - return language.formatConjunctionList( - data.contributionData.map(({ - hasContributionPart, - hasExternalPart, - contribution, - }, index) => { - const artistLink = relations.artistLinks[index]; - const artistIcons = relations.artistIcons?.[index]; - - const externalLinks = hasExternalPart && - html.tag('span', - {[html.noEdgeWhitespace]: true, class: 'icons'}, - language.formatUnitList(artistIcons)); - - return ( - (hasContributionPart - ? (hasExternalPart - ? language.$('misc.artistLink.withContribution.withExternalLinks', { - artist: artistLink, - contrib: contribution, - links: externalLinks, - }) - : language.$('misc.artistLink.withContribution', { - artist: artistLink, - contrib: contribution, - })) - : (hasExternalPart - ? language.$('misc.artistLink.withExternalLinks', { - artist: artistLink, - links: externalLinks, - }) - : language.$('misc.artistLink', { - artist: artistLink, - }))) - ); - })); - }, -}; diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js new file mode 100644 index 00000000..f7f14573 --- /dev/null +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -0,0 +1,40 @@ +export default { + contentDependencies: [ + 'generateTrackInfoPageContent', + 'generateAlbumStyleRules', + 'generateColorStyleRules', + 'generatePageLayout', + ], + + extraDependencies: ['language'], + + relations(relation, track) { + return { + layout: relation('generatePageLayout'), + + content: relation('generateTrackInfoPageContent', track), + albumStyleRules: relation('generateAlbumStyleRules', track.album), + colorStyleRules: relation('generateColorStyleRules', track.color), + }; + }, + + data(track) { + return { + name: track.name, + }; + }, + + generate(data, relations, {language}) { + return relations.layout + .slots({ + title: language.$('trackPage.title', {track: data.name}), + styleRules: [ + relations.albumStyleRules, + relations.colorStyleRules, + ], + + cover: relations.content.cover, + mainContent: relations.content.main.content, + }); + }, +} diff --git a/src/content/dependencies/generateTrackInfoPageContent.js b/src/content/dependencies/generateTrackInfoPageContent.js new file mode 100644 index 00000000..0ebb4121 --- /dev/null +++ b/src/content/dependencies/generateTrackInfoPageContent.js @@ -0,0 +1,645 @@ +import {empty} from '../../util/sugar.js'; + +export default { + contentDependencies: [ + 'generateContentHeading', + 'generateCoverArtwork', + 'linkAlbum', + 'linkContribution', + 'linkExternal', + 'linkTrack', + ], + + extraDependencies: [ + 'html', + 'language', + 'transformMultiline', + ], + + relations(relation, track) { + const relations = {}; + + const {album} = track; + + const contributionLinksRelation = contribs => + contribs.map(contrib => + relation('linkContribution', contrib.who, contrib.what)); + + if (track.hasUniqueCoverArt) { + relations.cover = + relation('generateCoverArtwork', track.artTags); + relations.coverArtistLinks = + contributionLinksRelation(track.coverArtistContribs); + } else if (album.hasCoverArt) { + relations.cover = + relation('generateCoverArtwork', album.artTags); + } + + relations.artistLinks = + contributionLinksRelation(track.artistContribs); + + relations.externalLinks = + track.urls.map(url => + relation('linkExternal', url)); + + relations.otherReleasesHeading = + relation('generateContentHeading'); + + relations.otherReleases = + track.otherReleases.map(track => ({ + trackLink: relation('linkTrack', track), + albumLink: relation('linkAlbum', track.album), + })); + + if (!empty(track.contributorContribs)) { + relations.contributorsHeading = + relation('generateContentHeading'); + relations.contributorLinks = + contributionLinksRelation(track.contributorContribs); + } + + return relations; + }, + + data(track) { + const data = {}; + + const {album} = track; + + data.date = track.date; + data.duration = track.duration; + + data.hasUniqueCoverArt = track.hasUniqueCoverArt; + data.hasAlbumCoverArt = album.hasCoverArt; + + if (track.hasUniqueCoverArt) { + data.albumCoverArtDirectory = album.directory; + data.trackCoverArtDirectory = track.directory; + data.coverArtFileExtension = track.coverArtFileExtension; + + if (track.coverArtDate && +track.coverArtDate !== +track.date) { + data.coverArtDate = track.coverArtDate; + } + } else if (track.album.hasCoverArt) { + data.albumCoverArtDirectory = album.directory; + data.coverArtFileExtension = album.coverArtFileExtension; + } + + return data; + }, + + generate(data, relations, { + html, + language, + // transformMultiline, + }) { + const content = {}; + + if (data.hasUniqueCoverArt) { + content.cover = relations.cover + .slots({ + path: [ + 'media.trackCover', + data.albumCoverArtDirectory, + data.trackCoverArtDirectory, + data.coverArtFileExtension, + ], + }); + } else if (data.hasAlbumCoverArt) { + content.cover = relations.cover + .slots({ + path: [ + 'media.albumCover', + data.albumCoverArtDirectory, + data.coverArtFileExtension, + ], + }); + } + + content.main = { + headingMode: 'sticky', + + content: html.tags([ + html.tag('p', { + [html.onlyIfContent]: true, + [html.joinChildren]: html.tag('br'), + }, [ + !empty(relations.artistLinks) && + language.$('releaseInfo.by', {artists: relations.artistLinks}), + + !empty(relations.coverArtistLinks) && + language.$('releaseInfo.coverArtBy', {artists: relations.coverArtistLinks}), + + data.date && + language.$('releaseInfo.released', { + date: language.formatDate(data.date), + }), + + data.coverArtDate && + language.$('releaseInfo.artReleased', { + date: language.formatDate(data.coverArtDate), + }), + + data.duration && + language.$('releaseInfo.duration', { + duration: language.formatDuration(data.duration), + }), + ]), + + /* + html.tag('p', + { + [html.onlyIfContent]: true, + [html.joinChildren]: '
', + }, + [ + hasSheetMusicFiles && + language.$('releaseInfo.sheetMusicFiles.shortcut', { + link: html.tag('a', + {href: '#sheet-music-files'}, + language.$('releaseInfo.sheetMusicFiles.shortcut.link')), + }), + + hasMidiProjectFiles && + language.$('releaseInfo.midiProjectFiles.shortcut', { + link: html.tag('a', + {href: '#midi-project-files'}, + language.$('releaseInfo.midiProjectFiles.shortcut.link')), + }), + + hasAdditionalFiles && + generateAdditionalFilesShortcut(track.additionalFiles), + ]), + */ + + html.tag('p', + (empty(relations.externalLinks) + ? language.$('releaseInfo.listenOn.noLinks') + : language.$('releaseInfo.listenOn', { + links: language.formatDisjunctionList(relations.externalLinks), + }))), + + !empty(relations.otherReleases) && [ + relations.otherReleasesHeading + .slots({ + id: 'also-released-as', + title: language.$('releaseInfo.alsoReleasedAs'), + }), + + html.tag('ul', + relations.otherReleases.map(({trackLink, albumLink}) => + html.tag('li', + language.$('releaseInfo.alsoReleasedAs.item', { + track: trackLink, + album: albumLink, + })))), + ], + + relations.contributorLinks && [ + relations.contributorsHeading + .slots({ + id: 'contributors', + title: language.$('releaseInfo.contributors'), + }), + + html.tag('ul', relations.contributorLinks.map(contributorLink => + html.tag('li', + contributorLink + .slots({ + showIcons: true, + showContribution: true, + })))), + ], + ]), + }; + + return content; + }, +}; + +/* +export function write(track, {wikiData}) { + const {wikiInfo} = wikiData; + + const {album, contributorContribs, referencedByTracks, referencedTracks, sampledByTracks, sampledTracks, otherReleases, } = track; + + const listTag = getAlbumListTag(album); + + let flashesThatFeature; + if (wikiInfo.enableFlashesAndGames) { + flashesThatFeature = sortChronologically( + [track, ...otherReleases].flatMap((track) => + track.featuredInFlashes.map((flash) => ({ + flash, + as: track, + directory: flash.directory, + name: flash.name, + date: flash.date, + })) + ) + ); + } + + const unbound_getTrackItem = (track, { + getArtistString, + html, + language, + link, + }) => + html.tag('li', + language.$('trackList.item.withArtists', { + track: link.track(track), + by: html.tag('span', + {class: 'by'}, + language.$('trackList.item.withArtists.by', { + artists: getArtistString(track.artistContribs), + })), + })); + + const hasCommentary = + track.commentary || otherReleases.some((t) => t.commentary); + + const hasAdditionalFiles = !empty(track.additionalFiles); + const hasSheetMusicFiles = !empty(track.sheetMusicFiles); + const hasMidiProjectFiles = !empty(track.midiProjectFiles); + const numAdditionalFiles = album.additionalFiles.flatMap((g) => g.files).length; + + const generateCommentary = ({language, link, transformMultiline}) => + transformMultiline([ + track.commentary, + ...otherReleases.map((track) => + track.commentary + ?.split('\n') + .filter((line) => line.replace(/<\/b>/g, '').includes(':')) + .flatMap(line => [ + line, + language.$('releaseInfo.artistCommentary.seeOriginalRelease', { + original: link.track(track), + }), + ]) + .join('\n') + ), + ].filter(Boolean).join('\n')); + + const data = { + type: 'data', + path: ['track', track.directory], + data: ({ + serializeContribs, + serializeCover, + serializeGroupsForTrack, + serializeLink, + }) => ({ + name: track.name, + directory: track.directory, + dates: { + released: track.date, + originallyReleased: track.originalDate, + coverArtAdded: track.coverArtDate, + }, + duration: track.duration, + color: track.color, + cover: serializeCover(track, getTrackCover), + artistsContribs: serializeContribs(track.artistContribs), + contributorContribs: serializeContribs(track.contributorContribs), + coverArtistContribs: serializeContribs(track.coverArtistContribs || []), + album: serializeLink(track.album), + groups: serializeGroupsForTrack(track), + references: track.references.map(serializeLink), + referencedBy: track.referencedBy.map(serializeLink), + alsoReleasedAs: otherReleases.map((track) => ({ + track: serializeLink(track), + album: serializeLink(track.album), + })), + }), + }; + + const getSocialEmbedDescription = ({ + getArtistString: _getArtistString, + language, + }) => { + const hasArtists = !empty(track.artistContribs); + const hasCoverArtists = !empty(track.coverArtistContribs); + const getArtistString = (contribs) => + _getArtistString(contribs, { + // We don't want to put actual HTML tags in social embeds (sadly + // they don't get parsed and displayed, generally speaking), so + // override the link argument so that artist "links" just show + // their names. + link: {artist: (artist) => artist.name}, + }); + if (!hasArtists && !hasCoverArtists) return ''; + return language.formatString( + 'trackPage.socialEmbed.body' + + [hasArtists && '.withArtists', hasCoverArtists && '.withCoverArtists'] + .filter(Boolean) + .join(''), + Object.fromEntries( + [ + hasArtists && ['artists', getArtistString(track.artistContribs)], + hasCoverArtists && [ + 'coverArtists', + getArtistString(track.coverArtistContribs), + ], + ].filter(Boolean) + ) + ); + }; + + const page = { + type: 'page', + path: ['track', track.directory], + page: ({ + absoluteTo, + fancifyURL, + generateAdditionalFilesList, + generateAdditionalFilesShortcut, + generateChronologyLinks, + generateContentHeading, + generateNavigationLinks, + generateTrackListDividedByGroups, + getAlbumStylesheet, + getArtistString, + getLinkThemeString, + getSizeOfAdditionalFile, + getThemeString, + getTrackCover, + html, + link, + language, + transformLyrics, + transformMultiline, + to, + urls, + }) => { + const getTrackItem = bindOpts(unbound_getTrackItem, { + getArtistString, + html, + language, + link, + }); + + const generateAlbumAdditionalFilesList = bindOpts(unbound_generateAlbumAdditionalFilesList, { + [bindOpts.bindIndex]: 2, + generateAdditionalFilesList, + getSizeOfAdditionalFile, + link, + urls, + }); + + return { + title: language.$('trackPage.title', {track: track.name}), + stylesheet: getAlbumStylesheet(album, {to}), + + themeColor: track.color, + theme: + getThemeString(track.color, { + additionalVariables: [ + `--album-directory: ${album.directory}`, + `--track-directory: ${track.directory}`, + ] + }), + + socialEmbed: { + heading: language.$('trackPage.socialEmbed.heading', { + album: track.album.name, + }), + headingLink: absoluteTo('localized.album', album.directory), + title: language.$('trackPage.socialEmbed.title', { + track: track.name, + }), + description: getSocialEmbedDescription({getArtistString, language}), + image: '/' + getTrackCover(track, {to: urls.from('shared.root').to}), + color: track.color, + }, + + // disabled for now! shifting banner position per height of page is disorienting + /* + banner: !empty(album.bannerArtistContribs) && { + classes: ['dim'], + dimensions: album.bannerDimensions, + path: ['media.albumBanner', album.directory, album.bannerFileExtension], + alt: language.$('misc.alt.albumBanner'), + position: 'bottom' + }, + * / + + main: { + headingMode: 'sticky', + + content: [ + ...html.fragment( + !empty(contributorContribs) && [ + generateContentHeading({ + id: 'contributors', + title: language.$('releaseInfo.contributors'), + }), + + html.tag('ul', contributorContribs.map(contrib => + html.tag('li', getArtistString([contrib], { + showContrib: true, + showIcons: true, + })))), + ]), + + ...html.fragment( + !empty(referencedTracks) && [ + generateContentHeading({ + id: 'references', + title: + language.$('releaseInfo.tracksReferenced', { + track: html.tag('i', track.name), + }), + }), + + html.tag('ul', referencedTracks.map(getTrackItem)), + ]), + + ...html.fragment( + !empty(referencedByTracks) && [ + generateContentHeading({ + id: 'referenced-by', + title: + language.$('releaseInfo.tracksThatReference', { + track: html.tag('i', track.name), + }), + }), + + generateTrackListDividedByGroups(referencedByTracks, { + getTrackItem, + wikiData, + }), + ]), + + ...html.fragment( + !empty(sampledTracks) && [ + generateContentHeading({ + id: 'samples', + title: + language.$('releaseInfo.tracksSampled', { + track: html.tag('i', track.name), + }), + }), + + html.tag('ul', sampledTracks.map(getTrackItem)), + ]), + + ...html.fragment( + !empty(sampledByTracks) && [ + generateContentHeading({ + id: 'sampled-by', + title: + language.$('releaseInfo.tracksThatSample', { + track: html.tag('i', track.name), + }) + }), + + html.tag('ul', sampledByTracks.map(getTrackItem)), + ]), + + ...html.fragment( + wikiInfo.enableFlashesAndGames && + !empty(flashesThatFeature) && [ + generateContentHeading({ + id: 'featured-in', + title: + language.$('releaseInfo.flashesThatFeature', { + track: html.tag('i', track.name), + }), + }), + + html.tag('ul', flashesThatFeature.map(({flash, as}) => + html.tag('li', + {class: as !== track && 'rerelease'}, + (as === track + ? language.$('releaseInfo.flashesThatFeature.item', { + flash: link.flash(flash), + }) + : language.$('releaseInfo.flashesThatFeature.item.asDifferentRelease', { + flash: link.flash(flash), + track: link.track(as), + }))))), + ]), + + ...html.fragment( + track.lyrics && [ + generateContentHeading({ + id: 'lyrics', + title: language.$('releaseInfo.lyrics'), + }), + + html.tag('blockquote', transformLyrics(track.lyrics)), + ]), + + ...html.fragment( + hasSheetMusicFiles && [ + generateContentHeading({ + id: 'sheet-music-files', + title: language.$('releaseInfo.sheetMusicFiles.heading'), + }), + + generateAlbumAdditionalFilesList(album, track.sheetMusicFiles, { + fileSize: false, + }), + ]), + + ...html.fragment( + hasMidiProjectFiles && [ + generateContentHeading({ + id: 'midi-project-files', + title: language.$('releaseInfo.midiProjectFiles.heading'), + }), + + generateAlbumAdditionalFilesList(album, track.midiProjectFiles), + ]), + + ...html.fragment( + hasAdditionalFiles && [ + generateContentHeading({ + id: 'additional-files', + title: language.$('releaseInfo.additionalFiles.heading', { + additionalFiles: language.countAdditionalFiles(numAdditionalFiles, { + unit: true, + }), + }) + }), + + generateAlbumAdditionalFilesList(album, track.additionalFiles), + ]), + + ...html.fragment( + hasCommentary && [ + generateContentHeading({ + id: 'artist-commentary', + title: language.$('releaseInfo.artistCommentary'), + }), + + html.tag('blockquote', generateCommentary({ + link, + language, + transformMultiline, + })), + ]), + ], + }, + + sidebarLeft: generateAlbumSidebar(album, track, { + fancifyURL, + getLinkThemeString, + html, + language, + link, + transformMultiline, + wikiData, + }), + + nav: { + linkContainerClasses: ['nav-links-hierarchy'], + links: [ + {toHome: true}, + { + path: ['localized.album', album.directory], + title: album.name, + }, + listTag === 'ol' && + { + html: language.$('trackPage.nav.track.withNumber', { + number: album.tracks.indexOf(track) + 1, + track: link.track(track, {class: 'current', to}), + }), + }, + listTag === 'ul' && + { + html: language.$('trackPage.nav.track', { + track: link.track(track, {class: 'current', to}), + }), + }, + ].filter(Boolean), + + content: generateAlbumChronologyLinks(album, track, { + generateChronologyLinks, + html, + }), + + bottomRowContent: + album.tracks.length > 1 && + generateAlbumNavLinks(album, track, { + generateNavigationLinks, + html, + language, + }), + }, + + secondaryNav: generateAlbumSecondaryNav(album, track, { + getLinkThemeString, + html, + language, + link, + }), + }; + }, + }; + + return [data, page]; +} +*/ diff --git a/src/content/dependencies/linkAlbum.js b/src/content/dependencies/linkAlbum.js new file mode 100644 index 00000000..36b0d13a --- /dev/null +++ b/src/content/dependencies/linkAlbum.js @@ -0,0 +1,8 @@ +export default { + contentDependencies: ['linkThing'], + + relations: (relation, album) => + ({link: relation('linkThing', 'localized.album', album)}), + + generate: (relations) => relations.link, +}; diff --git a/src/content/dependencies/linkContribution.js b/src/content/dependencies/linkContribution.js new file mode 100644 index 00000000..1d0e2d6a --- /dev/null +++ b/src/content/dependencies/linkContribution.js @@ -0,0 +1,74 @@ +import {empty} from '../../util/sugar.js'; + +export default { + contentDependencies: [ + 'linkArtist', + 'linkExternalAsIcon', + ], + + extraDependencies: [ + 'html', + 'language', + ], + + relations(relation, artist) { + const relations = {}; + + relations.artistLink = relation('linkArtist', artist); + + relations.artistIcons = + (artist.urls ?? []).map(url => + relation('linkExternalAsIcon', url)); + + return relations; + }, + + data(artist, contribution) { + return {contribution}; + }, + + generate(data, relations, { + html, + language, + }) { + return html.template({ + annotation: 'linkContribution', + + slots: { + showContribution: {type: 'boolean', default: false}, + showIcons: {type: 'boolean', default: false}, + }, + + content(slots) { + const hasContributionPart = !!(slots.showContribution && data.contribution); + const hasExternalPart = !!(slots.showIcons && !empty(relations.artistIcons)); + + const externalLinks = hasExternalPart && + html.tag('span', + {[html.noEdgeWhitespace]: true, class: 'icons'}, + language.formatUnitList(relations.artistIcons)); + + return ( + (hasContributionPart + ? (hasExternalPart + ? language.$('misc.artistLink.withContribution.withExternalLinks', { + artist: relations.artistLink, + contrib: data.contribution, + links: externalLinks, + }) + : language.$('misc.artistLink.withContribution', { + artist: relations.artistLink, + contrib: data.contribution, + })) + : (hasExternalPart + ? language.$('misc.artistLink.withExternalLinks', { + artist: relations.artistLink, + links: externalLinks, + }) + : language.$('misc.artistLink', { + artist: relations.artistLink, + })))); + }, + }); + }, +}; diff --git a/src/data/things/artist.js b/src/data/things/artist.js index 303f33f3..f144b21f 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -27,9 +27,8 @@ export class Artist extends Thing { aliasNames: { flags: {update: true, expose: true}, - update: { - validate: validateArrayItems(isName), - }, + update: {validate: validateArrayItems(isName)}, + expose: {transform: (names) => names ?? []}, }, isAlias: Thing.common.flag(), diff --git a/src/data/things/thing.js b/src/data/things/thing.js index 5ab15c0e..f0065b55 100644 --- a/src/data/things/thing.js +++ b/src/data/things/thing.js @@ -63,6 +63,7 @@ export default class Thing extends CacheableObject { urls: () => ({ flags: {update: true, expose: true}, update: {validate: validateArrayItems(isURL)}, + expose: {transform: (value) => value ?? []}, }), // A file extension! Or the default, if provided when calling this. diff --git a/src/page/index.js b/src/page/index.js index 8cf1d965..3cbddbfb 100644 --- a/src/page/index.js +++ b/src/page/index.js @@ -120,4 +120,4 @@ export * as album from './album.js'; // export * as news from './news.js'; // export * as static from './static.js'; // export * as tag from './tag.js'; -// export * as track from './track.js'; +export * as track from './track.js'; diff --git a/src/page/track.js b/src/page/track.js index 7f0d1cf2..e75b6958 100644 --- a/src/page/track.js +++ b/src/page/track.js @@ -1,553 +1,21 @@ // Track page specification. -import { - generateAlbumChronologyLinks, - generateAlbumNavLinks, - generateAlbumSecondaryNav, - generateAlbumSidebar, - generateAlbumAdditionalFilesList as unbound_generateAlbumAdditionalFilesList, -} from './album.js'; - -import { - bindOpts, - empty, -} from '../util/sugar.js'; - -import { - getTrackCover, - getAlbumListTag, - sortChronologically, -} from '../util/wiki-data.js'; - export const description = `per-track info pages`; export function targets({wikiData}) { return wikiData.trackData; } -export function write(track, {wikiData}) { - const {wikiInfo} = wikiData; - - const { - album, - contributorContribs, - referencedByTracks, - referencedTracks, - sampledByTracks, - sampledTracks, - otherReleases, - } = track; - - const listTag = getAlbumListTag(album); - - let flashesThatFeature; - if (wikiInfo.enableFlashesAndGames) { - flashesThatFeature = sortChronologically( - [track, ...otherReleases].flatMap((track) => - track.featuredInFlashes.map((flash) => ({ - flash, - as: track, - directory: flash.directory, - name: flash.name, - date: flash.date, - })) - ) - ); - } - - const unbound_getTrackItem = (track, { - getArtistString, - html, - language, - link, - }) => - html.tag('li', - language.$('trackList.item.withArtists', { - track: link.track(track), - by: html.tag('span', - {class: 'by'}, - language.$('trackList.item.withArtists.by', { - artists: getArtistString(track.artistContribs), - })), - })); - - const hasCommentary = - track.commentary || otherReleases.some((t) => t.commentary); +export function pathsForTarget(track) { + return [ + { + type: 'page', + path: ['track', track.directory], - const hasAdditionalFiles = !empty(track.additionalFiles); - const hasSheetMusicFiles = !empty(track.sheetMusicFiles); - const hasMidiProjectFiles = !empty(track.midiProjectFiles); - const numAdditionalFiles = album.additionalFiles.flatMap((g) => g.files).length; - - const generateCommentary = ({language, link, transformMultiline}) => - transformMultiline([ - track.commentary, - ...otherReleases.map((track) => - track.commentary - ?.split('\n') - .filter((line) => line.replace(/<\/b>/g, '').includes(':')) - .flatMap(line => [ - line, - language.$('releaseInfo.artistCommentary.seeOriginalRelease', { - original: link.track(track), - }), - ]) - .join('\n') - ), - ].filter(Boolean).join('\n')); - - const data = { - type: 'data', - path: ['track', track.directory], - data: ({ - serializeContribs, - serializeCover, - serializeGroupsForTrack, - serializeLink, - }) => ({ - name: track.name, - directory: track.directory, - dates: { - released: track.date, - originallyReleased: track.originalDate, - coverArtAdded: track.coverArtDate, + contentFunction: { + name: 'generateTrackInfoPage', + args: [track], }, - duration: track.duration, - color: track.color, - cover: serializeCover(track, getTrackCover), - artistsContribs: serializeContribs(track.artistContribs), - contributorContribs: serializeContribs(track.contributorContribs), - coverArtistContribs: serializeContribs(track.coverArtistContribs || []), - album: serializeLink(track.album), - groups: serializeGroupsForTrack(track), - references: track.references.map(serializeLink), - referencedBy: track.referencedBy.map(serializeLink), - alsoReleasedAs: otherReleases.map((track) => ({ - track: serializeLink(track), - album: serializeLink(track.album), - })), - }), - }; - - const getSocialEmbedDescription = ({ - getArtistString: _getArtistString, - language, - }) => { - const hasArtists = !empty(track.artistContribs); - const hasCoverArtists = !empty(track.coverArtistContribs); - const getArtistString = (contribs) => - _getArtistString(contribs, { - // We don't want to put actual HTML tags in social embeds (sadly - // they don't get parsed and displayed, generally speaking), so - // override the link argument so that artist "links" just show - // their names. - link: {artist: (artist) => artist.name}, - }); - if (!hasArtists && !hasCoverArtists) return ''; - return language.formatString( - 'trackPage.socialEmbed.body' + - [hasArtists && '.withArtists', hasCoverArtists && '.withCoverArtists'] - .filter(Boolean) - .join(''), - Object.fromEntries( - [ - hasArtists && ['artists', getArtistString(track.artistContribs)], - hasCoverArtists && [ - 'coverArtists', - getArtistString(track.coverArtistContribs), - ], - ].filter(Boolean) - ) - ); - }; - - const page = { - type: 'page', - path: ['track', track.directory], - page: ({ - absoluteTo, - fancifyURL, - generateAdditionalFilesList, - generateAdditionalFilesShortcut, - generateChronologyLinks, - generateContentHeading, - generateNavigationLinks, - generateTrackListDividedByGroups, - getAlbumStylesheet, - getArtistString, - getLinkThemeString, - getSizeOfAdditionalFile, - getThemeString, - getTrackCover, - html, - link, - language, - transformLyrics, - transformMultiline, - to, - urls, - }) => { - const getTrackItem = bindOpts(unbound_getTrackItem, { - getArtistString, - html, - language, - link, - }); - - const generateAlbumAdditionalFilesList = bindOpts(unbound_generateAlbumAdditionalFilesList, { - [bindOpts.bindIndex]: 2, - generateAdditionalFilesList, - getSizeOfAdditionalFile, - link, - urls, - }); - - return { - title: language.$('trackPage.title', {track: track.name}), - stylesheet: getAlbumStylesheet(album, {to}), - - themeColor: track.color, - theme: - getThemeString(track.color, { - additionalVariables: [ - `--album-directory: ${album.directory}`, - `--track-directory: ${track.directory}`, - ] - }), - - socialEmbed: { - heading: language.$('trackPage.socialEmbed.heading', { - album: track.album.name, - }), - headingLink: absoluteTo('localized.album', album.directory), - title: language.$('trackPage.socialEmbed.title', { - track: track.name, - }), - description: getSocialEmbedDescription({getArtistString, language}), - image: '/' + getTrackCover(track, {to: urls.from('shared.root').to}), - color: track.color, - }, - - // disabled for now! shifting banner position per height of page is disorienting - /* - banner: !empty(album.bannerArtistContribs) && { - classes: ['dim'], - dimensions: album.bannerDimensions, - path: ['media.albumBanner', album.directory, album.bannerFileExtension], - alt: language.$('misc.alt.albumBanner'), - position: 'bottom' - }, - */ - - cover: { - src: getTrackCover(track), - alt: language.$('misc.alt.trackCover'), - artTags: track.artTags, - }, - - main: { - headingMode: 'sticky', - - content: [ - html.tag('p', - { - [html.onlyIfContent]: true, - [html.joinChildren]: '
', - }, - [ - !empty(track.artistContribs) && - language.$('releaseInfo.by', { - artists: getArtistString(track.artistContribs, { - showContrib: true, - showIcons: true, - }), - }), - - !empty(track.coverArtistContribs) && - language.$('releaseInfo.coverArtBy', { - artists: getArtistString(track.coverArtistContribs, { - showContrib: true, - showIcons: true, - }), - }), - - track.date && - language.$('releaseInfo.released', { - date: language.formatDate(track.date), - }), - - track.hasCoverArt && - track.coverArtDate && - +track.coverArtDate !== +track.date && - language.$('releaseInfo.artReleased', { - date: language.formatDate(track.coverArtDate), - }), - - track.duration && - language.$('releaseInfo.duration', { - duration: language.formatDuration( - track.duration - ), - }), - ]), - - html.tag('p', - { - [html.onlyIfContent]: true, - [html.joinChildren]: '
', - }, - [ - hasSheetMusicFiles && - language.$('releaseInfo.sheetMusicFiles.shortcut', { - link: html.tag('a', - {href: '#sheet-music-files'}, - language.$('releaseInfo.sheetMusicFiles.shortcut.link')), - }), - - hasMidiProjectFiles && - language.$('releaseInfo.midiProjectFiles.shortcut', { - link: html.tag('a', - {href: '#midi-project-files'}, - language.$('releaseInfo.midiProjectFiles.shortcut.link')), - }), - - hasAdditionalFiles && - generateAdditionalFilesShortcut(track.additionalFiles), - ]), - - html.tag('p', - (empty(track.urls) - ? language.$('releaseInfo.listenOn.noLinks') - : language.$('releaseInfo.listenOn', { - links: language.formatDisjunctionList( - track.urls.map(url => fancifyURL(url, {language}))), - }))), - - ...html.fragment( - !empty(otherReleases) && [ - generateContentHeading({ - id: 'also-released-as', - title: language.$('releaseInfo.alsoReleasedAs'), - }), - - html.tag('ul', otherReleases.map(track => - html.tag('li', language.$('releaseInfo.alsoReleasedAs.item', { - track: link.track(track), - album: link.album(track.album), - })))), - ]), - - ...html.fragment( - !empty(contributorContribs) && [ - generateContentHeading({ - id: 'contributors', - title: language.$('releaseInfo.contributors'), - }), - - html.tag('ul', contributorContribs.map(contrib => - html.tag('li', getArtistString([contrib], { - showContrib: true, - showIcons: true, - })))), - ]), - - ...html.fragment( - !empty(referencedTracks) && [ - generateContentHeading({ - id: 'references', - title: - language.$('releaseInfo.tracksReferenced', { - track: html.tag('i', track.name), - }), - }), - - html.tag('ul', referencedTracks.map(getTrackItem)), - ]), - - ...html.fragment( - !empty(referencedByTracks) && [ - generateContentHeading({ - id: 'referenced-by', - title: - language.$('releaseInfo.tracksThatReference', { - track: html.tag('i', track.name), - }), - }), - - generateTrackListDividedByGroups(referencedByTracks, { - getTrackItem, - wikiData, - }), - ]), - - ...html.fragment( - !empty(sampledTracks) && [ - generateContentHeading({ - id: 'samples', - title: - language.$('releaseInfo.tracksSampled', { - track: html.tag('i', track.name), - }), - }), - - html.tag('ul', sampledTracks.map(getTrackItem)), - ]), - - ...html.fragment( - !empty(sampledByTracks) && [ - generateContentHeading({ - id: 'sampled-by', - title: - language.$('releaseInfo.tracksThatSample', { - track: html.tag('i', track.name), - }) - }), - - html.tag('ul', sampledByTracks.map(getTrackItem)), - ]), - - ...html.fragment( - wikiInfo.enableFlashesAndGames && - !empty(flashesThatFeature) && [ - generateContentHeading({ - id: 'featured-in', - title: - language.$('releaseInfo.flashesThatFeature', { - track: html.tag('i', track.name), - }), - }), - - html.tag('ul', flashesThatFeature.map(({flash, as}) => - html.tag('li', - {class: as !== track && 'rerelease'}, - (as === track - ? language.$('releaseInfo.flashesThatFeature.item', { - flash: link.flash(flash), - }) - : language.$('releaseInfo.flashesThatFeature.item.asDifferentRelease', { - flash: link.flash(flash), - track: link.track(as), - }))))), - ]), - - ...html.fragment( - track.lyrics && [ - generateContentHeading({ - id: 'lyrics', - title: language.$('releaseInfo.lyrics'), - }), - - html.tag('blockquote', transformLyrics(track.lyrics)), - ]), - - ...html.fragment( - hasSheetMusicFiles && [ - generateContentHeading({ - id: 'sheet-music-files', - title: language.$('releaseInfo.sheetMusicFiles.heading'), - }), - - generateAlbumAdditionalFilesList(album, track.sheetMusicFiles, { - fileSize: false, - }), - ]), - - ...html.fragment( - hasMidiProjectFiles && [ - generateContentHeading({ - id: 'midi-project-files', - title: language.$('releaseInfo.midiProjectFiles.heading'), - }), - - generateAlbumAdditionalFilesList(album, track.midiProjectFiles), - ]), - - ...html.fragment( - hasAdditionalFiles && [ - generateContentHeading({ - id: 'additional-files', - title: language.$('releaseInfo.additionalFiles.heading', { - additionalFiles: language.countAdditionalFiles(numAdditionalFiles, { - unit: true, - }), - }) - }), - - generateAlbumAdditionalFilesList(album, track.additionalFiles), - ]), - - ...html.fragment( - hasCommentary && [ - generateContentHeading({ - id: 'artist-commentary', - title: language.$('releaseInfo.artistCommentary'), - }), - - html.tag('blockquote', generateCommentary({ - link, - language, - transformMultiline, - })), - ]), - ], - }, - - sidebarLeft: generateAlbumSidebar(album, track, { - fancifyURL, - getLinkThemeString, - html, - language, - link, - transformMultiline, - wikiData, - }), - - nav: { - linkContainerClasses: ['nav-links-hierarchy'], - links: [ - {toHome: true}, - { - path: ['localized.album', album.directory], - title: album.name, - }, - listTag === 'ol' && - { - html: language.$('trackPage.nav.track.withNumber', { - number: album.tracks.indexOf(track) + 1, - track: link.track(track, {class: 'current', to}), - }), - }, - listTag === 'ul' && - { - html: language.$('trackPage.nav.track', { - track: link.track(track, {class: 'current', to}), - }), - }, - ].filter(Boolean), - - content: generateAlbumChronologyLinks(album, track, { - generateChronologyLinks, - html, - }), - - bottomRowContent: - album.tracks.length > 1 && - generateAlbumNavLinks(album, track, { - generateNavigationLinks, - html, - language, - }), - }, - - secondaryNav: generateAlbumSecondaryNav(album, track, { - getLinkThemeString, - html, - language, - link, - }), - }; }, - }; - - return [data, page]; + ]; } diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js index 6dbcf3ee..1e72e5a8 100644 --- a/src/write/build-modes/live-dev-server.js +++ b/src/write/build-modes/live-dev-server.js @@ -433,6 +433,11 @@ export async function go({ function runContentFunction({name, args, relations}) { const contentFunction = fulfilledContentDependencies[name]; + + if (!contentFunction) { + throw new Error(`Content function ${name} unfulfilled or not listed`); + } + const filledRelations = fillRelationsLayoutFromSlotResults(relationIdentifier, slotResults, relations); -- cgit 1.3.0-6-gf8a5