From ca6613c8585b6a7a46a390960b31a377c57f4028 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 1 Jun 2021 19:57:57 -0300 Subject: module-ify track pages --- src/page/album.js | 20 ++- src/page/index.js | 24 ++++ src/page/track.js | 331 ++++++++++++++++++++++++++++++++++++++++++++ src/upd8.js | 308 ++--------------------------------------- src/util/magic-constants.js | 11 ++ src/util/wiki-data.js | 6 +- 6 files changed, 400 insertions(+), 300 deletions(-) create mode 100644 src/page/track.js create mode 100644 src/util/magic-constants.js (limited to 'src') diff --git a/src/page/album.js b/src/page/album.js index f859779..bc1668a 100644 --- a/src/page/album.js +++ b/src/page/album.js @@ -5,6 +5,8 @@ // - generateAlbumNavLinks // - generateAlbumChronologyLinks +// Imports + import fixWS from 'fix-whitespace'; import { @@ -20,6 +22,8 @@ import { import * as html from '../util/html.js'; +// Page exports + export function targets({wikiData}) { return wikiData.albumData; } @@ -225,7 +229,7 @@ export function write(album, {wikiData}) { album.tracks.length > 1 && { divider: false, - html: generateAlbumNavLinks(album, null, {link, strings}) + html: generateAlbumNavLinks(album, null, {strings}) } ], content: html.tag('div', generateAlbumChronologyLinks(album, null, {chronologyLinks})) @@ -236,6 +240,8 @@ export function write(album, {wikiData}) { return [page, data]; } +// Utility exports + export function generateAlbumSidebar(album, currentTrack, { fancifyURL, link, @@ -344,13 +350,15 @@ export function generateAlbumSidebar(album, currentTrack, { } } -export function generateAlbumNavLinks(album, currentTrack, {link, strings}) { +export function generateAlbumNavLinks(album, currentTrack, { + generatePreviousNextLinks, + strings +}) { if (album.tracks.length <= 1) { return ''; } const previousNextLinks = currentTrack && generatePreviousNextLinks(currentTrack, { - link, strings, data: album.tracks, linkKey: 'track' }); @@ -365,14 +373,14 @@ export function generateAlbumNavLinks(album, currentTrack, {link, strings}) { : `(${randomLink})`); } -export function generateAlbumChronologyLinks(album, currentTrack, {chronologyLinks}) { +export function generateAlbumChronologyLinks(album, currentTrack, {generateChronologyLinks}) { return [ - currentTrack && chronologyLinks(currentTrack, { + currentTrack && generateChronologyLinks(currentTrack, { contribKey: 'artists', getThings: artist => [...artist.tracks.asArtist, ...artist.tracks.asContributor], headingString: 'misc.chronology.heading.track' }), - chronologyLinks(currentTrack || album, { + generateChronologyLinks(currentTrack || album, { contribKey: 'coverArtists', getThings: artist => [...artist.albums.asCoverArtist, ...artist.tracks.asCoverArtist], headingString: 'misc.chronology.heading.coverArt' diff --git a/src/page/index.js b/src/page/index.js index ca78238..a37f8a9 100644 --- a/src/page/index.js +++ b/src/page/index.js @@ -1,5 +1,29 @@ // NB: This is the index for the page/ directory and contains exports for all // other modules here! It's not the page spec for the homepage - see // homepage.js for that. +// +// Each module published in this list should follow a particular format, +// including the following exports: +// +// targets({wikiData}) +// Gets the objects which this page's write() function should be called on. +// Usually this will simply mean returning the appropriate thingData array, +// but it may also apply filter/map/etc if useful. +// +// write(thing, {wikiData}) +// Gets descriptors for any page and data writes associated with the given +// thing (which will be a value from the targets() array). This includes +// page (HTML) writes, data (JSON) writes, etc. Notably, this function does +// not perform any file operations itself; it only describes the operations +// which will be processed elsewhere, once for each translation language. +// The write function also immediately transforms any data which will be +// reused across writes of the same page, so that this data is effectively +// cached (rather than recalculated for each language/write). +// +// As these modules are effectively the HTML templates for all site layout, +// common patterns may also be exported alongside the special exports above. +// These functions should be referenced only from adjacent modules, as they +// pertain only to site page generation. export * as album from './album.js'; +export * as track from './track.js'; diff --git a/src/page/track.js b/src/page/track.js new file mode 100644 index 0000000..2dec9bd --- /dev/null +++ b/src/page/track.js @@ -0,0 +1,331 @@ +// Track page specification. + +// Imports + +import fixWS from 'fix-whitespace'; + +import { + generateAlbumChronologyLinks, + generateAlbumNavLinks, + generateAlbumSidebar +} from './album.js'; + +import { + getThemeString +} from '../util/colors.js'; + +import * as html from '../util/html.js'; + +import { + OFFICIAL_GROUP_DIRECTORY, + UNRELEASED_TRACKS_DIRECTORY +} from '../util/magic-constants.js'; + +import { + bindOpts +} from '../util/sugar.js'; + +import { + getTrackCover, + getAlbumListTag, + sortByDate +} from '../util/wiki-data.js'; + +// Page exports + +export function targets({wikiData}) { + return wikiData.trackData; +} + +export function write(track, {wikiData}) { + const { groupData, wikiInfo } = wikiData; + const { album } = track; + + const tracksThatReference = track.referencedBy; + const useDividedReferences = groupData.some(group => group.directory === OFFICIAL_GROUP_DIRECTORY); + const ttrFanon = (useDividedReferences && + tracksThatReference.filter(t => t.album.groups.every(group => group.directory !== OFFICIAL_GROUP_DIRECTORY))); + const ttrOfficial = (useDividedReferences && + tracksThatReference.filter(t => t.album.groups.some(group => group.directory === OFFICIAL_GROUP_DIRECTORY))); + + const tracksReferenced = track.references; + const otherReleases = track.otherReleases; + const listTag = getAlbumListTag(album); + + let flashesThatFeature; + if (wikiInfo.features.flashesAndGames) { + flashesThatFeature = sortByDate([track, ...otherReleases] + .flatMap(track => track.flashes.map(flash => ({flash, as: track})))); + } + + const unbound_generateTrackList = (tracks, {getArtistString, link, strings}) => html.tag('ul', + tracks.map(track => { + const line = strings('trackList.item.withArtists', { + track: link.track(track), + by: `${strings('trackList.item.withArtists.by', { + artists: getArtistString(track.artists) + })}` + }); + return (track.aka + ? `
  • ${strings('trackList.item.rerelease', {track: line})}
  • ` + : `
  • ${line}
  • `); + }) + ); + + const hasCommentary = track.commentary || otherReleases.some(t => t.commentary); + const generateCommentary = ({ + link, + strings, + transformMultiline + }) => transformMultiline([ + track.commentary, + ...otherReleases.map(track => + (track.commentary?.split('\n') + .filter(line => line.replace(/<\/b>/g, '').includes(':')) + .map(line => fixWS` + ${line} + ${strings('releaseInfo.artistCommentary.seeOriginalRelease', { + original: link.track(track) + })} + `) + .join('\n'))) + ].filter(Boolean).join('\n')); + + const data = { + type: 'data', + path: ['track', track.directory], + data: ({ + serializeContribs, + serializeCover, + serializeGroupsForTrack, + serializeLink + }) => ({ + name: track.name, + directory: track.directory, + dates: { + released: track.date, + originallyReleased: track.originalDate, + coverArtAdded: track.coverArtDate + }, + duration: track.duration, + color: track.color, + cover: serializeCover(track, getTrackCover), + artists: serializeContribs(track.artists), + contributors: serializeContribs(track.contributors), + coverArtists: serializeContribs(track.coverArtists || []), + album: serializeLink(track.album), + groups: serializeGroupsForTrack(track), + references: track.references.map(serializeLink), + referencedBy: track.referencedBy.map(serializeLink), + alsoReleasedAs: otherReleases.map(track => ({ + track: serializeLink(track), + album: serializeLink(track.album) + })) + }) + }; + + const page = { + type: 'page', + path: ['track', track.directory], + page: ({ + fancifyURL, + generateChronologyLinks, + generateCoverLink, + generatePreviousNextLinks, + getAlbumStylesheet, + getArtistString, + getTrackCover, + link, + strings, + transformInline, + transformLyrics, + transformMultiline, + to + }) => { + const generateTrackList = bindOpts(unbound_generateTrackList, {getArtistString, link, strings}); + + return { + title: strings('trackPage.title', {track: track.name}), + stylesheet: getAlbumStylesheet(album, {to}), + theme: getThemeString(track.color, [ + `--album-directory: ${album.directory}`, + `--track-directory: ${track.directory}` + ]), + + // disabled for now! shifting banner position per height of page is disorienting + /* + banner: album.bannerArtists && { + classes: ['dim'], + dimensions: album.bannerDimensions, + path: ['media.albumBanner', album.directory], + alt: strings('misc.alt.albumBanner'), + position: 'bottom' + }, + */ + + main: { + content: fixWS` + ${generateCoverLink({ + src: getTrackCover(track), + alt: strings('misc.alt.trackCover'), + tags: track.artTags + })} +

    ${strings('trackPage.title', {track: track.name})}

    +

    + ${[ + strings('releaseInfo.by', { + artists: getArtistString(track.artists, { + showContrib: true, + showIcons: true + }) + }), + track.coverArtists && strings('releaseInfo.coverArtBy', { + artists: getArtistString(track.coverArtists, { + showContrib: true, + showIcons: true + }) + }), + album.directory !== UNRELEASED_TRACKS_DIRECTORY && strings('releaseInfo.released', { + date: strings.count.date(track.date) + }), + +track.coverArtDate !== +track.date && strings('releaseInfo.artReleased', { + date: strings.count.date(track.coverArtDate) + }), + track.duration && strings('releaseInfo.duration', { + duration: strings.count.duration(track.duration) + }) + ].filter(Boolean).join('
    \n')} +

    +

    ${ + (track.urls.length + ? strings('releaseInfo.listenOn', { + links: strings.list.or(track.urls.map(url => fancifyURL(url, {strings}))) + }) + : strings('releaseInfo.listenOn.noLinks')) + }

    + ${otherReleases.length && fixWS` +

    ${strings('releaseInfo.alsoReleasedAs')}

    + + `} + ${track.contributors.textContent && fixWS` +

    + ${strings('releaseInfo.contributors')} +
    + ${transformInline(track.contributors.textContent)} +

    + `} + ${track.contributors.length && fixWS` +

    ${strings('releaseInfo.contributors')}

    + + `} + ${tracksReferenced.length && fixWS` +

    ${strings('releaseInfo.tracksReferenced', {track: `${track.name}`})}

    + ${generateTrackList(tracksReferenced)} + `} + ${tracksThatReference.length && fixWS` +

    ${strings('releaseInfo.tracksThatReference', {track: `${track.name}`})}

    + ${useDividedReferences && fixWS` +
    + ${ttrOfficial.length && fixWS` +
    ${strings('trackPage.referenceList.official')}
    +
    ${generateTrackList(ttrOfficial)}
    + `} + ${ttrFanon.length && fixWS` +
    ${strings('trackPage.referenceList.fandom')}
    +
    ${generateTrackList(ttrFanon)}
    + `} +
    + `} + ${!useDividedReferences && generateTrackList(tracksThatReference)} + `} + ${wikiInfo.features.flashesAndGames && flashesThatFeature.length && fixWS` +

    ${strings('releaseInfo.flashesThatFeature', {track: `${track.name}`})}

    + + `} + ${track.lyrics && fixWS` +

    ${strings('releaseInfo.lyrics')}

    +
    + ${transformLyrics(track.lyrics)} +
    + `} + ${hasCommentary && fixWS` +

    ${strings('releaseInfo.artistCommentary')}

    +
    + ${generateCommentary({link, strings, transformMultiline})} +
    + `} + ` + }, + + sidebarLeft: generateAlbumSidebar(album, track, { + fancifyURL, + link, + strings, + transformMultiline, + wikiData + }), + + nav: { + links: [ + {toHome: true}, + { + path: ['localized.album', album.directory], + title: album.name + }, + listTag === 'ol' ? { + html: strings('trackPage.nav.track.withNumber', { + number: album.tracks.indexOf(track) + 1, + track: link.track(track, {class: 'current', to}) + }) + } : { + html: strings('trackPage.nav.track', { + track: link.track(track, {class: 'current', to}) + }) + }, + album.tracks.length > 1 && + { + divider: false, + html: generateAlbumNavLinks(album, track, { + generatePreviousNextLinks, + strings + }) + } + ].filter(Boolean), + content: fixWS` +
    + ${generateAlbumChronologyLinks(album, track, {generateChronologyLinks})} +
    + ` + } + }; + } + }; + + return [data, page]; +} + diff --git a/src/upd8.js b/src/upd8.js index 621cae5..e199b1f 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -183,6 +183,13 @@ import { thumb } from './util/urls.js'; +// Pensive emoji! +import { + FANDOM_GROUP_DIRECTORY, + OFFICIAL_GROUP_DIRECTORY, + UNRELEASED_TRACKS_DIRECTORY +} from './util/magic-constants.js'; + const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CACHEBUST = 7; @@ -197,10 +204,6 @@ const GROUP_DATA_FILE = 'groups.txt'; const STATIC_PAGE_DATA_FILE = 'static-pages.txt'; const DEFAULT_STRINGS_FILE = 'strings-default.json'; -const UNRELEASED_TRACKS_DIRECTORY = 'unreleased-tracks'; -const OFFICIAL_GROUP_DIRECTORY = 'official'; -const FANDOM_GROUP_DIRECTORY = 'fandom'; - // Code that's common 8etween the 8uild code (i.e. upd8.js) and gener8ted // site code should 8e put here. Which, uh, ~~only really means this one // file~~ is now a variety of useful utilities! @@ -2649,292 +2652,6 @@ function getAlbumStylesheet(album, {to}) { ].filter(Boolean).join('\n'); } -function writeTrackPages({wikiData}) { - return wikiData.trackData.map(track => writeTrackPage(track, {wikiData})); -} - -function writeTrackPage(track, {wikiData}) { - const { groupData, wikiInfo } = wikiData; - const { album } = track; - - const tracksThatReference = track.referencedBy; - const useDividedReferences = groupData.some(group => group.directory === OFFICIAL_GROUP_DIRECTORY); - const ttrFanon = (useDividedReferences && - tracksThatReference.filter(t => t.album.groups.every(group => group.directory !== OFFICIAL_GROUP_DIRECTORY))); - const ttrOfficial = (useDividedReferences && - tracksThatReference.filter(t => t.album.groups.some(group => group.directory === OFFICIAL_GROUP_DIRECTORY))); - - const tracksReferenced = track.references; - const otherReleases = track.otherReleases; - const listTag = getAlbumListTag(album); - - let flashesThatFeature; - if (wikiInfo.features.flashesAndGames) { - flashesThatFeature = sortByDate([track, ...otherReleases] - .flatMap(track => track.flashes.map(flash => ({flash, as: track})))); - } - - const unbound_generateTrackList = (tracks, {getArtistString, link, strings}) => html.tag('ul', - tracks.map(track => { - const line = strings('trackList.item.withArtists', { - track: link.track(track), - by: `${strings('trackList.item.withArtists.by', { - artists: getArtistString(track.artists) - })}` - }); - return (track.aka - ? `
  • ${strings('trackList.item.rerelease', {track: line})}
  • ` - : `
  • ${line}
  • `); - }) - ); - - const hasCommentary = track.commentary || otherReleases.some(t => t.commentary); - const generateCommentary = ({ - link, - strings, - transformMultiline - }) => transformMultiline([ - track.commentary, - ...otherReleases.map(track => - (track.commentary?.split('\n') - .filter(line => line.replace(/<\/b>/g, '').includes(':')) - .map(line => fixWS` - ${line} - ${strings('releaseInfo.artistCommentary.seeOriginalRelease', { - original: link.track(track) - })} - `) - .join('\n'))) - ].filter(Boolean).join('\n')); - - const data = { - type: 'data', - path: ['track', track.directory], - data: () => ({ - name: track.name, - directory: track.directory, - dates: { - released: track.date, - originallyReleased: track.originalDate, - coverArtAdded: track.coverArtDate - }, - duration: track.duration, - color: track.color, - cover: serializeCover(track, getTrackCover), - artists: serializeContribs(track.artists), - contributors: serializeContribs(track.contributors), - coverArtists: serializeContribs(track.coverArtists || []), - album: serializeLink(track.album), - groups: serializeGroupsForTrack(track), - references: track.references.map(serializeLink), - referencedBy: track.referencedBy.map(serializeLink), - alsoReleasedAs: otherReleases.map(track => ({ - track: serializeLink(track), - album: serializeLink(track.album) - })) - }) - }; - - const page = { - type: 'page', - path: ['track', track.directory], - page: ({ - generateCoverLink, - getArtistString, - getTrackCover, - link, - strings, - transformInline, - transformLyrics, - transformMultiline, - to - }) => { - const generateTrackList = bindOpts(unbound_generateTrackList, {getArtistString, link, strings}); - - return { - title: strings('trackPage.title', {track: track.name}), - stylesheet: getAlbumStylesheet(album, {to}), - theme: getThemeString(track.color, [ - `--album-directory: ${album.directory}`, - `--track-directory: ${track.directory}` - ]), - - // disabled for now! shifting banner position per height of page is disorienting - /* - banner: album.bannerArtists && { - classes: ['dim'], - dimensions: album.bannerDimensions, - path: ['media.albumBanner', album.directory], - alt: strings('misc.alt.albumBanner'), - position: 'bottom' - }, - */ - - main: { - content: fixWS` - ${generateCoverLink({ - src: getTrackCover(track), - alt: strings('misc.alt.trackCover'), - tags: track.artTags - })} -

    ${strings('trackPage.title', {track: track.name})}

    -

    - ${[ - strings('releaseInfo.by', { - artists: getArtistString(track.artists, { - showContrib: true, - showIcons: true - }) - }), - track.coverArtists && strings('releaseInfo.coverArtBy', { - artists: getArtistString(track.coverArtists, { - showContrib: true, - showIcons: true - }) - }), - album.directory !== UNRELEASED_TRACKS_DIRECTORY && strings('releaseInfo.released', { - date: strings.count.date(track.date) - }), - +track.coverArtDate !== +track.date && strings('releaseInfo.artReleased', { - date: strings.count.date(track.coverArtDate) - }), - track.duration && strings('releaseInfo.duration', { - duration: strings.count.duration(track.duration) - }) - ].filter(Boolean).join('
    \n')} -

    -

    ${ - (track.urls.length - ? strings('releaseInfo.listenOn', { - links: strings.list.or(track.urls.map(url => fancifyURL(url, {strings}))) - }) - : strings('releaseInfo.listenOn.noLinks')) - }

    - ${otherReleases.length && fixWS` -

    ${strings('releaseInfo.alsoReleasedAs')}

    - - `} - ${track.contributors.textContent && fixWS` -

    - ${strings('releaseInfo.contributors')} -
    - ${transformInline(track.contributors.textContent)} -

    - `} - ${track.contributors.length && fixWS` -

    ${strings('releaseInfo.contributors')}

    - - `} - ${tracksReferenced.length && fixWS` -

    ${strings('releaseInfo.tracksReferenced', {track: `${track.name}`})}

    - ${generateTrackList(tracksReferenced)} - `} - ${tracksThatReference.length && fixWS` -

    ${strings('releaseInfo.tracksThatReference', {track: `${track.name}`})}

    - ${useDividedReferences && fixWS` -
    - ${ttrOfficial.length && fixWS` -
    ${strings('trackPage.referenceList.official')}
    -
    ${generateTrackList(ttrOfficial)}
    - `} - ${ttrFanon.length && fixWS` -
    ${strings('trackPage.referenceList.fandom')}
    -
    ${generateTrackList(ttrFanon)}
    - `} -
    - `} - ${!useDividedReferences && generateTrackList(tracksThatReference)} - `} - ${wikiInfo.features.flashesAndGames && flashesThatFeature.length && fixWS` -

    ${strings('releaseInfo.flashesThatFeature', {track: `${track.name}`})}

    - - `} - ${track.lyrics && fixWS` -

    ${strings('releaseInfo.lyrics')}

    -
    - ${transformLyrics(track.lyrics)} -
    - `} - ${hasCommentary && fixWS` -

    ${strings('releaseInfo.artistCommentary')}

    -
    - ${generateCommentary({link, strings, transformMultiline})} -
    - `} - ` - }, - - sidebarLeft: generateSidebarForAlbum(album, { - currentTrack: track, - link, - strings, - transformMultiline, - wikiData - }), - - nav: { - links: [ - {toHome: true}, - { - path: ['localized.album', album.directory], - title: album.name - }, - listTag === 'ol' ? { - html: strings('trackPage.nav.track.withNumber', { - number: album.tracks.indexOf(track) + 1, - track: link.track(track, {class: 'current', to}) - }) - } : { - html: strings('trackPage.nav.track', { - track: link.track(track, {class: 'current', to}) - }) - }, - album.tracks.length > 1 && - { - divider: false, - html: generateAlbumNavLinks(album, track, {link, strings}) - } - ].filter(Boolean), - content: fixWS` -
    - ${generateAlbumChronologyLinks(album, track, {link, strings})} -
    - ` - } - }; - } - }; - - return [data, page]; -} - function writeArtistPages({wikiData}) { return [ ...wikiData.artistData.map(artist => writeArtistPage(artist, {wikiData})), @@ -3598,7 +3315,7 @@ function generateNavForFlash(flash, {link, strings, wikiData}) { content: fixWS`
    - ${chronologyLinks(flash, { + ${generateChronologyLinks(flash, { link, strings, wikiData, headingString: 'misc.chronology.heading.flash', contribKey: 'contributors', @@ -4863,7 +4580,7 @@ function iconifyURL(url, {strings, to}) { return fixWS`${msg}`; } -function chronologyLinks(currentThing, { +function generateChronologyLinks(currentThing, { contribKey, getThings, headingString, @@ -6069,7 +5786,7 @@ async function main() { to }); - bound.chronologyLinks = bindOpts(chronologyLinks, { + bound.generateChronologyLinks = bindOpts(generateChronologyLinks, { link: bound.link, strings, wikiData @@ -6083,6 +5800,11 @@ async function main() { wikiData }); + bound.generatePreviousNextLinks = bindOpts(generatePreviousNextLinks, { + link: bound.link, + strings + }); + bound.getGridHTML = bindOpts(getGridHTML, { [bindOpts.bindIndex]: 0, strings diff --git a/src/util/magic-constants.js b/src/util/magic-constants.js new file mode 100644 index 0000000..3174dae --- /dev/null +++ b/src/util/magic-constants.js @@ -0,0 +1,11 @@ +// Magic constants only! These are hard-coded, and any use of them should be +// considered a flaw in the codebase - areas where we use hard-coded behavior +// to support one use of the wiki software (i.e. HSMusic, usually), rather than +// implementing the feature more generally/customizably. +// +// All such uses should eventually be replaced with better code in due time +// (TM). + +export const UNRELEASED_TRACKS_DIRECTORY = 'unreleased-tracks'; +export const OFFICIAL_GROUP_DIRECTORY = 'official'; +export const FANDOM_GROUP_DIRECTORY = 'fandom'; diff --git a/src/util/wiki-data.js b/src/util/wiki-data.js index 13b8609..e4142c8 100644 --- a/src/util/wiki-data.js +++ b/src/util/wiki-data.js @@ -1,5 +1,9 @@ // Utility functions for interacting with wiki data. +import { + UNRELEASED_TRACKS_DIRECTORY +} from '../util/magic-constants.js'; + // Generic value operations export function getKebabCase(name) { @@ -95,7 +99,7 @@ export function getAlbumCover(album, {to}) { export function getAlbumListTag(album) { // TODO: This is hard-coded! No. 8ad. - return (album.directory === 'unreleased-tracks' ? 'ul' : 'ol'); + return (album.directory === UNRELEASED_TRACKS_DIRECTORY ? 'ul' : 'ol'); } // This gets all the track o8jects defined in every al8um, and sorts them 8y -- cgit 1.3.0-6-gf8a5