From 0b20efeb113fd51291dee3e4cfa76e044c22ce2e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 12 Jan 2021 12:31:33 -0400 Subject: traversing fandom --- common/common.js | 10 +- static/client.js | 4 +- static/site.css | 69 +++++---- upd8.js | 430 +++++++++++++++++++++++++++++++------------------------ 4 files changed, 296 insertions(+), 217 deletions(-) diff --git a/common/common.js b/common/common.js index 5db5ad9..4ee73a1 100644 --- a/common/common.js +++ b/common/common.js @@ -77,12 +77,16 @@ const C = { ABOUT_DIRECTORY: 'about', FEEDBACK_DIRECTORY: 'feedback', CHANGELOG_DIRECTORY: 'changelog', + DISCORD_DIRECTORY: 'discord', + DONATE_DIRECTORY: 'donate', FLASH_DIRECTORY: 'flash', NEWS_DIRECTORY: 'news', GROUP_DIRECTORY: 'group', JS_DISABLED_DIRECTORY: 'js-disabled', UNRELEASED_TRACKS_DIRECTORY: 'unreleased-tracks', + OFFICIAL_GROUP_DIRECTORY: 'official', + FANDOM_GROUP_DIRECTORY: 'fandom', // This function was originally made to sort just al8um data, 8ut its exact // code works fine for sorting tracks too, so I made the varia8les and names @@ -124,7 +128,11 @@ const C = { // "directories", we just reformat the artist's name. getArtistDirectory: artistName => C.getKebabCase(artistName), - getArtistNumContributions: artist => (artist.tracks.length + artist.albums.length + artist.flashes.length), + getArtistNumContributions: artist => ( + artist.tracks.asAny.length + + artist.albums.asCoverArtist.length + + artist.flashes.asContributor.length + ), getArtistCommentary: (artist, {justEverythingMan}) => justEverythingMan.filter(thing => thing.commentary && thing.commentary.replace(/<\/?b>/g, '').includes('' + artist.name + ':')) }; diff --git a/static/client.js b/static/client.js index 549fde2..83c82a2 100644 --- a/static/client.js +++ b/static/client.js @@ -167,8 +167,8 @@ fetch(rebase('data.json')).then(data => data.json()).then(data => { artistData = data.artistData; flashData = data.flashData; - officialAlbumData = albumData.filter(album => !album.isFanon); - fandomAlbumData = albumData.filter(album => album.isFanon); + officialAlbumData = albumData.filter(album => album.groups.includes('group:official')); + fandomAlbumData = albumData.filter(album => !album.groups.includes('group:official')); artistNames = artistData.filter(artist => !artist.alias).map(artist => artist.name); for (const element of elements1) element.style.display = 'none'; diff --git a/static/site.css b/static/site.css index 90f8ed3..e76054a 100644 --- a/static/site.css +++ b/static/site.css @@ -31,7 +31,7 @@ body::before { height: 100%; z-index: -1; - background-image: url("https://www.homestuck.com/images/desktops/johnhouse_1920x1080.jpg"); + background-image: url("bg.jpg"); background-position: center; background-size: cover; opacity: 0.5; @@ -41,7 +41,7 @@ body::before { background-color: var(--bg-color); color: rgb(var(--fg-shade), var(--fg-shade), var(--fg-shade)); - max-width: 1200px; + max-width: 1100px; margin: 10px auto 50px; padding: 15px; @@ -120,8 +120,12 @@ a:hover { } #header .chronology { - display: inline; - white-space: nowrap; + display: inline-block; +} + +#header .chronology .heading, +#header .chronology .buttons { + display: inline-block; } .nowrap { @@ -166,32 +170,6 @@ a:hover { font-size: 1em; } -@media (max-width: 780px) { - .sidebar:not(.no-hide) { - display: none; - } - - .layout-columns.vertical-when-thin { - flex-direction: column; - } - - .layout-columns.vertical-when-thin > *:not(:last-child) { - margin-bottom: 10px; - } - - .sidebar.no-hide { - max-width: unset !important; - flex-basis: unset !important; - margin-right: 0; - } -} - -@media (max-width: 600px) { - .content-columns { - columns: 1; - } -} - .sidebar, #content, #header, #skippers { background-color: rgba(var(--bg-shade), var(--bg-shade), var(--bg-shade), 0.6); border: 1px dotted var(--fg-color); @@ -729,3 +707,34 @@ li > ul { content: '.'; } } + +@media (max-width: 900px) { + .sidebar-column:not(.no-hide) { + display: none; + } + + .layout-columns.vertical-when-thin { + flex-direction: column; + } + + .layout-columns.vertical-when-thin > *:not(:last-child) { + margin-bottom: 10px; + } + + .sidebar-column.no-hide { + max-width: unset !important; + flex-basis: unset !important; + margin-right: 0 !important; + margin-left: 0 !important; + } + + .sidebar .news-entry:not(.first-news-entry) { + display: none; + } +} + +@media (max-width: 600px) { + .content-columns { + columns: 1; + } +} diff --git a/upd8.js b/upd8.js index 64a311c..94c054d 100644 --- a/upd8.js +++ b/upd8.js @@ -130,6 +130,8 @@ function readDataFile(file) { const SITE_ABOUT = readDataFile('about.html'); const SITE_CHANGELOG = readDataFile('changelog.html'); +const SITE_DISCORD = readDataFile('discord.html'); +const SITE_DONATE = readDataFile('donate.html'); const SITE_FEEDBACK = readDataFile('feedback.html'); const SITE_JS_DISABLED = readDataFile('js-disabled.html'); @@ -278,7 +280,7 @@ function getMultilineField(lines, name) { }; function transformInline(text) { - return text.replace(/\[\[(album:|artist:|flash:|track:|tag:)?(.+?)\]\]/g, (match, category, ref, offset) => { + return text.replace(/\[\[(album:|artist:|flash:|track:|tag:|group:)?(.+?)\]\]/g, (match, category, ref, offset) => { if (category === 'album:') { const album = getLinkedAlbum(ref); if (album) { @@ -334,6 +336,16 @@ function transformInline(text) { console.warn(`\x1b[33mThe linked tag ${match} does not exist!\x1b[0m`); return ref; } + } else if (category === 'group:') { + const group = getLinkedGroup(ref); + if (group) { + return fixWS` + ${group.name} + `; + } else { + console.warn(`\x1b[33mThe linked group ${group} does not exist!\x1b[0m`); + return ref; + } } else { const track = getLinkedTrack(ref); if (track) { @@ -497,13 +509,6 @@ async function processAlbumDataFile(file) { album.urls = getListField(albumSection, 'URLs') || []; album.groups = getListField(albumSection, 'Groups') || []; album.directory = getBasicField(albumSection, 'Directory'); - - const canon = getBasicField(albumSection, 'Canon'); - album.isCanon = canon === 'Canon' || !canon; - album.isBeyond = canon === 'Beyond'; - album.isOfficial = album.isCanon || album.isBeyond; - album.isFanon = canon === 'Fanon'; - album.isMajorRelease = getBasicField(albumSection, 'Major Release') === 'yes'; if (album.artists && album.artists.error) { @@ -873,7 +878,14 @@ async function processGroupDataFile(file) { const contentLines = contents.split('\n'); const sections = Array.from(getSections(contentLines)); + let category, color; return sections.map(section => { + if (getBasicField(section, 'Category')) { + category = getBasicField(section, 'Category'); + color = getBasicField(section, 'Color'); + return {isCategory: true, name: category, color}; + } + const name = getBasicField(section, 'Group'); if (!name) { return {error: 'Expected "Group" field!'}; @@ -897,12 +909,14 @@ async function processGroupDataFile(file) { const urls = (getListField(section, 'URLs') || []).filter(Boolean); return { + isGroup: true, name, directory, description, descriptionShort, urls, - color: '#00ffff' + category, + color }; }); } @@ -955,21 +969,31 @@ function getTotalDuration(tracks) { const stringifyIndent = 0; -const toRefs = (label, array) => array.filter(Boolean).map(x => `${label}:${x.directory}`); +const toRefs = (label, objectOrArray) => { + if (Array.isArray(objectOrArray)) { + return objectOrArray.filter(Boolean).map(x => `${label}:${x.directory}`); + } else if (objectOrArray.directory) { + throw new Error('toRefs should not be passed a single object with directory'); + } else if (typeof objectOrArray === 'object') { + return Object.fromEntries(Object.entries(objectOrArray) + .map(([ key, value ]) => [key, toRefs(key, value)])); + } else { + throw new Error('toRefs should be passed an array or object of arrays'); + } +}; function stringifyRefs(key, value) { switch (key) { - case 'albums': return toRefs('album', value); case 'tracks': case 'references': case 'referencedBy': - if (!Array.isArray(value)) console.log(Object.keys(value)); return toRefs('track', value); case 'artists': case 'contributors': case 'coverArtists': case 'trackCoverArtists': return value && value.map(({ who, what }) => ({who: `artist:${who.directory}`, what})); + case 'albums': return toRefs('album', value); case 'flashes': return toRefs('flash', value); case 'groups': return toRefs('group', value); case 'artTags': return toRefs('tag', value); @@ -1018,8 +1042,6 @@ function stringifyFlashData() { function stringifyArtistData() { return JSON.stringify(artistData, (key, value) => { switch (key) { - case 'tracks': // skip stringifyRefs handling 'tracks' key as an array - return value; case 'asAny': return; case 'asArtist': @@ -1119,16 +1141,17 @@ async function writePage(directoryParts, { main = { classes: [], - collapseSidebars: true, content: '' }, sidebar = { + collapse: true, classes: [], content: '' }, sidebarRight = { + collapse: true, classes: [], content: '' }, @@ -1149,9 +1172,7 @@ async function writePage(directoryParts, { } const canonical = SITE_CANONICAL_BASE + targetPath; - const { - collapseSidebars = true - } = main; + const collapseSidebars = (sidebar.collapse !== false) && (sidebarRight.collapse !== false); const mainHTML = main.content && fixWS`
@@ -1163,13 +1184,14 @@ async function writePage(directoryParts, { content, multiple, classes: sidebarClasses = [], + collapse = true, wide = false }) => (content ? fixWS`
${content} @@ -1179,7 +1201,7 @@ async function writePage(directoryParts, { 'sidebar-column', 'sidebar-multiple', wide && 'wide', - !collapseSidebars && 'no-hide' + !collapse && 'no-hide' )}> ${multiple.map(content => fixWS`
album.isOfficial || album.isMajorRelease); + const majorReleases = latestFirst.filter(album => album.groups.some(g => g.directory === C.OFFICIAL_GROUP_DIRECTORY) || album.isMajorRelease); majorReleases.splice(1); const otherReleases = latestFirst @@ -1373,7 +1397,6 @@ function writeMiscellaneousPages() { }, main: { classes: ['top-index'], - collapseSidebars: false, content: fixWS`

${SITE_TITLE}

New Releases

@@ -1387,7 +1410,7 @@ function writeMiscellaneousPages() {
${getAlbumGridHTML({ entries: (albumData - .filter(album => album.isFanon) + .filter(album => album.groups.some(g => g.directory === C.FANDOM_GROUP_DIRECTORY)) .reverse() .slice(0, 6) .concat([albumData.find(album => album.directory === C.UNRELEASED_TRACKS_DIRECTORY)]) @@ -1395,7 +1418,7 @@ function writeMiscellaneousPages() { lazy: true })}
@@ -1403,30 +1426,32 @@ function writeMiscellaneousPages() {
${getAlbumGridHTML({ entries: (albumData - .filter(album => album.isOfficial) + .filter(album => album.groups.some(g => g.directory === C.OFFICIAL_GROUP_DIRECTORY)) .reverse() .slice(0, 11) .map(album => ({item: album}))), lazy: true })}
` }, sidebar: { wide: true, + collapse: false, content: fixWS`

Get involved!


News

- ${newsData.slice(0, 3).map(entry => fixWS` -
+ ${newsData.slice(0, 3).map((entry, i) => fixWS` +

${entry.name}

${entry.bodyShort} ${entry.bodyShort !== entry.body && `(View rest of entry!)`} @@ -1439,61 +1464,23 @@ function writeMiscellaneousPages() {

${SITE_SHORT_TITLE} Listings + News Flashes & Games About & Credits Feedback & Suggestions - Donate + Donate

` } }), - writePage(['albums', 'fandom'], { - title: `Albums - Fandom`, - main: { - classes: ['top-index'], - content: fixWS` -

Albums - Fandom

-

More listings!

-
- ${getAlbumGridHTML({ - details: true, - entries: (albumData - .filter(album => album.isFanon) - .reverse() - .map(album => ({item: album}))), - lazy: 4 - })} -
- ` - }, - sidebar: { - content: generateSidebarForGroup(true, null) - }, - nav: {simple: true} - }), + mkdirp(path.join(C.SITE_DIRECTORY, 'albums', 'fandom')) + .then(() => writeFile(path.join(C.SITE_DIRECTORY, 'albums', 'fandom', 'index.html'), + generateRedirectPage('Fandom - Gallery', `/${C.GROUP_DIRECTORY}/fandom/gallery/`))), - writePage(['albums', 'official'], { - title: `Albums - Official`, - main: { - classes: ['top-index'], - content: fixWS` -

Albums - Official

-

More listings!

-
- ${getAlbumGridHTML({ - details: true, - entries: (albumData - .filter(album => album.isOfficial) - .reverse() - .map(album => ({item: album}))), - lazy: 4 - })} -
- ` - }, - nav: {simple: true} - }), + mkdirp(path.join(C.SITE_DIRECTORY, 'albums', 'official')) + .then(() => writeFile(path.join(C.SITE_DIRECTORY, 'albums', 'official', 'index.html'), + generateRedirectPage('Official - Gallery', `/${C.GROUP_DIRECTORY}/official/gallery/`))), writePage([C.FLASH_DIRECTORY], { title: `Flashes & Games`, @@ -1571,6 +1558,32 @@ function writeMiscellaneousPages() { nav: {simple: true} }), + writePage([C.DONATE_DIRECTORY], { + title: `Donate`, + main: { + content: fixWS` +
+

Donate

+ ${SITE_DONATE} +
+ ` + }, + nav: {simple: true} + }), + + writePage([C.DISCORD_DIRECTORY], { + title: `Discord`, + main: { + content: fixWS` +
+

HSMusic Community Discord Server

+ ${SITE_DISCORD} +
+ ` + }, + nav: {simple: true} + }), + writePage([C.JS_DISABLED_DIRECTORY], { title: 'JavaScript Disabled', main: { @@ -1735,8 +1748,8 @@ function writeTrackPages() { async function writeTrackPage(track) { const { album } = track; const tracksThatReference = track.referencedBy; - const ttrFanon = tracksThatReference.filter(t => t.album.isFanon); - const ttrOfficial = tracksThatReference.filter(t => t.album.isOfficial); + const ttrFanon = tracksThatReference.filter(t => t.album.groups.every(group => group.directory !== C.OFFICIAL_GROUP_DIRECTORY)); + const ttrOfficial = tracksThatReference.filter(t => t.album.groups.some(group => group.directory === C.OFFICIAL_GROUP_DIRECTORY)); const tracksReferenced = track.references; const otherReleases = track.otherReleases; const listTag = getAlbumListTag(track.album); @@ -2013,7 +2026,7 @@ async function writeArtistPage(artist) { [`${C.LISTING_DIRECTORY}/`, 'Listings'], [null, 'Artist:'], [`${C.ARTIST_DIRECTORY}/${kebab}/`, name], - [null, `(${[ + artThings.length && [null, `(${[ `Info`, `Gallery` ].join(', ')})`] @@ -2068,11 +2081,15 @@ async function writeArtistAliasPage(artist) { const target = `/${C.ARTIST_DIRECTORY}/${alias.directory}/`; await mkdirp(directory); - await writeFile(file, fixWS` + await writeFile(file, generateRedirectPage(alias.name, target)); +} + +function generateRedirectPage(title, target) { + return fixWS` - Moved to ${alias.name} + Moved to ${title} @@ -2080,12 +2097,12 @@ async function writeArtistAliasPage(artist) {
-

Moved to ${alias.name}

+

Moved to ${title}

This page has been moved to ${target}.

- `); + `; } function albumChunkedList(tracks, getLI, showDate = true, datePropertyOrFn = 'date') { @@ -2291,8 +2308,10 @@ function writeListingPages() { `; const sortByName = (a, b) => { - const an = a.name.toLowerCase(); - const bn = b.name.toLowerCase(); + let an = a.name.toLowerCase(); + let bn = b.name.toLowerCase(); + if (an.startsWith('the ')) an = an.slice(4); + if (bn.startsWith('the ')) bn = bn.slice(4); return an < bn ? -1 : an > bn ? 1 : 0; }; @@ -2300,15 +2319,15 @@ function writeListingPages() { [['albums', 'by-name'], `Albums - by Name`, albumData.slice() .sort(sortByName) .map(album => getAlbumLI(album, `(${album.tracks.length} tracks)`))], - [['albums', 'by-date'], `Albums - by Date`, C.sortByDate(albumData.filter(album => album.directory !== C.UNRELEASED_TRACKS_DIRECTORY)) - .map(album => getAlbumLI(album, `(${getDateString(album)})`))], + [['albums', 'by-tracks'], `Albums - by Tracks`, albumData.slice() + .sort((a, b) => b.tracks.length - a.tracks.length) + .map(album => getAlbumLI(album, `(${s(album.tracks.length, 'track')})`))], [['albums', 'by-duration'], `Albums - by Duration`, albumData.slice() .map(album => ({album, duration: getTotalDuration(album.tracks)})) .sort((a, b) => b.duration - a.duration) .map(({ album, duration }) => getAlbumLI(album, `(${getDurationString(duration)})`))], - [['albums', 'by-tracks'], `Albums - by Tracks`, albumData.slice() - .sort((a, b) => b.tracks.length - a.tracks.length) - .map(album => getAlbumLI(album, `(${s(album.tracks.length, 'track')})`))], + [['albums', 'by-date'], `Albums - by Date`, C.sortByDate(albumData.filter(album => album.directory !== C.UNRELEASED_TRACKS_DIRECTORY)) + .map(album => getAlbumLI(album, `(${getDateString(album)})`))], [['artists', 'by-name'], `Artists - by Name`, artistData .filter(artist => !artist.alias) .sort(sortByName) @@ -2318,17 +2337,6 @@ function writeListingPages() { (${'' + C.getArtistNumContributions(artist)} c.) `)], - [['artists', 'by-commentary'], `Artists - by Commentary`, artistData - .filter(artist => !artist.alias) - .map(artist => ({artist, commentary: C.getArtistCommentary(artist, {justEverythingMan}).length})) - .filter(({ commentary }) => commentary > 0) - .sort((a, b) => b.commentary - a.commentary) - .map(({ artist, commentary }) => fixWS` -
  • - ${artist.name} - (${commentary} ${commentary === 1 ? 'entry' : 'entries'}) -
  • - `)], [['artists', 'by-contribs'], `Artists - by Contributions`, fixWS`
    @@ -2338,12 +2346,10 @@ function writeListingPages() { .filter(artist => !artist.alias) .map(artist => ({ name: artist.name, - contribs: trackData.filter(({ album, artists, contributors }) => - album?.directory !== C.UNRELEASED_TRACKS_DIRECTORY && - [ - ...artists, - ...contributors - ].some(({ who }) => who === artist)).length + contribs: ( + artist.tracks.asContributor.length + + artist.tracks.asArtist.length + ) })) .sort((a, b) => b.contribs - a.contribs) .filter(({ contribs }) => contribs) @@ -2364,13 +2370,11 @@ function writeListingPages() { .filter(artist => !artist.alias) .map(artist => ({ artist, - contribs: justEverythingMan.filter(({ album, contributors, coverArtists }) => ( - album?.directory !== C.UNRELEASED_TRACKS_DIRECTORY && - [ - ...!album && contributors || [], - ...coverArtists || [] - ].some(({ who }) => who === artist) - )).length + contribs: ( + artist.tracks.asCoverArtist.length + + artist.albums.asCoverArtist.length + + artist.flashes.asContributor.length + ) })) .sort((a, b) => b.contribs - a.contribs) .filter(({ contribs }) => contribs) @@ -2386,6 +2390,17 @@ function writeListingPages() {
    `], + [['artists', 'by-commentary'], `Artists - by Commentary Entries`, artistData + .filter(artist => !artist.alias) + .map(artist => ({artist, commentary: C.getArtistCommentary(artist, {justEverythingMan}).length})) + .filter(({ commentary }) => commentary > 0) + .sort((a, b) => b.commentary - a.commentary) + .map(({ artist, commentary }) => fixWS` +
  • + ${artist.name} + (${commentary} ${commentary === 1 ? 'entry' : 'entries'}) +
  • + `)], [['artists', 'by-duration'], `Artists - by Duration`, artistData .filter(artist => !artist.alias) .map(artist => ({artist, duration: getTotalDuration( @@ -2452,6 +2467,62 @@ function writeListingPages() {
    `], + [['groups', 'by-name'], `Groups - by Name`, groupData + .filter(x => x.isGroup) + .sort(sortByName) + .map(group => fixWS` +
  • ${group.name}
  • + `)], + [['groups', 'by-category'], `Groups - by Category`, fixWS` +
    + ${groupData.filter(x => x.isCategory).map(category => fixWS` +
    ${category.name} +
      + ${category.groups.map(group => fixWS` +
    • ${group.name}
    • + `).join('\n')} +
    + `).join('\n')} +
    + `], + [['groups', 'by-albums'], `Groups - by Albums`, groupData + .filter(x => x.isGroup) + .map(group => ({group, albums: group.albums.length})) + .sort((a, b) => b.albums - a.albums) + .map(({ group, albums }) => fixWS` +
  • ${group.name} (${s(albums, 'album')})
  • + `)], + [['groups', 'by-tracks'], `Groups - by Tracks`, groupData + .filter(x => x.isGroup) + .map(group => ({group, tracks: group.albums.reduce((acc, album) => acc + album.tracks.length, 0)})) + .sort((a, b) => b.tracks - a.tracks) + .map(({ group, tracks }) => fixWS` +
  • ${group.name} (${s(tracks, 'track')})
  • + `)], + [['groups', 'by-duration'], `Groups - by Duration`, groupData + .filter(x => x.isGroup) + .map(group => ({group, duration: getTotalDuration(group.albums.flatMap(album => album.tracks))})) + .sort((a, b) => b.duration - a.duration) + .map(({ group, duration }) => fixWS` +
  • ${group.name} (${getDurationString(duration)})
  • + `)], + [['groups', 'by-latest'], `Groups - by Latest Album`, C.sortByDate(groupData + .filter(x => x.isGroup) + .map(group => ({group, date: group.albums[group.albums.length - 1].date})) + // So this is kinda tough to explain, 8ut 8asically, when we reverse the list after sorting it 8y d8te + // (so that the latest d8tes come first), it also flips the order of groups which share the same d8te. + // This happens mostly when a single al8um is the l8test in two groups. So, say one such al8um is in + // the groups "Fandom" and "UMSPAF". Per category order, Fandom is meant to show up 8efore UMSPAF, 8ut + // when we do the reverse l8ter, that flips them, and UMSPAF ends up displaying 8efore Fandom. So we do + // an extra reverse here, which will fix that and only affect groups that share the same d8te (8ecause + // groups that don't will 8e moved 8y the sortByDate call surrounding this). + .reverse() + ).reverse().map(({ group, date }) => fixWS` +
  • + ${group.name} + (${getDateString({date})}) +
  • + `)], [['tracks', 'by-name'], `Tracks - by Name`, trackData.slice() .sort(sortByName) .map(track => fixWS` @@ -2996,6 +3067,7 @@ function fancifyURL(url, {album = false} = {}) { url.includes('wikipedia.org') ? 'Wikipedia' : url.includes('poetryfoundation.org') ? 'Poetry Foundation' : url.includes('instagram.com') ? 'Instagram' : + url.includes('patreon.com') ? 'Patreon' : new URL(url).hostname }`; } @@ -3159,6 +3231,7 @@ function generateSidebarRightForAlbum(album, currentTrack = null) { const { groups } = album; if (groups.length) { return { + collapse: false, multiple: groups.map(group => { const index = group.albums.indexOf(album); const next = group.albums[index + 1]; @@ -3167,7 +3240,7 @@ function generateSidebarRightForAlbum(album, currentTrack = null) { }).map(({group, next, previous}) => fixWS`

    ${group.name}

    ${!currentTrack && group.descriptionShort} -

    Visit on ${joinNoOxford(group.urls.map(fancifyURL), 'or')}.

    + ${group.urls.length && `

    Visit on ${joinNoOxford(group.urls.map(fancifyURL), 'or')}.

    `} ${!currentTrack && fixWS` ${next && ``} ${previous && ``} @@ -3180,18 +3253,27 @@ function generateSidebarRightForAlbum(album, currentTrack = null) { function generateSidebarForGroup(isGallery = false, currentGroup = null) { return `

    Groups

    - - ` +
    + ${groupData.filter(x => x.isCategory).map(category => [ + fixWS` +
    + ${category.name} +
    +
      + ${category.groups.map(group => fixWS` +
    • + ${group.name} +
    • + `).join('\n')} +
    + ` + ]).join('\n')} +
    + `; } function writeGroupPages() { - return progressPromiseAll(`Writing group pages.`, queue(groupData.map(curry(writeGroupPage)), queueSize)); + return progressPromiseAll(`Writing group pages.`, queue(groupData.filter(x => x.isGroup).map(curry(writeGroupPage)), queueSize)); } async function writeGroupPage(group) { @@ -3199,6 +3281,19 @@ async function writeGroupPage(group) { const releasedTracks = releasedAlbums.flatMap(album => album.tracks); const totalDuration = getTotalDuration(releasedTracks); + const groups = groupData.filter(x => x.isGroup); + const index = groups.indexOf(group); + const previous = groups[index - 1]; + const next = groups[index + 1]; + + const generateNextPrevious = isGallery => [ + previous && `Previous`, + next && `Next` + ].filter(Boolean).join(', '); + + const npInfo = generateNextPrevious(false); + const npGallery = generateNextPrevious(true); + await writePage([C.GROUP_DIRECTORY, group.directory], { title: group.name, body: { @@ -3207,10 +3302,12 @@ async function writeGroupPage(group) { main: { content: fixWS`

    ${group.name}

    + ${group.urls.length && `

    Visit on ${joinNoOxford(group.urls.map(fancifyURL), 'or')}.

    `}
    ${transformMultiline(group.description)}
    -

    Albums:

    +

    Albums

    +

    View album gallery! Or browse the list: