diff options
Diffstat (limited to 'src/content/dependencies')
53 files changed, 1141 insertions, 655 deletions
diff --git a/src/content/dependencies/generateAdditionalFilesShortcut.js b/src/content/dependencies/generateAdditionalFilesShortcut.js deleted file mode 100644 index 9e119bce..00000000 --- a/src/content/dependencies/generateAdditionalFilesShortcut.js +++ /dev/null @@ -1,27 +0,0 @@ -import {empty} from '#sugar'; - -export default { - extraDependencies: ['html', 'language'], - - data(additionalFiles) { - return { - titles: additionalFiles.map(fileGroup => fileGroup.title), - }; - }, - - generate(data, {html, language}) { - if (empty(data.titles)) { - return html.blank(); - } - - return language.$('releaseInfo.additionalFiles.shortcut', { - anchorLink: - html.tag('a', - {href: '#additional-files'}, - language.$('releaseInfo.additionalFiles.shortcut.anchorLink')), - - titles: - language.formatUnitList(data.titles), - }); - }, -} diff --git a/src/content/dependencies/generateAlbumCommentaryPage.js b/src/content/dependencies/generateAlbumCommentaryPage.js index 751a0c91..05dbdcf3 100644 --- a/src/content/dependencies/generateAlbumCommentaryPage.js +++ b/src/content/dependencies/generateAlbumCommentaryPage.js @@ -1,16 +1,15 @@ -import {empty, stitchArrays} from '#sugar'; +import {stitchArrays} from '#sugar'; export default { contentDependencies: [ + 'generateAlbumCommentarySidebar', 'generateAlbumCoverArtwork', 'generateAlbumNavAccent', - 'generateAlbumSidebarTrackSection', 'generateAlbumStyleRules', 'generateCommentaryEntry', 'generateContentHeading', 'generateTrackCoverArtwork', 'generatePageLayout', - 'generatePageSidebar', 'linkAlbum', 'linkExternal', 'linkTrack', @@ -25,7 +24,7 @@ export default { relation('generatePageLayout'); relations.sidebar = - relation('generatePageSidebar'); + relation('generateAlbumCommentarySidebar', album); relations.albumStyleRules = relation('generateAlbumStyleRules', album, null); @@ -86,13 +85,6 @@ export default { track.commentary .map(entry => relation('generateCommentaryEntry', entry))); - relations.sidebarAlbumLink = - relation('linkAlbum', album); - - relations.sidebarTrackSections = - album.trackSections.map(trackSection => - relation('generateAlbumSidebarTrackSection', album, null, trackSection)); - return relations; }, @@ -174,17 +166,22 @@ export default { album: relations.albumCommentaryLink, }), + stickyTitle: + language.$('albumCommentaryPage.entry.title.albumCommentary.sticky', { + album: data.name, + }), + accent: - !empty(relations.albumCommentaryListeningLinks) && - language.$('albumCommentaryPage.entry.title.albumCommentary.accent', { - listeningLinks: - language.formatUnitList( - relations.albumCommentaryListeningLinks - .map(link => link.slots({ - context: 'album', - tab: 'separate', - }))), - }), + language.$('albumCommentaryPage.entry.title.albumCommentary.accent', { + [language.onlyIfOptions]: ['listeningLinks'], + listeningLinks: + language.formatUnitList( + relations.albumCommentaryListeningLinks + .map(link => link.slots({ + context: 'album', + tab: 'separate', + }))), + }), }), relations.albumCommentaryCover @@ -212,7 +209,7 @@ export default { }) => [ heading.slots({ tag: 'h3', - id: directory, + attributes: {id: directory}, color, title: @@ -221,13 +218,13 @@ export default { }), accent: - !empty(listeningLinks) && - language.$('albumCommentaryPage.entry.title.trackCommentary.accent', { - listeningLinks: - language.formatUnitList( - listeningLinks.map(link => - link.slot('tab', 'separate'))), - }), + language.$('albumCommentaryPage.entry.title.trackCommentary.accent', { + [language.onlyIfOptions]: ['listeningLinks'], + listeningLinks: + language.formatUnitList( + listeningLinks.map(link => + link.slot('tab', 'separate'))), + }), }), cover?.slots({mode: 'commentary'}), @@ -253,22 +250,7 @@ export default { }, ], - leftSidebar: - relations.sidebar.slots({ - attributes: {class: 'commentary-track-list-sidebar-box'}, - - stickyMode: 'column', - - content: [ - html.tag('h1', relations.sidebarAlbumLink), - relations.sidebarTrackSections.map(section => - section.slots({ - anchor: true, - open: true, - mode: 'commentary', - })), - ], - }), + leftSidebar: relations.sidebar, }); }, }; diff --git a/src/content/dependencies/generateAlbumCommentarySidebar.js b/src/content/dependencies/generateAlbumCommentarySidebar.js new file mode 100644 index 00000000..435860cb --- /dev/null +++ b/src/content/dependencies/generateAlbumCommentarySidebar.js @@ -0,0 +1,47 @@ +export default { + contentDependencies: [ + 'generateAlbumSidebarTrackSection', + 'generatePageSidebar', + 'generatePageSidebarBox', + 'linkAlbum', + ], + + extraDependencies: ['html'], + + relations: (relation, album) => ({ + sidebar: + relation('generatePageSidebar'), + + sidebarBox: + relation('generatePageSidebarBox'), + + albumLink: + relation('linkAlbum', album), + + trackSections: + album.trackSections.map(trackSection => + relation('generateAlbumSidebarTrackSection', + album, + null, + trackSection)), + }), + + generate: (relations, {html}) => + relations.sidebar.slots({ + stickyMode: 'column', + boxes: [ + relations.sidebarBox.slots({ + attributes: {class: 'commentary-track-list-sidebar-box'}, + content: [ + html.tag('h1', relations.albumLink), + relations.trackSections.map(section => + section.slots({ + anchor: true, + open: true, + mode: 'commentary', + })), + ], + }), + ] + }), +} diff --git a/src/content/dependencies/generateAlbumGalleryPage.js b/src/content/dependencies/generateAlbumGalleryPage.js index b4f9268c..aa025688 100644 --- a/src/content/dependencies/generateAlbumGalleryPage.js +++ b/src/content/dependencies/generateAlbumGalleryPage.js @@ -30,7 +30,7 @@ export default { const allCoverArtistArrays = tracksWithUniqueCoverArt .map(track => track.coverArtistContribs) - .map(contribs => contribs.map(contrib => contrib.who)); + .map(contribs => contribs.map(contrib => contrib.artist)); const allSameCoverArtists = allCoverArtistArrays @@ -116,7 +116,7 @@ export default { data.coverArtists = [ (album.hasCoverArt - ? album.coverArtistContribs.map(({who: artist}) => artist.name) + ? album.coverArtistContribs.map(({artist}) => artist.name) : null), ... @@ -126,7 +126,7 @@ export default { } if (track.hasUniqueCoverArt) { - return track.coverArtistContribs.map(({who: artist}) => artist.name); + return track.coverArtistContribs.map(({artist}) => artist.name); } return null; diff --git a/src/content/dependencies/generateAlbumInfoPage.js b/src/content/dependencies/generateAlbumInfoPage.js index e0f23bd0..d4ea52de 100644 --- a/src/content/dependencies/generateAlbumInfoPage.js +++ b/src/content/dependencies/generateAlbumInfoPage.js @@ -5,7 +5,6 @@ import getChronologyRelations from '../util/getChronologyRelations.js'; export default { contentDependencies: [ - 'generateAdditionalFilesShortcut', 'generateAlbumAdditionalFilesList', 'generateAlbumBanner', 'generateAlbumCoverArtwork', @@ -107,11 +106,6 @@ export default { relation('linkAlbumCommentary', album); } - if (!empty(album.additionalFiles)) { - extra.additionalFilesShortcut = - relation('generateAdditionalFilesShortcut', album.additionalFiles); - } - // Section: Track list relations.trackList = @@ -180,7 +174,12 @@ export default { {[html.joinChildren]: html.tag('br')}, [ - sec.extra.additionalFilesShortcut, + sec.additionalFiles && + language.$('releaseInfo.additionalFiles.shortcut', { + link: html.tag('a', + {href: '#additional-files'}, + language.$('releaseInfo.additionalFiles.shortcut.link')), + }), sec.extra.galleryLink && sec.extra.commentaryLink && language.$('releaseInfo.viewGalleryOrCommentary', { @@ -214,21 +213,17 @@ export default { {[html.joinChildren]: html.tag('br')}, [ - data.dateAddedToWiki && - language.$('releaseInfo.addedToWiki', { - date: language.formatDate(data.dateAddedToWiki), - }), + language.$('releaseInfo.addedToWiki', { + [language.onlyIfOptions]: ['date'], + date: language.formatDate(data.dateAddedToWiki), + }), ]), sec.additionalFiles && [ sec.additionalFiles.heading .slots({ - id: 'additional-files', - title: - language.$('releaseInfo.additionalFiles.heading', { - additionalFiles: - language.countAdditionalFiles(data.numAdditionalFiles, {unit: true}), - }), + attributes: {id: 'additional-files'}, + title: language.$('releaseInfo.additionalFiles.heading'), }), sec.additionalFiles.additionalFilesList, diff --git a/src/content/dependencies/generateAlbumReleaseInfo.js b/src/content/dependencies/generateAlbumReleaseInfo.js index 6fc1375b..1cd638ce 100644 --- a/src/content/dependencies/generateAlbumReleaseInfo.js +++ b/src/content/dependencies/generateAlbumReleaseInfo.js @@ -1,4 +1,4 @@ -import {accumulateSum, empty} from '#sugar'; +import {accumulateSum} from '#sugar'; export default { contentDependencies: [ @@ -23,11 +23,9 @@ export default { relations.bannerArtistContributionsLine = relation('generateReleaseInfoContributionsLine', album.bannerArtistContribs); - if (!empty(album.urls)) { - relations.externalLinks = - album.urls.map(url => - relation('linkExternal', url)); - } + relations.externalLinks = + album.urls.map(url => + relation('linkExternal', url)); return relations; }, @@ -70,41 +68,42 @@ export default { relations.bannerArtistContributionsLine .slots({stringKey: 'releaseInfo.bannerArtBy'}), - 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, { - approximate: data.durationApproximate, - }), - }), + language.$('releaseInfo.released', { + [language.onlyIfOptions]: ['date'], + date: language.formatDate(data.date), + }), + + language.$('releaseInfo.artReleased', { + [language.onlyIfOptions]: ['date'], + date: language.formatDate(data.coverArtDate), + }), + + language.$('releaseInfo.duration', { + [language.onlyIfOptions]: ['duration'], + duration: + language.formatDuration(data.duration, { + approximate: data.durationApproximate, + }), + }), ]), - relations.externalLinks && - html.tag('p', - language.$('releaseInfo.listenOn', { - links: - language.formatDisjunctionList( - relations.externalLinks - .map(link => - link.slot('context', [ - 'album', - (data.numTracks === 0 - ? 'albumNoTracks' - : data.numTracks === 1 - ? 'albumOneTrack' - : 'albumMultipleTracks'), - ]))), - })), + html.tag('p', + {[html.onlyIfContent]: true}, + language.$('releaseInfo.listenOn', { + [language.onlyIfOptions]: ['links'], + links: + language.formatDisjunctionList( + relations.externalLinks + .map(link => + link.slot('context', [ + 'album', + (data.numTracks === 0 + ? 'albumNoTracks' + : data.numTracks === 1 + ? 'albumOneTrack' + : 'albumMultipleTracks'), + ]))), + })), ]); }, }; diff --git a/src/content/dependencies/generateAlbumSecondaryNav.js b/src/content/dependencies/generateAlbumSecondaryNav.js index 400420ba..d6ff8a0a 100644 --- a/src/content/dependencies/generateAlbumSecondaryNav.js +++ b/src/content/dependencies/generateAlbumSecondaryNav.js @@ -59,11 +59,11 @@ export default { relation('generateSecondaryNav'); relations.groupLinks = - album.groups + query.groups .map(group => relation('linkGroup', group)); relations.colorStyles = - album.groups + query.groups .map(group => relation('generateColorStyleAttribute', group.color)); if (album.date) { @@ -102,7 +102,7 @@ export default { generate(relations, slots, {html, language}) { const navLinksShouldShowPreviousNext = (slots.mode === 'track' - ? Array.from(relations.previousNextLinks, () => false) + ? Array.from(relations.previousNextLinks ?? [], () => false) : stitchArrays({ previousAlbumLink: relations.previousAlbumLinks ?? null, nextAlbumLink: relations.nextAlbumLinks ?? null, @@ -151,11 +151,8 @@ export default { stitchArrays({ content: navLinkContents, colorStyle: relations.colorStyles, - }).map(({content, colorStyle}, index) => + }).map(({content, colorStyle}) => html.tag('span', {class: 'nav-link'}, - index > 0 && - {class: 'has-divider'}, - colorStyle.slot('context', 'primary-only'), content)); diff --git a/src/content/dependencies/generateAlbumSidebarGroupBox.js b/src/content/dependencies/generateAlbumSidebarGroupBox.js index 00a96c31..cc9b2c13 100644 --- a/src/content/dependencies/generateAlbumSidebarGroupBox.js +++ b/src/content/dependencies/generateAlbumSidebarGroupBox.js @@ -1,5 +1,5 @@ import {sortChronologically} from '#sort'; -import {atOffset, empty} from '#sugar'; +import {atOffset} from '#sugar'; export default { contentDependencies: [ @@ -89,26 +89,31 @@ export default { relations.description ?.slot('mode', 'multiline'), - !empty(relations.externalLinks) && - html.tag('p', - language.$('releaseInfo.visitOn', { - links: - language.formatDisjunctionList( - relations.externalLinks - .map(link => link.slot('context', 'group'))), - })), + html.tag('p', + {[html.onlyIfContent]: true}, + + language.$('releaseInfo.visitOn', { + [language.onlyIfOptions]: ['links'], + + links: + language.formatDisjunctionList( + relations.externalLinks + .map(link => link.slot('context', 'group'))), + })), slots.mode === 'album' && - relations.nextAlbumLink && html.tag('p', {class: 'group-chronology-link'}, + {[html.onlyIfContent]: true}, language.$('albumSidebar.groupBox.next', { + [language.onlyIfOptions]: ['album'], album: relations.nextAlbumLink, })), slots.mode === 'album' && - relations.previousAlbumLink && html.tag('p', {class: 'group-chronology-link'}, + {[html.onlyIfContent]: true}, language.$('albumSidebar.groupBox.previous', { + [language.onlyIfOptions]: ['album'], album: relations.previousAlbumLink, })), ], diff --git a/src/content/dependencies/generateAlbumTrackList.js b/src/content/dependencies/generateAlbumTrackList.js index ee06b9e6..dd3e85e3 100644 --- a/src/content/dependencies/generateAlbumTrackList.js +++ b/src/content/dependencies/generateAlbumTrackList.js @@ -149,6 +149,7 @@ export default { }) => [ heading.slots({ tag: 'dt', + title: (duration === 0 ? language.$('trackList.section', { @@ -161,6 +162,11 @@ export default { approximate: durationApproximate, }), })), + + stickyTitle: + language.$('trackList.section.sticky', { + section: name, + }), }), html.tag('dd', diff --git a/src/content/dependencies/generateAlbumTrackListItem.js b/src/content/dependencies/generateAlbumTrackListItem.js index 18980740..7190fb4c 100644 --- a/src/content/dependencies/generateAlbumTrackListItem.js +++ b/src/content/dependencies/generateAlbumTrackListItem.js @@ -64,8 +64,8 @@ export default { !empty(track.artistContribs) && (empty(album.artistContribs) || !compareArrays( - track.artistContribs.map(c => c.who), - album.artistContribs.map(c => c.who), + track.artistContribs.map(contrib => contrib.artist), + album.artistContribs.map(contrib => contrib.artist), {checkOrder: false})); return data; diff --git a/src/content/dependencies/generateArtTagGalleryPage.js b/src/content/dependencies/generateArtTagGalleryPage.js index 338d18fe..eae48f05 100644 --- a/src/content/dependencies/generateArtTagGalleryPage.js +++ b/src/content/dependencies/generateArtTagGalleryPage.js @@ -80,7 +80,7 @@ export default { data.coverArtists = query.things.map(thing => thing.coverArtistContribs - .map(({who: artist}) => artist.name)); + .map(({artist}) => artist.name)); return data; }, diff --git a/src/content/dependencies/generateArtistGalleryPage.js b/src/content/dependencies/generateArtistGalleryPage.js index 36343c18..db8f123f 100644 --- a/src/content/dependencies/generateArtistGalleryPage.js +++ b/src/content/dependencies/generateArtistGalleryPage.js @@ -75,8 +75,8 @@ export default { query.things.map(thing => (thing.coverArtistContribs.length > 1 ? thing.coverArtistContribs - .filter(({who}) => who !== artist) - .map(({who}) => who.name) + .filter(({artist: otherArtist}) => otherArtist !== artist) + .map(({artist: otherArtist}) => otherArtist.name) : null)); return data; diff --git a/src/content/dependencies/generateArtistGroupContributionsInfo.js b/src/content/dependencies/generateArtistGroupContributionsInfo.js index 1725d4b9..ef81739d 100644 --- a/src/content/dependencies/generateArtistGroupContributionsInfo.js +++ b/src/content/dependencies/generateArtistGroupContributionsInfo.js @@ -161,9 +161,15 @@ export default { slots.visible && 'visible', ]; + // TODO: It feels pretty awkward that this component is the only one that + // has enough knowledge to decide if the sort button is even applicable... + const switchingSortPossible = + !empty(relations.groupLinksSortedByCount) && + !empty(relations.groupLinksSortedByDuration); + return html.tags([ html.tag('dt', {class: topLevelClasses}, - (slots.showSortButton + (switchingSortPossible && slots.showSortButton ? language.$('artistPage.groupContributions.title.withSortButton', { title: slots.title, sort: diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js index ac9209a7..28b9e1d3 100644 --- a/src/content/dependencies/generateArtistInfoPage.js +++ b/src/content/dependencies/generateArtistInfoPage.js @@ -153,7 +153,9 @@ export default { mainContent: [ sec.contextNotes && [ - html.tag('p', language.$('releaseInfo.note')), + html.tag('p', + language.$('releaseInfo.note')), + html.tag('blockquote', sec.contextNotes.content), ], @@ -206,7 +208,7 @@ export default { sec.tracks.heading .slots({ tag: 'h2', - id: 'tracks', + attributes: {id: 'tracks'}, title: language.$('artistPage.trackList.title'), }), @@ -251,7 +253,7 @@ export default { sec.artworks.heading .slots({ tag: 'h2', - id: 'art', + attributes: {id: 'art'}, title: language.$('artistPage.artList.title'), }), @@ -280,7 +282,7 @@ export default { sec.flashes.heading .slots({ tag: 'h2', - id: 'flashes', + attributes: {id: 'flashes'}, title: language.$('artistPage.flashList.title'), }), @@ -291,7 +293,7 @@ export default { sec.commentary.heading .slots({ tag: 'h2', - id: 'commentary', + attributes: {id: 'commentary'}, title: language.$('artistPage.commentaryList.title'), }), diff --git a/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js b/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js index 0beeb271..44fb42f2 100644 --- a/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js @@ -171,8 +171,8 @@ export default { query.chunks.map(({chunk}) => chunk.map(({contribs}) => contribs - .find(({who}) => who === artist) - .what)), + .find(contrib => contrib.artist === artist) + .annotation)), }; }, diff --git a/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js b/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js index 88a97af2..447e697e 100644 --- a/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js @@ -92,8 +92,8 @@ export default { query.chunks.map(({chunk}) => chunk.map(({contribs}) => contribs - .find(({who}) => who === artist) - .what)), + .find(contrib => contrib.artist === artist) + .annotation)), }; }, diff --git a/src/content/dependencies/generateArtistInfoPageOtherArtistLinks.js b/src/content/dependencies/generateArtistInfoPageOtherArtistLinks.js index dea7742a..471ee26c 100644 --- a/src/content/dependencies/generateArtistInfoPageOtherArtistLinks.js +++ b/src/content/dependencies/generateArtistInfoPageOtherArtistLinks.js @@ -4,7 +4,8 @@ export default { contentDependencies: ['linkArtist'], relations(relation, contribs, artist) { - const otherArtistContribs = contribs.filter(({who}) => who !== artist); + const otherArtistContribs = + contribs.filter(contrib => contrib.artist !== artist); if (empty(otherArtistContribs)) { return {}; @@ -12,7 +13,7 @@ export default { const otherArtistLinks = otherArtistContribs - .map(({who}) => relation('linkArtist', who)); + .map(contrib => relation('linkArtist', contrib.artist)); return {otherArtistLinks}; }, diff --git a/src/content/dependencies/generateArtistInfoPageTracksChunkedList.js b/src/content/dependencies/generateArtistInfoPageTracksChunkedList.js index f003779d..bce6cedf 100644 --- a/src/content/dependencies/generateArtistInfoPageTracksChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageTracksChunkedList.js @@ -150,7 +150,7 @@ export default { query.chunks.map(({chunk}) => chunk .map(({contribs}) => - contribs.filter(({who}) => who === artist)) + contribs.filter(contrib => contrib.artist === artist)) .map(ownContribs => ({ creditedAsArtist: ownContribs @@ -162,7 +162,7 @@ export default { annotatedContribs: ownContribs - .filter(({what}) => what), + .filter(({annotation}) => annotation), })) .map(({annotatedContribs, ...rest}) => ({ ...rest, @@ -203,7 +203,7 @@ export default { ]; }) .map(contribs => - contribs.map(({what}) => what)) + contribs.map(({annotation}) => annotation)) .map(contributions => (empty(contributions) ? null diff --git a/src/content/dependencies/generateChronologyLinks.js b/src/content/dependencies/generateChronologyLinks.js index 8ec6ee0a..7f24ded7 100644 --- a/src/content/dependencies/generateChronologyLinks.js +++ b/src/content/dependencies/generateChronologyLinks.js @@ -4,6 +4,16 @@ export default { extraDependencies: ['html', 'language'], slots: { + allowCollapsing: { + type: 'boolean', + default: true, + }, + + showOnly: { + type: 'boolean', + default: false, + }, + chronologyInfoSets: { validate: v => v.strictArrayOf( @@ -11,6 +21,8 @@ export default { 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, @@ -24,22 +36,35 @@ export default { 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( - slots.chronologyInfoSets, + infoSets, ({contributions}) => contributions.length); if (totalContributionCount === 0) { return html.blank(); } - if (totalContributionCount > 8) { + if (slots.allowCollapsing && totalContributionCount > 8) { return html.tag('div', {class: 'chronology'}, language.$('misc.chronology.seeArtistPages')); } return html.tags( - slots.chronologyInfoSets.map(({ + infoSets.map(({ headingString, contributions, }) => @@ -48,16 +73,21 @@ export default { artistLink, previousLink, nextLink, + only, }) => { const heading = html.tag('span', {class: 'heading'}, language.$(headingString, { - index: language.formatIndex(index), + index: + (only + ? language.formatString('misc.chronology.heading.onlyIndex') + : language.formatIndex(index)), + artist: artistLink, })); const navigation = - (previousLink || nextLink) && + !only && html.tag('span', {class: 'buttons'}, language.formatUnitList([ previousLink?.slots({ diff --git a/src/content/dependencies/generateChronologyLinksScopeSwitcher.js b/src/content/dependencies/generateChronologyLinksScopeSwitcher.js new file mode 100644 index 00000000..23c44268 --- /dev/null +++ b/src/content/dependencies/generateChronologyLinksScopeSwitcher.js @@ -0,0 +1,67 @@ +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 <details> 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.$('trackPage.nav.chronology.scope.title', { + scope: + slots.scopes.map((scope, index) => + html.tag('a', {class: 'switcher-link'}, + {href: '#'}, + + (index === 0 + ? {style: 'display: inline'} + : {style: 'display: none'}), + + language.$('trackPage.nav.chronology.scope', 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/generateColorStyleVariables.js b/src/content/dependencies/generateColorStyleVariables.js index 069d85dd..5270dbe4 100644 --- a/src/content/dependencies/generateColorStyleVariables.js +++ b/src/content/dependencies/generateColorStyleVariables.js @@ -32,6 +32,7 @@ export default { dim, deep, deepGhost, + lightGhost, bg, bgBlack, shadow, @@ -43,6 +44,7 @@ export default { `--dim-color: ${dim}`, `--deep-color: ${deep}`, `--deep-ghost-color: ${deepGhost}`, + `--light-ghost-color: ${lightGhost}`, `--bg-color: ${bg}`, `--bg-black-color: ${bgBlack}`, `--shadow-color: ${shadow}`, diff --git a/src/content/dependencies/generateCommentaryEntry.js b/src/content/dependencies/generateCommentaryEntry.js index 522a0284..036f8a6f 100644 --- a/src/content/dependencies/generateCommentaryEntry.js +++ b/src/content/dependencies/generateCommentaryEntry.js @@ -61,19 +61,14 @@ export default { relations.annotationContent.slot('mode', 'inline'); } - if (data.date) { - accentParts.push('withDate'); - accentOptions.date = - language.formatDate(data.date); - } - const accent = (accentParts.length > 1 ? html.tag('span', {class: 'commentary-entry-accent'}, language.$(...accentParts, accentOptions)) : null); - const titleParts = ['misc.artistCommentary.entry.title']; + const titlePrefix = 'misc.artistCommentary.entry.title'; + const titleParts = [titlePrefix]; const titleOptions = {artists: artistsSpan}; if (accent) { @@ -88,7 +83,16 @@ export default { return html.tags([ html.tag('p', {class: 'commentary-entry-heading'}, style, - language.$(...titleParts, titleOptions)), + [ + html.tag('time', + {[html.onlyIfContent]: true}, + language.$(titlePrefix, 'date', { + [language.onlyIfOptions]: ['date'], + date: language.formatDate(data.date), + })), + + language.$(...titleParts, titleOptions), + ]), html.tag('blockquote', {class: 'commentary-entry-body'}, style, diff --git a/src/content/dependencies/generateCommentarySection.js b/src/content/dependencies/generateCommentarySection.js index 8ae1b2d0..39727360 100644 --- a/src/content/dependencies/generateCommentarySection.js +++ b/src/content/dependencies/generateCommentarySection.js @@ -16,12 +16,23 @@ export default { relation('generateCommentaryEntry', entry)), }), - generate: (relations, {html, language}) => + data: (entries) => ({ + firstEntryIsDated: + (entries[0] + ? !!entries[0].date + : null), + }), + + generate: (data, relations, {html, language}) => html.tags([ relations.heading .slots({ - id: 'artist-commentary', - title: language.$('misc.artistCommentary') + title: language.$('misc.artistCommentary'), + attributes: [ + {id: 'artist-commentary'}, + data.firstEntryIsDated && + {class: 'first-entry-is-dated'}, + ], }), relations.entries, diff --git a/src/content/dependencies/generateContentHeading.js b/src/content/dependencies/generateContentHeading.js index 469db876..eafe77d8 100644 --- a/src/content/dependencies/generateContentHeading.js +++ b/src/content/dependencies/generateContentHeading.js @@ -12,23 +12,34 @@ export default { mutable: false, }, + stickyTitle: { + type: 'html', + mutable: false, + }, + accent: { type: 'html', mutable: false, }, + attributes: { + type: 'attributes', + mutable: false, + }, + color: {validate: v => v.isColor}, - id: {type: 'string'}, - tag: {type: 'string', default: 'p'}, + tag: { + type: 'string', + default: 'p', + }, }, generate: (relations, slots, {html}) => html.tag(slots.tag, {class: 'content-heading'}, {tabindex: '0'}, - slots.id && - {id: slots.id}, + slots.attributes, slots.color && relations.colorStyle.slot('color', slots.color), @@ -38,6 +49,10 @@ export default { {[html.onlyIfContent]: true}, slots.title), + html.tag('template', {class: 'content-heading-sticky-title'}, + {[html.onlyIfContent]: true}, + slots.stickyTitle), + html.tag('span', {class: 'content-heading-accent'}, {[html.onlyIfContent]: true}, slots.accent), diff --git a/src/content/dependencies/generateCoverArtwork.js b/src/content/dependencies/generateCoverArtwork.js index 90c9db98..3d5a614f 100644 --- a/src/content/dependencies/generateCoverArtwork.js +++ b/src/content/dependencies/generateCoverArtwork.js @@ -1,4 +1,4 @@ -import {empty, stitchArrays} from '#sugar'; +import {stitchArrays} from '#sugar'; export default { contentDependencies: ['image', 'linkArtTag'], @@ -89,14 +89,15 @@ export default { ...sizeSlots, }), - !empty(relations.tagLinks) && - html.tag('ul', {class: 'image-details'}, - stitchArrays({ - tagLink: relations.tagLinks, - preferShortName: data.preferShortName, - }).map(({tagLink, preferShortName}) => - html.tag('li', - tagLink.slot('preferShortName', preferShortName)))), + html.tag('ul', {class: 'image-details'}, + {[html.onlyIfContent]: true}, + + stitchArrays({ + tagLink: relations.tagLinks, + preferShortName: data.preferShortName, + }).map(({tagLink, preferShortName}) => + html.tag('li', + tagLink.slot('preferShortName', preferShortName)))), ]); case 'thumbnail': diff --git a/src/content/dependencies/generateFlashCoverArtwork.js b/src/content/dependencies/generateFlashCoverArtwork.js index 374fa3f8..af03ae6b 100644 --- a/src/content/dependencies/generateFlashCoverArtwork.js +++ b/src/content/dependencies/generateFlashCoverArtwork.js @@ -1,12 +1,26 @@ export default { contentDependencies: ['generateCoverArtwork'], - relations: (relation) => - ({coverArtwork: relation('generateCoverArtwork')}), + relations: (relation) => ({ + coverArtwork: + relation('generateCoverArtwork'), + }), - data: (flash) => - ({path: ['media.flashArt', flash.directory, flash.coverArtFileExtension]}), + data: (flash) => ({ + path: + ['media.flashArt', flash.directory, flash.coverArtFileExtension], + + color: + flash.color, + + dimensions: + flash.coverArtDimensions, + }), generate: (data, relations) => - relations.coverArtwork.slot('path', data.path), + relations.coverArtwork.slots({ + path: data.path, + color: data.color, + dimensions: data.dimensions, + }), }; diff --git a/src/content/dependencies/generateFlashIndexPage.js b/src/content/dependencies/generateFlashIndexPage.js index 36bfabae..eaea7e9c 100644 --- a/src/content/dependencies/generateFlashIndexPage.js +++ b/src/content/dependencies/generateFlashIndexPage.js @@ -1,4 +1,4 @@ -import {empty, stitchArrays} from '#sugar'; +import {stitchArrays} from '#sugar'; export default { contentDependencies: [ @@ -87,11 +87,13 @@ export default { mainClasses: ['flash-index'], mainContent: [ - !empty(data.jumpLinkLabels) && [ + html.tags([ html.tag('p', {class: 'quick-info'}, + {[html.onlyIfSiblings]: true}, language.$('misc.jumpTo')), html.tag('ul', {class: 'quick-info'}, + {[html.onlyIfContent]: true}, stitchArrays({ colorStyle: relations.jumpLinkColorStyles, anchor: data.jumpLinkAnchors, @@ -102,7 +104,7 @@ export default { {href: '#' + anchor}, colorStyle, label)))), - ], + ]), stitchArrays({ colorStyle: relations.actColorStyles, diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js index 05964936..eec32157 100644 --- a/src/content/dependencies/generateFlashInfoPage.js +++ b/src/content/dependencies/generateFlashInfoPage.js @@ -19,16 +19,14 @@ export default { query(flash) { const query = {}; - if (flash.page || !empty(flash.urls)) { - query.urls = []; + query.urls = []; - if (flash.page) { - query.urls.push(`https://homestuck.com/story/${flash.page}`); - } + if (flash.page) { + query.urls.push(`https://homestuck.com/story/${flash.page}`); + } - if (!empty(flash.urls)) { - query.urls.push(...flash.urls); - } + if (!empty(flash.urls)) { + query.urls.push(...flash.urls); } return query; @@ -44,10 +42,9 @@ export default { relations.sidebar = relation('generateFlashActSidebar', flash.act, flash); - if (query.urls) { - relations.externalLinks = - query.urls.map(url => relation('linkExternal', url)); - } + relations.externalLinks = + query.urls + .map(url => relation('linkExternal', url)); // TODO: Flashes always have cover art (#175) /* eslint-disable-next-line no-constant-condition */ @@ -135,14 +132,15 @@ export default { date: language.formatDate(data.date), })), - relations.externalLinks && - html.tag('p', - language.$('releaseInfo.playOn', { - links: - language.formatDisjunctionList( - relations.externalLinks - .map(link => link.slot('context', 'flash'))), - })), + html.tag('p', + {[html.onlyIfContent]: true}, + language.$('releaseInfo.playOn', { + [language.onlyIfOptions]: ['links'], + links: + language.formatDisjunctionList( + relations.externalLinks + .map(link => link.slot('context', 'flash'))), + })), html.tag('p', {[html.onlyIfContent]: true}, @@ -160,7 +158,7 @@ export default { sec.featuredTracks && [ sec.featuredTracks.heading .slots({ - id: 'features', + attributes: {id: 'features'}, title: language.$('releaseInfo.tracksFeatured', { flash: html.tag('i', data.name), @@ -173,7 +171,7 @@ export default { sec.contributors && [ sec.contributors.heading .slots({ - id: 'contributors', + attributes: {id: 'contributors'}, title: language.$('releaseInfo.contributors'), }), diff --git a/src/content/dependencies/generateGroupInfoPage.js b/src/content/dependencies/generateGroupInfoPage.js index b5b456aa..e6b0ded1 100644 --- a/src/content/dependencies/generateGroupInfoPage.js +++ b/src/content/dependencies/generateGroupInfoPage.js @@ -69,11 +69,9 @@ export default { sec.info = {}; - if (!empty(group.urls)) { - sec.info.visitLinks = - group.urls - .map(url => relation('linkExternal', url)); - } + sec.info.visitLinks = + group.urls + .map(url => relation('linkExternal', url)); if (group.description) { sec.info.description = @@ -131,14 +129,15 @@ export default { color: data.color, mainContent: [ - sec.info.visitLinks && - html.tag('p', - language.$('releaseInfo.visitOn', { - links: - language.formatDisjunctionList( - sec.info.visitLinks - .map(link => link.slot('context', 'group'))), - })), + html.tag('p', + {[html.onlyIfContent]: true}, + language.$('releaseInfo.visitOn', { + [language.onlyIfOptions]: ['links'], + links: + language.formatDisjunctionList( + sec.info.visitLinks + .map(link => link.slot('context', 'group'))), + })), html.tag('blockquote', {[html.onlyIfContent]: true}, diff --git a/src/content/dependencies/generateGroupSecondaryNav.js b/src/content/dependencies/generateGroupSecondaryNav.js index 17eb5083..a4f81313 100644 --- a/src/content/dependencies/generateGroupSecondaryNav.js +++ b/src/content/dependencies/generateGroupSecondaryNav.js @@ -69,12 +69,16 @@ export default { }), generate(data, relations, {html, language}) { - const {content: previousNextPart} = - relations.previousNextLinks.slots({ - previousLink: relations.previousGroupLink, - nextLink: relations.nextGroupLink, - id: true, - }); + const previousNextPart = + (relations.previousNextLinks + ? relations.previousNextLinks + .slots({ + previousLink: relations.previousGroupLink, + nextLink: relations.nextGroupLink, + id: true, + }) + .content /* TODO: Kludge. */ + : null); const {categoryLink} = relations; @@ -83,7 +87,7 @@ export default { return relations.secondaryNav.slots({ class: 'nav-links-groups', content: - (relations.previousGroupLink || relations.nextGroupLink + (previousNextPart ? html.tag('span', {class: 'nav-link'}, relations.colorStyle.slot('context', 'primary-only'), diff --git a/src/content/dependencies/generateGroupSidebar.js b/src/content/dependencies/generateGroupSidebar.js index 3abb3392..0888cbbe 100644 --- a/src/content/dependencies/generateGroupSidebar.js +++ b/src/content/dependencies/generateGroupSidebar.js @@ -2,6 +2,7 @@ export default { contentDependencies: [ 'generateGroupSidebarCategoryDetails', 'generatePageSidebar', + 'generatePageSidebarBox', ], extraDependencies: ['html', 'language', 'wikiData'], @@ -12,6 +13,9 @@ export default { sidebar: relation('generatePageSidebar'), + sidebarBox: + relation('generatePageSidebarBox'), + categoryDetails: sprawl.groupCategoryData.map(category => relation('generateGroupSidebarCategoryDetails', category, group)), @@ -25,15 +29,18 @@ export default { generate: (relations, slots, {html, language}) => relations.sidebar.slots({ - attributes: {class: 'category-map-sidebar-box'}, - - content: [ - html.tag('h1', - language.$('groupSidebar.title')), - - relations.categoryDetails - .map(details => - details.slot('currentExtra', slots.currentExtra)), + boxes: [ + relations.sidebarBox.slots({ + attributes: {class: 'category-map-sidebar-box'}, + content: [ + html.tag('h1', + language.$('groupSidebar.title')), + + relations.categoryDetails + .map(details => + details.slot('currentExtra', slots.currentExtra)), + ], + }), ], }), }; diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 23377afb..5f9a99a9 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -34,13 +34,15 @@ export default { relations.sameTargetListingLinks = listing.target.listings .map(listing => relation('linkListing', listing)); + } else { + relations.sameTargetListingLinks = []; } - if (!empty(listing.seeAlso)) { - relations.seeAlsoLinks = - listing.seeAlso - .map(listing => relation('linkListing', listing)); - } + relations.seeAlsoLinks = + (!empty(listing.seeAlso) + ? listing.seeAlso + .map(listing => relation('linkListing', listing)) + : []); return relations; }, @@ -167,33 +169,37 @@ export default { headingMode: 'sticky', mainContent: [ - relations.sameTargetListingLinks && - html.tag('p', - language.$('listingPage.listingsFor', { - target: - language.$('listingPage.target', data.targetStringsKey), - - listings: - language.formatUnitList( - stitchArrays({ - link: relations.sameTargetListingLinks, - stringsKey: data.sameTargetListingStringsKeys, - }).map(({link, stringsKey}, index) => - html.tag('span', - index === data.sameTargetListingsCurrentIndex && - {class: 'current'}, - - link.slots({ - attributes: {class: 'nowrap'}, - content: language.$('listingPage', stringsKey, 'title.short'), - })))), - })), - - relations.seeAlsoLinks && - html.tag('p', - language.$('listingPage.seeAlso', { - listings: language.formatUnitList(relations.seeAlsoLinks), - })), + html.tag('p', + {[html.onlyIfContent]: true}, + language.$('listingPage.listingsFor', { + [language.onlyIfOptions]: ['listings'], + + target: + language.$('listingPage.target', data.targetStringsKey), + + listings: + language.formatUnitList( + stitchArrays({ + link: relations.sameTargetListingLinks, + stringsKey: data.sameTargetListingStringsKeys, + }).map(({link, stringsKey}, index) => + html.tag('span', + index === data.sameTargetListingsCurrentIndex && + {class: 'current'}, + + link.slots({ + attributes: {class: 'nowrap'}, + content: language.$('listingPage', stringsKey, 'title.short'), + })))), + })), + + html.tag('p', + {[html.onlyIfContent]: true}, + language.$('listingPage.seeAlso', { + [language.onlyIfOptions]: ['listings'], + listings: + language.formatUnitList(relations.seeAlsoLinks), + })), slots.content, @@ -243,7 +249,7 @@ export default { .clone() .slots({ tag: 'dt', - id, + attributes: [id && {id}], title: formatListingString({ diff --git a/src/content/dependencies/generateListingSidebar.js b/src/content/dependencies/generateListingSidebar.js index 1e5c8bfc..aeac05cf 100644 --- a/src/content/dependencies/generateListingSidebar.js +++ b/src/content/dependencies/generateListingSidebar.js @@ -2,6 +2,7 @@ export default { contentDependencies: [ 'generateListingIndexList', 'generatePageSidebar', + 'generatePageSidebarBox', 'linkListingIndex', ], @@ -11,6 +12,9 @@ export default { sidebar: relation('generatePageSidebar'), + sidebarBox: + relation('generatePageSidebarBox'), + listingIndexLink: relation('linkListingIndex'), @@ -20,10 +24,14 @@ export default { generate: (relations, {html}) => relations.sidebar.slots({ - attributes: {class: 'listing-map-sidebar-box'}, - content: [ - html.tag('h1', relations.listingIndexLink), - relations.listingIndexList.slot('mode', 'sidebar'), + boxes: [ + relations.sidebarBox.slots({ + attributes: {class: 'listing-map-sidebar-box'}, + content: [ + html.tag('h1', relations.listingIndexLink), + relations.listingIndexList.slot('mode', 'sidebar'), + ], + }), ], }), }; diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js index cbfc905a..e138a981 100644 --- a/src/content/dependencies/generatePageLayout.js +++ b/src/content/dependencies/generatePageLayout.js @@ -5,12 +5,13 @@ export default { contentDependencies: [ 'generateColorStyleRules', 'generateFooterLocalizationLinks', + 'generatePageSidebar', + 'generateSearchSidebarBox', 'generateStickyHeadingContainer', 'transformContent', ], extraDependencies: [ - 'cachebust', 'getColors', 'html', 'language', @@ -21,6 +22,7 @@ export default { sprawl({wikiInfo}) { return { + enableSearch: wikiInfo.enableSearch, footerContent: wikiInfo.footerContent, wikiColor: wikiInfo.color, wikiName: wikiInfo.nameShort, @@ -43,6 +45,14 @@ export default { relations.stickyHeadingContainer = relation('generateStickyHeadingContainer'); + relations.sidebar = + relation('generatePageSidebar'); + + if (sprawl.enableSearch) { + relations.searchBox = + relation('generateSearchSidebarBox'); + } + if (sprawl.footerContent) { relations.defaultFooterContent = relation('transformContent', sprawl.footerContent); @@ -65,6 +75,11 @@ export default { default: true, }, + showSearch: { + type: 'boolean', + default: true, + }, + additionalNames: { type: 'html', mutable: false, @@ -209,7 +224,6 @@ export default { }, generate(data, relations, slots, { - cachebust, getColors, html, language, @@ -348,9 +362,6 @@ export default { showAsCurrent && {class: 'current'}, - i > 0 && - {class: 'has-divider'}, - [ html.tag('span', {class: 'nav-link-content'}, // Use inline-block styling on the content span, @@ -377,29 +388,54 @@ export default { slots.navContent), ]); - const getSidebar = (side, id) => - (html.isBlank(slots[side]) - ? html.blank() - : slots[side].slots({ - attributes: - slots[side] - .getSlotValue('attributes') - .with({id}), - })); + const getSidebar = (side, id, needed) => { + const sidebar = + (html.isBlank(slots[side]) + ? (needed + ? relations.sidebar.clone() + : html.blank()) + : slots[side]); + + if (html.isBlank(sidebar) && !needed) { + return sidebar; + } + + return sidebar.slots({ + attributes: + sidebar + .getSlotValue('attributes') + .with({id}), + }); + } + + const willShowSearch = + slots.showSearch && relations.searchBox; + + let showingSidebarLeft; + let showingSidebarRight; + + const leftSidebar = getSidebar('leftSidebar', 'sidebar-left', willShowSearch); + const rightSidebar = getSidebar('rightSidebar', 'sidebar-right', false); - const leftSidebar = getSidebar('leftSidebar', 'sidebar-left'); - const rightSidebar = getSidebar('rightSidebar', 'sidebar-right'); + if (willShowSearch) { + if (html.isBlank(leftSidebar)) { + leftSidebar.setSlot('initiallyHidden', true); + showingSidebarLeft = false; + } + + leftSidebar.setSlot( + 'boxes', + html.tags([ + relations.searchBox, + leftSidebar.getSlotValue('boxes'), + ])); + } const hasSidebarLeft = !html.isBlank(html.resolve(leftSidebar)); const hasSidebarRight = !html.isBlank(html.resolve(rightSidebar)); - const collapseSidebars = - (hasSidebarLeft - ? leftSidebar.getSlotValue('collapse') - : true) && - (hasSidebarRight - ? rightSidebar.getSlotValue('collapse') - : true); + showingSidebarLeft ??= hasSidebarLeft; + showingSidebarRight ??= hasSidebarRight; const processSkippers = skipperList => skipperList @@ -407,8 +443,11 @@ export default { (condition === undefined ? hasID(id) : condition)) + .map(({id, string}) => html.tag('span', {class: 'skipper'}, + {'data-for': id}, + html.tag('a', {href: `#${id}`}, language.$('misc.skippers', string)))); @@ -512,15 +551,11 @@ export default { slots.secondaryNav, - html.tag('div', {class: 'layout-columns'}, - !collapseSidebars && - {class: 'vertical-when-thin'}, - - [ - leftSidebar, - mainHTML, - rightSidebar, - ]), + html.tag('div', {class: 'layout-columns'}, [ + leftSidebar, + mainHTML, + rightSidebar, + ]), slots.bannerPosition === 'bottom' && slots.banner, @@ -543,6 +578,8 @@ export default { {'data-rebase-localized': to('localized.root')}, {'data-rebase-shared': to('shared.root')}, {'data-rebase-media': to('media.root')}, + {'data-rebase-thumb': to('thumb.root')}, + {'data-rebase-lib': to('staticLib.root')}, {'data-rebase-data': to('data.root')}, [ @@ -613,7 +650,7 @@ export default { html.tag('link', { rel: 'stylesheet', - href: to('shared.staticFile', 'site6.css', cachebust), + href: to('staticCSS.path', 'site.css'), }), html.tag('style', [ @@ -623,25 +660,29 @@ export default { ]), html.tag('script', { - src: to('shared.staticFile', 'lazy-loading.js', cachebust), + src: to('staticLib.path', 'chroma-js/chroma.min.js'), + }), + + html.tag('script', { + blocking: 'render', + src: to('staticJS.path', 'lazy-loading.js'), + }), + + html.tag('script', { + blocking: 'render', + type: 'module', + src: to('staticJS.path', 'client.js'), }), ]), html.tag('body', [ html.tag('div', {id: 'page-container'}, - (hasSidebarLeft || hasSidebarRight - ? {class: 'has-one-sidebar'} - : {class: 'has-zero-sidebars'}), - - hasSidebarLeft && hasSidebarRight && - {class: 'has-two-sidebars'}, + showingSidebarLeft && + {class: 'showing-sidebar-left'}, - hasSidebarLeft && - {class: 'has-sidebar-left'}, - - hasSidebarRight && - {class: 'has-sidebar-right'}, + showingSidebarRight && + {class: 'showing-sidebar-right'}, [ skippersHTML, @@ -650,11 +691,6 @@ export default { // infoCardHTML, imageOverlayHTML, - - html.tag('script', { - type: 'module', - src: to('shared.staticFile', 'client3.js', cachebust), - }), ]), ]) ]).toString(); diff --git a/src/content/dependencies/generatePageSidebar.js b/src/content/dependencies/generatePageSidebar.js index a7da3d1d..d3b55580 100644 --- a/src/content/dependencies/generatePageSidebar.js +++ b/src/content/dependencies/generatePageSidebar.js @@ -1,29 +1,16 @@ export default { - contentDependencies: ['generatePageSidebarBox'], extraDependencies: ['html'], - relations: (relation) => ({ - box: - relation('generatePageSidebarBox'), - }), - slots: { - // Content is a flat HTML array. It'll all be placed into one sidebar box - // if specified. - content: { - type: 'html', - mutable: false, - }, - - // Attributes to apply to the whole sidebar. If specifying multiple - // sections, this be added to the containing sidebar-column, arr - specify - // attributes on each section if that's more suitable. + // Attributes to apply to the whole sidebar. This be added to the + // containing sidebar-column, arr - specify attributes on each section if + // that's more suitable. attributes: { type: 'attributes', mutable: false, }, - // Chunks of content to be split into separate boxes in the sidebar. + // Content boxes to line up vertically in the sidebar. boxes: { type: 'html', mutable: false, @@ -32,27 +19,16 @@ export default { // Sticky mode controls which sidebar sections, if any, follow the // scroll position, "sticking" to the top of the browser viewport. // - // 'last' - last or only sidebar box is sticky // 'column' - entire column, incl. multiple boxes from top, is sticky // 'static' - sidebar not sticky at all, stays at top of page // // Note: This doesn't affect the content of any sidebar section, only // the whole section's containing box (or the sidebar column as a whole). stickyMode: { - validate: v => v.is('last', 'column', 'static'), + validate: v => v.is('column', 'static'), default: 'static', }, - // Collapsing sidebars disappear when the viewport is sufficiently - // thin. (This is the default.) Override as false to make the sidebar - // stay visible in thinner viewports, where the page layout will be - // reflowed so the sidebar is as wide as the screen and appears below - // nav, above the main content. - collapse: { - type: 'boolean', - default: true, - }, - // Wide sidebars generally take up more horizontal space in the normal // page layout, and should be used if the content of the sidebar has // a greater than typical focus compared to main content. @@ -60,9 +36,19 @@ export default { type: 'boolean', default: false, }, + + // Provide to include all the HTML for the sidebar in place as usual, + // but start it out totally invisible. This is mainly so client-side + // JavaScript can show the sidebar if it needs to (and has a target + // to slot its own content into). If there are no boxes and this + // option *isn't* provided, then the sidebar will just be blank. + initiallyHidden: { + type: 'boolean', + default: false, + }, }, - generate(relations, slots, {html}) { + generate(slots, {html}) { const attributes = html.attributes({class: [ 'sidebar-column', @@ -71,33 +57,34 @@ export default { attributes.add(slots.attributes); - if (slots.class) { - attributes.add('class', slots.class); - } - if (slots.wide) { attributes.add('class', 'wide'); } - if (!slots.collapse) { - attributes.add('class', 'no-hide'); - } - if (slots.stickyMode !== 'static') { attributes.add('class', `sticky-${slots.stickyMode}`); } - const boxes = - (!html.isBlank(slots.boxes) - ? slots.boxes - : !html.isBlank(slots.content) - ? relations.box.slot('content', slots.content) - : html.blank()); + const {content: boxes} = html.smooth(slots.boxes); + + const allBoxesCollapsible = + boxes.every(box => + html.resolve(box) + .attributes + .has('class', 'collapsible')); + + if (allBoxesCollapsible) { + attributes.add('class', 'all-boxes-collapsible'); + } + + if (slots.initiallyHidden) { + attributes.add('class', 'initially-hidden'); + } - if (html.isBlank(boxes)) { + if (html.isBlank(slots.boxes) && !slots.initiallyHidden) { return html.blank(); } else { - return html.tag('div', attributes, boxes); + return html.tag('div', attributes, slots.boxes); } }, }; diff --git a/src/content/dependencies/generatePageSidebarBox.js b/src/content/dependencies/generatePageSidebarBox.js index 51835452..e11efc3f 100644 --- a/src/content/dependencies/generatePageSidebarBox.js +++ b/src/content/dependencies/generatePageSidebarBox.js @@ -11,10 +11,18 @@ export default { type: 'attributes', mutable: false, }, + + collapsible: { + type: 'boolean', + default: true, + }, }, generate: (slots, {html}) => html.tag('div', {class: 'sidebar'}, + slots.collapsible && + {class: 'collapsible'}, + slots.attributes, slots.content), }; diff --git a/src/content/dependencies/generateSearchSidebarBox.js b/src/content/dependencies/generateSearchSidebarBox.js new file mode 100644 index 00000000..6607c789 --- /dev/null +++ b/src/content/dependencies/generateSearchSidebarBox.js @@ -0,0 +1,57 @@ +export default { + contentDependencies: ['generatePageSidebarBox'], + extraDependencies: ['html', 'language'], + + relations: (relation) => ({ + sidebarBox: + relation('generatePageSidebarBox'), + }), + + generate: (relations, {html, language}) => + relations.sidebarBox.slots({ + attributes: {class: 'wiki-search-sidebar-box'}, + collapsible: false, + + content: [ + html.tag('input', {class: 'wiki-search-input'}, + { + placeholder: + language.$('misc.search.placeholder').toString(), + }, + {type: 'search'}), + + html.tag('template', {class: 'wiki-search-preparing-string'}, + language.$('misc.search.preparing')), + + html.tag('template', {class: 'wiki-search-loading-data-string'}, + language.$('misc.search.loadingData')), + + html.tag('template', {class: 'wiki-search-searching-string'}, + language.$('misc.search.searching')), + + html.tag('template', {class: 'wiki-search-failed-string'}, + language.$('misc.search.failed')), + + html.tag('template', {class: 'wiki-search-no-results-string'}, + language.$('misc.search.noResults')), + + html.tag('template', {class: 'wiki-search-current-result-string'}, + language.$('misc.search.currentResult')), + + html.tag('template', {class: 'wiki-search-end-search-string'}, + language.$('misc.search.endSearch')), + + html.tag('template', {class: 'wiki-search-album-result-kind-string'}, + language.$('misc.search.resultKind.album')), + + html.tag('template', {class: 'wiki-search-artist-result-kind-string'}, + language.$('misc.search.resultKind.artist')), + + html.tag('template', {class: 'wiki-search-group-result-kind-string'}, + language.$('misc.search.resultKind.group')), + + html.tag('template', {class: 'wiki-search-tag-result-kind-string'}, + language.$('misc.search.resultKind.artTag')), + ], + }), +}; diff --git a/src/content/dependencies/generateStickyHeadingContainer.js b/src/content/dependencies/generateStickyHeadingContainer.js index 9becfb26..7f271715 100644 --- a/src/content/dependencies/generateStickyHeadingContainer.js +++ b/src/content/dependencies/generateStickyHeadingContainer.js @@ -22,10 +22,17 @@ export default { html.tag('div', {class: 'content-sticky-heading-row'}, [ html.tag('h1', slots.title), - !html.isBlank(slots.cover) && - html.tag('div', {class: 'content-sticky-heading-cover-container'}, - html.tag('div', {class: 'content-sticky-heading-cover'}, - slots.cover.slot('mode', 'thumbnail'))), + html.tag('div', {class: 'content-sticky-heading-cover-container'}, + {[html.onlyIfContent]: true}, + + html.tag('div', {class: 'content-sticky-heading-cover'}, + {[html.onlyIfContent]: true}, + + // TODO: We shouldn't need to do an isBlank check here, + // but a live blank value doesn't have a slot functions, so. + (html.isBlank(slots.cover) + ? html.blank() + : slots.cover.slot('mode', 'thumbnail')))), ]), html.tag('div', {class: 'content-sticky-subheading-row'}, diff --git a/src/content/dependencies/generateTrackChronologyLinks.js b/src/content/dependencies/generateTrackChronologyLinks.js new file mode 100644 index 00000000..5f6b0771 --- /dev/null +++ b/src/content/dependencies/generateTrackChronologyLinks.js @@ -0,0 +1,166 @@ +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.tracksAsArtist, + ...artist.tracksAsContributor, + ]).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; + + const things = + ([ + ...artist.albumsAsCoverArtist, + ...artist.tracksAsCoverArtist, + ]).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 1b5fbbf8..ec93ab71 100644 --- a/src/content/dependencies/generateTrackInfoPage.js +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -1,18 +1,14 @@ -import {sortAlbumsTracksChronologically, sortFlashesChronologically} - from '#sort'; +import {sortFlashesChronologically} from '#sort'; import {empty, stitchArrays} from '#sugar'; -import getChronologyRelations from '../util/getChronologyRelations.js'; - export default { contentDependencies: [ 'generateAbsoluteDatetimestamp', - 'generateAdditionalFilesShortcut', 'generateAlbumAdditionalFilesList', 'generateAlbumNavAccent', + 'generateAlbumSecondaryNav', 'generateAlbumSidebar', 'generateAlbumStyleRules', - 'generateChronologyLinks', 'generateColorStyleAttribute', 'generateCommentarySection', 'generateContentHeading', @@ -20,13 +16,13 @@ export default { 'generatePageLayout', 'generateRelativeDatetimestamp', 'generateTrackAdditionalNamesBox', + 'generateTrackChronologyLinks', 'generateTrackCoverArtwork', 'generateTrackList', 'generateTrackListDividedByGroups', 'generateTrackReleaseInfo', 'generateTrackSocialEmbed', 'linkAlbum', - 'linkArtist', 'linkFlash', 'linkTrack', 'transformContent', @@ -55,51 +51,6 @@ export default { relations.socialEmbed = relation('generateTrackSocialEmbed', track); - relations.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.tracksAsArtist, - ...artist.tracksAsContributor, - ].filter(getDate); - - return sortAlbumsTracksChronologically(things, {getDate}); - }, - }); - - relations.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; - - const things = [ - ...artist.albumsAsCoverArtist, - ...artist.tracksAsCoverArtist, - ].filter(getDate); - - return sortAlbumsTracksChronologically(things, {getDate}); - }, - }), - relations.albumLink = relation('linkAlbum', track.album); @@ -109,8 +60,11 @@ export default { relations.albumNavAccent = relation('generateAlbumNavAccent', track.album, track); - relations.chronologyLinks = - relation('generateChronologyLinks'); + relations.trackChronologyLinks = + relation('generateTrackChronologyLinks', track); + + relations.secondaryNav = + relation('generateAlbumSecondaryNav', track.album); relations.sidebar = relation('generateAlbumSidebar', track.album, track); @@ -134,15 +88,6 @@ export default { relations.releaseInfo = relation('generateTrackReleaseInfo', track); - // Section: Extra links - - const extra = sections.extra = {}; - - if (!empty(track.additionalFiles)) { - extra.additionalFilesShortcut = - relation('generateAdditionalFilesShortcut', track.additionalFiles); - } - // Section: Other releases if (!empty(track.otherReleases)) { @@ -371,7 +316,11 @@ export default { }), sec.additionalFiles && - sec.extra.additionalFilesShortcut, + language.$('releaseInfo.additionalFiles.shortcut', { + link: html.tag('a', + {href: '#midi-project-files'}, + language.$('releaseInfo.additionalFiles.shortcut.link')), + }), sec.artistCommentary && language.$('releaseInfo.readCommentary', { @@ -384,7 +333,7 @@ export default { sec.otherReleases && [ sec.otherReleases.heading .slots({ - id: 'also-released-as', + attributes: {id: 'also-released-as'}, title: language.$('releaseInfo.alsoReleasedAs'), }), @@ -425,7 +374,7 @@ export default { sec.contributors && [ sec.contributors.heading .slots({ - id: 'contributors', + attributes: {id: 'contributors'}, title: language.$('releaseInfo.contributors'), }), @@ -435,11 +384,15 @@ export default { sec.references && [ sec.references.heading .slots({ - id: 'references', + attributes: {id: 'references'}, + title: language.$('releaseInfo.tracksReferenced', { track: html.tag('i', data.name), }), + + stickyTitle: + language.$('releaseInfo.tracksReferenced.sticky'), }), sec.references.list, @@ -448,11 +401,15 @@ export default { sec.samples && [ sec.samples.heading .slots({ - id: 'samples', + attributes: {id: 'samples'}, + title: language.$('releaseInfo.tracksSampled', { track: html.tag('i', data.name), }), + + stickyTitle: + language.$('releaseInfo.tracksSampled.sticky'), }), sec.samples.list, @@ -461,37 +418,55 @@ export default { sec.referencedBy && [ sec.referencedBy.heading .slots({ - id: 'referenced-by', + attributes: {id: 'referenced-by'}, + title: language.$('releaseInfo.tracksThatReference', { track: html.tag('i', data.name), }), + + stickyTitle: + language.$('releaseInfo.tracksThatReference.sticky'), }), - sec.referencedBy.list, + sec.referencedBy.list + .slots({ + headingString: 'releaseInfo.tracksThatReference', + }), ], sec.sampledBy && [ sec.sampledBy.heading .slots({ - id: 'referenced-by', + attributes: {id: 'referenced-by'}, + title: language.$('releaseInfo.tracksThatSample', { track: html.tag('i', data.name), }), + + stickyTitle: + language.$('releaseInfo.tracksThatSample.sticky'), }), - sec.sampledBy.list, + sec.sampledBy.list + .slots({ + headingString: 'releaseInfo.tracksThatSample', + }), ], sec.flashesThatFeature && [ sec.flashesThatFeature.heading .slots({ - id: 'featured-in', + attributes: {id: 'featured-in'}, + title: language.$('releaseInfo.flashesThatFeature', { track: html.tag('i', data.name), }), + + stickyTitle: + language.$('releaseInfo.flashesThatFeature.sticky'), }), html.tag('ul', sec.flashesThatFeature.entries.map(({flashLink, trackLink}) => @@ -510,7 +485,7 @@ export default { sec.lyrics && [ sec.lyrics.heading .slots({ - id: 'lyrics', + attributes: {id: 'lyrics'}, title: language.$('releaseInfo.lyrics'), }), @@ -522,7 +497,7 @@ export default { sec.sheetMusicFiles && [ sec.sheetMusicFiles.heading .slots({ - id: 'sheet-music-files', + attributes: {id: 'sheet-music-files'}, title: language.$('releaseInfo.sheetMusicFiles.heading'), }), @@ -532,7 +507,7 @@ export default { sec.midiProjectFiles && [ sec.midiProjectFiles.heading .slots({ - id: 'midi-project-files', + attributes: {id: 'midi-project-files'}, title: language.$('releaseInfo.midiProjectFiles.heading'), }), @@ -542,12 +517,8 @@ export default { sec.additionalFiles && [ sec.additionalFiles.heading .slots({ - id: 'additional-files', - title: - language.$('releaseInfo.additionalFiles.heading', { - additionalFiles: - language.countAdditionalFiles(data.numAdditionalFiles, {unit: true}), - }), + attributes: {id: 'additional-files'}, + title: language.$('releaseInfo.additionalFiles.heading'), }), sec.additionalFiles.list, @@ -582,18 +553,11 @@ export default { }), navContent: - relations.chronologyLinks.slots({ - chronologyInfoSets: [ - { - headingString: 'misc.chronology.heading.track', - contributions: relations.artistChronologyContributions, - }, - { - headingString: 'misc.chronology.heading.coverArt', - contributions: relations.coverArtistChronologyContributions, - }, - ], - }), + relations.trackChronologyLinks, + + secondaryNav: + relations.secondaryNav + .slot('mode', 'track'), leftSidebar: relations.sidebar, diff --git a/src/content/dependencies/generateTrackListDividedByGroups.js b/src/content/dependencies/generateTrackListDividedByGroups.js index e070ac35..327865f0 100644 --- a/src/content/dependencies/generateTrackListDividedByGroups.js +++ b/src/content/dependencies/generateTrackListDividedByGroups.js @@ -1,53 +1,132 @@ -import {empty} from '#sugar'; - -import groupTracksByGroup from '../util/groupTracksByGroup.js'; +import {empty, filterMultipleArrays, stitchArrays} from '#sugar'; export default { - contentDependencies: ['generateTrackList', 'linkGroup'], + contentDependencies: [ + 'generateContentHeading', + 'generateTrackList', + 'linkGroup', + ], + extraDependencies: ['html', 'language'], - relations(relation, tracks, groups) { - if (empty(tracks)) { - return {}; + query(tracks, dividingGroups) { + const groupings = new Map(); + const ungroupedTracks = []; + + // Entry order matters! Add blank lists for each group + // in the order that those groups are provided. + for (const group of dividingGroups) { + groupings.set(group, []); } - if (empty(groups)) { - return { - flatList: - relation('generateTrackList', tracks), - }; + for (const track of tracks) { + const firstMatchingGroup = + dividingGroups.find(group => group.albums.includes(track.album)); + + if (firstMatchingGroup) { + groupings.get(firstMatchingGroup).push(track); + } else { + ungroupedTracks.push(track); + } } - const lists = groupTracksByGroup(tracks, groups); + const groups = Array.from(groupings.keys()); + const groupedTracks = Array.from(groupings.values()); - return { - groupedLists: - Array.from(lists.entries()).map(([groupOrOther, tracks]) => ({ - ...(groupOrOther === 'other' - ? {other: true} - : {groupLink: relation('linkGroup', groupOrOther)}), + // Drop the empty lists, so just the groups which + // at least a single track matched are left. + filterMultipleArrays( + groups, + groupedTracks, + (_group, tracks) => !empty(tracks)); - list: - relation('generateTrackList', tracks), - })), - }; + return {groups, groupedTracks, ungroupedTracks}; }, - generate(relations, {html, language}) { - if (relations.flatList) { - return relations.flatList; - } + relations: (relation, query, tracks, groups) => ({ + flatList: + (empty(groups) + ? relation('generateTrackList', tracks) + : null), + + contentHeading: + relation('generateContentHeading'), + + groupLinks: + query.groups + .map(group => relation('linkGroup', group)), + + groupedTrackLists: + query.groupedTracks + .map(tracks => relation('generateTrackList', tracks)), + + ungroupedTrackList: + (empty(query.ungroupedTracks) + ? null + : relation('generateTrackList', query.ungroupedTracks)), + }), - return html.tag('dl', - relations.groupedLists.map(({other, groupLink, list}) => [ - html.tag('dt', - (other - ? language.$('trackList.group.fromOther') - : language.$('trackList.group', { - group: groupLink - }))), - - html.tag('dd', list), - ])); + data: (query) => ({ + groupNames: + query.groups + .map(group => group.name), + }), + + slots: { + headingString: { + type: 'string', + }, }, + + generate: (data, relations, slots, {html, language}) => + relations.flatList ?? + html.tag('dl', [ + stitchArrays({ + groupName: data.groupNames, + groupLink: relations.groupLinks, + trackList: relations.groupedTrackLists, + }).map(({ + groupName, + groupLink, + trackList, + }) => [ + (slots.headingString + ? relations.contentHeading.clone().slots({ + tag: 'dt', + + title: + language.$('trackList.fromGroup', { + group: groupLink + }), + + stickyTitle: + language.$(slots.headingString, 'sticky', 'fromGroup', { + group: groupName, + }), + }) + : html.tag('dt', + language.$('trackList.fromGroup', { + group: groupLink + }))), + + html.tag('dd', trackList), + ]), + + relations.ungroupedTrackList && [ + (slots.headingString + ? relations.contentHeading.clone().slots({ + tag: 'dt', + + title: + language.$('trackList.fromOther'), + + stickyTitle: + language.$(slots.headingString, 'sticky', 'fromOther'), + }) + : html.tag('dt', + language.$('trackList.fromOther'))), + + html.tag('dd', relations.ungroupedTrackList), + ], + ]), }; diff --git a/src/content/dependencies/generateTrackReleaseInfo.js b/src/content/dependencies/generateTrackReleaseInfo.js index 3bdeaa4f..88a4cdc7 100644 --- a/src/content/dependencies/generateTrackReleaseInfo.js +++ b/src/content/dependencies/generateTrackReleaseInfo.js @@ -59,20 +59,20 @@ export default { relations.coverArtistContributionsLine ?.slots({stringKey: 'releaseInfo.coverArtBy'}), - 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), - }), + language.$('releaseInfo.released', { + [language.onlyIfOptions]: ['date'], + date: language.formatDate(data.date), + }), + + language.$('releaseInfo.artReleased', { + [language.onlyIfOptions]: ['date'], + date: language.formatDate(data.coverArtDate), + }), + + language.$('releaseInfo.duration', { + [language.onlyIfOptions]: ['duration'], + duration: language.formatDuration(data.duration), + }), ]), html.tag('p', diff --git a/src/content/dependencies/generateWikiHomeAlbumsRow.js b/src/content/dependencies/generateWikiHomeAlbumsRow.js index a19f104c..16c22bb3 100644 --- a/src/content/dependencies/generateWikiHomeAlbumsRow.js +++ b/src/content/dependencies/generateWikiHomeAlbumsRow.js @@ -113,10 +113,10 @@ export default { image.slots({ path, missingSourceContent: - name && - language.$('misc.albumGrid.noCoverArt', { - album: name, - }), + language.$('misc.albumGrid.noCoverArt', { + [language.onlyIfOptions]: ['album'], + album: name, + }), })); commonSlots.actionLinks = diff --git a/src/content/dependencies/generateWikiHomeNewsBox.js b/src/content/dependencies/generateWikiHomeNewsBox.js index e054edda..bd0e4797 100644 --- a/src/content/dependencies/generateWikiHomeNewsBox.js +++ b/src/content/dependencies/generateWikiHomeNewsBox.js @@ -46,6 +46,8 @@ export default { return relations.box.slots({ attributes: {class: 'latest-news-sidebar-box'}, + collapsible: false, + content: [ html.tag('h1', language.$('homepage.news.title')), diff --git a/src/content/dependencies/generateWikiHomePage.js b/src/content/dependencies/generateWikiHomePage.js index 35461d03..ee14a587 100644 --- a/src/content/dependencies/generateWikiHomePage.js +++ b/src/content/dependencies/generateWikiHomePage.js @@ -79,13 +79,14 @@ export default { leftSidebar: relations.sidebar.slots({ - collapse: false, wide: true, boxes: [ relations.customSidebarContent && relations.customSidebarBox.slots({ attributes: {class: 'custom-content-sidebar-box'}, + collapsible: false, + content: relations.customSidebarContent .slot('mode', 'multiline'), diff --git a/src/content/dependencies/image.js b/src/content/dependencies/image.js index 6b24f386..b1f02819 100644 --- a/src/content/dependencies/image.js +++ b/src/content/dependencies/image.js @@ -1,9 +1,8 @@ -import {logInfo, logWarn} from '#cli'; +import {logWarn} from '#cli'; import {empty} from '#sugar'; export default { extraDependencies: [ - 'cachebust', 'checkIfImagePathHasCachedThumbnails', 'getDimensionsOfImagePath', 'getSizeOfImagePath', @@ -82,7 +81,6 @@ export default { }, generate(data, relations, slots, { - cachebust, checkIfImagePathHasCachedThumbnails, getDimensionsOfImagePath, getSizeOfImagePath, @@ -119,10 +117,6 @@ export default { const isMissingImageFile = missingImagePaths.includes(mediaSrc); - if (isMissingImageFile) { - logInfo`No image file for ${mediaSrc} - build again for list of missing images.`; - } - const willLink = !isMissingImageFile && (typeof slots.link === 'string' || slots.link); @@ -137,15 +131,8 @@ export default { !isMissingImageFile && !empty(contentWarnings); - const hasBothDimensions = - !!(slots.dimensions && - slots.dimensions[0] !== null && - slots.dimensions[1] !== null); - const willSquare = - (hasBothDimensions - ? slots.dimensions[0] === slots.dimensions[1] - : slots.square); + slots.square; const imgAttributes = html.attributes([ {class: 'image'}, @@ -156,7 +143,7 @@ export default { {width: slots.dimensions[0]}, slots.dimensions?.[1] && - {width: slots.dimensions[1]}, + {height: slots.dimensions[1]}, ]); const isPlaceholder = @@ -176,7 +163,7 @@ export default { if (willReveal) { reveal = [ html.tag('img', {class: 'reveal-symbol'}, - {src: to('shared.staticFile', 'warning.svg', cachebust)}), + {src: to('staticMisc.path', 'warning.svg')}), html.tag('br'), diff --git a/src/content/dependencies/linkContribution.js b/src/content/dependencies/linkContribution.js index 41ce1146..1a51c387 100644 --- a/src/content/dependencies/linkContribution.js +++ b/src/content/dependencies/linkContribution.js @@ -14,7 +14,7 @@ export default { const relations = {}; relations.artistLink = - relation('linkArtist', contribution.who); + relation('linkArtist', contribution.artist); relations.textWithTooltip = relation('generateTextWithTooltip'); @@ -22,9 +22,9 @@ export default { relations.tooltip = relation('generateTooltip'); - if (!empty(contribution.who.urls)) { + if (!empty(contribution.artist.urls)) { relations.artistIcons = - contribution.who.urls + contribution.artist.urls .map(url => relation('linkExternalAsIcon', url)); } @@ -33,8 +33,8 @@ export default { data(contribution) { return { - what: contribution.what, - urls: contribution.who.urls, + contribution: contribution.annotation, + urls: contribution.artist.urls, }; }, @@ -50,7 +50,7 @@ export default { }, generate(data, relations, slots, {html, language}) { - const hasContribution = !!(slots.showContribution && data.what); + const hasContribution = !!(slots.showContribution && data.contribution); const hasExternalIcons = !!(slots.showIcons && relations.artistIcons); const parts = ['misc.artistLink']; @@ -111,7 +111,7 @@ export default { if (hasContribution) { parts.push('withContribution'); - options.contrib = data.what; + options.contrib = data.contribution; } if (hasExternalIcons && slots.iconMode === 'inline') { diff --git a/src/content/dependencies/linkExternalAsIcon.js b/src/content/dependencies/linkExternalAsIcon.js index 6f37529e..e2ce4b3c 100644 --- a/src/content/dependencies/linkExternalAsIcon.js +++ b/src/content/dependencies/linkExternalAsIcon.js @@ -37,7 +37,7 @@ export default { html.tag('title', platformText), html.tag('use', { - href: to('shared.staticIcon', iconId), + href: to('staticMisc.icon', iconId), }), ]), diff --git a/src/content/dependencies/listArtistsByDuration.js b/src/content/dependencies/listArtistsByDuration.js index f677d82c..66fab8be 100644 --- a/src/content/dependencies/listArtistsByDuration.js +++ b/src/content/dependencies/listArtistsByDuration.js @@ -1,5 +1,5 @@ import {sortAlphabetically, sortByCount} from '#sort'; -import {filterByCount, stitchArrays} from '#sugar'; +import {filterByCount, stitchArrays, unique} from '#sugar'; import {getTotalDuration} from '#wiki-data'; export default { @@ -17,10 +17,12 @@ export default { const durations = artists.map(artist => - getTotalDuration([ - ...(artist.tracksAsArtist ?? []), - ...(artist.tracksAsContributor ?? []), - ], {originalReleasesOnly: true})); + getTotalDuration( + unique([ + ...(artist.tracksAsArtist ?? []), + ...(artist.tracksAsContributor ?? []), + ]), + {originalReleasesOnly: true})); filterByCount(artists, durations); sortByCount(artists, durations, {greatestFirst: true}); diff --git a/src/content/dependencies/listArtistsByGroup.js b/src/content/dependencies/listArtistsByGroup.js index 30884d24..f221fe8c 100644 --- a/src/content/dependencies/listArtistsByGroup.js +++ b/src/content/dependencies/listArtistsByGroup.js @@ -1,6 +1,11 @@ import {sortAlphabetically} from '#sort'; -import {empty, filterMultipleArrays, stitchArrays, unique} from '#sugar'; -import {getArtistNumContributions} from '#wiki-data'; +import { + empty, + filterByCount, + filterMultipleArrays, + stitchArrays, + transposeArrays, +} from '#sugar'; export default { contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], @@ -15,29 +20,52 @@ export default { sortAlphabetically( sprawl.artistData.filter(artist => !artist.isAlias)); - const groups = + const interestingGroups = sprawl.wikiInfo.divideTrackListsByGroups; - if (empty(groups)) { - return {spec, artists}; + if (empty(interestingGroups)) { + return {spec}; } - const artistGroups = + // We don't actually care about *which* things belong to each group, only + // how many belong to each group. So we'll just compute a list of all the + // (interesting) groups that each of each artists' things belongs to. + const artistThingGroups = artists.map(artist => - unique( - unique([ - ...artist.albumsAsAny, - ...artist.tracksAsAny.map(track => track.album), - ]).flatMap(album => album.groups))) - - const artistsByGroup = - groups.map(group => - artists.filter((artist, index) => artistGroups[index].includes(group))); - - filterMultipleArrays(groups, artistsByGroup, - (group, artists) => !empty(artists)); - - return {spec, groups, artistsByGroup}; + ([...artist.albumsAsAny.map(album => album.groups), + ...artist.tracksAsAny.map(track => track.album.groups)]) + .map(groups => groups + .filter(group => interestingGroups.includes(group)))); + + const [artistsByGroup, countsByGroup] = + transposeArrays(interestingGroups.map(group => { + const counts = + artistThingGroups + .map(thingGroups => thingGroups + .filter(thingGroups => thingGroups.includes(group)) + .length); + + const filteredArtists = artists.slice(); + + filterByCount(filteredArtists, counts); + + return [filteredArtists, counts]; + })); + + const groups = interestingGroups; + + filterMultipleArrays( + groups, + artistsByGroup, + countsByGroup, + (_group, artists, _counts) => !empty(artists)); + + return { + spec, + groups, + artistsByGroup, + countsByGroup, + }; }, relations(relation, query) { @@ -46,12 +74,6 @@ export default { relations.page = relation('generateListingPage', query.spec); - if (query.artists) { - relations.artistLinks = - query.artists - .map(artist => relation('linkArtist', artist)); - } - if (query.artistsByGroup) { relations.groupLinks = query.groups @@ -69,65 +91,43 @@ export default { data(query) { const data = {}; - if (query.artists) { - data.counts = - query.artists - .map(artist => getArtistNumContributions(artist)); - } - if (query.artistsByGroup) { data.groupDirectories = query.groups .map(group => group.directory); data.countsByGroup = - query.artistsByGroup - .map(artists => artists - .map(artist => getArtistNumContributions(artist))); + query.countsByGroup; } return data; }, - generate(data, relations, {language}) { - return ( - (relations.artistLinksByGroup - ? relations.page.slots({ - type: 'chunks', - - showSkipToSection: true, - chunkIDs: - data.groupDirectories - .map(directory => `contributed-to-${directory}`), - - chunkTitles: - relations.groupLinks.map(groupLink => ({ - group: groupLink, - })), - - chunkRows: - stitchArrays({ - artistLinks: relations.artistLinksByGroup, - counts: data.countsByGroup, - }).map(({artistLinks, counts}) => - stitchArrays({ - link: artistLinks, - count: counts, - }).map(({link, count}) => ({ - artist: link, - contributions: language.countContributions(count, {unit: true}), - }))), - }) - : relations.page.slots({ - type: 'rows', - rows: - stitchArrays({ - link: relations.artistLinks, - count: data.counts, - }).map(({link, count}) => ({ - artist: link, - contributions: language.countContributions(count, {unit: true}), - })), - }))); - }, + generate: (data, relations, {language}) => + relations.page.slots({ + type: 'chunks', + + showSkipToSection: true, + chunkIDs: + data.groupDirectories + .map(directory => `contributed-to-${directory}`), + + chunkTitles: + relations.groupLinks.map(groupLink => ({ + group: groupLink, + })), + + chunkRows: + stitchArrays({ + artistLinks: relations.artistLinksByGroup, + counts: data.countsByGroup, + }).map(({artistLinks, counts}) => + stitchArrays({ + link: artistLinks, + count: counts, + }).map(({link, count}) => ({ + artist: link, + contributions: language.countContributions(count, {unit: true}), + }))), + }), }; diff --git a/src/content/dependencies/listArtistsByLatestContribution.js b/src/content/dependencies/listArtistsByLatestContribution.js index 0f709577..27a2faa3 100644 --- a/src/content/dependencies/listArtistsByLatestContribution.js +++ b/src/content/dependencies/listArtistsByLatestContribution.js @@ -83,7 +83,8 @@ export default { }); }; - const getArtists = (thing, key) => thing[key].map(({who}) => who); + const getArtists = (thing, key) => + thing[key].map(({artist}) => artist); const albumsLatestFirst = sortAlbumsTracksChronologically(sprawl.albumData.slice()); const tracksLatestFirst = sortAlbumsTracksChronologically(sprawl.trackData.slice()); diff --git a/src/content/dependencies/listTracksByDate.js b/src/content/dependencies/listTracksByDate.js index 01ce4e2d..0a2bfd6c 100644 --- a/src/content/dependencies/listTracksByDate.js +++ b/src/content/dependencies/listTracksByDate.js @@ -15,7 +15,8 @@ export default { chunks: chunkByProperties( - sortAlbumsTracksChronologically(trackData.slice()), + sortAlbumsTracksChronologically( + trackData.filter(track => track.date)), ['album', 'date']), }; }, diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js index 0904cde6..84fbe361 100644 --- a/src/content/dependencies/transformContent.js +++ b/src/content/dependencies/transformContent.js @@ -262,6 +262,10 @@ export default { height && {height}, style && {style}, + align === 'center' && + !link && + {class: 'align-center'}, + pixelate && {class: 'pixelate'}); @@ -271,6 +275,9 @@ export default { {href: link}, {target: '_blank'}, + align === 'center' && + {class: 'align-center'}, + {title: language.$('misc.external.opensInNewTab', { link: @@ -530,9 +537,9 @@ export default { // Expand line breaks which don't follow a list, quote, // or <br> / " ", and which don't precede or follow // indented text (by at least two spaces). - .replace(/(?<!^ *-.*|^>.*|^ .*\n*| $|<br>$)\n+(?! |\n)/gm, '\n\n') /* eslint-disable-line no-regex-spaces */ + .replace(/(?<!^ *(?:-|\d\.).*|^>.*|^ .*\n*| $|<br>$)\n+(?! |\n)/gm, '\n\n') /* eslint-disable-line no-regex-spaces */ // Expand line breaks which are at the end of a list. - .replace(/(?<=^ *-.*)\n+(?!^ *-)/gm, '\n\n') + .replace(/(?<=^ *(?:-|\d\.).*)\n+(?!^ *(?:-|\d\.))/gm, '\n\n') // Expand line breaks which are at the end of a quote. .replace(/(?<=^>.*)\n+(?!^>)/gm, '\n\n'); |