diff options
-rw-r--r-- | src/content-function.js | 177 | ||||
-rw-r--r-- | src/misc-templates.js | 236 | ||||
-rw-r--r-- | src/page/album.js | 767 |
3 files changed, 754 insertions, 426 deletions
diff --git a/src/content-function.js b/src/content-function.js new file mode 100644 index 00000000..51383793 --- /dev/null +++ b/src/content-function.js @@ -0,0 +1,177 @@ +import {empty} from './util/sugar.js'; + +export default function contentFunction({ + contentDependencies = [], + extraDependencies = [], + + data, + generate, +}) { + return expectDependencies({ + data, + generate, + + expectedContentDependencyKeys: contentDependencies, + expectedExtraDependencyKeys: extraDependencies, + fulfilledDependencies: {}, + }); +} + +contentFunction.identifyingSymbol = Symbol(`Is a content function?`); + +export function expectDependencies({ + generate, + data, + + expectedContentDependencyKeys, + expectedExtraDependencyKeys, + fulfilledDependencies, +}) { + if (!generate) { + throw new Error(`Expected generate function`); + } + + if (!data) { + throw new Error(`Expected data function`); + } + + const fulfilledDependencyKeys = Object.keys(fulfilledDependencies); + + const invalidatingDependencyKeys = Object.entries(fulfilledDependencies) + .filter(([key, value]) => value.fulfilled === false) + .map(([key]) => key); + + const missingContentDependencyKeys = expectedContentDependencyKeys + .filter(key => !fulfilledDependencyKeys.includes(key)); + + const missingExtraDependencyKeys = expectedExtraDependencyKeys + .filter(key => !fulfilledDependencyKeys.includes(key)); + + let wrappedGenerate; + + if (!empty(invalidatingDependencyKeys)) { + wrappedGenerate = function() { + throw new Error(`Generate invalidated because unfulfilled dependencies provided: ${invalidatingDependencyKeys.join(', ')}`); + }; + + wrappedGenerate.fulfilled ??= false; + } + + if (empty(missingContentDependencyKeys) && empty(missingExtraDependencyKeys)) { + wrappedGenerate ??= function(data) { + return generate(data, fulfilledDependencies); + }; + + wrappedGenerate.fulfill = function() { + throw new Error(`All dependencies already fulfilled`); + }; + + wrappedGenerate.fulfilled ??= true; + } + + wrappedGenerate ??= function() { + throw new Error(`Dependencies still needed: ${missingContentDependencyKeys.concat(missingExtraDependencyKeys).join(', ')}`); + }; + + wrappedGenerate.fulfilled ??= false; + wrappedGenerate[contentFunction.identifyingSymbol] = true; + + if (empty(missingContentDependencyKeys)) { + const dataDependencies = {}; + + for (const key of expectedContentDependencyKeys) { + const wrappedDependency = function() { + throw new Error(`Expected call to this dependency's .data()`); + }; + + wrappedDependency.data = fulfilledDependencies[key].data; + dataDependencies[key] = wrappedDependency; + } + + wrappedGenerate.data = function(...args) { + return data(...args, dataDependencies); + }; + } + + wrappedGenerate.data ??= function() { + throw new Error(`Dependencies still needed: ${missingContentDependencyKeys.join(', ')}`); + }; + + wrappedGenerate.fulfill ??= function(dependencies) { + return expectDependencies({ + generate, + data, + + expectedContentDependencyKeys, + expectedExtraDependencyKeys, + + fulfilledDependencies: fulfillDependencies({ + name: generate.name, + dependencies, + + expectedContentDependencyKeys, + expectedExtraDependencyKeys, + fulfilledDependencies, + }), + }); + }; + + return wrappedGenerate; +} + +export function fulfillDependencies({ + name, + dependencies, + expectedContentDependencyKeys, + expectedExtraDependencyKeys, + fulfilledDependencies, +}) { + const newFulfilledDependencies = {...fulfilledDependencies}; + const fulfilledDependencyKeys = Object.keys(fulfilledDependencies); + + const errors = []; + let bail = false; + + for (let [key, value] of Object.entries(dependencies)) { + if (key.startsWith('u_')) { + key = key.slice(2); + } + + if (fulfilledDependencyKeys.includes(key)) { + errors.push(new Error(`Dependency ${key} is already fulfilled`)); + bail = true; + continue; + } + + const isContentKey = expectedContentDependencyKeys.includes(key); + const isExtraKey = expectedExtraDependencyKeys.includes(key); + + if (!isContentKey && !isExtraKey) { + errors.push(new Error(`Dependency ${key} is not expected`)); + bail = true; + continue; + } + + if (isContentKey && !value[contentFunction.identifyingSymbol]) { + errors.push(new Error(`Content dependency ${key} is not a content function`)); + bail = true; + continue; + } + + if (isExtraKey && value[contentFunction.identifyingSymbol]) { + errors.push(new Error(`Extra dependency ${key} is a content function`)); + bail = true; + continue; + } + + if (!bail) { + newFulfilledDependencies[key] = value; + } + } + + if (!empty(errors)) { + throw new AggregateError(errors, `Errors fulfilling dependencies for ${name}`); + } + + return newFulfilledDependencies; +} diff --git a/src/misc-templates.js b/src/misc-templates.js index 0d749d1d..cbdedfe0 100644 --- a/src/misc-templates.js +++ b/src/misc-templates.js @@ -18,6 +18,8 @@ import { sortChronologically, } from './util/wiki-data.js'; +import contentFunction from './util/content-function.js'; + import u_link from './util/link.js'; const BANDCAMP_DOMAINS = ['bc.s3m.us', 'music.solatrux.com']; @@ -80,72 +82,74 @@ function unbound_generateAdditionalFilesList(additionalFiles, { // Artist strings -unbound_generateContributionLinks.data = (contributions, { - showContribution = false, - showIcons = false, -}) => { - return { - showContribution, - showIcons, - - contributionData: - contributions.map(({who, what}) => ({ - artistLinkData: u_link.artist.data(who), +export const u_generateContributionLinks = contentFunction({ + data: function(contributions, { + showContribution = false, + showIcons = false, + }) { + return { + showContribution, + showIcons, - hasContributionPart: !!(showContribution && what), - hasExternalPart: !!(showIcons && !empty(who.urls)), + contributionData: + contributions.map(({who, what}) => ({ + artistLinkData: u_link.artist.data(who), - artistUrls: who.urls, - contribution: showContribution && what, - })), - }; -}; + hasContributionPart: !!(showContribution && what), + hasExternalPart: !!(showIcons && !empty(who.urls)), -function unbound_generateContributionLinks(data, { - html, - iconifyURL, - language, - link, -}) { - return language.formatConjunctionList( - data.contributionData.map(({ - artistLinkData, - hasContributionPart, - hasExternalPart, - artistUrls, - contribution, - }) => { - const artistLink = link.artist(artistLinkData); - - const externalLinks = hasExternalPart && - html.tag('span', - {[html.noEdgeWhitespace]: true, class: 'icons'}, - language.formatUnitList( - artistUrls.map(url => iconifyURL(url, {language})))); - - 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, - }))) - ); - })); -} + artistUrls: who.urls, + contribution: showContribution && what, + })), + }; + }, + + generate: function generateContributionLinks(data, { + html, + iconifyURL, + language, + link, + }) { + return language.formatConjunctionList( + data.contributionData.map(({ + artistLinkData, + hasContributionPart, + hasExternalPart, + artistUrls, + contribution, + }) => { + const artistLink = link.artist(artistLinkData); + + const externalLinks = hasExternalPart && + html.tag('span', + {[html.noEdgeWhitespace]: true, class: 'icons'}, + language.formatUnitList( + artistUrls.map(url => iconifyURL(url, {language})))); + + 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, + }))) + ); + })); + }, +}); // Chronology links @@ -337,52 +341,65 @@ function unbound_getThemeString(color, { ].join('\n'); } -function unbound_getAlbumStylesheet(album, { - to, -}) { - const hasWallpaper = album.wallpaperArtistContribs.length >= 1; - const hasWallpaperStyle = !!album.wallpaperStyle; - const hasBannerStyle = !!album.bannerStyle; - - const wallpaperSource = - (hasWallpaper && - to( - 'media.albumWallpaper', - album.directory, - album.wallpaperFileExtension)); - - const wallpaperPart = - (hasWallpaper - ? [ - `body::before {`, - ` background-image: url("${wallpaperSource}");`, - ...(hasWallpaperStyle - ? album.wallpaperStyle - .split('\n') - .map(line => ` ${line}`) - : []), - `}`, - ] - : []); - - const bannerPart = - (hasBannerStyle - ? [ - `#banner img {`, - ...album.bannerStyle - .split('\n') - .map(line => ` ${line}`), - `}`, - ] - : []); +export const u_generateAlbumStylesheet = contentFunction({ + extraDependencies: [ + 'to', + ], - return [ - ...wallpaperPart, - ...bannerPart, - ] - .filter(Boolean) - .join('\n'); -} + data: function(album) { + const data = {}; + + data.hasWallpaper = !empty(album.wallpaperArtistContribs); + data.hasBanner = !empty(album.bannerArtistContribs); + + if (data.hasWallpaper) { + data.hasWallpaperStyle = !!album.wallpaperStyle; + data.wallpaperPath = ['media.albumWallpaper', album.directory, album.wallpaperFileExtension]; + data.wallpaperStyle = album.wallpaperStyle; + } + + if (data.hasBanner) { + data.hasBannerStyle = !!album.bannerStyle; + data.bannerStyle = album.bannerStyle; + } + + return data; + }, + + generate: function generateAlbumStylesheet(data, {to}) { + const wallpaperPart = + (data.hasWallpaper + ? [ + `body::before {`, + ` background-image: url("${to(...data.wallpaperPath)}");`, + ...(data.hasWallpaperStyle + ? data.wallpaperStyle + .split('\n') + .map(line => ` ${line}`) + : []), + `}`, + ] + : []); + + const bannerPart = + (data.hasBannerStyle + ? [ + `#banner img {`, + ...data.bannerStyle + .split('\n') + .map(line => ` ${line}`), + `}`, + ] + : []); + + return [ + ...wallpaperPart, + ...bannerPart, + ] + .filter(Boolean) + .join('\n'); + }, +}); // Divided track lists @@ -1057,8 +1074,6 @@ export { unbound_generateAdditionalFilesList as generateAdditionalFilesList, unbound_generateAdditionalFilesShortcut as generateAdditionalFilesShortcut, - unbound_generateContributionLinks as generateContributionLinks, - unbound_generateChronologyLinks as generateChronologyLinks, unbound_getRevealStringFromContentWarningMessage as getRevealStringFromContentWarningMessage, @@ -1067,7 +1082,6 @@ export { unbound_generateCoverLink as generateCoverLink, unbound_getThemeString as getThemeString, - unbound_getAlbumStylesheet as getAlbumStylesheet, unbound_generateTrackListDividedByGroups as generateTrackListDividedByGroups, diff --git a/src/page/album.js b/src/page/album.js index a266b911..ab1e1b2f 100644 --- a/src/page/album.js +++ b/src/page/album.js @@ -13,10 +13,11 @@ import { } from '../util/wiki-data.js'; import { - generateContributionLinks as u_generateContributionLinks, + u_generateAlbumStylesheet, } from '../misc-templates.js'; import u_link from '../util/link.js'; +import contentFunction from '../util/content-function.js'; export const description = `per-album info & track artwork gallery pages`; @@ -27,7 +28,7 @@ export function targets({wikiData}) { export const dataSteps = { computePathsForTarget(data, album) { data.hasGalleryPage = album.tracks.some(t => t.hasUniqueCoverArt); - data.hasCommentaryPage = !!album.commentary || album.tracks.some(t => t.commentary);; + data.hasCommentaryPage = !!album.commentary || album.tracks.some(t => t.commentary); return [ { @@ -62,6 +63,16 @@ export const dataSteps = { computeDataForPageWrite: { album(data, album, _pathArgs) { + // TODO: We can't use content-unfulfilled functions here. + // But how do we express that these need to be fulfilled + // from within data steps? + data.socialEmbedData = u_generateAlbumSocialEmbed.data(album); + data.stylesheetData = u_generateAlbumStylesheet.data(album); + + data.name = album.name; + data.color = album.color; + data.directory = album.directory; + data.hasAdditionalFiles = !empty(album.additionalFiles); data.numAdditionalFiles = album.additionalFiles.flatMap((g) => g.files).length; @@ -101,56 +112,455 @@ export const dataSteps = { link, }); + const generateAlbumSocialEmbedDescription = u_generateAlbumSocialEmbedDescription.fulfill({ + language, + }); + + const generateAlbumSocialEmbed = u_generateAlbumSocialEmbed.fulfill({ + generateSocialEmbedDescription: generateAlbumSocialEmbedDescription, + }); + void generateTrackListItem; + + return { + title: language.$('albumPage.title', {album: data.name}), + stylesheet: getAlbumStylesheet(data.stylesheetData), + + themeColor: data.color, + theme: + getThemeString(data.color, { + additionalVariables: [ + `--album-directory: ${data.directory}`, + ], + }), + + socialEmbed: generateAlbumSocialEmbed(data.socialEmbedData, { + absoluteTo, + getAlbumCover, + getSocialEmbedDescription, + to, + }), + }; }, }, }; -function u_generateTrackListItem(data, { - generateContributionLinks, - getLinkThemeString, - html, - language, - link, -}) { - const stringOpts = { - duration: language.formatDuration(data.duration), - track: link.track(data.linkData), - }; +const u_generateAlbumSocialEmbedDescription = contentFunction({ + extraDependencies: ['language'], + + data: function(album) { + const data = {}; + + const duration = getTotalDuration(album); + + data.hasDuration = duration > 0; + data.hasTracks = album.tracks.length > 0; + data.hasDate = !!album.date; + data.hasAny = (data.hasDuration || data.hasTracks || data.hasDuration); + + if (!data.hasAny) + return data; + + if (data.hasDuration) + data.duration = duration; + + if (data.hasTracks) + data.tracks = album.tracks.length; + + if (data.hasDate) + data.date = album.date; + + return data; + }, + + generate: function generateAlbumSocialEmbedDescription(data, { + language, + }) { + return language.formatString( + 'albumPage.socialEmbed.body' + [ + data.hasDuration && '.withDuration', + data.hasTracks && '.withTracks', + data.hasDate && '.withReleaseDate', + ].filter(Boolean).join(''), + + Object.fromEntries([ + data.hasDuration && + ['duration', language.formatDuration(data.duration)], + data.hasTracks && + ['tracks', language.countTracks(data.tracks, {unit: true})], + data.hasDate && + ['date', language.formatDate(data.date)], + ].filter(Boolean))); + }, +}); + +const u_generateAlbumSocialEmbed = contentFunction({ + contentDependencies: [ + 'generateSocialEmbedDescription', + ], + + extraDependencies: [ + 'absoluteTo', + 'language', + 'to', + 'urls', + ], + + data: function(album, { + generateSocialEmbedDescription, + }) { + const data = {}; + + data.descriptionData = generateSocialEmbedDescription.data(album); + + data.hasHeading = !empty(album.groups); + + if (data.hasHeading) { + const firstGroup = album.groups[0]; + data.headingGroupName = firstGroup.directory; + data.headingGroupDirectory = firstGroup.directory; + } + + data.albumName = album.name; + data.albumColor = album.color; + + return data; + }, + + generate: function generateAlbumSocialEmbed(data, { + generateSocialEmbedDescription, + + absoluteTo, + language, + to, + urls, + }) { + const socialEmbed = {}; + + if (data.hasHeading) { + socialEmbed.heading = + language.$('albumPage.socialEmbed.heading', { + group: data.headingGroupName, + }); + + socialEmbed.headingLink = + absoluteTo('localized.album', data.headingGroupDirectory); + } else { + socialEmbed.heading = ''; + socialEmbed.headingLink = null; + } + + socialEmbed.title = + language.$('albumPage.socialEmbed.title', { + album: data.albumName, + }); + + socialEmbed.description = generateSocialEmbedDescription(data.descriptionData); + + socialEmbed.image = + '/' + getAlbumCover(album, {to: urls.from('shared.root').to}); + + socialEmbed.color = data.albumColor; + + return socialEmbed; + }, +}); + +const u_generateTrackListItem = contentFunction({ + contentDependencies: [ + 'generateContributionLinks', + ], + + extraDependencies: [ + 'getLinkThemeString', + 'html', + 'language', + 'link', + ], + + data: function(track, { + generateContributionLinks, + }) { + return { + color: track.color, + duration: track.duration ?? 0, + linkData: u_link.track.data(track), + + showArtists: + !compareArrays( + track.artistContribs.map(c => c.who), + track.album.artistContribs.map(c => c.who), + {checkOrder: false}), + + contributionLinksData: + generateContributionLinks.data(track.artistContribs, { + showContribution: false, + showIcons: false, + }), + }; + }, - return html.tag('li', - {style: getLinkThemeString(data.color)}, - (!data.showArtists - ? language.$('trackList.item.withDuration', stringOpts) - : language.$('trackList.item.withDuration.withArtists', { - ...stringOpts, - by: - html.tag('span', {class: 'by'}, - language.$('trackList.item.withArtists.by', { - artists: generateContributionLinks(data.contributionLinksData), + generate: function generateTrackListItem(data, { + generateContributionLinks, + + getLinkThemeString, + html, + language, + link, + }) { + const stringOpts = { + duration: language.formatDuration(data.duration), + track: link.track(data.linkData), + }; + + return html.tag('li', + {style: getLinkThemeString(data.color)}, + (!data.showArtists + ? language.$('trackList.item.withDuration', stringOpts) + : language.$('trackList.item.withDuration.withArtists', { + ...stringOpts, + by: + html.tag('span', {class: 'by'}, + language.$('trackList.item.withArtists.by', { + artists: generateContributionLinks(data.contributionLinksData), + })), + }))); + }, +}); + +/* +const infoPage = { + page: () => { + return { + banner: !empty(album.bannerArtistContribs) && { + dimensions: album.bannerDimensions, + path: [ + 'media.albumBanner', + album.directory, + album.bannerFileExtension, + ], + alt: language.$('misc.alt.albumBanner'), + position: 'top', + }, + + cover: { + src: getAlbumCover(album), + alt: language.$('misc.alt.albumCover'), + artTags: album.artTags, + }, + + main: { + headingMode: 'sticky', + + content: [ + html.tag('p', + { + [html.onlyIfContent]: true, + [html.joinChildren]: '<br>', + }, + [ + !empty(album.artistContribs) && + language.$('releaseInfo.by', { + artists: getArtistString(album.artistContribs, { + showContrib: true, + showIcons: true, + }), + }), + + !empty(album.coverArtistContribs) && + language.$('releaseInfo.coverArtBy', { + artists: getArtistString(album.coverArtistContribs, { + showContrib: true, + showIcons: true, + }), + }), + + !empty(album.wallpaperArtistContribs) && + language.$('releaseInfo.wallpaperArtBy', { + artists: getArtistString(album.wallpaperArtistContribs, { + showContrib: true, + showIcons: true, + }), + }), + + !empty(album.bannerArtistContribs) && + language.$('releaseInfo.bannerArtBy', { + artists: getArtistString(album.bannerArtistContribs, { + showContrib: true, + showIcons: true, + }), + }), + + album.date && + language.$('releaseInfo.released', { + date: language.formatDate(album.date), + }), + + album.hasCoverArt && + album.coverArtDate && + +album.coverArtDate !== +album.date && + language.$('releaseInfo.artReleased', { + date: language.formatDate(album.coverArtDate), + }), + + albumDuration > 0 && + language.$('releaseInfo.duration', { + duration: language.formatDuration(albumDuration, { + approximate: album.tracks.length > 1, + }), + }), + ]), + + html.tag('p', + { + [html.onlyIfContent]: true, + [html.joinChildren]: '<br>', + }, + [ + hasAdditionalFiles && + generateAdditionalFilesShortcut(album.additionalFiles), + + checkGalleryPage(album) && + language.$('releaseInfo.viewGallery', { + link: link.albumGallery(album, { + text: language.$('releaseInfo.viewGallery.link'), + }), + }), + + checkCommentaryPage(album) && + language.$('releaseInfo.viewCommentary', { + link: link.albumCommentary(album, { + text: language.$('releaseInfo.viewCommentary.link'), + }), + }), + ]), + + !empty(album.urls) && + html.tag('p', + language.$('releaseInfo.listenOn', { + links: language.formatDisjunctionList( + album.urls.map(url => fancifyURL(url, {album: true})) + ), })), - }))); -} -u_generateTrackListItem.data = track => { - return { - color: track.color, - duration: track.duration ?? 0, - linkData: u_link.track.data(track), - - showArtists: - !compareArrays( - track.artistContribs.map((c) => c.who), - track.album.artistContribs.map((c) => c.who), - {checkOrder: false}), - - contributionLinksData: - u_generateContributionLinks.data(track.artistContribs, { - showContribution: false, - showIcons: false, + displayTrackSections && + !empty(album.trackSections) && + html.tag('dl', + {class: 'album-group-list'}, + album.trackSections.flatMap(({ + name, + startIndex, + tracks, + }) => [ + html.tag('dt', + {class: ['content-heading']}, + language.$('trackList.section.withDuration', { + duration: language.formatDuration(getTotalDuration(tracks), { + approximate: tracks.length > 1, + }), + section: name, + })), + html.tag('dd', + html.tag(listTag, + listTag === 'ol' ? {start: startIndex + 1} : {}, + tracks.map(trackToListItem))), + ])), + + !displayTrackSections && + !empty(album.tracks) && + html.tag(listTag, + album.tracks.map(trackToListItem)), + + html.tag('p', + { + [html.onlyIfContent]: true, + [html.joinChildren]: '<br>', + }, + [ + album.dateAddedToWiki && + language.$('releaseInfo.addedToWiki', { + date: language.formatDate( + album.dateAddedToWiki + ), + }) + ]), + + ...html.fragment( + hasAdditionalFiles && [ + generateContentHeading({ + id: 'additional-files', + title: language.$('releaseInfo.additionalFiles.heading', { + additionalFiles: language.countAdditionalFiles(numAdditionalFiles, { + unit: true, + }), + }), + }), + + generateAlbumAdditionalFilesList(album, album.additionalFiles, { + generateAdditionalFilesList, + getSizeOfAdditionalFile, + link, + urls, + }), + ]), + + ...html.fragment( + album.commentary && [ + generateContentHeading({ + id: 'artist-commentary', + title: language.$('releaseInfo.artistCommentary'), + }), + + html.tag('blockquote', transformMultiline(album.commentary)), + ]), + ], + }, + + sidebarLeft: generateAlbumSidebar(album, null, { + fancifyURL, + getLinkThemeString, + html, + link, + language, + transformMultiline, + wikiData, }), - }; + + nav: { + linkContainerClasses: ['nav-links-hierarchy'], + links: [ + {toHome: true}, + { + html: language.$('albumPage.nav.album', { + album: link.album(album, {class: 'current'}), + }), + }, + { + divider: false, + html: generateAlbumNavLinks(album, null, { + generateNavigationLinks, + html, + language, + link, + }), + } + ], + content: generateAlbumChronologyLinks(album, null, { + generateChronologyLinks, + html, + }), + }, + + secondaryNav: generateAlbumSecondaryNav(album, null, { + getLinkThemeString, + html, + language, + link, + }), + }; + }, }; +*/ /* export function write(album, {wikiData}) { @@ -216,279 +626,6 @@ export function write(album, {wikiData}) { }), }; - const infoPage = { - type: 'page', - path: ['album', album.directory], - page: ({ - }) => { - const trackToListItem = bindOpts(unbound_trackToListItem, { - getArtistString, - getLinkThemeString, - html, - language, - link, - }); - - return { - title: language.$('albumPage.title', {album: album.name}), - stylesheet: getAlbumStylesheet(album), - - themeColor: album.color, - theme: - getThemeString(album.color, { - additionalVariables: [ - `--album-directory: ${album.directory}`, - ], - }), - - socialEmbed: { - heading: - (empty(album.groups) - ? '' - : language.$('albumPage.socialEmbed.heading', { - group: album.groups[0].name, - })), - headingLink: - (empty(album.groups) - ? null - : absoluteTo('localized.album', album.groups[0].directory)), - title: language.$('albumPage.socialEmbed.title', { - album: album.name, - }), - description: getSocialEmbedDescription({getArtistString, language}), - image: '/' + getAlbumCover(album, {to: urls.from('shared.root').to}), - color: album.color, - }, - - banner: !empty(album.bannerArtistContribs) && { - dimensions: album.bannerDimensions, - path: [ - 'media.albumBanner', - album.directory, - album.bannerFileExtension, - ], - alt: language.$('misc.alt.albumBanner'), - position: 'top', - }, - - cover: { - src: getAlbumCover(album), - alt: language.$('misc.alt.albumCover'), - artTags: album.artTags, - }, - - main: { - headingMode: 'sticky', - - content: [ - html.tag('p', - { - [html.onlyIfContent]: true, - [html.joinChildren]: '<br>', - }, - [ - !empty(album.artistContribs) && - language.$('releaseInfo.by', { - artists: getArtistString(album.artistContribs, { - showContrib: true, - showIcons: true, - }), - }), - - !empty(album.coverArtistContribs) && - language.$('releaseInfo.coverArtBy', { - artists: getArtistString(album.coverArtistContribs, { - showContrib: true, - showIcons: true, - }), - }), - - !empty(album.wallpaperArtistContribs) && - language.$('releaseInfo.wallpaperArtBy', { - artists: getArtistString(album.wallpaperArtistContribs, { - showContrib: true, - showIcons: true, - }), - }), - - !empty(album.bannerArtistContribs) && - language.$('releaseInfo.bannerArtBy', { - artists: getArtistString(album.bannerArtistContribs, { - showContrib: true, - showIcons: true, - }), - }), - - album.date && - language.$('releaseInfo.released', { - date: language.formatDate(album.date), - }), - - album.hasCoverArt && - album.coverArtDate && - +album.coverArtDate !== +album.date && - language.$('releaseInfo.artReleased', { - date: language.formatDate(album.coverArtDate), - }), - - albumDuration > 0 && - language.$('releaseInfo.duration', { - duration: language.formatDuration(albumDuration, { - approximate: album.tracks.length > 1, - }), - }), - ]), - - html.tag('p', - { - [html.onlyIfContent]: true, - [html.joinChildren]: '<br>', - }, - [ - hasAdditionalFiles && - generateAdditionalFilesShortcut(album.additionalFiles), - - checkGalleryPage(album) && - language.$('releaseInfo.viewGallery', { - link: link.albumGallery(album, { - text: language.$('releaseInfo.viewGallery.link'), - }), - }), - - checkCommentaryPage(album) && - language.$('releaseInfo.viewCommentary', { - link: link.albumCommentary(album, { - text: language.$('releaseInfo.viewCommentary.link'), - }), - }), - ]), - - !empty(album.urls) && - html.tag('p', - language.$('releaseInfo.listenOn', { - links: language.formatDisjunctionList( - album.urls.map(url => fancifyURL(url, {album: true})) - ), - })), - - displayTrackSections && - !empty(album.trackSections) && - html.tag('dl', - {class: 'album-group-list'}, - album.trackSections.flatMap(({ - name, - startIndex, - tracks, - }) => [ - html.tag('dt', - {class: ['content-heading']}, - language.$('trackList.section.withDuration', { - duration: language.formatDuration(getTotalDuration(tracks), { - approximate: tracks.length > 1, - }), - section: name, - })), - html.tag('dd', - html.tag(listTag, - listTag === 'ol' ? {start: startIndex + 1} : {}, - tracks.map(trackToListItem))), - ])), - - !displayTrackSections && - !empty(album.tracks) && - html.tag(listTag, - album.tracks.map(trackToListItem)), - - html.tag('p', - { - [html.onlyIfContent]: true, - [html.joinChildren]: '<br>', - }, - [ - album.dateAddedToWiki && - language.$('releaseInfo.addedToWiki', { - date: language.formatDate( - album.dateAddedToWiki - ), - }) - ]), - - ...html.fragment( - hasAdditionalFiles && [ - generateContentHeading({ - id: 'additional-files', - title: language.$('releaseInfo.additionalFiles.heading', { - additionalFiles: language.countAdditionalFiles(numAdditionalFiles, { - unit: true, - }), - }), - }), - - generateAlbumAdditionalFilesList(album, album.additionalFiles, { - generateAdditionalFilesList, - getSizeOfAdditionalFile, - link, - urls, - }), - ]), - - ...html.fragment( - album.commentary && [ - generateContentHeading({ - id: 'artist-commentary', - title: language.$('releaseInfo.artistCommentary'), - }), - - html.tag('blockquote', transformMultiline(album.commentary)), - ]), - ], - }, - - sidebarLeft: generateAlbumSidebar(album, null, { - fancifyURL, - getLinkThemeString, - html, - link, - language, - transformMultiline, - wikiData, - }), - - nav: { - linkContainerClasses: ['nav-links-hierarchy'], - links: [ - {toHome: true}, - { - html: language.$('albumPage.nav.album', { - album: link.album(album, {class: 'current'}), - }), - }, - { - divider: false, - html: generateAlbumNavLinks(album, null, { - generateNavigationLinks, - html, - language, - link, - }), - } - ], - content: generateAlbumChronologyLinks(album, null, { - generateChronologyLinks, - html, - }), - }, - - secondaryNav: generateAlbumSecondaryNav(album, null, { - getLinkThemeString, - html, - language, - link, - }), - }; - }, - }; - // TODO: only gen if there are any tracks with art const galleryPage = { type: 'page', |