From 28b1a728f429c4c04ee3a16584f8433a1d312c7c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 15 Sep 2022 15:20:17 -0300 Subject: update some misc-templates and refactor things --- src/misc-templates.js | 312 +++++++++++++++++++++++++---------------------- src/page/album.js | 72 ++++++----- src/page/flash.js | 8 +- src/page/group.js | 14 +-- src/page/news.js | 8 +- src/page/track.js | 5 +- src/strings-default.json | 5 + src/upd8.js | 37 ++---- src/util/link.js | 54 ++++++++ src/util/sugar.js | 16 ++- 10 files changed, 307 insertions(+), 224 deletions(-) (limited to 'src') diff --git a/src/misc-templates.js b/src/misc-templates.js index 53a0595..b5a635d 100644 --- a/src/misc-templates.js +++ b/src/misc-templates.js @@ -30,10 +30,12 @@ export function generateAdditionalFilesShortcut(additionalFiles, {language}) { if (!additionalFiles?.length) return ''; return language.$('releaseInfo.additionalFiles.shortcut', { - anchorLink: `${language.$( - 'releaseInfo.additionalFiles.shortcut.anchorLink' - )}`, - titles: language.formatUnitList(additionalFiles.map((g) => g.title)), + anchorLink: + html.tag('a', + {href: '#additional-files'}, + language.$('releaseInfo.additionalFiles.shortcut.anchorLink')), + titles: language.formatUnitList( + additionalFiles.map(g => g.title)), }); } @@ -41,62 +43,45 @@ export function generateAdditionalFilesList( additionalFiles, {language, getFileSize, linkFile} ) { - if (!additionalFiles?.length) return ''; + if (!additionalFiles?.length) return []; const fileCount = additionalFiles.flatMap((g) => g.files).length; - return fixWS` -

${language.$( - 'releaseInfo.additionalFiles.heading', - { - additionalFiles: language.countAdditionalFiles(fileCount, { - unit: true, - }), - } - )}

-
- ${additionalFiles - .map( - ({title, description, files}) => fixWS` -
${ - description - ? language.$( - 'releaseInfo.additionalFiles.entry.withDescription', - { - title, - description, - } - ) - : language.$('releaseInfo.additionalFiles.entry', {title}) - }
-
    - ${files - .map((file) => { - const size = getFileSize(file); - return size - ? `
  • ${language.$( - 'releaseInfo.additionalFiles.file.withSize', - { - file: linkFile(file), - size: language.formatFileSize( - getFileSize(file) - ), - } - )}
  • ` - : `
  • ${language.$( - 'releaseInfo.additionalFiles.file', - { - file: linkFile(file), - } - )}
  • `; - }) - .join('\n')} -
- ` - ) - .join('\n')} -
- `; + return html.fragment([ + html.tag('p', + {id: 'additional-files'}, + language.$('releaseInfo.additionalFiles.heading', { + additionalFiles: language.countAdditionalFiles(fileCount, { + unit: true, + }), + })), + + html.tag('dl', + additionalFiles.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) => { + const size = getFileSize(file); + return html.tag('li', + (size + ? language.$('releaseInfo.additionalFiles.file.withSize', { + file: linkFile(file), + size: language.formatFileSize(size), + }) + : language.$('releaseInfo.additionalFiles.file', { + file: linkFile(file), + }))); + }))), + ])), + ]); } // Artist strings @@ -108,35 +93,53 @@ export function getArtistString( return language.formatConjunctionList( artists.map(({who, what}) => { const {urls} = who; - return [ - link.artist(who), - showContrib && what && `(${what})`, - showIcons && - urls?.length && - `(${language.formatUnitList( - urls.map((url) => iconifyURL(url, {language})) - )})`, - ] - .filter(Boolean) - .join(' '); - }) - ); + + const hasContribPart = !!(showContrib && what); + const hasExternalPart = !!(showIcons && urls?.length); + + const artistLink = link.artist(who); + + const externalLinks = hasExternalPart && + html.tag('span', + {class: 'icons'}, + language.formatUnitList( + urls.map(url => iconifyURL(url, {language})))); + + return ( + (hasContribPart + ? (hasExternalPart + ? language.$('misc.artistLink.withContribution.withExternalLinks', { + artist: artistLink, + contrib: what, + links: externalLinks, + }) + : language.$('misc.artistLink.withContribution', { + artist: artistLink, + contrib: what, + })) + : (hasExternalPart + ? language.$('misc.artistLink.withExternalLinks', { + artist: artistLink, + links: externalLinks, + }) + : language.$('misc.artistLink', { + artist: artistLink, + }))) + ); + })); } // Chronology links -export function generateChronologyLinks( - currentThing, - { - dateKey = 'date', - contribKey, - getThings, - headingString, - link, - linkAnythingMan, - language, - } -) { +export function generateChronologyLinks(currentThing, { + dateKey = 'date', + contribKey, + getThings, + generateNavigationLinks, + headingString, + link, + language, +}) { const contributions = currentThing[contribKey]; if (!contributions) { return ''; @@ -157,55 +160,45 @@ export function generateChronologyLinks( // Kinda a hack, but we automatically detect which is (probably) the // right function to use here. const args = [thingsUnsorted, {getDate: (t) => t[dateKey]}]; - const things = thingsUnsorted.every( - (t) => t instanceof Album || t instanceof Track - ) - ? sortAlbumsTracksChronologically(...args) - : sortChronologically(...args); + const things = ( + thingsUnsorted.every(t => t instanceof Album || t instanceof Track) + ? sortAlbumsTracksChronologically(...args) + : sortChronologically(...args)); + + if (things.length === 0) return ''; const index = things.indexOf(currentThing); if (index === -1) return ''; - // TODO: This can pro8a8ly 8e made to use generatePreviousNextLinks? - // We'd need to make generatePreviousNextLinks use toAnythingMan tho. - const previous = things[index - 1]; - const next = things[index + 1]; - const parts = [ - previous && - linkAnythingMan(previous, { - color: false, - text: language.$('misc.nav.previous'), - }), - next && - linkAnythingMan(next, { - color: false, - text: language.$('misc.nav.next'), - }), - ].filter(Boolean); - - if (!parts.length) { - return ''; - } - - const stringOpts = { - index: language.formatIndex(index + 1, {language}), - artist: link.artist(artist), - }; - - return fixWS` -
- ${language.$( - headingString, - stringOpts - )} - ${ - parts.length && - `(${parts.join(', ')})` - } -
- `; + const heading = ( + html.tag('span', {class: 'heading'}, + language.$(headingString, { + index: language.formatIndex(index + 1, {language}), + artist: link.artist(artist), + }))); + + const navigation = things.length > 1 && + html.tag('span', + { + [html.onlyIfContent]: true, + class: 'buttons', + }, + generateNavigationLinks(currentThing, { + data: things, + isMain: false, + })); + + return ( + html.tag('div', {class: 'chronology'}, + (navigation + ? language.$('misc.chronology.withNavigation', { + heading, + navigation, + }) + : heading))); }) + // TODO: use html.fragment when calling and get rid of these lines .filter(Boolean) .join('\n'); } @@ -570,38 +563,63 @@ export function generateInfoGalleryLinks( ].join(', '); } -export function generatePreviousNextLinks( - current, - {data, link, linkKey, language} -) { - const linkFn = link[linkKey]; +// Generate "previous" and "next" links relative to a given current thing and a +// data set (array of things) which includes it, optionally including additional +// provided links like "random". This is for use in navigation bars and other +// inline areas. +// +// By default, generated links include ID attributes which enable client-side +// keyboard shortcuts. Provide isMain: false to disable this (if the generated +// links aren't the for the page's primary navigation). +export function generateNavigationLinks(current, { + additionalLinks = [], + data, + isMain = true, + language, + link, + linkKey = 'anything', +}) { + let previousLink, nextLink; - const index = data.indexOf(current); - const previous = data[index - 1]; - const next = data[index + 1]; + if (current) { + const linkFn = link[linkKey].bind(link); - return [ - previous && - linkFn(previous, { + const index = data.indexOf(current); + const previousThing = data[index - 1]; + const nextThing = data[index + 1]; + + previousLink = previousThing && + linkFn(previousThing, { attributes: { - id: 'previous-button', - title: previous.name, + id: isMain && 'previous-button', + title: previousThing.name, }, text: language.$('misc.nav.previous'), color: false, - }), - next && - linkFn(next, { + }); + + nextLink = nextThing && + linkFn(nextThing, { attributes: { - id: 'next-button', - title: next.name, + id: isMain && 'next-button', + title: nextThing.name, }, text: language.$('misc.nav.next'), color: false, - }), - ] - .filter(Boolean) - .join(', '); + }); + } + + const links = [ + previousLink, + nextLink, + ...additionalLinks, + ].filter(Boolean); + + if (!links.length) { + return ''; + } + + return language.formatUnitList(links); } // Footer stuff diff --git a/src/page/album.js b/src/page/album.js index 11c6da2..1cceead 100644 --- a/src/page/album.js +++ b/src/page/album.js @@ -102,6 +102,7 @@ export function write(album, {wikiData}) { generateAdditionalFilesList, generateChronologyLinks, generateCoverLink, + generateNavigationLinks, getAlbumCover, getAlbumStylesheet, getArtistString, @@ -277,18 +278,19 @@ export function write(album, {wikiData}) { }) ]), - hasAdditionalFiles && - generateAdditionalFilesList(album.additionalFiles, { - // TODO: Kinda near the metal here... - getFileSize: (file) => - getSizeOfAdditionalFile( - urls.from('media.root').to( - 'media.albumAdditionalFile', - album.directory, - file)), - linkFile: (file) => - link.albumAdditionalFile({album, file}), - }), + ...html.fragment( + hasAdditionalFiles && + generateAdditionalFilesList(album.additionalFiles, { + // TODO: Kinda near the metal here... + getFileSize: (file) => + getSizeOfAdditionalFile( + urls.from('media.root').to( + 'media.albumAdditionalFile', + album.directory, + file)), + linkFile: (file) => + link.albumAdditionalFile({album, file}), + })), ...album.commentary ? [ html.tag('p', language.$('releaseInfo.artistCommentary')), @@ -317,7 +319,11 @@ export function write(album, {wikiData}) { }), }, ], - bottomRowContent: generateAlbumNavLinks(album, null, {language}), + bottomRowContent: generateAlbumNavLinks(album, null, { + generateNavigationLinks, + html, + language, + }), content: generateAlbumChronologyLinks(album, null, { generateChronologyLinks, html, @@ -526,33 +532,35 @@ export function generateAlbumSecondaryNav(album, currentTrack, { }; } -export function generateAlbumNavLinks( - album, - currentTrack, - {generatePreviousNextLinks, language} -) { +export function generateAlbumNavLinks(album, currentTrack, { + generateNavigationLinks, + html, + language, +}) { const isTrackPage = !!currentTrack; if (album.tracks.length <= 1) { return ''; } - const previousNextLinks = - isTrackPage && - generatePreviousNextLinks(currentTrack, { - data: album.tracks, - linkKey: 'track', - }); - - const randomLink = `${ - isTrackPage + const randomLink = html.tag('a', + { + href: '#', + dataRandom: 'track-in-album', + id: 'random-button' + }, + (isTrackPage ? language.$('trackPage.nav.random') - : language.$('albumPage.nav.randomTrack') - }`; + : language.$('albumPage.nav.randomTrack'))); + + const navigationLinks = + generateNavigationLinks(currentTrack, { + additionalLinks: [randomLink], + data: album.tracks, + linkKey: 'track', + }); - return previousNextLinks - ? `(${previousNextLinks}, ${randomLink})` - : `(${randomLink})`; + return `(${navigationLinks})`; } export function generateAlbumChronologyLinks(album, currentTrack, { diff --git a/src/page/flash.js b/src/page/flash.js index 3ce7646..c123c1b 100644 --- a/src/page/flash.js +++ b/src/page/flash.js @@ -20,7 +20,7 @@ export function write(flash, {wikiData}) { fancifyFlashURL, generateChronologyLinks, generateCoverLink, - generatePreviousNextLinks, + generateNavigationLinks, getArtistString, getFlashCover, getThemeString, @@ -106,7 +106,7 @@ export function write(flash, {wikiData}) { nav: generateNavForFlash(flash, { generateChronologyLinks, - generatePreviousNextLinks, + generateNavigationLinks, html, link, language, @@ -195,7 +195,7 @@ export function writeTargetless({ function generateNavForFlash(flash, { generateChronologyLinks, - generatePreviousNextLinks, + generateNavigationLinks, html, language, link, @@ -203,7 +203,7 @@ function generateNavForFlash(flash, { }) { const {flashData} = wikiData; - const previousNextLinks = generatePreviousNextLinks(flash, { + const previousNextLinks = generateNavigationLinks(flash, { data: flashData, linkKey: 'flash', }); diff --git a/src/page/group.js b/src/page/group.js index 9959ec2..ef3813f 100644 --- a/src/page/group.js +++ b/src/page/group.js @@ -26,7 +26,7 @@ export function write(group, {wikiData}) { page: ({ fancifyURL, generateInfoGalleryLinks, - generatePreviousNextLinks, + generateNavigationLinks, getLinkThemeString, getThemeString, html, @@ -104,7 +104,7 @@ export function write(group, {wikiData}) { nav: generateGroupNav(group, false, { generateInfoGalleryLinks, - generatePreviousNextLinks, + generateNavigationLinks, language, link, wikiData, @@ -117,7 +117,7 @@ export function write(group, {wikiData}) { path: ['groupGallery', group.directory], page: ({ generateInfoGalleryLinks, - generatePreviousNextLinks, + generateNavigationLinks, getAlbumGridHTML, getLinkThemeString, getThemeString, @@ -151,7 +151,7 @@ export function write(group, {wikiData}) { language.formatDuration(totalDuration, { unit: true, })), - })), + })), wikiInfo.enableGroupUI && wikiInfo.enableListings && @@ -191,7 +191,7 @@ export function write(group, {wikiData}) { nav: generateGroupNav(group, true, { generateInfoGalleryLinks, - generatePreviousNextLinks, + generateNavigationLinks, language, link, wikiData, @@ -253,7 +253,7 @@ function generateGroupSidebar(currentGroup, isGallery, { function generateGroupNav(currentGroup, isGallery, { generateInfoGalleryLinks, - generatePreviousNextLinks, + generateNavigationLinks, link, language, wikiData, @@ -271,7 +271,7 @@ function generateGroupNav(currentGroup, isGallery, { linkKeyInfo: 'groupInfo', }); - const previousNextLinks = generatePreviousNextLinks(currentGroup, { + const previousNextLinks = generateNavigationLinks(currentGroup, { data: groupData, linkKey, }); diff --git a/src/page/news.js b/src/page/news.js index 49cee7b..62f94fb 100644 --- a/src/page/news.js +++ b/src/page/news.js @@ -13,7 +13,7 @@ export function write(entry, {wikiData}) { type: 'page', path: ['newsEntry', entry.directory], page: ({ - generatePreviousNextLinks, + generateNavigationLinks, html, language, link, @@ -39,7 +39,7 @@ export function write(entry, {wikiData}) { }, nav: generateNewsEntryNav(entry, { - generatePreviousNextLinks, + generateNavigationLinks, html, language, link, @@ -104,7 +104,7 @@ export function writeTargetless({wikiData}) { } function generateNewsEntryNav(entry, { - generatePreviousNextLinks, + generateNavigationLinks, html, language, link, @@ -112,7 +112,7 @@ function generateNewsEntryNav(entry, { }) { // The newsData list is sorted reverse chronologically (newest ones first), // so the way we find next/previous entries is flipped from normal. - const previousNextLinks = generatePreviousNextLinks(entry, { + const previousNextLinks = generateNavigationLinks(entry, { data: newsData.slice().reverse(), linkKey: 'newsEntry', diff --git a/src/page/track.js b/src/page/track.js index 258e11d..514f2cb 100644 --- a/src/page/track.js +++ b/src/page/track.js @@ -151,7 +151,7 @@ export function write(track, {wikiData}) { fancifyURL, generateChronologyLinks, generateCoverLink, - generatePreviousNextLinks, + generateNavigationLinks, generateTrackListDividedByGroups, getAlbumStylesheet, getArtistString, @@ -377,7 +377,8 @@ export function write(track, {wikiData}) { bottomRowContent: album.tracks.length > 1 && generateAlbumNavLinks(album, track, { - generatePreviousNextLinks, + generateNavigationLinks, + html, language, }), }, diff --git a/src/strings-default.json b/src/strings-default.json index d94f6de..15f9a33 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -131,10 +131,15 @@ "misc.alt.trackCover": "track cover", "misc.alt.artistAvatar": "artist avatar", "misc.alt.flashArt": "flash art", + "misc.artistLink": "{ARTIST}", + "misc.artistLink.withContribution": "{ARTIST} ({CONTRIB})", + "misc.artistLink.withExternalLinks": "{ARTIST} ({LINKS})", + "misc.artistLink.withContribution.withExternalLinks": "{ARTIST} ({CONTRIB}) ({LINKS})", "misc.chronology.seeArtistPages": "(See artist pages for chronology info!)", "misc.chronology.heading.coverArt": "{INDEX} cover art by {ARTIST}", "misc.chronology.heading.flash": "{INDEX} flash/game by {ARTIST}", "misc.chronology.heading.track": "{INDEX} track by {ARTIST}", + "misc.chronology.withNavigation": "{HEADING} ({NAVIGATION})", "misc.external.domain": "External ({DOMAIN})", "misc.external.local": "Wiki Archive (local upload)", "misc.external.bandcamp": "Bandcamp", diff --git a/src/upd8.js b/src/upd8.js index df5380b..86b2f52 100755 --- a/src/upd8.js +++ b/src/upd8.js @@ -89,7 +89,7 @@ import { generateChronologyLinks, generateCoverLink, generateInfoGalleryLinks, - generatePreviousNextLinks, + generateNavigationLinks, generateTrackListDividedByGroups, getAlbumGridHTML, getAlbumStylesheet, @@ -1562,18 +1562,6 @@ function generateRedirectPage(title, target, {language}) { `; } -// RIP toAnythingMan (previously getHrefOfAnythingMan), 2020-05-25<>2021-05-14. -// ........Yet the function 8reathes life anew as linkAnythingMan! ::::) -function linkAnythingMan(anythingMan, {link, wikiData, ...opts}) { - return wikiData.albumData.includes(anythingMan) - ? link.album(anythingMan, opts) - : wikiData.trackData.includes(anythingMan) - ? link.track(anythingMan, opts) - : wikiData.flashData?.includes(anythingMan) - ? link.flash(anythingMan, opts) - : 'idk bud'; -} - async function processLanguageFile(file) { const contents = await readFile(file, 'utf-8'); const json = JSON.parse(contents); @@ -2353,11 +2341,6 @@ async function main() { entries.map(([key, fn]) => [key, bindOpts(fn, {to})]) ); - bound.linkAnythingMan = bindOpts(linkAnythingMan, { - link: bound.link, - wikiData, - }); - bound.parseAttributes = bindOpts(parseAttributes, { to, }); @@ -2437,9 +2420,17 @@ async function main() { } ); + bound.generateNavigationLinks = bindOpts( + generateNavigationLinks, + { + link: bound.link, + language, + } + ); + bound.generateChronologyLinks = bindOpts(generateChronologyLinks, { + generateNavigationLinks: bound.generateNavigationLinks, link: bound.link, - linkAnythingMan: bound.linkAnythingMan, language, wikiData, }); @@ -2462,14 +2453,6 @@ async function main() { } ); - bound.generatePreviousNextLinks = bindOpts( - generatePreviousNextLinks, - { - link: bound.link, - language, - } - ); - bound.generateTrackListDividedByGroups = bindOpts( generateTrackListDividedByGroups, { diff --git a/src/util/link.js b/src/util/link.js index ee3579d..8fe3c2f 100644 --- a/src/util/link.js +++ b/src/util/link.js @@ -14,6 +14,17 @@ import * as html from './html.js'; import {getColors} from './colors.js'; +import { + Album, + Artist, + ArtTag, + Flash, + Group, + NewsEntry, + StaticPage, + Track, +} from '../data/things.js'; + export function getLinkThemeString(color) { if (!color) return ''; @@ -80,6 +91,21 @@ const linkPathname = (key, conf) => const linkIndex = (key, conf) => linkHelper((_, {to}) => to('localized.' + key), conf); +// Mapping of Thing constructor classes to the key for a link.x() function. +// These represent a sensible "default" link, i.e. to the primary page for +// the given thing based on what it's an instance of. This is used for the +// link.anything() function. +const linkAnythingMapping = [ + [Album, 'album'], + [Artist, 'artist'], + [ArtTag, 'tag'], + [Flash, 'flash'], + [Group, 'groupInfo'], + [NewsEntry, 'newsEntry'], + [StaticPage, 'staticPage'], + [Track, 'track'], +]; + const link = { globalOptions: { // This should usually only 8e used during development! It'll take any @@ -134,6 +160,34 @@ const link = { root: linkPathname('shared.path', {color: false}), data: linkPathname('data.path', {color: false}), site: linkPathname('localized.path', {color: false}), + + // This is NOT an arrow functions because it should be callable for other + // "this" objects - i.e, if we bind arguments in other functions on the same + // link object, link.anything() should use those bound functions, not the + // original ones we're exporting here. + // + // This function has been through a lot of names: + // - getHrefOfAnythingMan (2020-05-25) + // - toAnythingMan (2021-03-02) + // - linkAnythingMan (2021-05-14) + // - link.anything (2022-09-15) + // ...And it'll probably end up being renamed yet again one day! + // + anything(...args) { + if (!this) { + throw new Error(`Missing value for \`this\` - investigate JS call stack`); + } + + const [thing] = args; + + for (const [constructor, fnKey] of linkAnythingMapping) { + if (thing instanceof constructor) { + return Reflect.apply(this[fnKey], this, args); + } + } + + throw new Error(`Unrecognized type of thing for linking: ${thing}`); + }, }; export default link; diff --git a/src/util/sugar.js b/src/util/sugar.js index 754f199..8b59dee 100644 --- a/src/util/sugar.js +++ b/src/util/sugar.js @@ -107,12 +107,26 @@ export function escapeRegex(string) { return string.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); } +// Binds default values for arguments in a {key: value} type function argument +// (typically the second argument, but may be overridden by providing a +// [bindOpts.bindIndex] argument). Typically useful for preparing a function for +// reuse within one or multiple other contexts, which may not be aware of +// required or relevant values provided in the initial context. +// +// This function also passes the identity of `this` through (the returned value +// is not an arrow function), though note it's not a true bound function either +// (since Function.prototype.bind only supports positional arguments, not +// "options" specified via key/value). +// export function bindOpts(fn, bind) { const bindIndex = bind[bindOpts.bindIndex] ?? 1; const bound = function (...args) { const opts = args[bindIndex] ?? {}; - return fn(...args.slice(0, bindIndex), {...bind, ...opts}); + return Reflect.apply(fn, this, [ + ...args.slice(0, bindIndex), + {...bind, ...opts} + ]); }; Object.defineProperty(bound, 'name', { -- cgit 1.3.0-6-gf8a5