diff options
Diffstat (limited to 'src/content')
57 files changed, 1503 insertions, 1010 deletions
diff --git a/src/content/dependencies/generateAdditionalFilesList.js b/src/content/dependencies/generateAdditionalFilesList.js index 92948c7..f504cf8 100644 --- a/src/content/dependencies/generateAdditionalFilesList.js +++ b/src/content/dependencies/generateAdditionalFilesList.js @@ -1,97 +1,24 @@ -import {empty} from '#sugar'; - -function validateFileMapping(v, validateValue) { - return value => { - v.isObject(value); - - const valueErrors = []; - for (const [fileKey, fileValue] of Object.entries(value)) { - if (fileValue === null) { - continue; - } - - try { - validateValue(fileValue); - } catch (error) { - error.message = `(${fileKey}) ` + error.message; - valueErrors.push(error); - } - } - - if (!empty(valueErrors)) { - throw new AggregateError(valueErrors, `Errors validating values`); - } - }; -} +import {stitchArrays} from '#sugar'; export default { - extraDependencies: ['html', 'language'], - - data(additionalFiles) { - return { - // Additional files are already a serializable format. - additionalFiles, - }; - }, + extraDependencies: ['html'], slots: { - fileLinks: { - validate: v => validateFileMapping(v, v.isHTML), + chunks: { + validate: v => v.strictArrayOf(v.isHTML), }, - fileSizes: { - validate: v => validateFileMapping(v, v.isWholeNumber), + chunkItems: { + validate: v => v.strictArrayOf(v.isHTML), }, }, - generate(data, slots, {html, language}) { - if (!slots.fileLinks) { - return html.blank(); - } - - const filesWithLinks = new Set( - Object.entries(slots.fileLinks) - .filter(([key, value]) => value) - .map(([key]) => key)); - - if (empty(filesWithLinks)) { - return html.blank(); - } - - const filteredFileGroups = data.additionalFiles - .map(({title, description, files}) => ({ - title, - description, - files: files.filter(f => filesWithLinks.has(f)), - })) - .filter(({files}) => !empty(files)); - - if (empty(filteredFileGroups)) { - return html.blank(); - } - - return html.tag('dl', - filteredFileGroups.flatMap(({title, description, files}) => [ - html.tag('dt', - (description - ? language.$('releaseInfo.additionalFiles.entry.withDescription', { - title, - description, - }) - : language.$('releaseInfo.additionalFiles.entry', {title}))), - - html.tag('dd', - html.tag('ul', - files.map(file => - html.tag('li', - (slots.fileSizes?.[file] - ? language.$('releaseInfo.additionalFiles.file.withSize', { - file: slots.fileLinks[file], - size: language.formatFileSize(slots.fileSizes[file]), - }) - : language.$('releaseInfo.additionalFiles.file', { - file: slots.fileLinks[file], - })))))), - ])); - }, + generate: (slots, {html}) => + html.tag('ul', {class: 'additional-files-list'}, + stitchArrays({ + chunk: slots.chunks, + items: slots.chunkItems, + }).map(({chunk, items}) => + chunk.clone() + .slot('items', items))), }; diff --git a/src/content/dependencies/generateAdditionalFilesListChunk.js b/src/content/dependencies/generateAdditionalFilesListChunk.js new file mode 100644 index 0000000..5804115 --- /dev/null +++ b/src/content/dependencies/generateAdditionalFilesListChunk.js @@ -0,0 +1,53 @@ +export default { + extraDependencies: ['html', 'language'], + + slots: { + title: { + type: 'html', + mutable: false, + }, + + description: { + type: 'html', + mutable: false, + }, + + items: { + validate: v => v.looseArrayOf(v.isHTML), + }, + }, + + generate(slots, {html, language}) { + const summary = + html.tag('summary', + html.tag('span', + language.$('releaseInfo.additionalFiles.entry', { + title: + html.tag('span', {class: 'group-name'}, + slots.title), + }))); + + const description = + html.tag('li', {class: 'entry-description'}, + {[html.onlyIfContent]: true}, + slots.description); + + const items = + (html.isBlank(slots.items) + ? html.tag('li', + language.$('releaseInfo.additionalFiles.entry.noFilesAvailable')) + : slots.items); + + const content = + html.tag('ul', [description, items]); + + const details = + html.tag('details', + html.isBlank(slots.items) && + {open: true}, + + [summary, content]); + + return html.tag('li', details); + }, +}; diff --git a/src/content/dependencies/generateAdditionalFilesListChunkItem.js b/src/content/dependencies/generateAdditionalFilesListChunkItem.js new file mode 100644 index 0000000..c37d6bb --- /dev/null +++ b/src/content/dependencies/generateAdditionalFilesListChunkItem.js @@ -0,0 +1,30 @@ +export default { + extraDependencies: ['html', 'language'], + + slots: { + fileLink: { + type: 'html', + mutable: false, + }, + + fileSize: { + validate: v => v.isWholeNumber, + }, + }, + + generate(slots, {html, language}) { + const itemParts = ['releaseInfo.additionalFiles.file']; + const itemOptions = {file: slots.fileLink}; + + if (slots.fileSize) { + itemParts.push('withSize'); + itemOptions.size = language.formatFileSize(slots.fileSize); + } + + const li = + html.tag('li', + language.$(...itemParts, itemOptions)); + + return li; + }, +}; diff --git a/src/content/dependencies/generateAdditionalFilesShortcut.js b/src/content/dependencies/generateAdditionalFilesShortcut.js deleted file mode 100644 index 9e119bc..0000000 --- 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/generateAlbumAdditionalFilesList.js b/src/content/dependencies/generateAlbumAdditionalFilesList.js index 23f32bf..9818a43 100644 --- a/src/content/dependencies/generateAlbumAdditionalFilesList.js +++ b/src/content/dependencies/generateAlbumAdditionalFilesList.js @@ -1,59 +1,96 @@ +import {stitchArrays} from '#sugar'; + export default { contentDependencies: [ 'generateAdditionalFilesList', + 'generateAdditionalFilesListChunk', + 'generateAdditionalFilesListChunkItem', 'linkAlbumAdditionalFile', + 'transformContent', ], - extraDependencies: [ - 'getSizeOfAdditionalFile', - 'html', - 'urls', - ], + extraDependencies: ['getSizeOfAdditionalFile', 'html', 'urls'], - data(album, additionalFiles) { - return { - albumDirectory: album.directory, - fileLocations: additionalFiles.flatMap(({files}) => files), - }; - }, + relations: (relation, album, additionalFiles) => ({ + list: + relation('generateAdditionalFilesList', additionalFiles), - relations(relation, album, additionalFiles) { - return { - additionalFilesList: - relation('generateAdditionalFilesList', additionalFiles), - - additionalFileLinks: - Object.fromEntries( - additionalFiles - .flatMap(({files}) => files) - .map(file => [ - file, - relation('linkAlbumAdditionalFile', album, file), - ])), - }; - }, + chunks: + additionalFiles + .map(() => relation('generateAdditionalFilesListChunk')), + + chunkDescriptions: + additionalFiles + .map(({description}) => + (description + ? relation('transformContent', description) + : null)), + + chunkItems: + additionalFiles + .map(({files}) => + (files ?? []) + .map(() => relation('generateAdditionalFilesListChunkItem'))), + + chunkItemFileLinks: + additionalFiles + .map(({files}) => + (files ?? []) + .map(file => relation('linkAlbumAdditionalFile', album, file))), + }), + + data: (album, additionalFiles) => ({ + albumDirectory: album.directory, + + chunkTitles: + additionalFiles + .map(({title}) => title), + + chunkItemLocations: + additionalFiles + .map(({files}) => files ?? []), + }), slots: { showFileSizes: {type: 'boolean', default: true}, }, - generate(data, relations, slots, { - getSizeOfAdditionalFile, - urls, - }) { - return relations.additionalFilesList - .slots({ - fileLinks: relations.additionalFileLinks, - fileSizes: - Object.fromEntries(data.fileLocations.map(file => [ - file, - (slots.showFileSizes - ? getSizeOfAdditionalFile( - urls - .from('media.root') - .to('media.albumAdditionalFile', data.albumDirectory, file)) - : 0), - ])), - }); - }, + generate: (data, relations, slots, {getSizeOfAdditionalFile, urls}) => + relations.list.slots({ + chunks: + stitchArrays({ + chunk: relations.chunks, + description: relations.chunkDescriptions, + title: data.chunkTitles, + }).map(({chunk, title, description}) => + chunk.slots({ + title, + description: + (description + ? description.slot('mode', 'inline') + : null), + })), + + chunkItems: + stitchArrays({ + items: relations.chunkItems, + fileLinks: relations.chunkItemFileLinks, + locations: data.chunkItemLocations, + }).map(({items, fileLinks, locations}) => + stitchArrays({ + item: items, + fileLink: fileLinks, + location: locations, + }).map(({item, fileLink, location}) => + item.slots({ + fileLink: fileLink, + fileSize: + (slots.showFileSizes + ? getSizeOfAdditionalFile( + urls + .from('media.root') + .to('media.albumAdditionalFile', data.albumDirectory, location)) + : 0), + }))), + }), }; diff --git a/src/content/dependencies/generateAlbumCommentaryPage.js b/src/content/dependencies/generateAlbumCommentaryPage.js index 5a7142e..7879269 100644 --- a/src/content/dependencies/generateAlbumCommentaryPage.js +++ b/src/content/dependencies/generateAlbumCommentaryPage.js @@ -2,9 +2,9 @@ import {empty, stitchArrays} from '#sugar'; export default { contentDependencies: [ + 'generateAlbumCommentarySidebar', 'generateAlbumCoverArtwork', 'generateAlbumNavAccent', - 'generateAlbumSidebarTrackSection', 'generateAlbumStyleRules', 'generateCommentaryEntry', 'generateContentHeading', @@ -23,6 +23,9 @@ export default { relations.layout = relation('generatePageLayout'); + relations.sidebar = + relation('generateAlbumCommentarySidebar', album); + relations.albumStyleRules = relation('generateAlbumStyleRules', album, null); @@ -82,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; }, @@ -249,17 +245,7 @@ export default { }, ], - leftSidebarStickyMode: 'column', - leftSidebarClass: 'commentary-track-list-sidebar-box', - leftSidebarContent: [ - 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 0000000..435860c --- /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/generateAlbumCoverArtwork.js b/src/content/dependencies/generateAlbumCoverArtwork.js index ce8cde2..dbb22fe 100644 --- a/src/content/dependencies/generateAlbumCoverArtwork.js +++ b/src/content/dependencies/generateAlbumCoverArtwork.js @@ -12,11 +12,15 @@ export default { color: album.color, + + dimensions: + album.coverArtDimensions, }), generate: (data, relations) => relations.coverArtwork.slots({ path: data.path, color: data.color, + dimensions: data.dimensions, }), }; diff --git a/src/content/dependencies/generateAlbumGalleryPage.js b/src/content/dependencies/generateAlbumGalleryPage.js index f61b198..aa02568 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; @@ -145,6 +145,18 @@ export default { : null)), ]; + data.dimensions = [ + (album.hasCoverArt + ? album.coverArtDimensions + : null), + + ... + album.tracks.map(track => + (track.hasUniqueCoverArt + ? track.coverArtDimensions + : null)), + ]; + return data; }, @@ -175,10 +187,12 @@ export default { stitchArrays({ image: relations.images, path: data.paths, + dimensions: data.dimensions, name: data.names, - }).map(({image, path, name}) => + }).map(({image, path, dimensions, name}) => image.slots({ path, + dimensions, missingSourceContent: language.$('misc.albumGalleryGrid.noCoverArt', {name}), })), diff --git a/src/content/dependencies/generateAlbumInfoPage.js b/src/content/dependencies/generateAlbumInfoPage.js index 5853f11..739a666 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', { @@ -265,7 +264,7 @@ export default { secondaryNav: relations.secondaryNav, - ...relations.sidebar, + leftSidebar: relations.sidebar, socialEmbed: relations.socialEmbed, }); diff --git a/src/content/dependencies/generateAlbumReleaseInfo.js b/src/content/dependencies/generateAlbumReleaseInfo.js index 5128fba..6fc1375 100644 --- a/src/content/dependencies/generateAlbumReleaseInfo.js +++ b/src/content/dependencies/generateAlbumReleaseInfo.js @@ -96,17 +96,14 @@ export default { language.formatDisjunctionList( relations.externalLinks .map(link => - link.slots({ - context: [ - 'album', - (data.numTracks === 0 - ? 'albumNoTracks' - : data.numTracks === 1 - ? 'albumOneTrack' - : 'albumMultipleTracks'), - ], - style: 'normal', - }))), + 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 400420b..d6ff8a0 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/generateAlbumSidebar.js b/src/content/dependencies/generateAlbumSidebar.js index 5ef4501..355a9a9 100644 --- a/src/content/dependencies/generateAlbumSidebar.js +++ b/src/content/dependencies/generateAlbumSidebar.js @@ -1,79 +1,47 @@ export default { contentDependencies: [ 'generateAlbumSidebarGroupBox', - 'generateAlbumSidebarTrackSection', - 'linkAlbum', + 'generateAlbumSidebarTrackListBox', + 'generatePageSidebar', + 'generatePageSidebarConjoinedBox', ], - extraDependencies: ['html'], + relations: (relation, album, track) => ({ + sidebar: + relation('generatePageSidebar'), - relations(relation, album, track) { - const relations = {}; + conjoinedBox: + relation('generatePageSidebarConjoinedBox'), - relations.albumLink = - relation('linkAlbum', album); + trackListBox: + relation('generateAlbumSidebarTrackListBox', album, track), - relations.groupBoxes = + groupBoxes: album.groups.map(group => - relation('generateAlbumSidebarGroupBox', album, group)); - - relations.trackSections = - album.trackSections.map(trackSection => - relation('generateAlbumSidebarTrackSection', album, track, trackSection)); - - return relations; - }, - - data(album, track) { - return {isAlbumPage: !track}; - }, - - generate(data, relations, {html}) { - const trackListBox = { - class: 'track-list-sidebar-box', - content: - html.tags([ - html.tag('h1', relations.albumLink), - relations.trackSections, - ]), - }; - - if (data.isAlbumPage) { - const groupBoxes = - relations.groupBoxes - .map(content => ({ - class: 'individual-group-sidebar-box', - content: content.slot('mode', 'album'), - })); - - return { - leftSidebarMultiple: [ - ...groupBoxes, - trackListBox, - ], - }; - } - - const conjoinedGroupBox = { - class: 'conjoined-group-sidebar-box', - content: - relations.groupBoxes - .flatMap((content, i, {length}) => [ - content.slot('mode', 'track'), - i < length - 1 && - html.tag('hr', { - style: `border-color: var(--primary-color); border-style: none none dotted none` - }), - ]) - .filter(Boolean), - }; - - return { - // leftSidebarStickyMode: 'column', - leftSidebarMultiple: [ - trackListBox, - conjoinedGroupBox, + relation('generateAlbumSidebarGroupBox', album, group)), + }), + + data: (album, track) => ({ + isAlbumPage: !track, + }), + + generate: (data, relations) => + relations.sidebar.slots({ + boxes: [ + data.isAlbumPage && + relations.groupBoxes + .map(box => box.slot('mode', 'album')), + + relations.trackListBox, + + !data.isAlbumPage && + relations.conjoinedBox.slots({ + attributes: {class: 'conjoined-group-sidebar-box'}, + boxes: + relations.groupBoxes + .map(box => box.slot('mode', 'track')) + .map(box => box.content), /* TODO: Kludge. */ + }), ], - }; - }, + }), }; diff --git a/src/content/dependencies/generateAlbumSidebarGroupBox.js b/src/content/dependencies/generateAlbumSidebarGroupBox.js index 93ebf5d..00a96c3 100644 --- a/src/content/dependencies/generateAlbumSidebarGroupBox.js +++ b/src/content/dependencies/generateAlbumSidebarGroupBox.js @@ -3,6 +3,7 @@ import {atOffset, empty} from '#sugar'; export default { contentDependencies: [ + 'generatePageSidebarBox', 'linkAlbum', 'linkExternal', 'linkGroup', @@ -40,6 +41,9 @@ export default { relations(relation, query, album, group) { const relations = {}; + relations.box = + relation('generatePageSidebarBox'); + relations.groupLink = relation('linkGroup', group); @@ -72,39 +76,41 @@ export default { }, }, - generate(relations, slots, {html, language}) { - return html.tags([ - html.tag('h1', - language.$('albumSidebar.groupBox.title', { - group: relations.groupLink, - })), - - slots.mode === 'album' && - 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'))), - })), - - slots.mode === 'album' && - relations.nextAlbumLink && - html.tag('p', {class: 'group-chronology-link'}, - language.$('albumSidebar.groupBox.next', { - album: relations.nextAlbumLink, + generate: (relations, slots, {html, language}) => + relations.box.slots({ + attributes: {class: 'individual-group-sidebar-box'}, + content: [ + html.tag('h1', + language.$('albumSidebar.groupBox.title', { + group: relations.groupLink, })), - slots.mode === 'album' && - relations.previousAlbumLink && - html.tag('p', {class: 'group-chronology-link'}, - language.$('albumSidebar.groupBox.previous', { - album: relations.previousAlbumLink, - })), - ]); - }, + slots.mode === 'album' && + 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'))), + })), + + slots.mode === 'album' && + relations.nextAlbumLink && + html.tag('p', {class: 'group-chronology-link'}, + language.$('albumSidebar.groupBox.next', { + album: relations.nextAlbumLink, + })), + + slots.mode === 'album' && + relations.previousAlbumLink && + html.tag('p', {class: 'group-chronology-link'}, + language.$('albumSidebar.groupBox.previous', { + album: relations.previousAlbumLink, + })), + ], + }), }; diff --git a/src/content/dependencies/generateAlbumSidebarTrackListBox.js b/src/content/dependencies/generateAlbumSidebarTrackListBox.js new file mode 100644 index 0000000..3a244e3 --- /dev/null +++ b/src/content/dependencies/generateAlbumSidebarTrackListBox.js @@ -0,0 +1,31 @@ +export default { + contentDependencies: [ + 'generateAlbumSidebarTrackSection', + 'generatePageSidebarBox', + 'linkAlbum', + ], + + extraDependencies: ['html'], + + relations: (relation, album, track) => ({ + box: + relation('generatePageSidebarBox'), + + albumLink: + relation('linkAlbum', album), + + trackSections: + album.trackSections.map(trackSection => + relation('generateAlbumSidebarTrackSection', album, track, trackSection)), + }), + + generate: (relations, {html}) => + relations.box.slots({ + attributes: {class: 'track-list-sidebar-box'}, + + content: [ + html.tag('h1', relations.albumLink), + relations.trackSections, + ], + }) +}; diff --git a/src/content/dependencies/generateAlbumTrackListItem.js b/src/content/dependencies/generateAlbumTrackListItem.js index 11b6a1b..7190fb4 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; @@ -119,9 +119,11 @@ export default { parts.push('withArtists'); options.by = html.tag('span', {class: 'by'}, - language.$('trackList.item.withArtists.by', { - artists: language.formatConjunctionList(relations.contributionLinks), - })); + html.metatag('chunkwrap', {split: ','}, + html.resolve( + language.$('trackList.item.withArtists.by', { + artists: language.formatConjunctionList(relations.contributionLinks), + })))); } return html.tag('li', diff --git a/src/content/dependencies/generateArtTagGalleryPage.js b/src/content/dependencies/generateArtTagGalleryPage.js index 962f1b7..eae48f0 100644 --- a/src/content/dependencies/generateArtTagGalleryPage.js +++ b/src/content/dependencies/generateArtTagGalleryPage.js @@ -74,10 +74,13 @@ export default { ? ['media.trackCover', thing.album.directory, thing.directory, thing.coverArtFileExtension] : ['media.albumCover', thing.directory, thing.coverArtFileExtension])); + data.dimensions = + query.things.map(thing => thing.coverArtDimensions); + data.coverArtists = query.things.map(thing => thing.coverArtistContribs - .map(({who: artist}) => artist.name)); + .map(({artist}) => artist.name)); return data; }, @@ -111,8 +114,12 @@ export default { stitchArrays({ image: relations.images, path: data.paths, - }).map(({image, path}) => - image.slot('path', path)), + dimensions: data.dimensions, + }).map(({image, path, dimensions}) => + image.slots({ + path, + dimensions, + })), info: data.coverArtists.map(names => diff --git a/src/content/dependencies/generateArtistGalleryPage.js b/src/content/dependencies/generateArtistGalleryPage.js index 1377915..db8f123 100644 --- a/src/content/dependencies/generateArtistGalleryPage.js +++ b/src/content/dependencies/generateArtistGalleryPage.js @@ -68,12 +68,15 @@ export default { ? ['media.trackCover', thing.album.directory, thing.directory, thing.coverArtFileExtension] : ['media.albumCover', thing.directory, thing.coverArtFileExtension])); + data.dimensions = + query.things.map(thing => thing.coverArtDimensions); + data.otherCoverArtists = 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; @@ -107,8 +110,12 @@ export default { stitchArrays({ image: relations.images, path: data.paths, - }).map(({image, path}) => - image.slot('path', path)), + dimensions: data.dimensions, + }).map(({image, path, dimensions}) => + image.slots({ + path, + dimensions, + })), info: data.otherCoverArtists.map(names => diff --git a/src/content/dependencies/generateArtistGroupContributionsInfo.js b/src/content/dependencies/generateArtistGroupContributionsInfo.js index a51f516..1725d4b 100644 --- a/src/content/dependencies/generateArtistGroupContributionsInfo.js +++ b/src/content/dependencies/generateArtistGroupContributionsInfo.js @@ -45,6 +45,7 @@ export default { const groupsSortedByCount = allGroupsOrdered + .slice() .sort((a, b) => groupToCountMap.get(b) - groupToCountMap.get(a)); // The filter here ensures all displayed groups have at least some duration @@ -188,16 +189,16 @@ export default { data.groupDurationsSortedByCount, data.groupDurationsApproximateSortedByCount), }).map(({group, count, duration}) => - html.tag('li', - html.tag('div', {class: 'group-contributions-row'}, [ - group, - html.tag('span', {class: 'group-contributions-metrics'}, - // When sorting by count, duration details aren't necessarily - // available for all items. - (slots.showBothColumns && duration - ? language.$('artistPage.groupContributions.item.countDurationAccent', {count, duration}) - : language.$('artistPage.groupContributions.item.countAccent', {count}))), - ]))) + html.tag('li', + html.tag('div', {class: 'group-contributions-row'}, [ + group, + html.tag('span', {class: 'group-contributions-metrics'}, + // When sorting by count, duration details aren't necessarily + // available for all items. + (slots.showBothColumns && duration + ? language.$('artistPage.groupContributions.item.countDurationAccent', {count, duration}) + : language.$('artistPage.groupContributions.item.countAccent', {count}))), + ]))) : stitchArrays({ group: relations.groupLinksSortedByDuration, @@ -205,19 +206,19 @@ export default { duration: getDurations( data.groupDurationsSortedByDuration, - data.groupDurationsApproximateSortedByCount), + data.groupDurationsApproximateSortedByDuration), }).map(({group, count, duration}) => - html.tag('li', - html.tag('div', {class: 'group-contributions-row'}, [ - group, - html.tag('span', {class: 'group-contributions-metrics'}, - // Count details are always available, since they're just the - // number of contributions directly. And duration details are - // guaranteed for every item when sorting by duration. - (slots.showBothColumns - ? language.$('artistPage.groupContributions.item.durationCountAccent', {duration, count}) - : language.$('artistPage.groupContributions.item.durationAccent', {duration}))), - ])))))), + html.tag('li', + html.tag('div', {class: 'group-contributions-row'}, [ + group, + html.tag('span', {class: 'group-contributions-metrics'}, + // Count details are always available, since they're just the + // number of contributions directly. And duration details are + // guaranteed for every item when sorting by duration. + (slots.showBothColumns + ? language.$('artistPage.groupContributions.item.durationCountAccent', {duration, count}) + : language.$('artistPage.groupContributions.item.durationAccent', {duration}))), + ])))))), ]); }, }; diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js index 1b85680..ac9209a 100644 --- a/src/content/dependencies/generateArtistInfoPage.js +++ b/src/content/dependencies/generateArtistInfoPage.js @@ -163,11 +163,8 @@ export default { language.$('releaseInfo.visitOn', { links: language.formatDisjunctionList( - sec.visit.externalLinks.map(link => - link.slots({ - context: 'artist', - style: 'platform', - }))), + sec.visit.externalLinks + .map(link => link.slot('context', 'artist'))), })), sec.artworks?.artistGalleryLink && diff --git a/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js b/src/content/dependencies/generateArtistInfoPageArtworksChunkedList.js index 0beeb27..44fb42f 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/generateArtistInfoPageCommentaryChunkedList.js b/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js index 0bcadc7..133095e 100644 --- a/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js +++ b/src/content/dependencies/generateArtistInfoPageCommentaryChunkedList.js @@ -1,12 +1,19 @@ -import {sortAlbumsTracksChronologically, sortEntryThingPairs} from '#sort'; import {chunkByProperties, stitchArrays} from '#sugar'; +import { + sortAlbumsTracksChronologically, + sortByDate, + sortEntryThingPairs, +} from '#sort'; + export default { contentDependencies: [ 'generateArtistInfoPageChunk', 'generateArtistInfoPageChunkItem', 'generateArtistInfoPageOtherArtistLinks', 'linkAlbum', + 'linkFlash', + 'linkFlashAct', 'linkTrack', 'transformContent', ], @@ -14,34 +21,68 @@ export default { extraDependencies: ['html', 'language'], query(artist) { - const processEntry = ({thing, entry, type, track, album}) => ({ + const processEntry = ({ + thing, + entry, + + chunkType, + itemType, + + album = null, + track = null, + flashAct = null, + flash = null, + }) => ({ thing: thing, entry: { - type: type, - track: track, - album: album, + chunkType, + itemType, + + album, + track, + flashAct, + flash, + annotation: entry.annotation, }, }); - const processAlbumEntry = ({type, album, entry}) => + const processAlbumEntry = ({thing: album, entry}) => processEntry({ thing: album, entry: entry, - type: type, + + chunkType: 'album', + itemType: 'album', + album: album, track: null, }); - const processTrackEntry = ({type, track, entry}) => + const processTrackEntry = ({thing: track, entry}) => processEntry({ thing: track, entry: entry, - type: type, + + chunkType: 'album', + itemType: 'track', + album: track.album, track: track, }); + const processFlashEntry = ({thing: flash, entry}) => + processEntry({ + thing: flash, + entry: entry, + + chunkType: 'flash-act', + itemType: 'flash', + + flashAct: flash.act, + flash: flash, + }); + const processEntries = ({things, processEntry}) => things .flatMap(thing => @@ -49,136 +90,180 @@ export default { .filter(entry => entry.artists.includes(artist)) .map(entry => processEntry({thing, entry}))); - const processAlbumEntries = ({type, albums}) => + const processAlbumEntries = ({albums}) => processEntries({ things: albums, - processEntry: ({thing, entry}) => - processAlbumEntry({ - type: type, - album: thing, - entry: entry, - }), + processEntry: processAlbumEntry, }); - const processTrackEntries = ({type, tracks}) => + const processTrackEntries = ({tracks}) => processEntries({ things: tracks, - processEntry: ({thing, entry}) => - processTrackEntry({ - type: type, - track: thing, - entry: entry, - }), + processEntry: processTrackEntry, }); - const {albumsAsCommentator, tracksAsCommentator} = artist; - - const trackEntries = - processTrackEntries({ - type: 'track', - tracks: tracksAsCommentator, + const processFlashEntries = ({flashes}) => + processEntries({ + things: flashes, + processEntry: processFlashEntry, }); + const { + albumsAsCommentator, + tracksAsCommentator, + flashesAsCommentator, + } = artist; + const albumEntries = processAlbumEntries({ - type: 'album', albums: albumsAsCommentator, }); - const entries = [ - ...albumEntries, - ...trackEntries, - ]; + const trackEntries = + processTrackEntries({ + tracks: tracksAsCommentator, + }); - sortEntryThingPairs(entries, sortAlbumsTracksChronologically); + const flashEntries = + processFlashEntries({ + flashes: flashesAsCommentator, + }) + + const albumTrackEntries = + sortEntryThingPairs( + [...albumEntries, ...trackEntries], + sortAlbumsTracksChronologically); + + const allEntries = + sortEntryThingPairs( + [...albumTrackEntries, ...flashEntries], + sortByDate); const chunks = chunkByProperties( - entries.map(({entry}) => entry), - ['album']); + allEntries.map(({entry}) => entry), + ['chunkType', 'album', 'flashAct']); return {chunks}; }, - relations(relation, query) { - return { - chunks: - query.chunks.map(() => relation('generateArtistInfoPageChunk')), + relations: (relation, query) => ({ + chunks: + query.chunks + .map(() => relation('generateArtistInfoPageChunk')), - albumLinks: - query.chunks.map(({album}) => relation('linkAlbum', album)), + chunkLinks: + query.chunks + .map(({chunkType, album, flashAct}) => + (chunkType === 'album' + ? relation('linkAlbum', album) + : chunkType === 'flash-act' + ? relation('linkFlashAct', flashAct) + : null)), - items: - query.chunks.map(({chunk}) => - chunk.map(() => relation('generateArtistInfoPageChunkItem'))), + items: + query.chunks + .map(({chunk}) => chunk + .map(() => relation('generateArtistInfoPageChunkItem'))), - itemTrackLinks: - query.chunks.map(({chunk}) => - chunk.map(({track}) => + itemLinks: + query.chunks + .map(({chunk}) => chunk + .map(({track, flash}) => (track ? relation('linkTrack', track) + : flash + ? relation('linkFlash', flash) : null))), - itemAnnotations: - query.chunks.map(({chunk}) => - chunk.map(({annotation}) => + itemAnnotations: + query.chunks + .map(({chunk}) => chunk + .map(({annotation}) => (annotation ? relation('transformContent', annotation) : null))), - }; - }, + }), - data(query) { - return { - itemTypes: - query.chunks.map(({chunk}) => - chunk.map(({type}) => type)), - }; - }, + data: (query) => ({ + chunkTypes: + query.chunks + .map(({chunkType}) => chunkType), - generate(data, relations, {html, language}) { - return html.tag('dl', + itemTypes: + query.chunks + .map(({chunk}) => chunk + .map(({itemType}) => itemType)), + }), + + generate: (data, relations, {html, language}) => + html.tag('dl', stitchArrays({ chunk: relations.chunks, - albumLink: relations.albumLinks, + chunkLink: relations.chunkLinks, + chunkType: data.chunkTypes, items: relations.items, - itemTrackLinks: relations.itemTrackLinks, + itemLinks: relations.itemLinks, itemAnnotations: relations.itemAnnotations, itemTypes: data.itemTypes, }).map(({ chunk, - albumLink, + chunkLink, + chunkType, items, - itemTrackLinks, + itemLinks, itemAnnotations, itemTypes, }) => - chunk.slots({ - mode: 'album', - albumLink, - items: - stitchArrays({ - item: items, - trackLink: itemTrackLinks, - annotation: itemAnnotations, - type: itemTypes, - }).map(({item, trackLink, annotation, type}) => - item.slots({ - annotation: - (annotation - ? annotation.slot('mode', 'inline') - : null), - - content: - (type === 'album' - ? html.tag('i', - language.$('artistPage.creditList.entry.album.commentary')) - : language.$('artistPage.creditList.entry.track', { - track: trackLink, - })), - })), - }))); - }, + (chunkType === 'album' + ? chunk.slots({ + mode: 'album', + albumLink: chunkLink, + items: + stitchArrays({ + item: items, + link: itemLinks, + annotation: itemAnnotations, + type: itemTypes, + }).map(({item, link, annotation, type}) => + item.slots({ + annotation: + (annotation + ? annotation.slot('mode', 'inline') + : null), + + content: + (type === 'album' + ? html.tag('i', + language.$('artistPage.creditList.entry.album.commentary')) + : language.$('artistPage.creditList.entry.track', { + track: link, + })), + })), + }) + : chunkType === 'flash-act' + ? chunk.slots({ + mode: 'flash', + flashActLink: chunkLink, + items: + stitchArrays({ + item: items, + link: itemLinks, + annotation: itemAnnotations, + }).map(({item, link, annotation}) => + item.slots({ + annotation: + (annotation + ? annotation.slot('mode', 'inline') + : null), + + content: + language.$('artistPage.creditList.entry.flash', { + flash: link, + }), + })), + }) + : null))), }; diff --git a/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js b/src/content/dependencies/generateArtistInfoPageFlashesChunkedList.js index 88a97af..447e697 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 dea7742..471ee26 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 f003779..bce6ced 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/generateCoverArtwork.js b/src/content/dependencies/generateCoverArtwork.js index d0941d2..90c9db9 100644 --- a/src/content/dependencies/generateCoverArtwork.js +++ b/src/content/dependencies/generateCoverArtwork.js @@ -59,9 +59,23 @@ export default { validate: v => v.is('primary', 'thumbnail', 'commentary'), default: 'primary', }, + + dimensions: { + validate: v => v.isDimensions, + }, }, generate(data, relations, slots, {html}) { + const square = + (slots.dimensions + ? slots.dimensions[0] === slots.dimensions[1] + : true); + + const sizeSlots = + (square + ? {square: true} + : {dimensions: slots.dimensions}); + switch (slots.mode) { case 'primary': return html.tags([ @@ -72,7 +86,7 @@ export default { thumb: 'medium', reveal: true, link: true, - square: true, + ...sizeSlots, }), !empty(relations.tagLinks) && @@ -93,7 +107,7 @@ export default { thumb: 'small', reveal: false, link: false, - square: true, + ...sizeSlots, }); case 'commentary': @@ -104,8 +118,8 @@ export default { thumb: 'medium', reveal: true, link: true, - square: true, lazy: true, + ...sizeSlots, attributes: {class: 'commentary-art'}, diff --git a/src/content/dependencies/generateFlashActGalleryPage.js b/src/content/dependencies/generateFlashActGalleryPage.js index 8eea58b..1707812 100644 --- a/src/content/dependencies/generateFlashActGalleryPage.js +++ b/src/content/dependencies/generateFlashActGalleryPage.js @@ -85,7 +85,7 @@ export default { navBottomRowContent: relations.flashActNavAccent, - ...relations.sidebar, + leftSidebar: relations.sidebar, }); }, }; diff --git a/src/content/dependencies/generateFlashActSidebar.js b/src/content/dependencies/generateFlashActSidebar.js index 0bbfa1f..1421dde 100644 --- a/src/content/dependencies/generateFlashActSidebar.js +++ b/src/content/dependencies/generateFlashActSidebar.js @@ -1,216 +1,30 @@ -import find from '#find'; -import {filterMultipleArrays, stitchArrays} from '#sugar'; - export default { - contentDependencies: ['linkFlash', 'linkFlashAct', 'linkFlashIndex'], - extraDependencies: ['getColors', 'html', 'language', 'wikiData'], - - // So help me Gog, the flash sidebar is heavily hard-coded. - - sprawl: ({flashActData}) => ({flashActData}), - - query(sprawl, act, flash) { - const findFlashAct = directory => - find.flashAct(directory, sprawl.flashActData, {mode: 'quiet'}); - - const homestuckSide1 = findFlashAct('flash-act:a1'); - - const sideFirstActs = [ - sprawl.flashActData[0], - findFlashAct('flash-act:a6a1'), - findFlashAct('flash-act:hiveswap'), - findFlashAct('flash-act:cool-and-new-web-comic'), - findFlashAct('flash-act:sunday-night-strifin'), - ]; - - const sideNames = [ - (homestuckSide1 - ? `Side 1 (Acts 1-5)` - : `All flashes & games`), - `Side 2 (Acts 6-7)`, - `Additional Canon`, - `Fan Adventures`, - `Fan Games & More`, - ]; - - const sideColors = [ - (homestuckSide1 - ? '#4ac925' - : null), - '#3796c6', - '#f2a400', - '#c466ff', - '#32c7fe', - ]; - - filterMultipleArrays(sideFirstActs, sideNames, sideColors, - firstAct => firstAct); - - const sideFirstActIndexes = - sideFirstActs - .map(act => sprawl.flashActData.indexOf(act)); - - const actSideIndexes = - sprawl.flashActData - .map((act, actIndex) => actIndex) - .map(actIndex => - sideFirstActIndexes - .findIndex((firstActIndex, i) => - i === sideFirstActs.length - 1 || - firstActIndex <= actIndex && - sideFirstActIndexes[i + 1] > actIndex)); - - const sideActs = - sideNames - .map((name, sideIndex) => - stitchArrays({ - act: sprawl.flashActData, - actSideIndex: actSideIndexes, - }).filter(({actSideIndex}) => actSideIndex === sideIndex) - .map(({act}) => act)); - - const currentActFlashes = - act.flashes; - - const currentFlashIndex = - currentActFlashes.indexOf(flash); - - const currentSideIndex = - actSideIndexes[sprawl.flashActData.indexOf(act)]; - - const currentSideActs = - sideActs[currentSideIndex]; - - const currentActIndex = - currentSideActs.indexOf(act); - - const fallbackListTerminology = - (currentSideIndex <= 1 - ? 'flashesInThisAct' - : 'entriesInThisSection'); - - return { - sideNames, - sideColors, - sideActs, - - currentSideIndex, - currentSideActs, - currentActIndex, - currentActFlashes, - currentFlashIndex, + contentDependencies: [ + 'generateFlashActSidebarCurrentActBox', + 'generateFlashActSidebarSideMapBox', + 'generatePageSidebar', + ], - fallbackListTerminology, - }; - }, + relations: (relation, act, flash) => ({ + sidebar: + relation('generatePageSidebar'), - relations: (relation, query, sprawl, act, _flash) => ({ - currentActLink: - relation('linkFlashAct', act), + currentActBox: + relation('generateFlashActSidebarCurrentActBox', act, flash), - flashIndexLink: - relation('linkFlashIndex'), - - sideActLinks: - query.sideActs - .map(acts => acts - .map(act => relation('linkFlashAct', act))), - - currentActFlashLinks: - act.flashes - .map(flash => relation('linkFlash', flash)), + sideMapBox: + relation('generateFlashActSidebarSideMapBox', act, flash), }), - data: (query, sprawl, act, flash) => ({ + data: (_act, flash) => ({ isFlashActPage: !flash, - - sideColors: query.sideColors, - sideNames: query.sideNames, - - currentSideIndex: query.currentSideIndex, - currentActIndex: query.currentActIndex, - currentFlashIndex: query.currentFlashIndex, - - customListTerminology: act.listTerminology, - fallbackListTerminology: query.fallbackListTerminology, }), - generate(data, relations, {getColors, html, language}) { - const currentActBoxContent = html.tags([ - html.tag('h1', relations.currentActLink), - - html.tag('details', - (data.isFlashActPage - ? {} - : {class: 'current', open: true}), - [ - html.tag('summary', - html.tag('span', {class: 'group-name'}, - (data.customListTerminology - ? language.sanitize(data.customListTerminology) - : language.$('flashSidebar.flashList', data.fallbackListTerminology)))), - - html.tag('ul', - relations.currentActFlashLinks - .map((flashLink, index) => - html.tag('li', - index === data.currentFlashIndex && - {class: 'current'}, - - flashLink))), - ]), - ]); - - const sideMapBoxContent = html.tags([ - html.tag('h1', relations.flashIndexLink), - - stitchArrays({ - sideName: data.sideNames, - sideColor: data.sideColors, - actLinks: relations.sideActLinks, - }).map(({sideName, sideColor, actLinks}, sideIndex) => - html.tag('details', - sideIndex === data.currentSideIndex && - {class: 'current'}, - - data.isFlashActPage && - sideIndex === data.currentSideIndex && - {open: true}, - - sideColor && - {style: `--primary-color: ${getColors(sideColor).primary}`}, - - [ - html.tag('summary', - html.tag('span', {class: 'group-name'}, - sideName)), - - html.tag('ul', - actLinks.map((actLink, actIndex) => - html.tag('li', - sideIndex === data.currentSideIndex && - actIndex === data.currentActIndex && - {class: 'current'}, - - actLink))), - ])), - ]); - - const sideMapBox = { - class: 'flash-act-map-sidebar-box', - content: sideMapBoxContent, - }; - - const currentActBox = { - class: 'flash-current-act-sidebar-box', - content: currentActBoxContent, - }; - - return { - leftSidebarMultiple: + generate: (data, relations) => + relations.sidebar.slots({ + boxes: (data.isFlashActPage - ? [sideMapBox, currentActBox] - : [currentActBox, sideMapBox]), - }; - }, + ? [relations.sideMapBox, relations.currentActBox] + : [relations.currentActBox, relations.sideMapBox]), + }), }; diff --git a/src/content/dependencies/generateFlashActSidebarCurrentActBox.js b/src/content/dependencies/generateFlashActSidebarCurrentActBox.js new file mode 100644 index 0000000..c5426a4 --- /dev/null +++ b/src/content/dependencies/generateFlashActSidebarCurrentActBox.js @@ -0,0 +1,63 @@ +export default { + contentDependencies: [ + 'generatePageSidebarBox', + 'linkFlash', + 'linkFlashAct', + ], + + extraDependencies: ['html', 'language'], + + relations: (relation, act, _flash) => ({ + box: + relation('generatePageSidebarBox'), + + actLink: + relation('linkFlashAct', act), + + flashLinks: + act.flashes + .map(flash => relation('linkFlash', flash)), + }), + + data: (act, flash) => ({ + isFlashActPage: + !flash, + + currentFlashIndex: + act.flashes.indexOf(flash), + + customListTerminology: + act.listTerminology, + }), + + generate: (data, relations, {html, language}) => + relations.box.slots({ + attributes: {class: 'flash-act-map-sidebar-box'}, + + content: [ + html.tag('h1', relations.actLink), + + html.tag('details', + (data.isFlashActPage + ? {} + : {class: 'current', open: true}), + + [ + html.tag('summary', + html.tag('span', {class: 'group-name'}, + (data.customListTerminology + ? language.sanitize(data.customListTerminology) + : language.$('flashSidebar.flashList.entriesInThisSection')))), + + html.tag('ul', + relations.flashLinks + .map((flashLink, index) => + html.tag('li', + index === data.currentFlashIndex && + {class: 'current'}, + + flashLink))), + ]), + ], + }), +}; diff --git a/src/content/dependencies/generateFlashActSidebarSideMapBox.js b/src/content/dependencies/generateFlashActSidebarSideMapBox.js new file mode 100644 index 0000000..3d261ec --- /dev/null +++ b/src/content/dependencies/generateFlashActSidebarSideMapBox.js @@ -0,0 +1,85 @@ +import {stitchArrays} from '#sugar'; + +export default { + contentDependencies: [ + 'generateColorStyleAttribute', + 'generatePageSidebarBox', + 'linkFlashAct', + 'linkFlashIndex', + ], + + extraDependencies: ['html', 'wikiData'], + + sprawl: ({flashSideData}) => ({flashSideData}), + + relations: (relation, sprawl, _act, _flash) => ({ + box: + relation('generatePageSidebarBox'), + + flashIndexLink: + relation('linkFlashIndex'), + + sideColorStyles: + sprawl.flashSideData + .map(side => relation('generateColorStyleAttribute', side.color)), + + sideActLinks: + sprawl.flashSideData + .map(side => side.acts + .map(act => relation('linkFlashAct', act))), + }), + + data: (sprawl, act, flash) => ({ + isFlashActPage: + !flash, + + sideNames: + sprawl.flashSideData + .map(side => side.name), + + currentSideIndex: + sprawl.flashSideData.indexOf(act.side), + + currentActIndex: + act.side.acts.indexOf(act), + }), + + generate: (data, relations, {html}) => + relations.box.slots({ + attributes: {class: 'flash-act-map-sidebar-box'}, + + content: [ + html.tag('h1', relations.flashIndexLink), + + stitchArrays({ + sideName: data.sideNames, + sideColorStyle: relations.sideColorStyles, + actLinks: relations.sideActLinks, + }).map(({sideName, sideColorStyle, actLinks}, sideIndex) => + html.tag('details', + sideIndex === data.currentSideIndex && + {class: 'current'}, + + data.isFlashActPage && + sideIndex === data.currentSideIndex && + {open: true}, + + sideColorStyle.slot('context', 'primary-only'), + + [ + html.tag('summary', + html.tag('span', {class: 'group-name'}, + sideName)), + + html.tag('ul', + actLinks.map((actLink, actIndex) => + html.tag('li', + sideIndex === data.currentSideIndex && + actIndex === data.currentActIndex && + {class: 'current'}, + + actLink))), + ])), + ], + }), +}; diff --git a/src/content/dependencies/generateFlashIndexPage.js b/src/content/dependencies/generateFlashIndexPage.js index 57072a1..36bfaba 100644 --- a/src/content/dependencies/generateFlashIndexPage.js +++ b/src/content/dependencies/generateFlashIndexPage.js @@ -20,7 +20,7 @@ export default { const jumpActs = flashActs - .filter(act => act.jump); + .filter(act => act.side.acts.indexOf(act) === 0); return {flashActs, jumpActs}; }, @@ -31,7 +31,7 @@ export default { jumpLinkColorStyles: query.jumpActs - .map(act => relation('generateColorStyleAttribute', act.jumpColor)), + .map(act => relation('generateColorStyleAttribute', act.side.color)), actColorStyles: query.flashActs @@ -63,7 +63,7 @@ export default { jumpLinkLabels: query.jumpActs - .map(act => act.jump), + .map(act => act.side.name), actAnchors: query.flashActs diff --git a/src/content/dependencies/generateFlashInfoPage.js b/src/content/dependencies/generateFlashInfoPage.js index c60f969..0596493 100644 --- a/src/content/dependencies/generateFlashInfoPage.js +++ b/src/content/dependencies/generateFlashInfoPage.js @@ -2,6 +2,7 @@ import {empty} from '#sugar'; export default { contentDependencies: [ + 'generateCommentarySection', 'generateContentHeading', 'generateContributionList', 'generateFlashActSidebar', @@ -89,6 +90,13 @@ export default { relation('generateContributionList', flash.contributorContribs); } + // Section: Artist commentary + + if (flash.commentary) { + sections.artistCommentary = + relation('generateCommentarySection', flash.commentary); + } + return relations; }, @@ -136,6 +144,19 @@ export default { .map(link => link.slot('context', 'flash'))), })), + html.tag('p', + {[html.onlyIfContent]: true}, + {[html.joinChildren]: html.tag('br')}, + + [ + sec.artistCommentary && + language.$('releaseInfo.readCommentary', { + link: html.tag('a', + {href: '#artist-commentary'}, + language.$('releaseInfo.readCommentary.link')), + }), + ]), + sec.featuredTracks && [ sec.featuredTracks.heading .slots({ @@ -158,6 +179,8 @@ export default { sec.contributors.list, ], + + sec.artistCommentary, ], navLinkStyle: 'hierarchical', @@ -169,7 +192,7 @@ export default { navBottomRowContent: sec.nav.flashNavAccent, - ...relations.sidebar, + leftSidebar: relations.sidebar, }); }, }; diff --git a/src/content/dependencies/generateGroupGalleryPage.js b/src/content/dependencies/generateGroupGalleryPage.js index b29c586..d07847c 100644 --- a/src/content/dependencies/generateGroupGalleryPage.js +++ b/src/content/dependencies/generateGroupGalleryPage.js @@ -178,10 +178,12 @@ export default { }), ], - ... - relations.sidebar - ?.slot('currentExtra', 'gallery') - ?.content, + leftSidebar: + (relations.sidebar + ? relations.sidebar + .slot('currentExtra', 'gallery') + .content /* TODO: Kludge. */ + : null), navLinkStyle: 'hierarchical', navLinks: diff --git a/src/content/dependencies/generateGroupInfoPage.js b/src/content/dependencies/generateGroupInfoPage.js index 2e1d168..b5b456a 100644 --- a/src/content/dependencies/generateGroupInfoPage.js +++ b/src/content/dependencies/generateGroupInfoPage.js @@ -207,7 +207,11 @@ export default { ], ], - ...relations.sidebar?.content ?? {}, + leftSidebar: + (relations.sidebar + ? relations.sidebar + .content /* TODO: Kludge. */ + : null), navLinkStyle: 'hierarchical', navLinks: relations.navLinks.content, diff --git a/src/content/dependencies/generateGroupSecondaryNav.js b/src/content/dependencies/generateGroupSecondaryNav.js index 17eb508..a4f8131 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 98b288f..0888cbb 100644 --- a/src/content/dependencies/generateGroupSidebar.js +++ b/src/content/dependencies/generateGroupSidebar.js @@ -1,18 +1,25 @@ export default { - contentDependencies: ['generateGroupSidebarCategoryDetails'], + contentDependencies: [ + 'generateGroupSidebarCategoryDetails', + 'generatePageSidebar', + 'generatePageSidebarBox', + ], + extraDependencies: ['html', 'language', 'wikiData'], - sprawl({groupCategoryData}) { - return {groupCategoryData}; - }, + sprawl: ({groupCategoryData}) => ({groupCategoryData}), - relations(relation, sprawl, group) { - return { - categoryDetails: - sprawl.groupCategoryData.map(category => - relation('generateGroupSidebarCategoryDetails', category, group)), - }; - }, + relations: (relation, sprawl, group) => ({ + sidebar: + relation('generatePageSidebar'), + + sidebarBox: + relation('generatePageSidebarBox'), + + categoryDetails: + sprawl.groupCategoryData.map(category => + relation('generateGroupSidebarCategoryDetails', category, group)), + }), slots: { currentExtra: { @@ -20,17 +27,20 @@ export default { }, }, - generate(relations, slots, {html, language}) { - return { - leftSidebarClass: 'category-map-sidebar-box', - leftSidebarContent: [ - html.tag('h1', - language.$('groupSidebar.title')), + generate: (relations, slots, {html, language}) => + relations.sidebar.slots({ + 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)), + relations.categoryDetails + .map(details => + details.slot('currentExtra', slots.currentExtra)), + ], + }), ], - }; - }, + }), }; diff --git a/src/content/dependencies/generateListAllAdditionalFilesChunk.js b/src/content/dependencies/generateListAllAdditionalFilesChunk.js index b046cca..43a78cb 100644 --- a/src/content/dependencies/generateListAllAdditionalFilesChunk.js +++ b/src/content/dependencies/generateListAllAdditionalFilesChunk.js @@ -51,6 +51,12 @@ export default { }), })) + : additionalFileLinks.length === 0 + ? html.tag('li', + language.$('listingPage', slots.stringsKey, 'file.withNoFiles', { + title: additionalFileTitle, + })) + : html.tag('li', {class: 'has-details'}, html.tag('details', [ html.tag('summary', diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index aa661ab..23377af 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -276,7 +276,7 @@ export default { {auto: 'current'}, ], - ...relations.sidebar, + leftSidebar: relations.sidebar, }); }, }; diff --git a/src/content/dependencies/generateListingSidebar.js b/src/content/dependencies/generateListingSidebar.js index 1cdd236..aeac05c 100644 --- a/src/content/dependencies/generateListingSidebar.js +++ b/src/content/dependencies/generateListingSidebar.js @@ -1,21 +1,37 @@ export default { - contentDependencies: ['generateListingIndexList', 'linkListingIndex'], + contentDependencies: [ + 'generateListingIndexList', + 'generatePageSidebar', + 'generatePageSidebarBox', + 'linkListingIndex', + ], + extraDependencies: ['html'], - relations(relation, currentListing) { - return { - listingIndexLink: relation('linkListingIndex'), - listingIndexList: relation('generateListingIndexList', currentListing), - }; - }, + relations: (relation, currentListing) => ({ + sidebar: + relation('generatePageSidebar'), + + sidebarBox: + relation('generatePageSidebarBox'), + + listingIndexLink: + relation('linkListingIndex'), + + listingIndexList: + relation('generateListingIndexList', currentListing), + }), - generate(relations, {html}) { - return { - leftSidebarClass: 'listing-map-sidebar-box', - leftSidebarContent: [ - html.tag('h1', relations.listingIndexLink), - relations.listingIndexList.slot('mode', 'sidebar'), + generate: (relations, {html}) => + relations.sidebar.slots({ + 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/generateListingsIndexPage.js b/src/content/dependencies/generateListingsIndexPage.js index 1b1c855..b57ebe1 100644 --- a/src/content/dependencies/generateListingsIndexPage.js +++ b/src/content/dependencies/generateListingsIndexPage.js @@ -83,7 +83,7 @@ export default { {auto: 'current'}, ], - ...relations.sidebar, + leftSidebar: relations.sidebar, }); }, }; diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js index 9e9b461..51f9057 100644 --- a/src/content/dependencies/generatePageLayout.js +++ b/src/content/dependencies/generatePageLayout.js @@ -1,60 +1,6 @@ import {openAggregate} from '#aggregate'; import {empty} from '#sugar'; -function sidebarSlots(side) { - return { - // Content is a flat HTML array. It'll generate one sidebar section - // if specified. - [side + 'Content']: { - type: 'html', - mutable: false, - }, - - // A single class to apply to the whole sidebar. If specifying multiple - // sections, this be added to the containing sidebar-column - specify a - // class on each section if that's more suitable. - [side + 'Class']: {type: 'string'}, - - // Multiple is an array of objects, each specifying content (HTML) and - // optionally class (a string). Each of these will generate one sidebar - // section. - [side + 'Multiple']: { - validate: v => - v.sparseArrayOf( - v.validateProperties({ - class: v.optional(v.isString), - content: v.isHTML, - })), - }, - - // Sticky mode controls which sidebar section(s), 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 - // 'none' - 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). - [side + 'StickyMode']: { - validate: v => v.is('last', '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. - [side + '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. - [side + 'Wide']: {type: 'boolean', defualt: false}, - }; -} - export default { contentDependencies: [ 'generateColorStyleRules', @@ -162,8 +108,15 @@ export default { // Sidebars - ...sidebarSlots('leftSidebar'), - ...sidebarSlots('rightSidebar'), + leftSidebar: { + type: 'html', + mutable: true, + }, + + rightSidebar: { + type: 'html', + mutable: true, + }, // Banner @@ -266,6 +219,14 @@ export default { const colors = getColors(slots.color ?? data.wikiColor); const hasSocialEmbed = !html.isBlank(slots.socialEmbed); + // Hilariously jank. Sorry! We're going to need this content later ANYWAY, + // so it's "fine" to stringify it here, but this DOES mean that we're + // stringifying (and resolving) the content without the context that it's + // e.g. going to end up in a page HTML hierarchy. Might have implications + // later, mainly for: https://github.com/hsmusic/hsmusic-wiki/issues/434 + const mainContentHTML = html.tags([slots.mainContent]).toString(); + const hasID = id => mainContentHTML.includes(`id="${id}"`); + const titleContentsHTML = (html.isBlank(slots.title) ? null @@ -289,8 +250,11 @@ export default { let footerContent = slots.footerContent; if (html.isBlank(footerContent) && relations.defaultFooterContent) { - footerContent = relations.defaultFooterContent - .slot('mode', 'multiline'); + footerContent = + relations.defaultFooterContent.slots({ + mode: 'multiline', + indicateExternalLinks: false, + }); } const mainHTML = @@ -308,7 +272,7 @@ export default { html.tag('div', {class: 'main-content-container'}, {[html.onlyIfContent]: true}, - slots.mainContent), + mainContentHTML), ]); const footerHTML = @@ -384,9 +348,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, @@ -413,64 +374,21 @@ export default { slots.navContent), ]); - const generateSidebarHTML = (side, id) => { - const content = slots[side + 'Content']; - const topClass = slots[side + 'Class']; - const multiple = slots[side + 'Multiple']; - const stickyMode = slots[side + 'StickyMode']; - const wide = slots[side + 'Wide']; - const collapse = slots[side + 'Collapse']; - - let sidebarClasses = []; - let sidebarContent = html.blank(); - - if (!html.isBlank(content)) { - sidebarClasses = ['sidebar', topClass]; - sidebarContent = content; - } else if (multiple) { - sidebarClasses = ['sidebar-multiple', topClass]; - sidebarContent = - multiple - .filter(Boolean) - .map(box => - html.tag('div', {class: 'sidebar'}, - {[html.onlyIfContent]: true}, - {class: box.class}, - box.content)); - } - - if (html.isBlank(sidebarContent)) { - return html.blank(); - } - - return html.tag('div', {class: 'sidebar-column'}, - {id, class: sidebarClasses}, - - wide && - {class: 'wide'}, - - !collapse && - {class: 'no-hide'}, - - stickyMode !== 'static' && - {class: `sticky-${stickyMode}`}, - - sidebarContent); - } - - const sidebarLeftHTML = generateSidebarHTML('leftSidebar', 'sidebar-left'); - const sidebarRightHTML = generateSidebarHTML('rightSidebar', 'sidebar-right'); - - const hasSidebarLeft = !html.isBlank(sidebarLeftHTML); - const hasSidebarRight = !html.isBlank(sidebarRightHTML); + const getSidebar = (side, id) => + (html.isBlank(slots[side]) + ? html.blank() + : slots[side].slots({ + attributes: + slots[side] + .getSlotValue('attributes') + .with({id}), + })); - const collapseSidebars = slots.leftSidebarCollapse && slots.rightSidebarCollapse; + const leftSidebar = getSidebar('leftSidebar', 'sidebar-left'); + const rightSidebar = getSidebar('rightSidebar', 'sidebar-right'); - const hasID = (() => { - // Hilariously jank. Sorry! - const mainContentHTML = slots.mainContent.toString(); - return id => mainContentHTML.includes(`id="${id}"`); - })(); + const hasSidebarLeft = !html.isBlank(html.resolve(leftSidebar)); + const hasSidebarRight = !html.isBlank(html.resolve(rightSidebar)); const processSkippers = skipperList => skipperList @@ -583,15 +501,11 @@ export default { slots.secondaryNav, - html.tag('div', {class: 'layout-columns'}, - !collapseSidebars && - {class: 'vertical-when-thin'}, - - [ - sidebarLeftHTML, - mainHTML, - sidebarRightHTML, - ]), + html.tag('div', {class: 'layout-columns'}, [ + leftSidebar, + mainHTML, + rightSidebar, + ]), slots.bannerPosition === 'bottom' && slots.banner, @@ -684,7 +598,7 @@ export default { html.tag('link', { rel: 'stylesheet', - href: to('shared.staticFile', 'site6.css', cachebust), + href: to('shared.staticFile', 'site7.css', cachebust), }), html.tag('style', [ @@ -724,7 +638,7 @@ export default { html.tag('script', { type: 'module', - src: to('shared.staticFile', 'client3.js', cachebust), + src: to('shared.staticFile', 'client4.js', cachebust), }), ]), ]) diff --git a/src/content/dependencies/generatePageSidebar.js b/src/content/dependencies/generatePageSidebar.js new file mode 100644 index 0000000..43015aa --- /dev/null +++ b/src/content/dependencies/generatePageSidebar.js @@ -0,0 +1,77 @@ +export default { + extraDependencies: ['html'], + + slots: { + // 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, + }, + + // Content boxes to line up vertically in the sidebar. + boxes: { + type: 'html', + mutable: false, + }, + + // 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'), + default: 'static', + }, + + // 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. + wide: { + type: 'boolean', + default: false, + }, + }, + + generate(slots, {html}) { + const attributes = + html.attributes({class: [ + 'sidebar-column', + 'sidebar-multiple', + ]}); + + attributes.add(slots.attributes); + + if (slots.wide) { + attributes.add('class', 'wide'); + } + + if (slots.stickyMode !== 'static') { + attributes.add('class', `sticky-${slots.stickyMode}`); + } + + 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 (html.isBlank(slots.boxes)) { + return html.blank(); + } else { + return html.tag('div', attributes, slots.boxes); + } + }, +}; diff --git a/src/content/dependencies/generatePageSidebarBox.js b/src/content/dependencies/generatePageSidebarBox.js new file mode 100644 index 0000000..e11efc3 --- /dev/null +++ b/src/content/dependencies/generatePageSidebarBox.js @@ -0,0 +1,28 @@ +export default { + extraDependencies: ['html'], + + slots: { + content: { + type: 'html', + mutable: false, + }, + + attributes: { + 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/generatePageSidebarConjoinedBox.js b/src/content/dependencies/generatePageSidebarConjoinedBox.js new file mode 100644 index 0000000..05b1d46 --- /dev/null +++ b/src/content/dependencies/generatePageSidebarConjoinedBox.js @@ -0,0 +1,42 @@ +// This component is kind of unfortunately magical. It reads the content of +// various boxes and joins them together, discarding the boxes' attributes. +// Since it requires access to the actual box *templates* (rather than those +// templates' resolved content), take care when slotting into this. + +export default { + contentDependencies: ['generatePageSidebarBox'], + extraDependencies: ['html'], + + relations: (relation) => ({ + box: + relation('generatePageSidebarBox'), + }), + + slots: { + attributes: { + type: 'attributes', + mutable: false, + }, + + boxes: { + validate: v => v.looseArrayOf(v.isTemplate), + }, + }, + + generate: (relations, slots, {html}) => + relations.box.slots({ + attributes: slots.attributes, + content: + slots.boxes.slice() + .map(box => box.getSlotValue('content')) + .map((content, index, {length}) => [ + content, + index < length - 1 && + html.tag('hr', { + style: + `border-color: var(--primary-color); ` + + `border-style: none none dotted none`, + }), + ]), + }), +}; diff --git a/src/content/dependencies/generateTrackCoverArtwork.js b/src/content/dependencies/generateTrackCoverArtwork.js index 6c056c9..a241eaf 100644 --- a/src/content/dependencies/generateTrackCoverArtwork.js +++ b/src/content/dependencies/generateTrackCoverArtwork.js @@ -17,12 +17,18 @@ export default { color: track.color, + + dimensions: + (track.hasUniqueCoverArt + ? track.coverArtDimensions + : track.album.coverArtDimensions), }), generate: (data, relations) => relations.coverArtwork.slots({ path: data.path, color: data.color, + dimensions: data.dimensions, }), }; diff --git a/src/content/dependencies/generateTrackInfoPage.js b/src/content/dependencies/generateTrackInfoPage.js index 7b70d4f..f532451 100644 --- a/src/content/dependencies/generateTrackInfoPage.js +++ b/src/content/dependencies/generateTrackInfoPage.js @@ -7,9 +7,9 @@ import getChronologyRelations from '../util/getChronologyRelations.js'; export default { contentDependencies: [ 'generateAbsoluteDatetimestamp', - 'generateAdditionalFilesShortcut', 'generateAlbumAdditionalFilesList', 'generateAlbumNavAccent', + 'generateAlbumSecondaryNav', 'generateAlbumSidebar', 'generateAlbumStyleRules', 'generateChronologyLinks', @@ -112,6 +112,9 @@ export default { relations.chronologyLinks = relation('generateChronologyLinks'); + relations.secondaryNav = + relation('generateAlbumSecondaryNav', track.album); + relations.sidebar = relation('generateAlbumSidebar', track.album, track); @@ -134,15 +137,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 +365,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', { @@ -595,7 +593,11 @@ export default { ], }), - ...relations.sidebar, + secondaryNav: + relations.secondaryNav + .slot('mode', 'track'), + + leftSidebar: relations.sidebar, socialEmbed: relations.socialEmbed, }); diff --git a/src/content/dependencies/generateTrackList.js b/src/content/dependencies/generateTrackList.js index 65f5552..3c36d24 100644 --- a/src/content/dependencies/generateTrackList.js +++ b/src/content/dependencies/generateTrackList.js @@ -44,15 +44,16 @@ export default { track: trackLink, by: html.tag('span', {class: 'by'}, - language.$('trackList.item.withArtists.by', { - artists: - language.formatConjunctionList( - contributionLinks.map(link => - link.slots({ - showContribution: slots.showContribution, - showIcons: slots.showIcons, - }))), - })), + html.metatag('chunkwrap', {split: ','}, + language.$('trackList.item.withArtists.by', { + artists: + language.formatConjunctionList( + contributionLinks.map(link => + link.slots({ + showContribution: slots.showContribution, + showIcons: slots.showIcons, + }))), + }))), })))))); }, }; diff --git a/src/content/dependencies/generateWikiHomeNewsBox.js b/src/content/dependencies/generateWikiHomeNewsBox.js index f592ab9..bd0e479 100644 --- a/src/content/dependencies/generateWikiHomeNewsBox.js +++ b/src/content/dependencies/generateWikiHomeNewsBox.js @@ -1,48 +1,53 @@ import {empty, stitchArrays} from '#sugar'; export default { - contentDependencies: ['linkNewsEntry', 'transformContent'], + contentDependencies: [ + 'generatePageSidebarBox', + 'linkNewsEntry', + 'transformContent', + ], + extraDependencies: ['html', 'language', 'wikiData'], - sprawl({newsData}) { - return { - entries: newsData.slice(0, 3), - }; - }, + sprawl: ({newsData}) => ({ + entries: + newsData.slice(0, 3), + }), - relations(relation, sprawl) { - return { - entryContents: - sprawl.entries - .map(entry => relation('transformContent', entry.contentShort)), + relations: (relation, sprawl) => ({ + box: + relation('generatePageSidebarBox'), - entryMainLinks: - sprawl.entries - .map(entry => relation('linkNewsEntry', entry)), + entryContents: + sprawl.entries + .map(entry => relation('transformContent', entry.contentShort)), - entryReadMoreLinks: - sprawl.entries - .map(entry => - entry.contentShort !== entry.content && - relation('linkNewsEntry', entry)), - }; - }, + entryMainLinks: + sprawl.entries + .map(entry => relation('linkNewsEntry', entry)), - data(sprawl) { - return { - entryDates: - sprawl.entries - .map(entry => entry.date), - } - }, + entryReadMoreLinks: + sprawl.entries + .map(entry => + entry.contentShort !== entry.content && + relation('linkNewsEntry', entry)), + }), + + data: (sprawl) => ({ + entryDates: + sprawl.entries + .map(entry => entry.date), + }), generate(data, relations, {html, language}) { if (empty(relations.entryContents)) { return html.blank(); } - return { - class: 'latest-news-sidebar-box', + return relations.box.slots({ + attributes: {class: 'latest-news-sidebar-box'}, + collapsible: false, + content: [ html.tag('h1', language.$('homepage.news.title')), @@ -77,6 +82,6 @@ export default { })), ])), ], - }; + }); }, }; diff --git a/src/content/dependencies/generateWikiHomePage.js b/src/content/dependencies/generateWikiHomePage.js index 36fcc6f..ee14a58 100644 --- a/src/content/dependencies/generateWikiHomePage.js +++ b/src/content/dependencies/generateWikiHomePage.js @@ -1,6 +1,8 @@ export default { contentDependencies: [ 'generatePageLayout', + 'generatePageSidebar', + 'generatePageSidebarBox', 'generateWikiHomeAlbumsRow', 'generateWikiHomeNewsBox', 'transformContent', @@ -22,7 +24,13 @@ export default { relations.layout = relation('generatePageLayout'); + relations.sidebar = + relation('generatePageSidebar'); + if (homepageLayout.sidebarContent) { + relations.customSidebarBox = + relation('generatePageSidebarBox'); + relations.customSidebarContent = relation('transformContent', homepageLayout.sidebarContent); } @@ -69,21 +77,24 @@ export default { relations.contentRows, ], - leftSidebarCollapse: false, - leftSidebarWide: true, + leftSidebar: + relations.sidebar.slots({ + wide: true, - leftSidebarMultiple: [ - (relations.customSidebarContent - ? { - class: 'custom-content-sidebar-box', - content: - relations.customSidebarContent - .slot('mode', 'multiline'), - } - : null), + boxes: [ + relations.customSidebarContent && + relations.customSidebarBox.slots({ + attributes: {class: 'custom-content-sidebar-box'}, + collapsible: false, - relations.newsSidebarBox ?? null, - ], + content: + relations.customSidebarContent + .slot('mode', 'multiline'), + }), + + relations.newsSidebarBox, + ], + }), navLinkStyle: 'index', navLinks: [ diff --git a/src/content/dependencies/image.js b/src/content/dependencies/image.js index db307a6..822efe3 100644 --- a/src/content/dependencies/image.js +++ b/src/content/dependencies/image.js @@ -1,4 +1,4 @@ -import {logInfo, logWarn} from '#cli'; +import {logWarn} from '#cli'; import {empty} from '#sugar'; export default { @@ -61,11 +61,14 @@ export default { reveal: {type: 'boolean', default: true}, lazy: {type: 'boolean', default: false}, + square: {type: 'boolean', default: false}, + dimensions: { + validate: v => v.isDimensions, + }, + alt: {type: 'string'}, - width: {type: 'number'}, - height: {type: 'number'}, attributes: { type: 'attributes', @@ -116,10 +119,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); @@ -134,14 +133,26 @@ export default { !isMissingImageFile && !empty(contentWarnings); - const willSquare = slots.square; + const hasBothDimensions = + !!(slots.dimensions && + slots.dimensions[0] !== null && + slots.dimensions[1] !== null); + + const willSquare = + (hasBothDimensions + ? slots.dimensions[0] === slots.dimensions[1] + : slots.square); const imgAttributes = html.attributes([ {class: 'image'}, slots.alt && {alt: slots.alt}, - slots.width && {width: slots.width}, - slots.height && {height: slots.height}, + + slots.dimensions?.[0] && + {width: slots.dimensions[0]}, + + slots.dimensions?.[1] && + {width: slots.dimensions[1]}, ]); const isPlaceholder = @@ -220,11 +231,9 @@ export default { to('thumb.path', mediaSrcJpeg); } - const dimensions = getDimensionsOfImagePath(mediaSrc); - const availableThumbs = getThumbnailsAvailableForDimensions(dimensions); - - const [width, height] = dimensions; - const originalLength = Math.max(width, height) + const originalDimensions = getDimensionsOfImagePath(mediaSrc); + const availableThumbs = getThumbnailsAvailableForDimensions(originalDimensions); + const originalLength = Math.max(originalDimensions[0], originalDimensions[1]); const fileSize = (willLink && mediaSrc diff --git a/src/content/dependencies/linkContribution.js b/src/content/dependencies/linkContribution.js index cb57aa4..1a51c38 100644 --- a/src/content/dependencies/linkContribution.js +++ b/src/content/dependencies/linkContribution.js @@ -1,4 +1,4 @@ -import {empty} from '#sugar'; +import {empty, stitchArrays} from '#sugar'; export default { contentDependencies: [ @@ -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,7 +33,8 @@ export default { data(contribution) { return { - what: contribution.what, + contribution: contribution.annotation, + urls: contribution.artist.urls, }; }, @@ -49,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']; @@ -74,19 +75,43 @@ export default { {[html.joinChildren]: ''}, content: - relations.artistIcons - .map(icon => - icon.slots({ + stitchArrays({ + icon: relations.artistIcons, + url: data.urls, + }).map(({icon, url}) => { + icon.setSlots({ context: 'artist', withText: true, - })), + }); + + let platformText = + language.formatExternalLink(url, { + context: 'artist', + style: 'platform', + }); + + // This is a pretty ridiculous hack, but we currently + // don't have a way of telling formatExternalLink to *not* + // use the fallback string, which just formats the URL as + // its host/domain... so is technically detectable. + if (platformText.toString() === (new URL(url)).host) { + platformText = + language.$('misc.artistLink.noExternalLinkPlatformName'); + } + + const platformSpan = + html.tag('span', {class: 'icon-platform'}, + platformText); + + return [icon, platformSpan]; + }), }), }) : relations.artistLink); if (hasContribution) { parts.push('withContribution'); - options.contrib = data.what; + options.contrib = data.contribution; } if (hasExternalIcons && slots.iconMode === 'inline') { diff --git a/src/content/dependencies/linkExternal.js b/src/content/dependencies/linkExternal.js index ba2dbf2..f6b47db 100644 --- a/src/content/dependencies/linkExternal.js +++ b/src/content/dependencies/linkExternal.js @@ -6,12 +6,17 @@ export default { data: (url) => ({url}), slots: { + content: { + type: 'html', + mutable: false, + }, + style: { // This awkward syntax is because the slot descriptor validator can't // differentiate between a function that returns a validator (the usual // syntax) and a function that is itself a validator. validate: () => isExternalLinkStyle, - default: 'normal', + default: 'platform', }, context: { @@ -19,22 +24,113 @@ export default { default: 'generic', }, + fromContent: { + type: 'boolean', + default: false, + }, + + indicateExternal: { + type: 'boolean', + default: false, + }, + tab: { validate: v => v.is('default', 'separate'), default: 'default', }, }, - generate: (data, slots, {html, language}) => - html.tag('a', - {href: data.url}, - {class: 'nowrap'}, + generate(data, slots, {html, language}) { + let urlIsValid; + try { + new URL(data.url); + urlIsValid = true; + } catch (error) { + urlIsValid = false; + } + + let formattedLink; + if (urlIsValid) { + formattedLink = + language.formatExternalLink(data.url, { + style: slots.style, + context: slots.context, + }); + + // Fall back to platform if nothing matched the desired style. + if (html.isBlank(formattedLink) && slots.style !== 'platform') { + formattedLink = + language.formatExternalLink(data.url, { + style: 'platform', + context: slots.context, + }); + } + } else { + formattedLink = null; + } - slots.tab === 'separate' && - {target: '_blank'}, + const linkAttributes = html.attributes({ + class: 'external-link', + }); - language.formatExternalLink(data.url, { - style: slots.style, - context: slots.context, - })), + let linkContent; + if (urlIsValid) { + linkAttributes.set('href', data.url); + + if (html.isBlank(slots.content)) { + linkContent = formattedLink; + } else { + linkContent = slots.content; + } + } else { + if (html.isBlank(slots.content)) { + linkContent = + html.tag('i', + language.$('misc.external.invalidURL.annotation')); + } else { + linkContent = + language.$('misc.external.invalidURL', { + link: slots.content, + annotation: + html.tag('i', + language.$('misc.external.invalidURL.annotation')), + }); + } + } + + if (slots.fromContent) { + linkAttributes.add('class', 'from-content'); + } + + if (urlIsValid && slots.indicateExternal) { + linkAttributes.add('class', 'indicate-external'); + + let titleText; + if (slots.tab === 'separate') { + if (html.isBlank(slots.content)) { + titleText = + language.$('misc.external.opensInNewTab.annotation'); + } else { + titleText = + language.$('misc.external.opensInNewTab', { + link: formattedLink, + annotation: + language.$('misc.external.opensInNewTab.annotation'), + }); + } + } else if (!html.isBlank(slots.content)) { + titleText = formattedLink; + } + + if (titleText) { + linkAttributes.set('title', titleText.toString()); + } + } + + if (urlIsValid && slots.tab === 'separate') { + linkAttributes.set('target', '_blank'); + } + + return html.tag('a', linkAttributes, linkContent); + }, }; diff --git a/src/content/dependencies/linkExternalAsIcon.js b/src/content/dependencies/linkExternalAsIcon.js index 3eb355a..6f37529 100644 --- a/src/content/dependencies/linkExternalAsIcon.js +++ b/src/content/dependencies/linkExternalAsIcon.js @@ -21,8 +21,8 @@ export default { const format = style => language.formatExternalLink(data.url, {style, context: slots.context}); - const normalText = format('normal'); - const compactText = format('compact'); + const platformText = format('platform'); + const handleText = format('handle'); const iconId = format('icon-id'); return html.tag('a', {class: 'icon'}, @@ -34,7 +34,7 @@ export default { [ html.tag('svg', [ !slots.withText && - html.tag('title', normalText), + html.tag('title', platformText), html.tag('use', { href: to('shared.staticIcon', iconId), @@ -43,7 +43,9 @@ export default { slots.withText && html.tag('span', {class: 'icon-text'}, - compactText ?? normalText), + (html.isBlank(handleText) + ? platformText + : handleText)), ]); }, }; diff --git a/src/content/dependencies/listAllAdditionalFilesTemplate.js b/src/content/dependencies/listAllAdditionalFilesTemplate.js index bf48c96..e33ad7b 100644 --- a/src/content/dependencies/listAllAdditionalFilesTemplate.js +++ b/src/content/dependencies/listAllAdditionalFilesTemplate.js @@ -63,7 +63,7 @@ export default { const albumAdditionalFileFiles = albumAdditionalFileObjects .map(byAlbum => byAlbum - .map(({files}) => files)); + .map(({files}) => files ?? [])); const trackAdditionalFileTitles = trackAdditionalFileObjects @@ -75,7 +75,7 @@ export default { trackAdditionalFileObjects .map(byAlbum => byAlbum .map(byTrack => byTrack - .map(({files}) => files))); + .map(({files}) => files ?? []))); return { spec, diff --git a/src/content/dependencies/listArtistsByLatestContribution.js b/src/content/dependencies/listArtistsByLatestContribution.js index 0f70957..27a2faa 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/transformContent.js b/src/content/dependencies/transformContent.js index faae35a..0904cde 100644 --- a/src/content/dependencies/transformContent.js +++ b/src/content/dependencies/transformContent.js @@ -37,6 +37,7 @@ export default { .map(description => description.link) .filter(Boolean)), 'image', + 'linkExternal', ], extraDependencies: ['html', 'language', 'to', 'wikiData'], @@ -114,7 +115,7 @@ export default { data.hash = enteredHash ?? null; - return {i: node.i, iEnd: node.iEnd, type: 'link', data}; + return {i: node.i, iEnd: node.iEnd, type: 'internal-link', data}; } // This will be another {type: 'tag'} node which gets processed in @@ -140,10 +141,15 @@ export default { sprawl.nodes .map(node => { switch (node.type) { - // Replace link nodes with a stub. It'll be replaced (by position) - // with an item from relations. - case 'link': - return {type: 'link'}; + // Replace internal link nodes with a stub. It'll be replaced + // (by position) with an item from relations. + // + // TODO: This should be where label and hash get passed through, + // rather than in relations... (in which case there's no need to + // handle it specially here, and we can really just return + // data.nodes = sprawl.nodes) + case 'internal-link': + return {type: 'internal-link'}; // Other nodes will get processed in generate. default: @@ -167,9 +173,9 @@ export default { : getPlaceholder(node, content)); return { - links: + internalLinks: nodes - .filter(({type}) => type === 'link') + .filter(({type}) => type === 'internal-link') .map(node => { const {link, thing, value} = node.data; @@ -182,6 +188,15 @@ export default { } }), + externalLinks: + nodes + .filter(({type}) => type === 'external-link') + .map(node => { + const {href} = node.data; + + return relation('linkExternal', href); + }), + images: nodes .filter(({type}) => type === 'image') @@ -201,6 +216,11 @@ export default { default: false, }, + indicateExternalLinks: { + type: 'boolean', + default: true, + }, + thumb: { validate: v => v.is('small', 'medium', 'large'), default: 'large', @@ -208,12 +228,10 @@ export default { }, generate(data, relations, slots, {html, language, to}) { - let linkIndex = 0; let imageIndex = 0; + let internalLinkIndex = 0; + let externalLinkIndex = 0; - // This array contains only straight text and link nodes, which are directly - // representable in html (so no further processing is needed on the level of - // individual nodes). const contentFromNodes = data.nodes.map(node => { switch (node.type) { @@ -237,57 +255,83 @@ export default { } = node; if (node.inline) { + let content = + html.tag('img', + src && {src}, + width && {width}, + height && {height}, + style && {style}, + + pixelate && + {class: 'pixelate'}); + + if (link) { + content = + html.tag('a', + {href: link}, + {target: '_blank'}, + + {title: + language.$('misc.external.opensInNewTab', { + link: + language.formatExternalLink(link, { + style: 'platform', + }), + + annotation: + language.$('misc.external.opensInNewTab.annotation'), + }).toString()}, + + content); + } + return { - type: 'image', + type: 'processed-image', inline: true, - data: - html.tag('img', - src && {src}, - width && {width}, - height && {height}, - style && {style}, - - pixelate && - {class: 'pixelate'}), + data: content, }; } const image = relations.images[imageIndex++]; + image.setSlots({ + src, + + link: link ?? true, + warnings: warnings ?? null, + thumb: slots.thumb, + }); + + if (width || height) { + image.setSlot('dimensions', [width ?? null, height ?? null]); + } + + image.setSlot('attributes', [ + {class: 'content-image'}, + + pixelate && + {class: 'pixelate'}, + ]); + return { - type: 'image', + type: 'processed-image', inline: false, data: html.tag('div', {class: 'content-image-container'}, align === 'center' && {class: 'align-center'}, - image.slots({ - src, - - link: link ?? true, - width: width ?? null, - height: height ?? null, - warnings: warnings ?? null, - thumb: slots.thumb, - - attributes: [ - {class: 'content-image'}, - - pixelate && - {class: 'pixelate'}, - ], - })), + image), }; } - case 'link': { - const linkNode = relations.links[linkIndex++]; - if (linkNode.type === 'text') { - return {type: 'text', data: linkNode.data}; + case 'internal-link': { + const nodeFromRelations = relations.internalLinks[internalLinkIndex++]; + if (nodeFromRelations.type === 'text') { + return {type: 'text', data: nodeFromRelations.data}; } - const {link, label, hash} = linkNode; + const {link, label, hash} = nodeFromRelations; // These are removed from the typical combined slots({})-style // because we don't want to override slots that were already set @@ -322,7 +366,27 @@ export default { link.setSlot('tooltipStyle', 'none'); } - return {type: 'link', data: link}; + return {type: 'processed-internal-link', data: link}; + } + + case 'external-link': { + const {label} = node.data; + const externalLink = relations.externalLinks[externalLinkIndex++]; + + externalLink.setSlots({ + content: label, + fromContent: true, + }); + + if (slots.indicateExternalLinks) { + externalLink.setSlots({ + indicateExternal: true, + tab: 'separate', + style: 'platform', + }); + } + + return {type: 'processed-external-link', data: externalLink}; } case 'tag': { @@ -358,7 +422,10 @@ export default { // access to its slots. if (slots.mode === 'single-link') { - const link = contentFromNodes.find(node => node.type === 'link'); + const link = + contentFromNodes.find(node => + node.type === 'processed-internal-link' || + node.type === 'processed-external-link'); if (!link) { return html.blank(); @@ -385,13 +452,10 @@ export default { return getTextNodeContents(node, index); } - const attributes = html.attributes({ - class: 'INSERT-NON-TEXT', - 'data-type': node.type, - }); + let attributes = `class="INSERT-NON-TEXT" data-type="${node.type}"`; - if (node.type === 'image') { - attributes.set('data-inline', node.inline); + if (node.type === 'processed-image' && node.inline) { + attributes += ` data-inline`; } return `<span ${attributes}>${index}</span>`; @@ -426,7 +490,7 @@ export default { // the surrounding <p> tag that marked generates. The HTML parser // treats a <div> that starts inside a <p> as a Crocker-class // misgiving, and will treat you very badly if you feed it that. - if (attributes.get('data-type') === 'image') { + if (attributes.get('data-type') === 'processed-image') { if (!attributes.get('data-inline')) { tags[tags.length - 1] = tags[tags.length - 1].replace(/<p>$/, ''); deleteParagraph = true; diff --git a/src/content/util/getChronologyRelations.js b/src/content/util/getChronologyRelations.js index 67d6d5f..c4a62da 100644 --- a/src/content/util/getChronologyRelations.js +++ b/src/content/util/getChronologyRelations.js @@ -18,17 +18,17 @@ export default function getChronologyRelations(thing, { const artistsSoFar = new Set(); - contributions = contributions.filter(({who}) => { - if (artistsSoFar.has(who)) { + contributions = contributions.filter(({artist}) => { + if (artistsSoFar.has(artist)) { return false; } else { - artistsSoFar.add(who); + artistsSoFar.add(artist); return true; } }); - return contributions.map(({who}) => { - const things = Array.from(new Set(getThings(who))); + return contributions.map(({artist}) => { + const things = Array.from(new Set(getThings(artist))); // Don't show a line if this contribution isn't part of the artist's // chronology at all (usually because this thing isn't dated). @@ -47,7 +47,7 @@ export default function getChronologyRelations(thing, { return { index: index + 1, - artistLink: linkArtist(who), + artistLink: linkArtist(artist), previousLink: previous ? linkThing(previous) : null, nextLink: next ? linkThing(next) : null, }; |