diff options
Diffstat (limited to 'src/page')
-rw-r--r-- | src/page/album-commentary.js | 143 | ||||
-rw-r--r-- | src/page/album.js | 612 | ||||
-rw-r--r-- | src/page/artist-alias.js | 32 | ||||
-rw-r--r-- | src/page/artist.js | 593 | ||||
-rw-r--r-- | src/page/flash-act.js | 23 | ||||
-rw-r--r-- | src/page/flash.js | 277 | ||||
-rw-r--r-- | src/page/group.js | 300 | ||||
-rw-r--r-- | src/page/homepage.js | 138 | ||||
-rw-r--r-- | src/page/index.js | 43 | ||||
-rw-r--r-- | src/page/listing.js | 226 | ||||
-rw-r--r-- | src/page/news.js | 141 | ||||
-rw-r--r-- | src/page/static.js | 52 | ||||
-rw-r--r-- | src/page/tag.js | 115 | ||||
-rw-r--r-- | src/page/track.js | 337 |
14 files changed, 554 insertions, 2478 deletions
diff --git a/src/page/album-commentary.js b/src/page/album-commentary.js deleted file mode 100644 index c03ae3d..0000000 --- a/src/page/album-commentary.js +++ /dev/null @@ -1,143 +0,0 @@ -// Album commentary page and index specifications. - -// Imports - -import fixWS from 'fix-whitespace'; - -import { - filterAlbumsByCommentary -} from '../util/wiki-data.js'; - -// Page exports - -export function condition({wikiData}) { - return filterAlbumsByCommentary(wikiData.albumData).length; -} - -export function targets({wikiData}) { - return filterAlbumsByCommentary(wikiData.albumData); -} - -export function write(album, {wikiData}) { - const { wikiInfo } = wikiData; - - const entries = [album, ...album.tracks].filter(x => x.commentary).map(x => x.commentary); - const words = entries.join(' ').split(' ').length; - - const page = { - type: 'page', - path: ['albumCommentary', album.directory], - page: ({ - getAlbumStylesheet, - getLinkThemeString, - getThemeString, - link, - strings, - to, - transformMultiline - }) => ({ - title: strings('albumCommentaryPage.title', {album: album.name}), - stylesheet: getAlbumStylesheet(album), - theme: getThemeString(album.color), - - main: { - content: fixWS` - <div class="long-content"> - <h1>${strings('albumCommentaryPage.title', { - album: link.album(album) - })}</h1> - <p>${strings('albumCommentaryPage.infoLine', { - words: `<b>${strings.count.words(words, {unit: true})}</b>`, - entries: `<b>${strings.count.commentaryEntries(entries.length, {unit: true})}</b>` - })}</p> - ${album.commentary && fixWS` - <h3>${strings('albumCommentaryPage.entry.title.albumCommentary')}</h3> - <blockquote> - ${transformMultiline(album.commentary)} - </blockquote> - `} - ${album.tracks.filter(t => t.commentary).map(track => fixWS` - <h3 id="${track.directory}">${strings('albumCommentaryPage.entry.title.trackCommentary', { - track: link.track(track) - })}</h3> - <blockquote style="${getLinkThemeString(track.color)}"> - ${transformMultiline(track.commentary)} - </blockquote> - `).join('\n')} - </div> - ` - }, - - nav: { - links: [ - {toHome: true}, - { - path: ['localized.commentaryIndex'], - title: strings('commentaryIndex.title') - }, - { - html: strings('albumCommentaryPage.nav.album', { - album: link.albumCommentary(album, {class: 'current'}) - }) - } - ] - } - }) - }; - - return [page]; -} - -export function writeTargetless({wikiData}) { - const data = filterAlbumsByCommentary(wikiData.albumData) - .map(album => ({ - album, - entries: [album, ...album.tracks].filter(x => x.commentary).map(x => x.commentary) - })) - .map(({ album, entries }) => ({ - album, entries, - words: entries.join(' ').split(' ').length - })); - - const totalEntries = data.reduce((acc, {entries}) => acc + entries.length, 0); - const totalWords = data.reduce((acc, {words}) => acc + words, 0); - - const page = { - type: 'page', - path: ['commentaryIndex'], - page: ({ - link, - strings - }) => ({ - title: strings('commentaryIndex.title'), - - main: { - content: fixWS` - <div class="long-content"> - <h1>${strings('commentaryIndex.title')}</h1> - <p>${strings('commentaryIndex.infoLine', { - words: `<b>${strings.count.words(totalWords, {unit: true})}</b>`, - entries: `<b>${strings.count.commentaryEntries(totalEntries, {unit: true})}</b>` - })}</p> - <p>${strings('commentaryIndex.albumList.title')}</p> - <ul> - ${data - .map(({ album, entries, words }) => fixWS` - <li>${strings('commentaryIndex.albumList.item', { - album: link.albumCommentary(album), - words: strings.count.words(words, {unit: true}), - entries: strings.count.commentaryEntries(entries.length, {unit: true}) - })}</li> - `) - .join('\n')} - </ul> - </div> - ` - }, - - nav: {simple: true} - }) - }; - - return [page]; -} diff --git a/src/page/album.js b/src/page/album.js index 19efc70..af41076 100644 --- a/src/page/album.js +++ b/src/page/album.js @@ -1,402 +1,272 @@ -// Album page specification. - -// Imports - -import fixWS from 'fix-whitespace'; - -import * as html from '../util/html.js'; - -import { - bindOpts -} from '../util/sugar.js'; - -import { - getAlbumCover, - getAlbumListTag, - getTotalDuration -} from '../util/wiki-data.js'; - -// Page exports +export const description = `per-album info, artwork gallery & commentary pages`; export function targets({wikiData}) { - return wikiData.albumData; + return wikiData.albumData; } -export function write(album, {wikiData}) { - const { wikiInfo } = wikiData; - - const unbound_trackToListItem = (track, { - getArtistString, - getLinkThemeString, - link, - strings - }) => { - const itemOpts = { - duration: strings.count.duration(track.duration), - track: link.track(track) - }; - return `<li style="${getLinkThemeString(track.color)}">${ - (track.artists === album.artists - ? strings('trackList.item.withDuration', itemOpts) - : strings('trackList.item.withDuration.withArtists', { - ...itemOpts, - by: `<span class="by">${ - strings('trackList.item.withArtists.by', { - artists: getArtistString(track.artists) - }) - }</span>` - })) - }</li>`; - }; +export function pathsForTarget(album) { + const hasCommentaryPage = !!album.commentary || album.tracks.some(t => t.commentary); - const commentaryEntries = [album, ...album.tracks].filter(x => x.commentary).length; - const albumDuration = getTotalDuration(album.tracks); + return [ + { + type: 'page', + path: ['album', album.directory], - const listTag = getAlbumListTag(album); + contentFunction: { + name: 'generateAlbumInfoPage', + args: [album], + }, + }, - const data = { - type: 'data', - path: ['album', album.directory], - data: ({ - serializeContribs, - serializeCover, - serializeGroupsForAlbum, - serializeLink - }) => ({ - name: album.name, - directory: album.directory, - dates: { - released: album.date, - trackArtAdded: album.trackArtDate, - coverArtAdded: album.coverArtDate, - addedToWiki: album.dateAdded - }, - duration: albumDuration, - color: album.color, - cover: serializeCover(album, getAlbumCover), - artists: serializeContribs(album.artists || []), - coverArtists: serializeContribs(album.coverArtists || []), - wallpaperArtists: serializeContribs(album.wallpaperArtists || []), - bannerArtists: serializeContribs(album.bannerArtists || []), - groups: serializeGroupsForAlbum(album), - trackGroups: album.trackGroups?.map(trackGroup => ({ - name: trackGroup.name, - color: trackGroup.color, - tracks: trackGroup.tracks.map(track => track.directory) - })), - tracks: album.tracks.map(track => ({ - link: serializeLink(track), - duration: track.duration - })) - }) - }; + { + type: 'page', + path: ['albumGallery', album.directory], - const page = { - type: 'page', - path: ['album', album.directory], - page: ({ - fancifyURL, - generateChronologyLinks, - generateCoverLink, - getAlbumStylesheet, - getArtistString, - getLinkThemeString, - getThemeString, - link, - strings, - transformMultiline - }) => { - const trackToListItem = bindOpts(unbound_trackToListItem, { - getArtistString, - getLinkThemeString, - link, - strings - }); + contentFunction: { + name: 'generateAlbumGalleryPage', + args: [album], + }, + }, - return { - title: strings('albumPage.title', {album: album.name}), - stylesheet: getAlbumStylesheet(album), - theme: getThemeString(album.color, [ - `--album-directory: ${album.directory}` - ]), + hasCommentaryPage && { + type: 'page', + path: ['albumCommentary', album.directory], - banner: album.bannerArtists && { - dimensions: album.bannerDimensions, - path: ['media.albumBanner', album.directory], - alt: strings('misc.alt.albumBanner'), - position: 'top' - }, + contentFunction: { + name: 'generateAlbumCommentaryPage', + args: [album], + }, + }, - main: { - content: fixWS` - ${generateCoverLink({ - path: ['media.albumCover', album.directory], - alt: strings('misc.alt.albumCover'), - tags: album.artTags - })} - <h1>${strings('albumPage.title', {album: album.name})}</h1> - <p> - ${[ - album.artists && strings('releaseInfo.by', { - artists: getArtistString(album.artists, { - showContrib: true, - showIcons: true - }) - }), - album.coverArtists && strings('releaseInfo.coverArtBy', { - artists: getArtistString(album.coverArtists, { - showContrib: true, - showIcons: true - }) - }), - album.wallpaperArtists && strings('releaseInfo.wallpaperArtBy', { - artists: getArtistString(album.wallpaperArtists, { - showContrib: true, - showIcons: true - }) - }), - album.bannerArtists && strings('releaseInfo.bannerArtBy', { - artists: getArtistString(album.bannerArtists, { - showContrib: true, - showIcons: true - }) - }), - strings('releaseInfo.released', { - date: strings.count.date(album.date) - }), - +album.coverArtDate !== +album.date && strings('releaseInfo.artReleased', { - date: strings.count.date(album.coverArtDate) - }), - strings('releaseInfo.duration', { - duration: strings.count.duration(albumDuration, {approximate: album.tracks.length > 1}) - }) - ].filter(Boolean).join('<br>\n')} - </p> - ${commentaryEntries && `<p>${ - strings('releaseInfo.viewCommentary', { - link: link.albumCommentary(album, { - text: strings('releaseInfo.viewCommentary.link') - }) - }) - }</p>`} - ${album.urls.length && `<p>${ - strings('releaseInfo.listenOn', { - links: strings.list.or(album.urls.map(url => fancifyURL(url, {album: true}))) - }) - }</p>`} - ${album.trackGroups ? fixWS` - <dl class="album-group-list"> - ${album.trackGroups.map(({ name, color, startIndex, tracks }) => fixWS` - <dt>${ - strings('trackList.group', { - duration: strings.count.duration(getTotalDuration(tracks), {approximate: tracks.length > 1}), - group: name - }) - }</dt> - <dd><${listTag === 'ol' ? `ol start="${startIndex + 1}"` : listTag}> - ${tracks.map(trackToListItem).join('\n')} - </${listTag}></dd> - `).join('\n')} - </dl> - ` : fixWS` - <${listTag}> - ${album.tracks.map(trackToListItem).join('\n')} - </${listTag}> - `} - <p> - ${[ - strings('releaseInfo.addedToWiki', { - date: strings.count.date(album.dateAdded) - }) - ].filter(Boolean).join('<br>\n')} - </p> - ${album.commentary && fixWS` - <p>${strings('releaseInfo.artistCommentary')}</p> - <blockquote> - ${transformMultiline(album.commentary)} - </blockquote> - `} - ` - }, + /* + { + type: 'data', + path: ['album', album.directory], - sidebarLeft: generateAlbumSidebar(album, null, { - fancifyURL, - getLinkThemeString, - link, - strings, - transformMultiline, - wikiData - }), + contentFunction: { + name: 'generateAlbumDataFile', + args: [album], + }, + }, + */ + ]; +} - nav: { - links: [ - {toHome: true}, - { - html: strings('albumPage.nav.album', { - album: link.album(album, {class: 'current'}) - }) - }, - album.tracks.length > 1 && - { - divider: false, - html: generateAlbumNavLinks(album, null, {strings}) - } - ], - content: html.tag('div', generateAlbumChronologyLinks(album, null, {generateChronologyLinks})) - } - }; - } - }; +export function pathsTargetless({wikiData: {wikiInfo}}) { + return [ + { + type: 'page', + path: ['commentaryIndex'], + contentFunction: {name: 'generateCommentaryIndexPage'}, + }, - return [page, data]; + wikiInfo.canonicalBase === 'https://hsmusic.wiki/' && + { + type: 'redirect', + fromPath: ['page', 'list/all-commentary'], + toPath: ['commentaryIndex'], + title: 'Album Commentary', + }, + ]; } -// Utility functions +/* +export function write(album, {wikiData}) { + const getSocialEmbedDescription = ({ + getArtistString: _getArtistString, + language, + }) => { + const hasDuration = albumDuration > 0; + const hasTracks = album.tracks.length > 0; + const hasDate = !!album.date; + if (!hasDuration && !hasTracks && !hasDate) return ''; -export function generateAlbumSidebar(album, currentTrack, { - fancifyURL, - getLinkThemeString, - link, - strings, - transformMultiline, - wikiData -}) { - const listTag = getAlbumListTag(album); + return language.formatString( + 'albumPage.socialEmbed.body' + [ + hasDuration && '.withDuration', + hasTracks && '.withTracks', + hasDate && '.withReleaseDate', + ].filter(Boolean).join(''), + Object.fromEntries([ + hasDuration && + ['duration', language.formatDuration(albumDuration)], + hasTracks && + ['tracks', language.countTracks(album.tracks.length, {unit: true})], + hasDate && + ['date', language.formatDate(album.date)], + ].filter(Boolean))); + }; - const trackGroups = album.trackGroups || [{ - name: strings('albumSidebar.trackList.fallbackGroupName'), - color: album.color, - startIndex: 0, - tracks: album.tracks - }]; + const data = { + type: 'data', + path: ['album', album.directory], + data: ({ + serializeContribs, + serializeCover, + serializeGroupsForAlbum, + serializeLink, + }) => ({ + name: album.name, + directory: album.directory, + dates: { + released: album.date, + trackArtAdded: album.trackArtDate, + coverArtAdded: album.coverArtDate, + addedToWiki: album.dateAddedToWiki, + }, + duration: albumDuration, + color: album.color, + cover: serializeCover(album, getAlbumCover), + artistContribs: serializeContribs(album.artistContribs), + coverArtistContribs: serializeContribs(album.coverArtistContribs), + wallpaperArtistContribs: serializeContribs(album.wallpaperArtistContribs), + bannerArtistContribs: serializeContribs(album.bannerArtistContribs), + groups: serializeGroupsForAlbum(album), + trackSections: album.trackSections?.map((section) => ({ + name: section.name, + color: section.color, + tracks: section.tracks.map((track) => track.directory), + })), + tracks: album.tracks.map((track) => ({ + link: serializeLink(track), + duration: track.duration, + })), + }), + }; - const trackToListItem = track => html.tag('li', - {class: track === currentTrack && 'current'}, - strings('albumSidebar.trackList.item', { - track: link.track(track) - })); + // TODO: only gen if there are any tracks with art + const galleryPage = { + type: 'page', + path: ['albumGallery', album.directory], + page: ({ + // generateInfoGalleryLinks, + // generateNavigationLinks, + getAlbumCover, + getAlbumStylesheet, + getGridHTML, + getTrackCover, + // getLinkThemeString, + getThemeString, + html, + language, + link, + }) => ({ + title: language.$('albumGalleryPage.title', {album: album.name}), + stylesheet: getAlbumStylesheet(album), - const trackListPart = fixWS` - <h1>${link.album(album)}</h1> - ${trackGroups.map(({ name, color, startIndex, tracks }) => - html.tag('details', { - // Leave side8ar track groups collapsed on al8um homepage, - // since there's already a view of all the groups expanded - // in the main content area. - open: currentTrack && tracks.includes(currentTrack), - class: tracks.includes(currentTrack) && 'current' - }, [ - html.tag('summary', - {style: getLinkThemeString(color)}, - (listTag === 'ol' - ? strings('albumSidebar.trackList.group.withRange', { - group: `<span class="group-name">${name}</span>`, - range: `${startIndex + 1}–${startIndex + tracks.length}` - }) - : strings('albumSidebar.trackList.group', { - group: `<span class="group-name">${name}</span>` - })) - ), - fixWS` - <${listTag === 'ol' ? `ol start="${startIndex + 1}"` : listTag}> - ${tracks.map(trackToListItem).join('\n')} - </${listTag}> - ` - ])).join('\n')} - `; + themeColor: album.color, + theme: getThemeString(album.color), - const { groups } = album; + main: { + classes: ['top-index'], + headingMode: 'static', - const groupParts = groups.map(group => { - const index = group.albums.indexOf(album); - const next = group.albums[index + 1]; - const previous = group.albums[index - 1]; - return {group, next, previous}; - }).map(({group, next, previous}) => fixWS` - <h1>${ - strings('albumSidebar.groupBox.title', { - group: link.groupInfo(group) - }) - }</h1> - ${!currentTrack && transformMultiline(group.descriptionShort)} - ${group.urls.length && `<p>${ - strings('releaseInfo.visitOn', { - links: strings.list.or(group.urls.map(url => fancifyURL(url))) - }) - }</p>`} - ${!currentTrack && fixWS` - ${next && `<p class="group-chronology-link">${ - strings('albumSidebar.groupBox.next', { - album: link.album(next) - }) - }</p>`} - ${previous && `<p class="group-chronology-link">${ - strings('albumSidebar.groupBox.previous', { - album: link.album(previous) + content: [ + html.tag('p', + {class: 'quick-info'}, + (album.date + ? language.$('albumGalleryPage.infoLine.withDate', { + tracks: html.tag('b', + language.countTracks(album.tracks.length, {unit: true})), + duration: html.tag('b', + language.formatDuration(albumDuration, {unit: true})), + date: html.tag('b', + language.formatDate(album.date)), }) - }</p>`} - `} - `); + : language.$('albumGalleryPage.infoLine', { + tracks: html.tag('b', + language.countTracks(album.tracks.length, {unit: true})), + duration: html.tag('b', + language.formatDuration(albumDuration, {unit: true})), + }))), + + html.tag('div', + {class: 'grid-listing'}, + getGridHTML({ + linkFn: (t, opts) => t.album ? link.track(t, opts) : link.album(t, opts), + noSrcTextFn: t => + language.$('misc.albumGalleryGrid.noCoverArt', { + name: t.name, + }), - if (groupParts.length) { - if (currentTrack) { - const combinedGroupPart = groupParts.join('\n<hr>\n'); - return { - multiple: [ - trackListPart, - combinedGroupPart - ] - }; - } else { - return { - multiple: [ - ...groupParts, - trackListPart - ] - }; - } - } else { - return { - content: trackListPart - }; - } + srcFn(t) { + if (!t.album) { + return getAlbumCover(t); + } else if (t.hasUniqueCoverArt) { + return getTrackCover(t); + } else { + return null; + } + }, + + entries: [ + // {item: album}, + ...album.tracks.map(track => ({item: track})), + ], + })), + ], + }, + + nav: generateAlbumExtrasPageNav(album, 'gallery', { + html, + language, + link, + }), + }), + }; } -export function generateAlbumNavLinks(album, currentTrack, { - generatePreviousNextLinks, - strings +export function generateAlbumSecondaryNav(album, currentTrack, { + getLinkThemeString, + html, + language, + link, }) { - if (album.tracks.length <= 1) { - return ''; - } + const isAlbumPage = !currentTrack; - const previousNextLinks = currentTrack && generatePreviousNextLinks(currentTrack, { - data: album.tracks, - linkKey: 'track' - }); - const randomLink = `<a href="#" data-random="track-in-album" id="random-button">${ - (currentTrack - ? strings('trackPage.nav.random') - : strings('albumPage.nav.randomTrack')) - }</a>`; + const {groups} = album; - return (previousNextLinks - ? `(${previousNextLinks}<span class="js-hide-until-data">, ${randomLink}</span>)` - : `<span class="js-hide-until-data">(${randomLink})</span>`); -} + if (empty(groups)) { + return null; + } + + const groupParts = groups + .map((group) => { + const albums = group.albums.filter((album) => album.date); + const index = albums.indexOf(album); + const next = index >= 0 && albums[index + 1]; + const previous = index > 0 && albums[index - 1]; + return {group, next, previous}; + }) + .map(({group, next, previous}) => { + const previousLink = + isAlbumPage && + previous && + link.album(previous, { + color: false, + text: language.$('misc.nav.previous'), + }); + const nextLink = + isAlbumPage && + next && + link.album(next, { + color: false, + text: language.$('misc.nav.next'), + }); + const links = [previousLink, nextLink].filter(Boolean); + return html.tag('span', + {style: getLinkThemeString(group.color)}, + [ + language.$('albumSidebar.groupBox.title', { + group: link.groupInfo(group), + }), + !empty(links) && `(${language.formatUnitList(links)})`, + ]); + }); -export function generateAlbumChronologyLinks(album, currentTrack, {generateChronologyLinks}) { - return [ - currentTrack && generateChronologyLinks(currentTrack, { - contribKey: 'artists', - getThings: artist => [...artist.tracks.asArtist, ...artist.tracks.asContributor], - headingString: 'misc.chronology.heading.track' - }), - generateChronologyLinks(currentTrack || album, { - contribKey: 'coverArtists', - dateKey: 'coverArtDate', - getThings: artist => [...artist.albums.asCoverArtist, ...artist.tracks.asCoverArtist], - headingString: 'misc.chronology.heading.coverArt' - }) - ].filter(Boolean).join('\n'); + return { + classes: ['nav-links-groups'], + content: groupParts, + }; } +*/ diff --git a/src/page/artist-alias.js b/src/page/artist-alias.js index d03510a..d230522 100644 --- a/src/page/artist-alias.js +++ b/src/page/artist-alias.js @@ -1,22 +1,24 @@ -// Artist alias redirect pages. -// (Makes old permalinks bring visitors to the up-to-date page.) +export const description = `redirects for aliased artist names`; export function targets({wikiData}) { - return wikiData.artistAliasData; + return wikiData.artistAliasData; } -export function write(aliasArtist, {wikiData}) { - // This function doesn't actually use wikiData, 8ut, um, consistency? +export function pathsForTarget(aliasArtist) { + const {aliasedArtist} = aliasArtist; - const { alias: targetArtist } = aliasArtist; + // Don't generate a redirect page if this aliased name resolves to the same + // directory as the original artist! See issue #280. + if (aliasArtist.directory === aliasedArtist.directory) { + return []; + } - const redirect = { - type: 'redirect', - fromPath: ['artist', aliasArtist.directory], - toPath: ['artist', targetArtist.directory], - title: () => aliasArtist.name - }; - - return [redirect]; + return [ + { + type: 'redirect', + fromPath: ['artist', aliasArtist.directory], + toPath: ['artist', aliasedArtist.directory], + title: aliasedArtist.name, + }, + ]; } - diff --git a/src/page/artist.js b/src/page/artist.js index e6160be..27ff896 100644 --- a/src/page/artist.js +++ b/src/page/artist.js @@ -1,512 +1,103 @@ -// Artist page specification. -// -// NB: See artist-alias.js for artist alias redirect pages. - -// Imports - -import fixWS from 'fix-whitespace'; - -import * as html from '../util/html.js'; - -import { - UNRELEASED_TRACKS_DIRECTORY -} from '../util/magic-constants.js'; - -import { - bindOpts, - unique -} from '../util/sugar.js'; +import {empty} from '#sugar'; -import { - chunkByProperties, - getTotalDuration, - sortByDate -} from '../util/wiki-data.js'; - -// Page exports +export const description = `per-artist info & artwork gallery pages`; +// NB: See artist-alias.js for artist alias redirect pages. export function targets({wikiData}) { - return wikiData.artistData; + return wikiData.artistData; } -export function write(artist, {wikiData}) { - const { groupData, wikiInfo } = wikiData; - - const { - name, - urls = [], - note = '' - } = artist; - - const artThingsAll = sortByDate(unique([...artist.albums.asCoverArtist, ...artist.albums.asWallpaperArtist, ...artist.albums.asBannerArtist, ...artist.tracks.asCoverArtist])); - const artThingsGallery = sortByDate([...artist.albums.asCoverArtist, ...artist.tracks.asCoverArtist]); - const commentaryThings = sortByDate([...artist.albums.asCommentator, ...artist.tracks.asCommentator]); - - const hasGallery = artThingsGallery.length > 0; - - const getArtistsAndContrib = (thing, key) => ({ - artists: thing[key]?.filter(({ who }) => who !== artist), - contrib: thing[key]?.find(({ who }) => who === artist), - thing, - key - }); - - const artListChunks = chunkByProperties(sortByDate(artThingsAll.flatMap(thing => - (['coverArtists', 'wallpaperArtists', 'bannerArtists'] - .map(key => getArtistsAndContrib(thing, key)) - .filter(({ contrib }) => contrib) - .map(props => ({ - album: thing.album || thing, - track: thing.album ? thing : null, - date: +(thing.coverArtDate || thing.date), - ...props - }))) - )), ['date', 'album']); - - const commentaryListChunks = chunkByProperties(commentaryThings.map(thing => ({ - album: thing.album || thing, - track: thing.album ? thing : null - })), ['album']); - - const allTracks = sortByDate(unique([...artist.tracks.asArtist, ...artist.tracks.asContributor])); - const unreleasedTracks = allTracks.filter(track => track.album.directory === UNRELEASED_TRACKS_DIRECTORY); - const releasedTracks = allTracks.filter(track => track.album.directory !== UNRELEASED_TRACKS_DIRECTORY); - - const chunkTracks = tracks => ( - chunkByProperties(tracks.map(track => ({ - track, - date: +track.date, - album: track.album, - duration: track.duration, - artists: (track.artists.some(({ who }) => who === artist) - ? track.artists.filter(({ who }) => who !== artist) - : track.contributors.filter(({ who }) => who !== artist)), - contrib: { - who: artist, - what: [ - track.artists.find(({ who }) => who === artist)?.what, - track.contributors.find(({ who }) => who === artist)?.what - ].filter(Boolean).join(', ') - } - })), ['date', 'album']) - .map(({date, album, chunk}) => ({ - date, album, chunk, - duration: getTotalDuration(chunk), - }))); - - const unreleasedTrackListChunks = chunkTracks(unreleasedTracks); - const releasedTrackListChunks = chunkTracks(releasedTracks); - - const totalReleasedDuration = getTotalDuration(releasedTracks); - - const countGroups = things => { - const usedGroups = things.flatMap(thing => thing.groups || thing.album?.groups || []); - return groupData - .map(group => ({ - group, - contributions: usedGroups.filter(g => g === group).length - })) - .filter(({ contributions }) => contributions > 0) - .sort((a, b) => b.contributions - a.contributions); - }; - - const musicGroups = countGroups(releasedTracks); - const artGroups = countGroups(artThingsAll); - - let flashes, flashListChunks; - if (wikiInfo.features.flashesAndGames) { - flashes = sortByDate(artist.flashes.asContributor.slice()); - flashListChunks = ( - chunkByProperties(flashes.map(flash => ({ - act: flash.act, - flash, - date: flash.date, - // Manual artists/contrib properties here, 8ecause we don't - // want to show the full list of other contri8utors inline. - // (It can often 8e very, very large!) - artists: [], - contrib: flash.contributors.find(({ who }) => who === artist) - })), ['act']) - .map(({ act, chunk }) => ({ - act, chunk, - dateFirst: chunk[0].date, - dateLast: chunk[chunk.length - 1].date - }))); - } - - const generateEntryAccents = ({ - getArtistString, strings, - aka, entry, artists, contrib - }) => - (aka - ? strings('artistPage.creditList.entry.rerelease', {entry}) - : (artists.length - ? (contrib.what - ? strings('artistPage.creditList.entry.withArtists.withContribution', { - entry, - artists: getArtistString(artists), - contribution: contrib.what - }) - : strings('artistPage.creditList.entry.withArtists', { - entry, - artists: getArtistString(artists) - })) - : (contrib.what - ? strings('artistPage.creditList.entry.withContribution', { - entry, - contribution: contrib.what - }) - : entry))); - - const unbound_generateTrackList = (chunks, { - getArtistString, link, strings - }) => fixWS` - <dl> - ${chunks.map(({date, album, chunk, duration}) => fixWS` - <dt>${strings('artistPage.creditList.album.withDate.withDuration', { - album: link.album(album), - date: strings.count.date(date), - duration: strings.count.duration(duration, {approximate: true}) - })}</dt> - <dd><ul> - ${(chunk - .map(({track, ...props}) => ({ - aka: track.aka, - entry: strings('artistPage.creditList.entry.track.withDuration', { - track: link.track(track), - duration: strings.count.duration(track.duration) - }), - ...props - })) - .map(({aka, ...opts}) => html.tag('li', - {class: aka && 'rerelease'}, - generateEntryAccents({getArtistString, strings, aka, ...opts}))) - .join('\n'))} - </ul></dd> - `).join('\n')} - </dl> - `; - - const unbound_serializeArtistsAndContrib = (key, { - serializeContribs, - serializeLink - }) => thing => { - const { artists, contrib } = getArtistsAndContrib(thing, key); - const ret = {}; - ret.link = serializeLink(thing); - if (contrib.what) ret.contribution = contrib.what; - if (artists.length) ret.otherArtists = serializeContribs(artists); - return ret; - }; - - const unbound_serializeTrackListChunks = (chunks, {serializeLink}) => - chunks.map(({date, album, chunk, duration}) => ({ - album: serializeLink(album), - date, - duration, - tracks: chunk.map(({ track }) => ({ - link: serializeLink(track), - duration: track.duration - })) - })); - - const data = { - type: 'data', - path: ['artist', artist.directory], - data: ({ - serializeContribs, - serializeLink - }) => { - const serializeArtistsAndContrib = bindOpts(unbound_serializeArtistsAndContrib, { - serializeContribs, - serializeLink - }); - - const serializeTrackListChunks = bindOpts(unbound_serializeTrackListChunks, { - serializeLink - }); - - return { - albums: { - asCoverArtist: artist.albums.asCoverArtist.map(serializeArtistsAndContrib('coverArtists')), - asWallpaperArtist: artist.albums.asWallpaperArtist.map(serializeArtistsAndContrib('wallpaperArtists')), - asBannerArtist: artist.albums.asBannerArtist.map(serializeArtistsAndContrib('bannerArtists')) - }, - flashes: wikiInfo.features.flashesAndGames ? { - asContributor: artist.flashes.asContributor - .map(flash => getArtistsAndContrib(flash, 'contributors')) - .map(({ contrib, thing: flash }) => ({ - link: serializeLink(flash), - contribution: contrib.what - })) - } : null, - tracks: { - asArtist: artist.tracks.asArtist.map(serializeArtistsAndContrib('artists')), - asContributor: artist.tracks.asContributor.map(serializeArtistsAndContrib('contributors')), - chunked: { - released: serializeTrackListChunks(releasedTrackListChunks), - unreleased: serializeTrackListChunks(unreleasedTrackListChunks) - } - } - }; - } - }; - - const infoPage = { - type: 'page', - path: ['artist', artist.directory], - page: ({ - fancifyURL, - generateCoverLink, - generateInfoGalleryLinks, - getArtistString, - link, - strings, - to, - transformMultiline - }) => { - const generateTrackList = bindOpts(unbound_generateTrackList, { - getArtistString, - link, - strings - }); - - return { - title: strings('artistPage.title', {artist: name}), - - main: { - content: fixWS` - ${artist.hasAvatar && generateCoverLink({ - path: ['localized.artistAvatar', artist.directory], - alt: strings('misc.alt.artistAvatar') - })} - <h1>${strings('artistPage.title', {artist: name})}</h1> - ${note && fixWS` - <p>${strings('releaseInfo.note')}</p> - <blockquote> - ${transformMultiline(note)} - </blockquote> - <hr> - `} - ${urls.length && `<p>${strings('releaseInfo.visitOn', { - links: strings.list.or(urls.map(url => fancifyURL(url, {strings}))) - })}</p>`} - ${hasGallery && `<p>${strings('artistPage.viewArtGallery', { - link: link.artistGallery(artist, { - text: strings('artistPage.viewArtGallery.link') - }) - })}</p>`} - <p>${strings('misc.jumpTo.withLinks', { - links: strings.list.unit([ - [ - [...releasedTracks, ...unreleasedTracks].length && `<a href="#tracks">${strings('artistPage.trackList.title')}</a>`, - unreleasedTracks.length && `(<a href="#unreleased-tracks">${strings('artistPage.unreleasedTrackList.title')}</a>)` - ].filter(Boolean).join(' '), - artThingsAll.length && `<a href="#art">${strings('artistPage.artList.title')}</a>`, - wikiInfo.features.flashesAndGames && flashes.length && `<a href="#flashes">${strings('artistPage.flashList.title')}</a>`, - commentaryThings.length && `<a href="#commentary">${strings('artistPage.commentaryList.title')}</a>` - ].filter(Boolean)) - })}</p> - ${(releasedTracks.length || unreleasedTracks.length) && fixWS` - <h2 id="tracks">${strings('artistPage.trackList.title')}</h2> - `} - ${releasedTracks.length && fixWS` - <p>${strings('artistPage.contributedDurationLine', { - artist: artist.name, - duration: strings.count.duration(totalReleasedDuration, {approximate: true, unit: true}) - })}</p> - <p>${strings('artistPage.musicGroupsLine', { - groups: strings.list.unit(musicGroups - .map(({ group, contributions }) => strings('artistPage.groupsLine.item', { - group: link.groupInfo(group), - contributions: strings.count.contributions(contributions) - }))) - })}</p> - ${generateTrackList(releasedTrackListChunks)} - `} - ${unreleasedTracks.length && fixWS` - <h3 id="unreleased-tracks">${strings('artistPage.unreleasedTrackList.title')}</h3> - ${generateTrackList(unreleasedTrackListChunks)} - `} - ${artThingsAll.length && fixWS` - <h2 id="art">${strings('artistPage.artList.title')}</h2> - ${hasGallery && `<p>${strings('artistPage.viewArtGallery.orBrowseList', { - link: link.artistGallery(artist, { - text: strings('artistPage.viewArtGallery.link') - }) - })}</p>`} - <p>${strings('artistPage.artGroupsLine', { - groups: strings.list.unit(artGroups - .map(({ group, contributions }) => strings('artistPage.groupsLine.item', { - group: link.groupInfo(group), - contributions: strings.count.contributions(contributions) - }))) - })}</p> - <dl> - ${artListChunks.map(({date, album, chunk}) => fixWS` - <dt>${strings('artistPage.creditList.album.withDate', { - album: link.album(album), - date: strings.count.date(date) - })}</dt> - <dd><ul> - ${(chunk - .map(({album, track, key, ...props}) => ({ - entry: (track - ? strings('artistPage.creditList.entry.track', { - track: link.track(track) - }) - : `<i>${strings('artistPage.creditList.entry.album.' + { - wallpaperArtists: 'wallpaperArt', - bannerArtists: 'bannerArt', - coverArtists: 'coverArt' - }[key])}</i>`), - ...props - })) - .map(opts => generateEntryAccents({getArtistString, strings, ...opts})) - .map(row => `<li>${row}</li>`) - .join('\n'))} - </ul></dd> - `).join('\n')} - </dl> - `} - ${wikiInfo.features.flashesAndGames && flashes.length && fixWS` - <h2 id="flashes">${strings('artistPage.flashList.title')}</h2> - <dl> - ${flashListChunks.map(({act, chunk, dateFirst, dateLast}) => fixWS` - <dt>${strings('artistPage.creditList.flashAct.withDateRange', { - act: link.flash(chunk[0].flash, {text: act.name}), - dateRange: strings.count.dateRange([dateFirst, dateLast]) - })}</dt> - <dd><ul> - ${(chunk - .map(({flash, ...props}) => ({ - entry: strings('artistPage.creditList.entry.flash', { - flash: link.flash(flash) - }), - ...props - })) - .map(opts => generateEntryAccents({getArtistString, strings, ...opts})) - .map(row => `<li>${row}</li>`) - .join('\n'))} - </ul></dd> - `).join('\n')} - </dl> - `} - ${commentaryThings.length && fixWS` - <h2 id="commentary">${strings('artistPage.commentaryList.title')}</h2> - <dl> - ${commentaryListChunks.map(({album, chunk}) => fixWS` - <dt>${strings('artistPage.creditList.album', { - album: link.album(album) - })}</dt> - <dd><ul> - ${(chunk - .map(({album, track, ...props}) => track - ? strings('artistPage.creditList.entry.track', { - track: link.track(track) - }) - : `<i>${strings('artistPage.creditList.entry.album.commentary')}</i>`) - .map(row => `<li>${row}</li>`) - .join('\n'))} - </ul></dd> - `).join('\n')} - </dl> - `} - ` - }, - - nav: generateNavForArtist(artist, false, hasGallery, { - generateInfoGalleryLinks, - link, - strings, - wikiData - }) - }; - } - }; - - const galleryPage = hasGallery && { - type: 'page', - path: ['artistGallery', artist.directory], - page: ({ - generateInfoGalleryLinks, - getAlbumCover, - getGridHTML, - getTrackCover, - link, - strings, - to - }) => ({ - title: strings('artistGalleryPage.title', {artist: name}), - - main: { - classes: ['top-index'], - content: fixWS` - <h1>${strings('artistGalleryPage.title', {artist: name})}</h1> - <p class="quick-info">${strings('artistGalleryPage.infoLine', { - coverArts: strings.count.coverArts(artThingsGallery.length, {unit: true}) - })}</p> - <div class="grid-listing"> - ${getGridHTML({ - entries: artThingsGallery.map(item => ({item})), - srcFn: thing => (thing.album - ? getTrackCover(thing) - : getAlbumCover(thing)), - linkFn: (thing, opts) => (thing.album - ? link.track(thing, opts) - : link.album(thing, opts)) - })} - </div> - ` - }, - - nav: generateNavForArtist(artist, true, hasGallery, { - generateInfoGalleryLinks, - link, - strings, - wikiData - }) - }) - }; - - return [data, infoPage, galleryPage].filter(Boolean); +export function pathsForTarget(artist) { + const hasGalleryPage = + !empty(artist.tracksAsCoverArtist) || + !empty(artist.albumsAsCoverArtist); + + return [ + { + type: 'page', + path: ['artist', artist.directory], + + contentFunction: { + name: 'generateArtistInfoPage', + args: [artist], + }, + }, + + hasGalleryPage && { + type: 'page', + path: ['artistGallery', artist.directory], + + contentFunction: { + name: 'generateArtistGalleryPage', + args: [artist], + }, + }, + ]; } -// Utility functions - -function generateNavForArtist(artist, isGallery, hasGallery, { - generateInfoGalleryLinks, - link, - strings, - wikiData -}) { - const { wikiInfo } = wikiData; +/* +const unbound_serializeArtistsAndContrib = + (key, {serializeContribs, serializeLink}) => + (thing) => { + const {artists, contrib} = getArtistsAndContrib(thing, key); + const ret = {}; + ret.link = serializeLink(thing); + if (contrib.what) ret.contribution = contrib.what; + if (!empty(artists)) ret.otherArtists = serializeContribs(artists); + return ret; + }; + +const unbound_serializeTrackListChunks = (chunks, {serializeLink}) => + chunks.map(({date, album, chunk, duration}) => ({ + album: serializeLink(album), + date, + duration, + tracks: chunk.map(({track}) => ({ + link: serializeLink(track), + duration: track.duration, + })), + })); + +const data = { + type: 'data', + path: ['artist', artist.directory], + data: ({serializeContribs, serializeLink}) => { + const serializeArtistsAndContrib = bindOpts(unbound_serializeArtistsAndContrib, { + serializeContribs, + serializeLink, + }); - const infoGalleryLinks = (hasGallery && - generateInfoGalleryLinks(artist, isGallery, { - link, strings, - linkKeyGallery: 'artistGallery', - linkKeyInfo: 'artist' - })) + const serializeTrackListChunks = bindOpts(unbound_serializeTrackListChunks, { + serializeLink, + }); return { - links: [ - {toHome: true}, - wikiInfo.features.listings && - { - path: ['localized.listingIndex'], - title: strings('listingIndex.title') - }, - { - html: strings('artistPage.nav.artist', { - artist: link.artist(artist, {class: 'current'}) - }) - }, - hasGallery && - { - divider: false, - html: `(${infoGalleryLinks})` - } - ] + albums: { + asCoverArtist: artist.albumsAsCoverArtist + .map(serializeArtistsAndContrib('coverArtistContribs')), + asWallpaperArtist: artist.albumsAsWallpaperArtist + .map(serializeArtistsAndContrib('wallpaperArtistContribs')), + asBannerArtist: artist.albumsAsBannerArtis + .map(serializeArtistsAndContrib('bannerArtistContribs')), + }, + flashes: wikiInfo.enableFlashesAndGames + ? { + asContributor: artist.flashesAsContributor + .map(flash => getArtistsAndContrib(flash, 'contributorContribs')) + .map(({contrib, thing: flash}) => ({ + link: serializeLink(flash), + contribution: contrib.what, + })), + } + : null, + tracks: { + asArtist: artist.tracksAsArtist + .map(serializeArtistsAndContrib('artistContribs')), + asContributor: artist.tracksAsContributo + .map(serializeArtistsAndContrib('contributorContribs')), + chunked: serializeTrackListChunks(trackListChunks), + }, }; -} + }, +}; +*/ diff --git a/src/page/flash-act.js b/src/page/flash-act.js new file mode 100644 index 0000000..e54525a --- /dev/null +++ b/src/page/flash-act.js @@ -0,0 +1,23 @@ +export const description = `flash act gallery pages`; + +export function condition({wikiData}) { + return wikiData.wikiInfo.enableFlashesAndGames; +} + +export function targets({wikiData}) { + return wikiData.flashActData; +} + +export function pathsForTarget(flashAct) { + return [ + { + type: 'page', + path: ['flashActGallery', flashAct.directory], + + contentFunction: { + name: 'generateFlashActGalleryPage', + args: [flashAct], + }, + }, + ]; +} diff --git a/src/page/flash.js b/src/page/flash.js index 9c59016..7df7415 100644 --- a/src/page/flash.js +++ b/src/page/flash.js @@ -1,264 +1,33 @@ -// Flash page and index specifications. - -// Imports - -import fixWS from 'fix-whitespace'; - -import * as html from '../util/html.js'; - -import { - getFlashLink -} from '../util/wiki-data.js'; - -// Page exports +export const description = `flash & game pages`; export function condition({wikiData}) { - return wikiData.wikiInfo.features.flashesAndGames; + return wikiData.wikiInfo.enableFlashesAndGames; } export function targets({wikiData}) { - return wikiData.flashData; + return wikiData.flashData; } -export function write(flash, {wikiData}) { - const page = { - type: 'page', - path: ['flash', flash.directory], - page: ({ - fancifyFlashURL, - generateChronologyLinks, - generateCoverLink, - generatePreviousNextLinks, - getArtistString, - getFlashCover, - getThemeString, - link, - strings, - transformInline - }) => ({ - title: strings('flashPage.title', {flash: flash.name}), - theme: getThemeString(flash.color, [ - `--flash-directory: ${flash.directory}` - ]), - - main: { - content: fixWS` - <h1>${strings('flashPage.title', {flash: flash.name})}</h1> - ${generateCoverLink({ - src: getFlashCover(flash), - alt: strings('misc.alt.flashArt') - })} - <p>${strings('releaseInfo.released', {date: strings.count.date(flash.date)})}</p> - ${(flash.page || flash.urls.length) && `<p>${strings('releaseInfo.playOn', { - links: strings.list.or([ - flash.page && getFlashLink(flash), - ...flash.urls - ].map(url => fancifyFlashURL(url, flash))) - })}</p>`} - ${flash.tracks.length && fixWS` - <p>Tracks featured in <i>${flash.name.replace(/\.$/, '')}</i>:</p> - <ul> - ${(flash.tracks - .map(track => strings('trackList.item.withArtists', { - track: link.track(track), - by: `<span class="by">${ - strings('trackList.item.withArtists.by', { - artists: getArtistString(track.artists) - }) - }</span>` - })) - .map(row => `<li>${row}</li>`) - .join('\n'))} - </ul> - `} - ${flash.contributors.textContent && fixWS` - <p> - ${strings('releaseInfo.contributors')} - <br> - ${transformInline(flash.contributors.textContent)} - </p> - `} - ${flash.contributors.length && fixWS` - <p>${strings('releaseInfo.contributors')}</p> - <ul> - ${flash.contributors - .map(contrib => `<li>${getArtistString([contrib], { - showContrib: true, - showIcons: true - })}</li>`) - .join('\n')} - </ul> - `} - ` - }, - - sidebarLeft: generateSidebarForFlash(flash, {link, strings, wikiData}), - nav: generateNavForFlash(flash, { - generateChronologyLinks, - generatePreviousNextLinks, - link, - strings, - wikiData - }) - }) - }; - - return [page]; +export function pathsForTarget(flash) { + return [ + { + type: 'page', + path: ['flash', flash.directory], + + contentFunction: { + name: 'generateFlashInfoPage', + args: [flash], + }, + }, + ]; } -export function writeTargetless({wikiData}) { - const { flashActData } = wikiData; - - const page = { - type: 'page', - path: ['flashIndex'], - page: ({ - getFlashGridHTML, - getLinkThemeString, - link, - strings - }) => ({ - title: strings('flashIndex.title'), - - main: { - classes: ['flash-index'], - content: fixWS` - <h1>${strings('flashIndex.title')}</h1> - <div class="long-content"> - <p class="quick-info">${strings('misc.jumpTo')}</p> - <ul class="quick-info"> - ${flashActData.filter(act => act.jump).map(({ anchor, jump, jumpColor }) => fixWS` - <li><a href="#${anchor}" style="${getLinkThemeString(jumpColor)}">${jump}</a></li> - `).join('\n')} - </ul> - </div> - ${flashActData.map((act, i) => fixWS` - <h2 id="${act.anchor}" style="${getLinkThemeString(act.color)}">${link.flash(act.flashes[0], {text: act.name})}</h2> - <div class="grid-listing"> - ${getFlashGridHTML({ - entries: act.flashes.map(flash => ({item: flash})), - lazy: i === 0 ? 4 : true - })} - </div> - `).join('\n')} - ` - }, - - nav: {simple: true} - }) - }; - - return [page]; -} - -// Utility functions - -function generateNavForFlash(flash, { - generateChronologyLinks, - generatePreviousNextLinks, - link, - strings, - wikiData -}) { - const { flashData, wikiInfo } = wikiData; - - const previousNextLinks = generatePreviousNextLinks(flash, { - data: flashData, - linkKey: 'flash' - }); - - return { - links: [ - { - path: ['localized.home'], - title: wikiInfo.shortName - }, - { - path: ['localized.flashIndex'], - title: strings('flashIndex.title') - }, - { - html: strings('flashPage.nav.flash', { - flash: link.flash(flash, {class: 'current'}) - }) - }, - previousNextLinks && - { - divider: false, - html: `(${previousNextLinks})` - } - ], - - content: fixWS` - <div> - ${generateChronologyLinks(flash, { - headingString: 'misc.chronology.heading.flash', - contribKey: 'contributors', - getThings: artist => artist.flashes.asContributor - })} - </div> - ` - }; -} - -function generateSidebarForFlash(flash, {link, strings, wikiData}) { - // all hard-coded, sorry :( - // this doesnt have a super portable implementation/design...yet!! - - const { flashActData } = wikiData; - - const act6 = flashActData.findIndex(act => act.name.startsWith('Act 6')); - const postCanon = flashActData.findIndex(act => act.name.includes('Post Canon')); - const outsideCanon = postCanon + flashActData.slice(postCanon).findIndex(act => !act.name.includes('Post Canon')); - const actIndex = flashActData.indexOf(flash.act); - const side = ( - (actIndex < 0) ? 0 : - (actIndex < act6) ? 1 : - (actIndex <= outsideCanon) ? 2 : - 3 - ); - const currentAct = flash && flash.act; - - return { - content: fixWS` - <h1>${link.flashIndex('', {text: strings('flashIndex.title')})}</h1> - <dl> - ${flashActData.filter(act => - act.name.startsWith('Act 1') || - act.name.startsWith('Act 6 Act 1') || - act.name.startsWith('Hiveswap') || - // Sorry not sorry -Yiffy - (({index = flashActData.indexOf(act)} = {}) => ( - index < act6 ? side === 1 : - index < outsideCanon ? side === 2 : - true - ))() - ).flatMap(act => [ - act.name.startsWith('Act 1') && html.tag('dt', - {class: ['side', side === 1 && 'current']}, - link.flash(act.flashes[0], {color: '#4ac925', text: `Side 1 (Acts 1-5)`})) - || act.name.startsWith('Act 6 Act 1') && html.tag('dt', - {class: ['side', side === 2 && 'current']}, - link.flash(act.flashes[0], {color: '#1076a2', text: `Side 2 (Acts 6-7)`})) - || act.name.startsWith('Hiveswap Act 1') && html.tag('dt', - {class: ['side', side === 3 && 'current']}, - link.flash(act.flashes[0], {color: '#008282', text: `Outside Canon (Misc. Games)`})), - (({index = flashActData.indexOf(act)} = {}) => ( - index < act6 ? side === 1 : - index < outsideCanon ? side === 2 : - true - ))() && html.tag('dt', - {class: act === currentAct && 'current'}, - link.flash(act.flashes[0], {text: act.name})), - act === currentAct && fixWS` - <dd><ul> - ${act.flashes.map(f => html.tag('li', - {class: f === flash && 'current'}, - link.flash(f))).join('\n')} - </ul></dd> - ` - ]).filter(Boolean).join('\n')} - </dl> - ` - }; +export function pathsTargetless() { + return [ + { + type: 'page', + path: ['flashIndex'], + contentFunction: {name: 'generateFlashIndexPage'}, + }, + ]; } diff --git a/src/page/group.js b/src/page/group.js index 7282fc8..b0ed5ba 100644 --- a/src/page/group.js +++ b/src/page/group.js @@ -1,263 +1,53 @@ -// Group page specifications. +import {empty} from '#sugar'; -// Imports - -import fixWS from 'fix-whitespace'; - -import { - UNRELEASED_TRACKS_DIRECTORY -} from '../util/magic-constants.js'; - -import * as html from '../util/html.js'; - -import { - getTotalDuration, - sortByDate -} from '../util/wiki-data.js'; - -// Page exports +export const description = `per-group info & album gallery pages`; export function targets({wikiData}) { - return wikiData.groupData; + return wikiData.groupData; } -export function write(group, {wikiData}) { - const { listingSpec, wikiInfo } = wikiData; - - const releasedAlbums = group.albums.filter(album => album.directory !== UNRELEASED_TRACKS_DIRECTORY); - const releasedTracks = releasedAlbums.flatMap(album => album.tracks); - const totalDuration = getTotalDuration(releasedTracks); - - const albumLines = group.albums.map(album => ({ - album, - otherGroup: album.groups.find(g => g !== group) - })); - - const infoPage = { - type: 'page', - path: ['groupInfo', group.directory], - page: ({ - generateInfoGalleryLinks, - generatePreviousNextLinks, - getLinkThemeString, - getThemeString, - fancifyURL, - link, - strings, - transformMultiline - }) => ({ - title: strings('groupInfoPage.title', {group: group.name}), - theme: getThemeString(group.color), - - main: { - content: fixWS` - <h1>${strings('groupInfoPage.title', {group: group.name})}</h1> - ${group.urls.length && `<p>${ - strings('releaseInfo.visitOn', { - links: strings.list.or(group.urls.map(url => fancifyURL(url, {strings}))) - }) - }</p>`} - <blockquote> - ${transformMultiline(group.description)} - </blockquote> - <h2>${strings('groupInfoPage.albumList.title')}</h2> - <p>${ - strings('groupInfoPage.viewAlbumGallery', { - link: link.groupGallery(group, { - text: strings('groupInfoPage.viewAlbumGallery.link') - }) - }) - }</p> - <ul> - ${albumLines.map(({ album, otherGroup }) => { - const item = strings('groupInfoPage.albumList.item', { - year: album.date.getFullYear(), - album: link.album(album) - }); - return html.tag('li', (otherGroup - ? strings('groupInfoPage.albumList.item.withAccent', { - item, - accent: html.tag('span', - {class: 'other-group-accent'}, - strings('groupInfoPage.albumList.item.otherGroupAccent', { - group: link.groupInfo(otherGroup, {color: false}) - })) - }) - : item)); - }).join('\n')} - </ul> - ` - }, - - sidebarLeft: generateGroupSidebar(group, false, { - getLinkThemeString, - link, - strings, - wikiData - }), - - nav: generateGroupNav(group, false, { - generateInfoGalleryLinks, - generatePreviousNextLinks, - link, - strings, - wikiData - }) - }) - }; - - const galleryPage = { - type: 'page', - path: ['groupGallery', group.directory], - page: ({ - generateInfoGalleryLinks, - generatePreviousNextLinks, - getAlbumGridHTML, - getLinkThemeString, - getThemeString, - link, - strings - }) => ({ - title: strings('groupGalleryPage.title', {group: group.name}), - theme: getThemeString(group.color), - - main: { - classes: ['top-index'], - content: fixWS` - <h1>${strings('groupGalleryPage.title', {group: group.name})}</h1> - <p class="quick-info">${ - strings('groupGalleryPage.infoLine', { - tracks: `<b>${strings.count.tracks(releasedTracks.length, {unit: true})}</b>`, - albums: `<b>${strings.count.albums(releasedAlbums.length, {unit: true})}</b>`, - time: `<b>${strings.count.duration(totalDuration, {unit: true})}</b>` - }) - }</p> - ${wikiInfo.features.groupUI && wikiInfo.features.listings && html.tag('p', - {class: 'quick-info'}, - strings('groupGalleryPage.anotherGroupLine', { - link: link.listing(listingSpec.find(l => l.directory === 'groups/by-category'), { - text: strings('groupGalleryPage.anotherGroupLine.link') - }) - }) - )} - <div class="grid-listing"> - ${getAlbumGridHTML({ - entries: sortByDate(group.albums.map(item => ({item}))).reverse(), - details: true - })} - </div> - ` - }, - - sidebarLeft: generateGroupSidebar(group, true, { - getLinkThemeString, - link, - strings, - wikiData - }), - - nav: generateGroupNav(group, true, { - generateInfoGalleryLinks, - generatePreviousNextLinks, - link, - strings, - wikiData - }) - }) - }; - - return [infoPage, galleryPage]; +export function pathsForTarget(group) { + const hasGalleryPage = !empty(group.albums); + + return [ + { + type: 'page', + path: ['groupInfo', group.directory], + + contentFunction: { + name: 'generateGroupInfoPage', + args: [group], + }, + }, + + hasGalleryPage && { + type: 'page', + path: ['groupGallery', group.directory], + + contentFunction: { + name: 'generateGroupGalleryPage', + args: [group], + }, + }, + ]; } -// Utility functions - -function generateGroupSidebar(currentGroup, isGallery, { - getLinkThemeString, - link, - strings, - wikiData -}) { - const { groupCategoryData, wikiInfo } = wikiData; - - if (!wikiInfo.features.groupUI) { - return null; - } - - const linkKey = isGallery ? 'groupGallery' : 'groupInfo'; - - return { - content: fixWS` - <h1>${strings('groupSidebar.title')}</h1> - ${groupCategoryData.map(category => - html.tag('details', { - open: category === currentGroup.category, - class: category === currentGroup.category && 'current' - }, [ - html.tag('summary', - {style: getLinkThemeString(category.color)}, - strings('groupSidebar.groupList.category', { - category: `<span class="group-name">${category.name}</span>` - })), - html.tag('ul', - category.groups.map(group => html.tag('li', - { - class: group === currentGroup && 'current', - style: getLinkThemeString(group.color) - }, - strings('groupSidebar.groupList.item', { - group: link[linkKey](group) - })))) - ])).join('\n')} - </dl> - ` - }; -} - -function generateGroupNav(currentGroup, isGallery, { - generateInfoGalleryLinks, - generatePreviousNextLinks, - link, - strings, - wikiData -}) { - const { groupData, wikiInfo } = wikiData; - - if (!wikiInfo.features.groupUI) { - return {simple: true}; - } - - const urlKey = isGallery ? 'localized.groupGallery' : 'localized.groupInfo'; - const linkKey = isGallery ? 'groupGallery' : 'groupInfo'; - - const infoGalleryLinks = generateInfoGalleryLinks(currentGroup, isGallery, { - linkKeyGallery: 'groupGallery', - linkKeyInfo: 'groupInfo' - }); - - const previousNextLinks = generatePreviousNextLinks(currentGroup, { - data: groupData, - linkKey - }); - - return { - links: [ - {toHome: true}, - wikiInfo.features.listings && - { - path: ['localized.listingIndex'], - title: strings('listingIndex.title') - }, - { - html: strings('groupPage.nav.group', { - group: link[linkKey](currentGroup, {class: 'current'}) - }) - }, - { - divider: false, - html: (previousNextLinks - ? `(${infoGalleryLinks}; ${previousNextLinks})` - : `(${previousNextLinks})`) - } - ] - }; +export function pathsTargetless({wikiData: {wikiInfo}}) { + return [ + wikiInfo.canonicalBase === 'https://hsmusic.wiki/' && + { + type: 'redirect', + fromPath: ['page', 'albums/fandom'], + toPath: ['groupGallery', 'fandom'], + title: 'Fandom - Gallery', + }, + + wikiInfo.canonicalBase === 'https://hsmusic.wiki/' && + { + type: 'redirect', + fromPath: ['page', 'albums/official'], + toPath: ['groupGallery', 'official'], + title: 'Official - Gallery', + }, + ]; } diff --git a/src/page/homepage.js b/src/page/homepage.js index e60256d..53ee6e4 100644 --- a/src/page/homepage.js +++ b/src/page/homepage.js @@ -1,125 +1,15 @@ -// Homepage specification. - -// Imports - -import fixWS from 'fix-whitespace'; - -import find from '../util/find.js'; - -import * as html from '../util/html.js'; - -import { - getNewAdditions, - getNewReleases -} from '../util/wiki-data.js'; - -// Page exports - -export function writeTargetless({wikiData}) { - const { newsData, staticPageData, homepageInfo, wikiInfo } = wikiData; - - const page = { - type: 'page', - path: ['home'], - page: ({ - getAlbumGridHTML, - getLinkThemeString, - link, - strings, - to, - transformInline, - transformMultiline - }) => ({ - title: wikiInfo.name, - - meta: { - description: wikiInfo.description - }, - - main: { - classes: ['top-index'], - content: fixWS` - <h1>${wikiInfo.name}</h1> - ${homepageInfo.rows.map((row, i) => fixWS` - <section class="row" style="${getLinkThemeString(row.color)}"> - <h2>${row.name}</h2> - ${row.type === 'albums' && fixWS` - <div class="grid-listing"> - ${getAlbumGridHTML({ - entries: ( - row.group === 'new-releases' ? getNewReleases(row.groupCount, {wikiData}) : - row.group === 'new-additions' ? getNewAdditions(row.groupCount, {wikiData}) : - ((find.group(row.group, {wikiData})?.albums || []) - .slice() - .reverse() - .slice(0, row.groupCount) - .map(album => ({item: album}))) - ).concat(row.albums - .map(album => find.album(album, {wikiData})) - .map(album => ({item: album})) - ), - lazy: i > 0 - })} - ${row.actions.length && fixWS` - <div class="grid-actions"> - ${row.actions.map(action => transformInline(action) - .replace('<a', '<a class="box grid-item"')).join('\n')} - </div> - `} - </div> - `} - </section> - `).join('\n')} - ` - }, - - sidebarLeft: homepageInfo.sidebar && { - wide: true, - collapse: false, - // This is a pretty filthy hack! 8ut otherwise, the [[news]] part - // gets treated like it's a reference to the track named "news", - // which o8viously isn't what we're going for. Gotta catch that - // 8efore we pass it to transformMultiline, 'cuz otherwise it'll - // get repl8ced with just the word "news" (or anything else that - // transformMultiline does with references it can't match) -- and - // we can't match that for replacing it with the news column! - // - // And no, I will not make [[news]] into part of transformMultiline - // (even though that would 8e hilarious). - content: (transformMultiline(homepageInfo.sidebar.replace('[[news]]', '__GENERATE_NEWS__')) - .replace('<p>__GENERATE_NEWS__</p>', wikiInfo.features.news ? fixWS` - <h1>${strings('homepage.news.title')}</h1> - ${newsData.slice(0, 3).map((entry, i) => html.tag('article', - {class: ['news-entry', i === 0 && 'first-news-entry']}, - fixWS` - <h2><time>${strings.count.date(entry.date)}</time> ${link.newsEntry(entry)}</h2> - ${transformMultiline(entry.bodyShort)} - ${entry.bodyShort !== entry.body && link.newsEntry(entry, { - text: strings('homepage.news.entry.viewRest') - })} - `)).join('\n')} - ` : `<p><i>News requested in content description but this feature isn't enabled</i></p>`)) - }, - - nav: { - content: fixWS` - <h2 class="dot-between-spans"> - ${[ - link.home('', {text: wikiInfo.shortName, class: 'current', to}), - wikiInfo.features.listings && - link.listingIndex('', {text: strings('listingIndex.title'), to}), - wikiInfo.features.news && - link.newsIndex('', {text: strings('newsIndex.title'), to}), - wikiInfo.features.flashesAndGames && - link.flashIndex('', {text: strings('flashIndex.title'), to}), - ...staticPageData.filter(page => page.listed).map(page => - link.staticPage(page, {text: page.shortName})) - ].filter(Boolean).map(link => `<span>${link}</span>`).join('\n')} - </h2> - ` - } - }) - }; - - return [page]; +export const description = `main wiki homepage`; + +export function pathsTargetless({wikiData}) { + return [ + { + type: 'page', + path: ['home'], + + contentFunction: { + name: 'generateWikiHomePage', + args: [wikiData.homepageLayout], + }, + }, + ]; } diff --git a/src/page/index.js b/src/page/index.js index f580cbe..21d93c8 100644 --- a/src/page/index.js +++ b/src/page/index.js @@ -1,49 +1,8 @@ -// 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 any of the following exports: -// -// condition({wikiData}) -// Returns a boolean indicating whether to process targets/writes (true) or -// skip this page spec altogether (false). This is usually used for -// selectively toggling pages according to site feature flags, though it may -// also be used to e.g. skip out if no targets would be found (preventing -// writeTargetless from generating an empty index page). -// -// 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}) -// Provides 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). -// -// writeTargetless({wikiData}) -// Provides descriptors for page/data/etc writes which will be used -// without concern for targets. This is usually used for writing index pages -// which should be generated just once (rather than corresponding to -// targets). -// -// 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 albumCommentary from './album-commentary.js'; export * as artist from './artist.js'; export * as artistAlias from './artist-alias.js'; export * as flash from './flash.js'; +export * as flashAct from './flash-act.js'; export * as group from './group.js'; export * as homepage from './homepage.js'; export * as listing from './listing.js'; diff --git a/src/page/listing.js b/src/page/listing.js index d3ab79e..bb22c21 100644 --- a/src/page/listing.js +++ b/src/page/listing.js @@ -1,207 +1,41 @@ -// Listing page specification. -// +export const description = `wiki-wide listing pages & index`; + // The targets here are a bit different than for most pages: rather than data // objects loaded from text files in the wiki data directory, they're hard- -// coded specifications, with various JS functions for processing wiki data -// and turning it into user-readable HTML listings. +// coded specifications, each directly identifying the hard-coded content +// function used to generate that listing. // // Individual listing specs are described in src/listing-spec.js, but are // provided via wikiData like other (normal) data objects. - -// Imports - -import fixWS from 'fix-whitespace'; - -import * as html from '../util/html.js'; - -import { - UNRELEASED_TRACKS_DIRECTORY -} from '../util/magic-constants.js'; - -import { - getTotalDuration -} from '../util/wiki-data.js'; - -// Page exports - -export function condition({wikiData}) { - return wikiData.wikiInfo.features.listings; -} - +// export function targets({wikiData}) { - return wikiData.listingSpec; + return ( + wikiData.listingSpec + .filter(listing => listing.contentFunction) + .filter(listing => + !listing.featureFlag || + wikiData.wikiInfo[listing.featureFlag])); } -export function write(listing, {wikiData}) { - if (listing.condition && !listing.condition({wikiData})) { - return null; - } - - const { wikiInfo } = wikiData; - - const data = (listing.data - ? listing.data({wikiData}) - : null); - - const page = { - type: 'page', - path: ['listing', listing.directory], - page: opts => { - const { getLinkThemeString, link, strings } = opts; - const titleKey = `listingPage.${listing.stringsKey}.title`; - - return { - title: strings(titleKey), - - main: { - content: fixWS` - <h1>${strings(titleKey)}</h1> - ${listing.html && (listing.data - ? listing.html(data, opts) - : listing.html(opts))} - ${listing.row && fixWS` - <ul> - ${(data - .map(item => listing.row(item, opts)) - .map(row => `<li>${row}</li>`) - .join('\n'))} - </ul> - `} - ` - }, - - sidebarLeft: { - content: generateSidebarForListings(listing, { - getLinkThemeString, - link, - strings, - wikiData - }) - }, - - nav: { - links: [ - {toHome: true}, - { - path: ['localized.listingIndex'], - title: strings('listingIndex.title') - }, - {toCurrentPage: true} - ] - } - }; - } - }; - - return [page]; -} - -export function writeTargetless({wikiData}) { - const { albumData, trackData, wikiInfo } = wikiData; - - const releasedTracks = trackData.filter(track => track.album.directory !== UNRELEASED_TRACKS_DIRECTORY); - const releasedAlbums = albumData.filter(album => album.directory !== UNRELEASED_TRACKS_DIRECTORY); - const duration = getTotalDuration(releasedTracks); - - const page = { - type: 'page', - path: ['listingIndex'], - page: ({ - getLinkThemeString, - strings, - link - }) => ({ - title: strings('listingIndex.title'), - - main: { - content: fixWS` - <h1>${strings('listingIndex.title')}</h1> - <p>${strings('listingIndex.infoLine', { - wiki: wikiInfo.name, - tracks: `<b>${strings.count.tracks(releasedTracks.length, {unit: true})}</b>`, - albums: `<b>${strings.count.albums(releasedAlbums.length, {unit: true})}</b>`, - duration: `<b>${strings.count.duration(duration, {approximate: true, unit: true})}</b>` - })}</p> - <hr> - <p>${strings('listingIndex.exploreList')}</p> - ${generateLinkIndexForListings(null, false, {link, strings, wikiData})} - ` - }, - - sidebarLeft: { - content: generateSidebarForListings(null, { - getLinkThemeString, - link, - strings, - wikiData - }) - }, - - nav: {simple: true} - }) - }; - - return [page]; -}; - -// Utility functions - -function generateSidebarForListings(currentListing, { - getLinkThemeString, - link, - strings, - wikiData -}) { - return fixWS` - <h1>${link.listingIndex('', {text: strings('listingIndex.title')})}</h1> - ${generateLinkIndexForListings(currentListing, true, { - getLinkThemeString, - link, - strings, - wikiData - })} - `; +export function pathsForTarget(listing) { + return [ + { + type: 'page', + path: ['listing', listing.directory], + contentFunction: { + name: listing.contentFunction, + args: [listing], + }, + }, + ]; } -function generateLinkIndexForListings(currentListing, forSidebar, { - getLinkThemeString, - link, - strings, - wikiData -}) { - const { listingTargetSpec, wikiInfo } = wikiData; - - const filteredByCondition = listingTargetSpec - .map(({ listings, ...rest }) => ({ - ...rest, - listings: listings.filter(({ condition: c }) => !c || c({wikiData})) - })) - .filter(({ listings }) => listings.length > 0); - - const genUL = listings => html.tag('ul', - listings.map(listing => html.tag('li', - {class: [listing === currentListing && 'current']}, - link.listing(listing, {text: strings(`listingPage.${listing.stringsKey}.title.short`)}) - ))); - - if (forSidebar) { - return filteredByCondition.map(({ title, listings }) => - html.tag('details', { - open: !forSidebar || listings.includes(currentListing), - class: listings.includes(currentListing) && 'current' - }, [ - html.tag('summary', - {style: getLinkThemeString(wikiInfo.color)}, - html.tag('span', - {class: 'group-name'}, - title({strings}))), - genUL(listings) - ])).join('\n'); - } else { - return html.tag('dl', - filteredByCondition.flatMap(({ title, listings }) => [ - html.tag('dt', title({strings})), - html.tag('dd', genUL(listings)) - ])); - } +export function pathsTargetless() { + return [ + { + type: 'page', + path: ['listingIndex'], + contentFunction: {name: 'generateListingsIndexPage'}, + }, + ]; } diff --git a/src/page/news.js b/src/page/news.js index 99cbe8d..194ffdc 100644 --- a/src/page/news.js +++ b/src/page/news.js @@ -1,129 +1,32 @@ -// News entry & index page specifications. - -// Imports - -import fixWS from 'fix-whitespace'; - -// Page exports +export const description = `per-entry news pages & index`; export function condition({wikiData}) { - return wikiData.wikiInfo.features.news; + return wikiData.wikiInfo.enableNews; } export function targets({wikiData}) { - return wikiData.newsData; + return wikiData.newsData; } -export function write(entry, {wikiData}) { - const page = { - type: 'page', - path: ['newsEntry', entry.directory], - page: ({ - generatePreviousNextLinks, - link, - strings, - transformMultiline, - }) => ({ - title: strings('newsEntryPage.title', {entry: entry.name}), - - main: { - content: fixWS` - <div class="long-content"> - <h1>${strings('newsEntryPage.title', {entry: entry.name})}</h1> - <p>${strings('newsEntryPage.published', {date: strings.count.date(entry.date)})}</p> - ${transformMultiline(entry.body)} - </div> - ` - }, - - nav: generateNewsEntryNav(entry, { - generatePreviousNextLinks, - link, - strings, - wikiData - }) - }) - }; - - return [page]; +export function pathsForTarget(newsEntry) { + return [ + { + type: 'page', + path: ['newsEntry', newsEntry.directory], + contentFunction: { + name: 'generateNewsEntryPage', + args: [newsEntry], + }, + }, + ]; } -export function writeTargetless({wikiData}) { - const { newsData } = wikiData; - - const page = { - type: 'page', - path: ['newsIndex'], - page: ({ - link, - strings, - transformMultiline - }) => ({ - title: strings('newsIndex.title'), - - main: { - content: fixWS` - <div class="long-content news-index"> - <h1>${strings('newsIndex.title')}</h1> - ${newsData.map(entry => fixWS` - <article id="${entry.directory}"> - <h2><time>${strings.count.date(entry.date)}</time> ${link.newsEntry(entry)}</h2> - ${transformMultiline(entry.bodyShort)} - ${entry.bodyShort !== entry.body && `<p>${link.newsEntry(entry, { - text: strings('newsIndex.entry.viewRest') - })}</p>`} - </article> - `).join('\n')} - </div> - ` - }, - - nav: {simple: true} - }) - }; - - return [page]; -} - -// Utility functions - -function generateNewsEntryNav(entry, { - generatePreviousNextLinks, - link, - strings, - wikiData -}) { - const { wikiInfo, newsData } = wikiData; - - // 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, { - link, strings, - data: newsData.slice().reverse(), - linkKey: 'newsEntry' - }); - - return { - links: [ - { - path: ['localized.home'], - title: wikiInfo.shortName - }, - { - path: ['localized.newsIndex'], - title: strings('newsEntryPage.nav.news') - }, - { - html: strings('newsEntryPage.nav.entry', { - date: strings.count.date(entry.date), - entry: link.newsEntry(entry, {class: 'current'}) - }) - }, - previousNextLinks && - { - divider: false, - html: `(${previousNextLinks})` - } - ] - }; +export function pathsTargetless() { + return [ + { + type: 'page', + path: ['newsIndex'], + contentFunction: {name: 'generateNewsIndexPage'}, + }, + ]; } diff --git a/src/page/static.js b/src/page/static.js index ff57c4f..c9d806f 100644 --- a/src/page/static.js +++ b/src/page/static.js @@ -1,40 +1,22 @@ -// Static content page specification. (These are static pages coded into the -// wiki data folder, used for a variety of purposes, e.g. wiki info, -// changelog, and so on.) - -// Imports - -import fixWS from 'fix-whitespace'; - -// Page exports +export const description = `static wiki-wide content pages specified in data`; +// Static pages are written in the wiki's data folder and contain content and +// basic page metadata. They're used for a variety of purposes, such as an +// "about" page, a changelog, links to places beyond the wiki, and so on. export function targets({wikiData}) { - return wikiData.staticPageData; + return wikiData.staticPageData; } -export function write(staticPage, {wikiData}) { - const page = { - type: 'page', - path: ['staticPage', staticPage.directory], - page: ({ - strings, - transformMultiline - }) => ({ - title: staticPage.name, - stylesheet: staticPage.stylesheet, - - main: { - content: fixWS` - <div class="long-content"> - <h1>${staticPage.name}</h1> - ${transformMultiline(staticPage.content)} - </div> - ` - }, - - nav: {simple: true} - }) - }; - - return [page]; +export function pathsForTarget(staticPage) { + return [ + { + type: 'page', + path: ['staticPage', staticPage.directory], + + contentFunction: { + name: 'generateStaticPage', + args: [staticPage], + }, + }, + ]; } diff --git a/src/page/tag.js b/src/page/tag.js index 791c713..8942aea 100644 --- a/src/page/tag.js +++ b/src/page/tag.js @@ -1,110 +1,25 @@ // Art tag page specification. -// Imports - -import fixWS from 'fix-whitespace'; - -// Page exports +export const description = `per-artwork-tag gallery pages`; export function condition({wikiData}) { - return wikiData.wikiInfo.features.artTagUI; + return wikiData.wikiInfo.enableArtTagUI; } export function targets({wikiData}) { - return wikiData.tagData.filter(tag => !tag.isCW); -} - -export function write(tag, {wikiData}) { - const { wikiInfo } = wikiData; - const { things } = tag; - - // Display things featuring this art tag in reverse chronological order, - // sticking the most recent additions near the top! - const thingsReversed = things.slice().reverse(); - - const entries = thingsReversed.map(item => ({item})); - - const page = { - type: 'page', - path: ['tag', tag.directory], - page: ({ - generatePreviousNextLinks, - getAlbumCover, - getGridHTML, - getThemeString, - getTrackCover, - link, - strings, - to - }) => ({ - title: strings('tagPage.title', {tag: tag.name}), - theme: getThemeString(tag.color), - - main: { - classes: ['top-index'], - content: fixWS` - <h1>${strings('tagPage.title', {tag: tag.name})}</h1> - <p class="quick-info">${strings('tagPage.infoLine', { - coverArts: strings.count.coverArts(things.length, {unit: true}) - })}</p> - <div class="grid-listing"> - ${getGridHTML({ - entries, - srcFn: thing => (thing.album - ? getTrackCover(thing) - : getAlbumCover(thing)), - linkFn: (thing, opts) => (thing.album - ? link.track(thing, opts) - : link.album(thing, opts)) - })} - </div> - ` - }, - - nav: generateTagNav(tag, { - generatePreviousNextLinks, - link, - strings, - wikiData - }) - }) - }; - - return [page]; + return wikiData.artTagData.filter((tag) => !tag.isContentWarning); } -// Utility functions - -function generateTagNav(tag, { - generatePreviousNextLinks, - link, - strings, - wikiData -}) { - const previousNextLinks = generatePreviousNextLinks(tag, { - data: wikiData.tagData.filter(tag => !tag.isCW), - linkKey: 'tag' - }); - - return { - links: [ - {toHome: true}, - wikiData.wikiInfo.features.listings && - { - path: ['localized.listingIndex'], - title: strings('listingIndex.title') - }, - { - html: strings('tagPage.nav.tag', { - tag: link.tag(tag, {class: 'current'}) - }) - }, - /* - previousNextLinks && { - divider: false, - html: `(${previousNextLinks})` - } - */ - ] - }; +export function pathsForTarget(tag) { + return [ + { + type: 'page', + path: ['tag', tag.directory], + + contentFunction: { + name: 'generateArtTagGalleryPage', + args: [tag], + }, + }, + ]; } diff --git a/src/page/track.js b/src/page/track.js index 0941ee8..e75b695 100644 --- a/src/page/track.js +++ b/src/page/track.js @@ -1,330 +1,21 @@ // Track page specification. -// Imports - -import fixWS from 'fix-whitespace'; - -import { - generateAlbumChronologyLinks, - generateAlbumNavLinks, - generateAlbumSidebar -} from './album.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 const description = `per-track info pages`; export function targets({wikiData}) { - return wikiData.trackData; + 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: `<span class="by">${strings('trackList.item.withArtists.by', { - artists: getArtistString(track.artists) - })}</span>` - }); - return (track.aka - ? `<li class="rerelease">${strings('trackList.item.rerelease', {track: line})}</li>` - : `<li>${line}</li>`); - }) - ); - - 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(':</i>')) - .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, - getLinkThemeString, - getThemeString, - 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 - })} - <h1>${strings('trackPage.title', {track: track.name})}</h1> - <p> - ${[ - 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('<br>\n')} - </p> - <p>${ - (track.urls.length - ? strings('releaseInfo.listenOn', { - links: strings.list.or(track.urls.map(url => fancifyURL(url, {strings}))) - }) - : strings('releaseInfo.listenOn.noLinks')) - }</p> - ${otherReleases.length && fixWS` - <p>${strings('releaseInfo.alsoReleasedAs')}</p> - <ul> - ${otherReleases.map(track => fixWS` - <li>${strings('releaseInfo.alsoReleasedAs.item', { - track: link.track(track), - album: link.album(track.album) - })}</li> - `).join('\n')} - </ul> - `} - ${track.contributors.textContent && fixWS` - <p> - ${strings('releaseInfo.contributors')} - <br> - ${transformInline(track.contributors.textContent)} - </p> - `} - ${track.contributors.length && fixWS` - <p>${strings('releaseInfo.contributors')}</p> - <ul> - ${(track.contributors - .map(contrib => `<li>${getArtistString([contrib], { - showContrib: true, - showIcons: true - })}</li>`) - .join('\n'))} - </ul> - `} - ${tracksReferenced.length && fixWS` - <p>${strings('releaseInfo.tracksReferenced', {track: `<i>${track.name}</i>`})}</p> - ${generateTrackList(tracksReferenced)} - `} - ${tracksThatReference.length && fixWS` - <p>${strings('releaseInfo.tracksThatReference', {track: `<i>${track.name}</i>`})}</p> - ${useDividedReferences && fixWS` - <dl> - ${ttrOfficial.length && fixWS` - <dt>${strings('trackPage.referenceList.official')}</dt> - <dd>${generateTrackList(ttrOfficial)}</dd> - `} - ${ttrFanon.length && fixWS` - <dt>${strings('trackPage.referenceList.fandom')}</dt> - <dd>${generateTrackList(ttrFanon)}</dd> - `} - </dl> - `} - ${!useDividedReferences && generateTrackList(tracksThatReference)} - `} - ${wikiInfo.features.flashesAndGames && flashesThatFeature.length && fixWS` - <p>${strings('releaseInfo.flashesThatFeature', {track: `<i>${track.name}</i>`})}</p> - <ul> - ${flashesThatFeature.map(({ flash, as }) => html.tag('li', - {class: as !== track && 'rerelease'}, - (as === track - ? strings('releaseInfo.flashesThatFeature.item', { - flash: link.flash(flash) - }) - : strings('releaseInfo.flashesThatFeature.item.asDifferentRelease', { - flash: link.flash(flash), - track: link.track(as) - })))).join('\n')} - </ul> - `} - ${track.lyrics && fixWS` - <p>${strings('releaseInfo.lyrics')}</p> - <blockquote> - ${transformLyrics(track.lyrics)} - </blockquote> - `} - ${hasCommentary && fixWS` - <p>${strings('releaseInfo.artistCommentary')}</p> - <blockquote> - ${generateCommentary({link, strings, transformMultiline})} - </blockquote> - `} - ` - }, - - sidebarLeft: generateAlbumSidebar(album, track, { - fancifyURL, - getLinkThemeString, - 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` - <div> - ${generateAlbumChronologyLinks(album, track, {generateChronologyLinks})} - </div> - ` - } - }; - } - }; - - return [data, page]; +export function pathsForTarget(track) { + return [ + { + type: 'page', + path: ['track', track.directory], + + contentFunction: { + name: 'generateTrackInfoPage', + args: [track], + }, + }, + ]; } - |