diff options
-rw-r--r-- | static/client.js | 8 | ||||
-rwxr-xr-x | upd8.js | 401 |
2 files changed, 264 insertions, 145 deletions
diff --git a/static/client.js b/static/client.js index 1f8d51ed..649ca312 100644 --- a/static/client.js +++ b/static/client.js @@ -277,10 +277,10 @@ const infoCard = (() => { link(nameLink, 'track', data); const albumLink = container.querySelector('.info-card-album a'); - link(albumLink, 'album', data.links.album); + link(albumLink, 'album', data.album); const artistSpan = container.querySelector('.info-card-artists span'); - artistSpan.innerHTML = joinElements('conjunction', data.links.artists.map(({ who: artist }) => { + artistSpan.innerHTML = joinElements('conjunction', data.artists.map(({ artist }) => { const a = document.createElement('a'); a.href = getLinkHref('artist', artist.directory); a.innerText = artist.name; @@ -289,9 +289,9 @@ const infoCard = (() => { const coverArtistParagraph = container.querySelector('.info-card-cover-artists'); const coverArtistSpan = coverArtistParagraph.querySelector('span'); - if (data.links.coverArtists.length) { + if (data.coverArtists.length) { coverArtistParagraph.style.display = 'block'; - coverArtistSpan.innerHTML = joinElements('conjunction', data.links.coverArtists.map(({ who: artist }) => { + coverArtistSpan.innerHTML = joinElements('conjunction', data.coverArtists.map(({ artist }) => { const a = document.createElement('a'); a.href = getLinkHref('artist', artist.directory); a.innerText = artist.name; diff --git a/upd8.js b/upd8.js index f9727fa2..68d95ba3 100755 --- a/upd8.js +++ b/upd8.js @@ -191,6 +191,8 @@ const urlSpec = { root: '', path: '<>', + album: 'album/<>', + artist: 'artist/<>', track: 'track/<>' } }, @@ -1514,6 +1516,7 @@ async function processArtistDataFile(file) { const name = getBasicField(section, 'Artist'); const urls = (getListField(section, 'URLs') || []).filter(Boolean); const alias = getBasicField(section, 'Alias'); + const hasAvatar = getBooleanField(section, 'Has Avatar') ?? false; const note = getMultilineField(section, 'Note'); let directory = getBasicField(section, 'Directory'); @@ -1528,7 +1531,7 @@ async function processArtistDataFile(file) { if (alias) { return {name, directory, alias}; } else { - return {name, directory, urls, note}; + return {name, directory, urls, note, hasAvatar}; } }); } @@ -2122,18 +2125,20 @@ function serializeImagePaths(original) { } function serializeLink(thing) { - return Object.fromEntries([ - ['name', thing.name], - ['directory', thing.directory], - ['color', thing.color] - ].filter(([ key, value ]) => value)); + const ret = {}; + ret.name = thing.name; + ret.directory = thing.directory; + if (thing.color) ret.color = thing.color; + return ret; } function serializeContribs(contribs) { - return contribs.map(({ who, what }) => ({ - who: serializeLink(who), - what - })); + return contribs.map(({ who, what }) => { + const ret = {}; + ret.artist = serializeLink(who); + if (what) ret.contribution = what; + return ret; + }); } function serializeCover(thing, pathFunction) { @@ -2153,6 +2158,29 @@ function serializeCover(thing, pathFunction) { }; } +function serializeGroupsForAlbum(album) { + return album.groups.map(group => { + const index = group.albums.indexOf(album); + const next = group.albums[index + 1] || null; + const previous = group.albums[index - 1] || null; + return {group, index, next, previous}; + }).map(({group, index, next, previous}) => ({ + link: serializeLink(group), + descriptionShort: group.descriptionShort, + albumIndex: index, + nextAlbum: next && serializeLink(next), + previousAlbum: previous && serializeLink(previous), + urls: group.urls + })); +} + +function serializeGroupsForTrack(track) { + return track.album.groups.map(group => ({ + link: serializeLink(group), + urls: group.urls, + })); +} + function validateWritePath(path, urlGroup) { if (!Array.isArray(path)) { return {error: `Expected array, got ${path}`}; @@ -3028,10 +3056,43 @@ function writeAlbumPage(album) { }; const commentaryEntries = [album, ...album.tracks].filter(x => x.commentary).length; + const albumDuration = getTotalDuration(album.tracks); const listTag = getAlbumListTag(album); - return ({strings, writePage}) => writePage('album', album.directory, ({to}) => ({ + const data = { + type: 'data', + path: ['album', album.directory], + data: () => ({ + 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 + })) + }) + }; + + const page = {type: 'page', path: ['album', album.directory], page: ({strings, to}) => ({ title: strings('albumPage.title', {album: album.name}), stylesheet: getAlbumStylesheet(album, {to}), theme: getThemeString(album, [ @@ -3090,7 +3151,7 @@ function writeAlbumPage(album) { date: strings.count.date(album.coverArtDate) }), strings('releaseInfo.duration', { - duration: strings.count.duration(getTotalDuration(album.tracks), {approximate: album.tracks.length > 1}) + duration: strings.count.duration(albumDuration, {approximate: album.tracks.length > 1}) }) ].filter(Boolean).join('<br>\n')} </p> @@ -3142,7 +3203,6 @@ function writeAlbumPage(album) { }, sidebarLeft: generateSidebarForAlbum(album, null, {strings, to}), - sidebarRight: generateSidebarRightForAlbum(album, null, {strings, to}), nav: { links: [ @@ -3166,7 +3226,9 @@ function writeAlbumPage(album) { </div> ` } - })); + })}; + + return [page, data]; } function getAlbumStylesheet(album, {to}) { @@ -3252,23 +3314,28 @@ function writeTrackPage(track) { data: () => ({ name: track.name, directory: track.directory, - date: track.date, + dates: { + released: track.date, + originallyReleased: track.originalDate, + coverArtAdded: track.coverArtDate + }, duration: track.duration, color: track.color, cover: serializeCover(track, getTrackCover), - links: { - artists: serializeContribs(track.artists), - contributors: serializeContribs(track.contributors), - coverArtists: serializeContribs(track.coverArtists || []), - album: serializeLink(track.album), - groups: track.album.groups.map(serializeLink), - references: track.references.map(serializeLink), - referencedBy: track.referencedBy.map(serializeLink) - } + 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 = ({strings, writePage}) => writePage('track', track.directory, ({to}) => ({ const page = {type: 'page', path: ['track', track.directory], page: ({strings, to}) => ({ title: strings('trackPage.title', {track: track.name}), stylesheet: getAlbumStylesheet(album, {to}), @@ -3413,7 +3480,6 @@ function writeTrackPage(track) { }, sidebarLeft: generateSidebarForAlbum(album, track, {strings, to}), - sidebarRight: generateSidebarRightForAlbum(album, track, {strings, to}), nav: { links: [ @@ -3474,6 +3540,7 @@ function writeArtistPage(artist) { const getArtistsAndContrib = (thing, key) => ({ artists: thing[key]?.filter(({ who }) => who !== artist), contrib: thing[key]?.find(({ who }) => who === artist), + thing, key }); @@ -3606,26 +3673,63 @@ function writeArtistPage(artist) { </dl> `; - const avatarPath = path.join(C.MEDIA_ARTIST_AVATAR_DIRECTORY, artist.directory + '.jpg'); - let avatarFileExists = null; + const serializeArtistsAndContrib = key => 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; + }; - return async ({strings, writePage}) => { - // The outer step, used for gathering data, is always sync. This is - // normally fine 8ecause pretty much all the data we will ever need - // across 8uilds is available for synchronous access - 8ut this here - // is an exception, and we have to evaluate it asynchronously. Still, - // we don't want to perform that access() oper8tion any more than - // necessary, so we cache the value in a varia8le shared across calls - // to this 8uild function. - avatarFileExists = avatarFileExists ?? (wikiInfo.features.artistAvatars && - await access(path.join(mediaPath, avatarPath)).then(() => true, () => false)); - - await writePage('artist', artist.directory, ({to}) => ({ + const serializeTrackListChunks = chunks => + 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: () => ({ + 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: ({strings, to}) => ({ title: strings('artistPage.title', {artist: name}), main: { content: fixWS` - ${avatarFileExists && generateCoverLink({ + ${artist.hasAvatar && generateCoverLink({ strings, to, src: to('localized.artistAvatar', artist.directory), alt: strings('misc.alt.artistAvatar') @@ -3768,38 +3872,42 @@ function writeArtistPage(artist) { }, nav: generateNavForArtist(artist, {strings, to, isGallery: false, hasGallery}) - })); + }) + }; - if (hasGallery) { - await writePage('artistGallery', artist.directory, ({to}) => ({ - title: strings('artistGalleryPage.title', {artist: name}), + const galleryPage = hasGallery && { + type: 'page', + path: ['artistGallery', artist.directory], + page: ({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({ - strings, to, - entries: artThingsGallery.map(item => ({item})), - srcFn: thing => (thing.album - ? getTrackCover(thing, {to}) - : getAlbumCover(thing, {to})), - hrefFn: thing => (thing.album - ? to('localized.track', thing.directory) - : to('localized.album', thing.directory)) - })} - </div> - ` - }, + 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({ + strings, to, + entries: artThingsGallery.map(item => ({item})), + srcFn: thing => (thing.album + ? getTrackCover(thing, {to}) + : getAlbumCover(thing, {to})), + hrefFn: thing => (thing.album + ? to('localized.track', thing.directory) + : to('localized.album', thing.directory)) + })} + </div> + ` + }, - nav: generateNavForArtist(artist, {strings, to, isGallery: true, hasGallery}) - })); - } - } + nav: generateNavForArtist(artist, {strings, to, isGallery: true, hasGallery}) + }) + }; + + return [data, infoPage, galleryPage].filter(Boolean); } function generateNavForArtist(artist, {strings, to, isGallery, hasGallery}) { @@ -5311,79 +5419,90 @@ function generateSidebarForAlbum(album, currentTrack, {strings, to}) { }) }</li>`; - return { - content: fixWS` - <h1><a href="${to('localized.album', album.directory)}">${album.name}</a></h1> - ${album.trackGroups ? fixWS` - <dl> - ${album.trackGroups.map(({ name, color, startIndex, tracks }) => fixWS` - <dt ${classes(tracks.includes(currentTrack) && 'current')}>${ - (listTag === 'ol' - ? strings('albumSidebar.trackList.group.withRange', { - group: strings.link.track(tracks[0], {to, text: name}), - range: `${startIndex + 1}–${startIndex + tracks.length}` - }) - : strings('albumSidebar.trackList.group', { - group: strings.link.track(tracks[0], {to, text: name}) - })) - }</dt> - ${(!currentTrack || tracks.includes(currentTrack)) && fixWS` - <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}> - `} - ` - }; -} - -function generateSidebarRightForAlbum(album, currentTrack, {strings, to}) { - if (!wikiInfo.features.groupUI) { - return null; - } + const trackListPart = fixWS` + <h1><a href="${to('localized.album', album.directory)}">${album.name}</a></h1> + ${album.trackGroups ? fixWS` + <dl> + ${album.trackGroups.map(({ name, color, startIndex, tracks }) => fixWS` + <dt ${classes(tracks.includes(currentTrack) && 'current')}>${ + (listTag === 'ol' + ? strings('albumSidebar.trackList.group.withRange', { + group: strings.link.track(tracks[0], {to, text: name}), + range: `${startIndex + 1}–${startIndex + tracks.length}` + }) + : strings('albumSidebar.trackList.group', { + group: strings.link.track(tracks[0], {to, text: name}) + })) + }</dt> + ${(!currentTrack || tracks.includes(currentTrack)) && fixWS` + <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}> + `} + `; const { groups } = album; - if (groups.length) { + + 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: `<a href="${to('localized.groupInfo', group.directory)}">${group.name}</a>` + }) + }</h1> + ${!currentTrack && transformMultiline(group.descriptionShort, {strings, to})} + ${group.urls.length && `<p>${ + strings('releaseInfo.visitOn', { + links: strings.list.or(group.urls.map(url => fancifyURL(url, {strings}))) + }) + }</p>`} + ${!currentTrack && fixWS` + ${next && `<p class="group-chronology-link">${ + strings('albumSidebar.groupBox.next', { + album: `<a href="${to('localized.album', next.directory)}" style="${getLinkThemeString(next)}">${next.name}</a>` + }) + }</p>`} + ${previous && `<p class="group-chronology-link">${ + strings('albumSidebar.groupBox.previous', { + album: `<a href="${to('localized.album', previous.directory)}" style="${getLinkThemeString(previous)}">${previous.name}</a>` + }) + }</p>`} + `} + `); + + if (groupParts.length) { + if (currentTrack) { + const combinedGroupPart = groupParts.join('\n<hr>\n'); + return { + multiple: [ + trackListPart, + combinedGroupPart + ] + }; + } else { + return { + multiple: [ + ...groupParts, + trackListPart + ] + }; + } + } else { return { - collapse: false, - multiple: 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: `<a href="${to('localized.groupInfo', group.directory)}">${group.name}</a>` - }) - }</h1> - ${!currentTrack && transformMultiline(group.descriptionShort, {strings, to})} - ${group.urls.length && `<p>${ - strings('releaseInfo.visitOn', { - links: strings.list.or(group.urls.map(url => fancifyURL(url, {strings}))) - }) - }</p>`} - ${!currentTrack && fixWS` - ${next && `<p class="group-chronology-link">${ - strings('albumSidebar.groupBox.next', { - album: `<a href="${to('localized.album', next.directory)}" style="${getLinkThemeString(next)}">${next.name}</a>` - }) - }</p>`} - ${previous && `<p class="group-chronology-link">${ - strings('albumSidebar.groupBox.previous', { - album: `<a href="${to('localized.album', previous.directory)}" style="${getLinkThemeString(previous)}">${previous.name}</a>` - }) - }</p>`} - `} - `) + content: trackListPart }; - }; + } } function generateSidebarForGroup(currentGroup, {strings, to, isGallery}) { @@ -6185,8 +6304,8 @@ async function main() { trackData.forEach(track => track.otherReleases = [ track.aka, - ...trackData.filter(({ aka }) => aka === track) - ].filter(Boolean)); + ...trackData.filter(({ aka }) => aka === track || (track.aka && aka === track.aka)), + ].filter(x => x && x !== track)); if (wikiInfo.features.flashesAndGames) { flashData.forEach(flash => mapInPlace(flash.tracks, search.track)); |