diff options
-rw-r--r-- | src/content/dependencies/generateListingPage.js | 50 | ||||
-rw-r--r-- | src/content/dependencies/listArtistsByContributions.js | 116 | ||||
-rw-r--r-- | src/content/dependencies/listArtistsByGroup.js | 133 | ||||
-rw-r--r-- | src/content/dependencies/listArtistsByLatestContribution.js | 594 | ||||
-rw-r--r-- | src/content/dependencies/listArtistsByName.js | 45 | ||||
-rw-r--r-- | src/data/things/artist.js | 17 | ||||
-rw-r--r-- | src/listing-spec.js | 10 | ||||
-rw-r--r-- | src/strings-default.json | 17 |
8 files changed, 564 insertions, 418 deletions
diff --git a/src/content/dependencies/generateListingPage.js b/src/content/dependencies/generateListingPage.js index 08eb40c6..f527f16f 100644 --- a/src/content/dependencies/generateListingPage.js +++ b/src/content/dependencies/generateListingPage.js @@ -7,6 +7,7 @@ export default { 'generatePageLayout', 'linkListing', 'linkListingIndex', + 'linkTemplate', ], extraDependencies: ['html', 'language', 'wikiData'], @@ -26,6 +27,9 @@ export default { relations.chunkHeading = relation('generateContentHeading'); + relations.showSkipToSectionLinkTemplate = + relation('linkTemplate'); + if (listing.target.listings.length > 1) { relations.sameTargetListingLinks = listing.target.listings @@ -65,6 +69,9 @@ export default { chunkTitles: {validate: v => v.strictArrayOf(v.isObject)}, chunkRows: {validate: v => v.strictArrayOf(v.isObject)}, + showSkipToSection: {type: 'boolean', default: false}, + chunkIDs: {validate: v => v.strictArrayOf(v.isString)}, + listStyle: { validate: v => v.is('ordered', 'unordered'), default: 'unordered', @@ -84,12 +91,13 @@ export default { const parts = [baseStringsKey, contextStringsKey]; - if (options.stringsKey) { + const {stringsKey, ...passOptions} = options; + + if (stringsKey) { parts.push(options.stringsKey); - delete options.stringsKey; } - return language.formatString(parts.join('.'), options); + return language.formatString(parts.join('.'), passOptions); }; return relations.layout.slots({ @@ -121,6 +129,8 @@ export default { listings: language.formatUnitList(relations.seeAlsoLinks), })), + slots.content, + slots.type === 'rows' && html.tag(listTag, slots.rows.map(row => @@ -128,16 +138,40 @@ export default { formatListingString('item', row)))), slots.type === 'chunks' && - html.tag('dl', + html.tag('dl', [ + slots.showSkipToSection && [ + html.tag('dt', + language.$('listingPage.skipToSection')), + + html.tag('dd', + html.tag('ul', + stitchArrays({ + title: slots.chunkTitles, + id: slots.chunkIDs, + }).filter(({id}) => id) + .map(({title, id}) => + html.tag('li', + relations.showSkipToSectionLinkTemplate + .clone() + .slots({ + hash: id, + content: + formatListingString('chunk.title', title) + .replace(/:$/, ''), + }))))), + ], + stitchArrays({ title: slots.chunkTitles, rows: slots.chunkRows, - }).map(({title, rows}) => [ + id: slots.chunkIDs, + }).map(({title, rows, id}) => [ relations.chunkHeading .clone() .slots({ tag: 'dt', title: formatListingString('chunk.title', title), + id, }), html.tag('dd', @@ -146,10 +180,8 @@ export default { html.tag('li', {class: row.stringsKey === 'rerelease' && 'rerelease'}, formatListingString('chunk.item', row))))), - ])), - - slots.type === 'custom' && - slots.content, + ]), + ]), ], navLinkStyle: 'hierarchical', diff --git a/src/content/dependencies/listArtistsByContributions.js b/src/content/dependencies/listArtistsByContributions.js index 86c8cfa2..58c51a40 100644 --- a/src/content/dependencies/listArtistsByContributions.js +++ b/src/content/dependencies/listArtistsByContributions.js @@ -1,5 +1,11 @@ -import {stitchArrays, unique} from '#sugar'; -import {filterByCount, sortAlphabetically, sortByCount} from '#wiki-data'; +import {empty, stitchArrays, unique} from '#sugar'; + +import { + filterByCount, + filterMultipleArrays, + sortAlphabetically, + sortByCount, +} from '#wiki-data'; export default { contentDependencies: ['generateListingPage', 'linkArtist'], @@ -96,68 +102,54 @@ export default { return data; }, - generate(data, relations, {html, language}) { - const lists = Object.fromEntries( - ([ - ['tracks', [ - relations.artistLinksByTrackContributions, - data.countsByTrackContributions, - 'countTracks', - ]], - - ['artworks', [ - relations.artistLinksByArtworkContributions, - data.countsByArtworkContributions, - 'countArtworks', - ]], - - data.enableFlashesAndGames && - ['flashes', [ - relations.artistLinksByFlashContributions, - data.countsByFlashContributions, - 'countFlashes', - ]], - ]).filter(Boolean) - .map(([key, [artistLinks, counts, countFunction]]) => [ - key, - html.tag('ul', - stitchArrays({ - artistLink: artistLinks, - count: counts, - }).map(({artistLink, count}) => - html.tag('li', - language.$('listingPage.listArtists.byContribs.item', { - artist: artistLink, - contributions: language[countFunction](count, {unit: true}), - })))), - ])); + generate(data, relations, {language}) { + const listChunkIDs = ['tracks', 'artworks', 'flashes']; + const listTitleStringsKeys = ['trackContributors', 'artContributors', 'flashContributors']; + const listCountFunctions = ['countTracks', 'countArtworks', 'countFlashes']; + + const listArtistLinks = [ + relations.artistLinksByTrackContributions, + relations.artistLinksByArtworkContributions, + relations.artistLinksByFlashContributions, + ]; + + const listArtistCounts = [ + data.countsByTrackContributions, + data.countsByArtworkContributions, + data.countsByFlashContributions, + ]; + + filterMultipleArrays( + listChunkIDs, + listTitleStringsKeys, + listCountFunctions, + listArtistLinks, + listArtistCounts, + (_chunkID, _titleStringsKey, _countFunction, artistLinks, _artistCounts) => + !empty(artistLinks)); return relations.page.slots({ - type: 'custom', - content: - html.tag('div', {class: 'content-columns'}, [ - html.tag('div', {class: 'column'}, [ - html.tag('h2', - language.$('listingPage.misc.trackContributors')), - - lists.tracks, - ]), - - html.tag('div', {class: 'column'}, [ - html.tag('h2', - language.$( - 'listingPage.misc.artContributors')), - - lists.artworks, - - lists.flashes && [ - html.tag('h2', - language.$('listingPage.misc.flashContributors')), - - lists.flashes, - ], - ]), - ]), + type: 'chunks', + + showSkipToSection: true, + chunkIDs: listChunkIDs, + + chunkTitles: + listTitleStringsKeys.map(stringsKey => ({stringsKey})), + + chunkRows: + stitchArrays({ + artistLinks: listArtistLinks, + artistCounts: listArtistCounts, + countFunction: listCountFunctions, + }).map(({artistLinks, artistCounts, countFunction}) => + stitchArrays({ + artistLink: artistLinks, + artistCount: artistCounts, + }).map(({artistLink, artistCount}) => ({ + artist: artistLink, + contributions: language[countFunction](artistCount, {unit: true}), + }))), }); }, }; diff --git a/src/content/dependencies/listArtistsByGroup.js b/src/content/dependencies/listArtistsByGroup.js new file mode 100644 index 00000000..3778b9e3 --- /dev/null +++ b/src/content/dependencies/listArtistsByGroup.js @@ -0,0 +1,133 @@ +import {empty, stitchArrays, unique} from '#sugar'; + +import { + filterMultipleArrays, + getArtistNumContributions, + sortAlphabetically, +} from '#wiki-data'; + +export default { + contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], + extraDependencies: ['language', 'wikiData'], + + sprawl({artistData, wikiInfo}) { + return {artistData, wikiInfo}; + }, + + query(sprawl, spec) { + const artists = sortAlphabetically(sprawl.artistData.slice()); + const groups = sprawl.wikiInfo.divideTrackListsByGroups; + + if (empty(groups)) { + return {spec, artists}; + } + + const artistGroups = + artists.map(artist => + unique( + unique([ + ...artist.albumsAsAny, + ...artist.tracksAsAny.map(track => track.album), + ]).flatMap(album => album.groups))) + + const artistsByGroup = + groups.map(group => + artists.filter((artist, index) => artistGroups[index].includes(group))); + + filterMultipleArrays(groups, artistsByGroup, + (group, artists) => !empty(artists)); + + return {spec, groups, artistsByGroup}; + }, + + relations(relation, query) { + const relations = {}; + + relations.page = + relation('generateListingPage', query.spec); + + if (query.artists) { + relations.artistLinks = + query.artists + .map(artist => relation('linkArtist', artist)); + } + + if (query.artistsByGroup) { + relations.groupLinks = + query.groups + .map(group => relation('linkGroup', group)); + + relations.artistLinksByGroup = + query.artistsByGroup + .map(artists => artists + .map(artist => relation('linkArtist', artist))); + } + + return relations; + }, + + data(query) { + const data = {}; + + if (query.artists) { + data.counts = + query.artists + .map(artist => getArtistNumContributions(artist)); + } + + if (query.artistsByGroup) { + data.groupDirectories = + query.groups + .map(group => group.directory); + + data.countsByGroup = + query.artistsByGroup + .map(artists => artists + .map(artist => getArtistNumContributions(artist))); + } + + return data; + }, + + generate(data, relations, {language}) { + return ( + (relations.artistLinksByGroup + ? relations.page.slots({ + type: 'chunks', + + showSkipToSection: true, + chunkIDs: + data.groupDirectories + .map(directory => `contributed-to-${directory}`), + + chunkTitles: + relations.groupLinks.map(groupLink => ({ + group: groupLink, + })), + + chunkRows: + stitchArrays({ + artistLinks: relations.artistLinksByGroup, + counts: data.countsByGroup, + }).map(({artistLinks, counts}) => + stitchArrays({ + link: artistLinks, + count: counts, + }).map(({link, count}) => ({ + artist: link, + contributions: language.countContributions(count, {unit: true}), + }))), + }) + : relations.page.slots({ + type: 'rows', + rows: + stitchArrays({ + link: relations.artistLinks, + count: data.counts, + }).map(({link, count}) => ({ + artist: link, + contributions: language.countContributions(count, {unit: true}), + })), + }))); + }, +}; diff --git a/src/content/dependencies/listArtistsByLatestContribution.js b/src/content/dependencies/listArtistsByLatestContribution.js index b6ea8e96..edb02e0d 100644 --- a/src/content/dependencies/listArtistsByLatestContribution.js +++ b/src/content/dependencies/listArtistsByLatestContribution.js @@ -1,15 +1,16 @@ -import {transposeArrays, empty, stitchArrays} from '#sugar'; +import {empty, stitchArrays} from '#sugar'; +import T from '#things'; import { chunkMultipleArrays, - compareCaseLessSensitive, - compareDates, - filterMultipleArrays, - reduceMultipleArrays, sortAlphabetically, + sortAlbumsTracksChronologically, + sortFlashesChronologically, sortMultipleArrays, } from '#wiki-data'; +const {Album, Flash} = T; + export default { contentDependencies: [ 'generateListingPage', @@ -20,348 +21,303 @@ export default { extraDependencies: ['html', 'language', 'wikiData'], - sprawl({artistData, wikiInfo}) { - return { - artistData, - enableFlashesAndGames: wikiInfo.enableFlashesAndGames, - }; - }, + sprawl: ({albumData, artistData, flashData, trackData, wikiInfo}) => + ({albumData, artistData, flashData, trackData, + enableFlashesAndGames: wikiInfo.enableFlashesAndGames}), query(sprawl, spec) { - const query = { - spec, - enableFlashesAndGames: sprawl.enableFlashesAndGames, - }; - - const queryContributionInfo = ( - artistsKey, - chunkThingsKey, - datesKey, - datelessArtistsKey, - fn, - ) => { - const artists = sortAlphabetically(sprawl.artistData.slice()); - - // Each value stored in dateLists, corresponding to each artist, - // is going to be a list of dates and nulls. Any nulls represent - // a contribution which isn't associated with a particular date. - const [chunkThingLists, dateLists] = - transposeArrays(artists.map(artist => fn(artist))); - - // Scrap artists who don't even have any relevant contributions. - // These artists may still have other contributions across the wiki, but - // they weren't returned by the callback and so aren't relevant to this - // list. - filterMultipleArrays( - artists, - chunkThingLists, - dateLists, - (artists, chunkThings, dates) => !empty(dates)); - - // Also exclude artists whose remaining contributions are all dateless. - // But keep track of the artists removed here, since they'll be displayed - // in an additional list in the final listing page. - const {removed: [datelessArtists]} = - filterMultipleArrays( - artists, - chunkThingLists, - dateLists, - (artist, chunkThings, dates) => !empty(dates.filter(Boolean))); - - // Cut out dateless contributions. They're not relevant to finding the - // latest date. - for (const [chunkThings, dates] of transposeArrays([chunkThingLists, dateLists])) { - filterMultipleArrays(chunkThings, dates, (chunkThing, date) => date); + // + // First main step is to get the latest thing each artist has contributed + // to, and the date associated with that contribution! Some notes: + // + // * Album and track contributions are considered before flashes, so + // they'll take priority if an artist happens to have multiple contribs + // landing on the same date to both an album and a flash. + // + // * The final (album) contribution list is chunked by album, but also by + // date, because an individual album can cover a variety of dates. + // + // * If an artist has contributed both artworks and tracks to the album + // containing their latest contribution, then that will be indicated + // in an annotation, but *only if* those contributions were also on + // the same date. + // + // * If an artist made contributions to multiple albums on the same date, + // then the first of the *albums* sorted chronologically (latest first) + // is the one that will count. + // + // * Same for artists who've contributed to multiple flashes which were + // released on the same date. + // + // * The map may exclude artists none of whose contributions were dated. + // + + const artistLatestContribMap = new Map(); + + const considerDate = (artist, date, thing, contribution) => { + if (!date) { + return; } - const [chunkThings, dates] = - transposeArrays( - transposeArrays([chunkThingLists, dateLists]) - .map(([chunkThings, dates]) => - reduceMultipleArrays( - chunkThings, dates, - (accChunkThing, accDate, chunkThing, date) => - (date && date > accDate - ? [chunkThing, date] - : [accChunkThing, accDate])))); - - sortMultipleArrays(artists, dates, chunkThings, - (artistA, artistB, dateA, dateB, chunkThingA, chunkThingB) => { - const dateComparison = compareDates(dateA, dateB, {latestFirst: true}); - if (dateComparison !== 0) { - return dateComparison; - } - - // TODO: Compare alphabetically, not just by directory. - return compareCaseLessSensitive(chunkThingA.directory, chunkThingB.directory); - }); - - const chunks = - chunkMultipleArrays(artists, dates, chunkThings, - (artist, lastArtist, date, lastDate, chunkThing, lastChunkThing) => - +date !== +lastDate || chunkThing !== lastChunkThing); + if (artistLatestContribMap.has(artist)) { + const latest = artistLatestContribMap.get(artist); + if (latest.date > date) { + return; + } - query[chunkThingsKey] = - chunks.map(([artists, dates, chunkThings]) => chunkThings[0]); - - query[datesKey] = - chunks.map(([artists, dates, chunkThings]) => dates[0]); + if (latest.date === date) { + if (latest.thing === thing) { + // May combine differnt contributions to the same thing and date. + latest.contribution.add(contribution); + } - query[artistsKey] = - chunks.map(([artists, dates, chunkThings]) => artists); + // Earlier-processed things of same date take priority. + return; + } + } - query[datelessArtistsKey] = datelessArtists; + // First entry for artist or more recent contribution than latest date. + artistLatestContribMap.set(artist, { + date, + thing, + contribution: new Set([contribution]), + }); }; - queryContributionInfo( - 'artistsByTrackContributions', - 'albumsByTrackContributions', - 'datesByTrackContributions', - 'datelessArtistsByTrackContributions', - artist => { - const tracks = - [...artist.tracksAsArtist, ...artist.tracksAsContributor] - .filter(track => !track.originalReleaseTrack); - - const albums = tracks.map(track => track.album); - const dates = tracks.map(track => track.date); + const getArtists = (thing, key) => thing[key].map(({who}) => who); - return [albums, dates]; - }); + const albumsLatestFirst = sortAlbumsTracksChronologically(sprawl.albumData.slice()); + const tracksLatestFirst = sortAlbumsTracksChronologically(sprawl.trackData.slice()); + const flashesLatestFirst = sortFlashesChronologically(sprawl.flashData.slice()); - queryContributionInfo( - 'artistsByArtworkContributions', - 'albumsByArtworkContributions', - 'datesByArtworkContributions', - 'datelessArtistsByArtworkContributions', - artist => [ - [ - ...artist.tracksAsCoverArtist.map(track => track.album), - ...artist.albumsAsCoverArtist, - ...artist.albumsAsWallpaperArtist, - ...artist.albumsAsBannerArtist, - ], - [ - // TODO: Per-artwork dates, see #90. - ...artist.tracksAsCoverArtist.map(track => track.coverArtDate), - ...artist.albumsAsCoverArtist.map(album => album.coverArtDate), - ...artist.albumsAsWallpaperArtist.map(album => album.coverArtDate), - ...artist.albumsAsBannerArtist.map(album => album.coverArtDate), - ], - ]); - - if (sprawl.enableFlashesAndGames) { - queryContributionInfo( - 'artistsByFlashContributions', - 'flashesByFlashContributions', - 'datesByFlashContributions', - 'datelessArtistsByFlashContributions', - artist => [ - [ - ...artist.flashesAsContributor, - ], - [ - ...artist.flashesAsContributor.map(flash => flash.date), - ], - ]); + for (const album of albumsLatestFirst) { + for (const artist of new Set([ + ...getArtists(album, 'coverArtistContribs'), + ...getArtists(album, 'wallpaperArtistContribs'), + ...getArtists(album, 'bannerArtistContribs'), + ])) { + // Might combine later with 'track' of the same album and date. + considerDate(artist, album.coverArtDate, album, 'artwork'); + } } - return query; - }, - - relations(relation, query) { - const relations = {}; - - relations.page = - relation('generateListingPage', query.spec); - - // Track contributors - - relations.albumLinksByTrackContributions = - query.albumsByTrackContributions - .map(album => relation('linkAlbum', album)); - - relations.artistLinksByTrackContributions = - query.artistsByTrackContributions - .map(artists => - artists.map(artist => relation('linkArtist', artist))); - - relations.datelessArtistLinksByTrackContributions = - query.datelessArtistsByTrackContributions - .map(artist => relation('linkArtist', artist)); - - // Artwork contributors - - relations.albumLinksByArtworkContributions = - query.albumsByArtworkContributions - .map(album => relation('linkAlbum', album)); + for (const track of tracksLatestFirst) { + for (const artist of getArtists(track, 'coverArtistContribs')) { + // No special effect if artist already has 'artwork' for the same album and date. + considerDate(artist, track.coverArtDate, track.album, 'artwork'); + } - relations.artistLinksByArtworkContributions = - query.artistsByArtworkContributions - .map(artists => - artists.map(artist => relation('linkArtist', artist))); + for (const artist of new Set([ + ...getArtists(track, 'artistContribs'), + ...getArtists(track, 'contributorContribs'), + ])) { + // Might be combining with 'artwork' of the same album and date. + considerDate(artist, track.date, track.album, 'track'); + } + } - relations.datelessArtistLinksByArtworkContributions = - query.datelessArtistsByArtworkContributions - .map(artist => relation('linkArtist', artist)); + for (const flash of flashesLatestFirst) { + for (const artist of getArtists(flash, 'contributorContribs')) { + // Won't take priority above album contributions of the same date. + considerDate(artist, flash.date, flash, 'flash'); + } + } - // Flash contributors + // + // Next up is to sort all the processed artist information! + // + // Entries with the same album/flash and the same date go together first, + // with the following rules for sorting artists therein: + // + // * If the contributions are different, which can only happen for albums, + // then it's tracks-only first, tracks + artworks next, and artworks-only + // last. + // + // * If the contributions are the same, then sort alphabetically. + // + // Entries with different albums/flashes follow another set of rules: + // + // * Later dates come before earlier dates. + // + // * On the same date, albums come before flashes. + // + // * Things of the same type *and* date are sorted alphabetically. + // + + const artistsAlphabetically = + sortAlphabetically(sprawl.artistData.slice()); + + const artists = + Array.from(artistLatestContribMap.keys()); + + const artistContribEntries = + Array.from(artistLatestContribMap.values()); + + const artistThings = + artistContribEntries.map(({thing}) => thing); + + const artistDates = + artistContribEntries.map(({date}) => date); + + const artistContributions = + artistContribEntries.map(({contribution}) => contribution); + + sortMultipleArrays(artistThings, artistDates, artistContributions, artists, + (thing1, thing2, date1, date2, contrib1, contrib2, artist1, artist2) => { + if (date1 === date2 && thing1 === thing2) { + // Move artwork-only contribs after contribs with tracks. + if (!contrib1.has('track') && contrib2.has('track')) return 1; + if (!contrib2.has('track') && contrib1.has('track')) return -1; + + // Move track-only contribs before tracks with tracks and artwork. + if (!contrib1.has('artwork') && contrib2.has('artwork')) return -1; + if (!contrib2.has('artwork') && contrib1.has('artwork')) return 1; + + // Sort artists of the same type of contribution alphabetically, + // referring to a previous sort. + const index1 = artistsAlphabetically.indexOf(artist1); + const index2 = artistsAlphabetically.indexOf(artist2); + return index1 - index2; + } else { + // Move later dates before earlier ones. + if (date1 !== date2) return date2 - date1; + + // Move albums before flashes. + if (thing1 instanceof Album && thing2 instanceof Flash) return -1; + if (thing1 instanceof Flash && thing2 instanceof Album) return 1; + + // Sort two albums or two flashes alphabetically, referring to a + // previous sort (which was chronological but includes the correct + // ordering for things released on the same date). + const thingsLatestFirst = + (thing1 instanceof Album + ? albumsLatestFirst + : flashesLatestFirst); + const index1 = thingsLatestFirst.indexOf(thing1); + const index2 = thingsLatestFirst.indexOf(thing2); + return index2 - index1; + } + }); - if (query.enableFlashesAndGames) { - relations.flashLinksByFlashContributions = - query.flashesByFlashContributions - .map(flash => relation('linkFlash', flash)); + // Last off, turn the flat sorted list into a proper chunked list, now that + // entries going in the same chunk are sorted correctly next to each other. + // Then extract the parts that are useful for displaying on the listing! - relations.artistLinksByFlashContributions = - query.artistsByFlashContributions - .map(artists => - artists.map(artist => relation('linkArtist', artist))); + const chunks = + chunkMultipleArrays(artistThings, artistDates, artistContributions, artists, + (thing, lastThing, date, lastDate) => + thing !== lastThing || + +date !== +lastDate); - relations.datelessArtistLinksByFlashContributions = - query.datelessArtistsByFlashContributions - .map(artist => relation('linkArtist', artist)); - } + const chunkThings = + chunks.map(([artistThings, , , ]) => artistThings[0]); - return relations; - }, + const chunkDates = + chunks.map(([, artistDates, , ]) => artistDates[0]); - data(query) { - const data = {}; + const chunkArtistContributions = + chunks.map(([, , artistContributions, ]) => artistContributions); - data.enableFlashesAndGames = query.enableFlashesAndGames; + const chunkArtists = + chunks.map(([, , , artists]) => artists); - data.datesByTrackContributions = query.datesByTrackContributions; - data.datesByArtworkContributions = query.datesByArtworkContributions; + // And one bonus step - keep track of all the artists whose contributions + // were all without date. - if (query.enableFlashesAndGames) { - data.datesByFlashContributions = query.datesByFlashContributions; - } + const datelessArtists = + artistsAlphabetically + .filter(artist => !artists.includes(artist)); - return data; + return { + spec, + chunkThings, + chunkDates, + chunkArtistContributions, + chunkArtists, + datelessArtists, + }; }, - generate(data, relations, {html, language}) { - const chunkTitles = Object.fromEntries( - ([ - ['tracks', [ - 'album', - relations.albumLinksByTrackContributions, - data.datesByTrackContributions, - ]], - - ['artworks', [ - 'album', - relations.albumLinksByArtworkContributions, - data.datesByArtworkContributions, - ]], - - data.enableFlashesAndGames && - ['flashes', [ - 'flash', - relations.flashLinksByFlashContributions, - data.datesByFlashContributions, - ]], - ]).filter(Boolean) - .map(([key, [stringsKey, links, dates]]) => [ - key, - stitchArrays({link: links, date: dates}) - .map(({link, date}) => - html.tag('dt', - language.$(`listingPage.listArtists.byLatest.chunk.title.${stringsKey}`, { - [stringsKey]: link, - date: language.formatDate(date), - }))), - ])); - - const chunkItems = Object.fromEntries( - ([ - ['tracks', relations.artistLinksByTrackContributions], - ['artworks', relations.artistLinksByArtworkContributions], - data.enableFlashesAndGames && - ['flashes', relations.artistLinksByFlashContributions], - ]).filter(Boolean) - .map(([key, artistLinkLists]) => [ - key, - artistLinkLists.map(artistLinks => - html.tag('dd', - html.tag('ul', - artistLinks.map(artistLink => - html.tag('li', - language.$('listingPage.listArtists.byLatest.chunk.item', { - artist: artistLink, - })))))), - ])); - - const lists = Object.fromEntries( - ([ - ['tracks', [ - chunkTitles.tracks, - chunkItems.tracks, - relations.datelessArtistLinksByTrackContributions, - ]], - - ['artworks', [ - chunkTitles.artworks, - chunkItems.artworks, - relations.datelessArtistLinksByArtworkContributions, - ]], - - data.enableFlashesAndGames && - ['flashes', [ - chunkTitles.flashes, - chunkItems.flashes, - relations.datelessArtistLinksByFlashContributions, - ]], - ]).filter(Boolean) - .map(([key, [titles, items, datelessArtistLinks]]) => [ - key, - html.tags([ - html.tag('dl', - stitchArrays({ - title: titles, - items: items, - }).map(({title, items}) => [title, items])), - - !empty(datelessArtistLinks) && [ - html.tag('p', - language.$('listingPage.listArtists.byLatest.dateless.title')), - - html.tag('ul', - datelessArtistLinks.map(artistLink => - html.tag('li', - language.$('listingPage.listArtists.byLatest.dateless.item', { - artist: artistLink, - })))), - ], - ]), - ])); - + relations: (relation, query) => ({ + page: + relation('generateListingPage', query.spec), + + chunkAlbumLinks: + query.chunkThings + .map(thing => + (thing instanceof Album + ? relation('linkAlbum', thing) + : null)), + + chunkFlashLinks: + query.chunkThings + .map(thing => + (thing instanceof Flash + ? relation('linkFlash', thing) + : null)), + + chunkArtistLinks: + query.chunkArtists + .map(artists => artists + .map(artist => relation('linkArtist', artist))), + + datelessArtistLinks: + query.datelessArtists + .map(artist => relation('linkArtist', artist)), + }), + + data: (query) => ({ + chunkDates: query.chunkDates, + chunkArtistContributions: query.chunkArtistContributions, + }), + + generate(data, relations, {language}) { return relations.page.slots({ - type: 'custom', - content: - html.tag('div', {class: 'content-columns'}, [ - html.tag('div', {class: 'column'}, [ - html.tag('h2', - language.$('listingPage.misc.trackContributors')), - - lists.tracks, - ]), - - html.tag('div', {class: 'column'}, [ - html.tag('h2', - language.$( - 'listingPage.misc.artContributors')), - - lists.artworks, - - lists.flashes && [ - html.tag('h2', - language.$('listingPage.misc.flashContributors')), - - lists.flashes, - ], - ]), - ]), + type: 'chunks', + + chunkTitles: + stitchArrays({ + albumLink: relations.chunkAlbumLinks, + flashLink: relations.chunkFlashLinks, + date: data.chunkDates, + }).map(({albumLink, flashLink, date}) => ({ + date: language.formatDate(date), + ...(albumLink + ? {stringsKey: 'album', album: albumLink} + : {stringsKey: 'flash', flash: flashLink}), + })) + .concat( + (empty(relations.datelessArtistLinks) + ? [] + : [{stringsKey: 'dateless'}])), + + chunkRows: + stitchArrays({ + artistLinks: relations.chunkArtistLinks, + contributions: data.chunkArtistContributions, + }).map(({artistLinks, contributions}) => + stitchArrays({ + artistLink: artistLinks, + contribution: contributions, + }).map(({artistLink, contribution}) => ({ + artist: artistLink, + stringsKey: + (contribution.has('track') && contribution.has('artwork') + ? 'tracksAndArt' + : contribution.has('track') + ? 'tracks' + : contribution.has('artwork') + ? 'art' + : null), + }))) + .concat( + (empty(relations.datelessArtistLinks) + ? [] + : [ + relations.datelessArtistLinks.map(artistLink => ({ + artist: artistLink, + })), + ])), }); }, }; diff --git a/src/content/dependencies/listArtistsByName.js b/src/content/dependencies/listArtistsByName.js index 6c0ad836..554b4587 100644 --- a/src/content/dependencies/listArtistsByName.js +++ b/src/content/dependencies/listArtistsByName.js @@ -2,38 +2,33 @@ import {stitchArrays} from '#sugar'; import {getArtistNumContributions, sortAlphabetically} from '#wiki-data'; export default { - contentDependencies: ['generateListingPage', 'linkArtist'], + contentDependencies: ['generateListingPage', 'linkArtist', 'linkGroup'], extraDependencies: ['language', 'wikiData'], - sprawl({artistData}) { - return {artistData}; - }, + sprawl: ({artistData, wikiInfo}) => + ({artistData, wikiInfo}), - query({artistData}, spec) { - return { - spec, + query: (sprawl, spec) => ({ + spec, - artists: sortAlphabetically(artistData.slice()), - }; - }, + artists: + sortAlphabetically(sprawl.artistData.slice()), + }), - relations(relation, query) { - return { - page: relation('generateListingPage', query.spec), + relations: (relation, query) => ({ + page: + relation('generateListingPage', query.spec), - artistLinks: - query.artists - .map(artist => relation('linkArtist', artist)), - }; - }, + artistLinks: + query.artists + .map(artist => relation('linkArtist', artist)), + }), - data(query) { - return { - counts: - query.artists - .map(artist => getArtistNumContributions(artist)), - }; - }, + data: (query) => ({ + counts: + query.artists + .map(artist => getArtistNumContributions(artist)), + }), generate(data, relations, {language}) { return relations.page.slots({ diff --git a/src/data/things/artist.js b/src/data/things/artist.js index ea19d2ba..1b313db6 100644 --- a/src/data/things/artist.js +++ b/src/data/things/artist.js @@ -96,6 +96,23 @@ export class Artist extends Thing { albumsAsBannerArtist: Artist.filterByContrib('albumData', 'bannerArtistContribs'), + albumsAsAny: { + flags: {expose: true}, + + expose: { + dependencies: ['albumData'], + + compute: ({albumData, [Artist.instance]: artist}) => + albumData?.filter((album) => + [ + ...album.artistContribs, + ...album.coverArtistContribs, + ...album.wallpaperArtistContribs, + ...album.bannerArtistContribs, + ].some(({who}) => who === artist)) ?? [], + }, + }, + albumsAsCommentator: { flags: {expose: true}, diff --git a/src/listing-spec.js b/src/listing-spec.js index 2b33744a..f57762b0 100644 --- a/src/listing-spec.js +++ b/src/listing-spec.js @@ -44,12 +44,14 @@ listingSpec.push({ directory: 'artists/by-name', stringsKey: 'listArtists.byName', contentFunction: 'listArtistsByName', + seeAlso: ['artists/by-contribs', 'artists/by-group'], }); listingSpec.push({ directory: 'artists/by-contribs', stringsKey: 'listArtists.byContribs', contentFunction: 'listArtistsByContributions', + seeAlso: ['artists/by-name', 'artists/by-group'], }); listingSpec.push({ @@ -65,6 +67,14 @@ listingSpec.push({ }); listingSpec.push({ + directory: 'artists/by-group', + stringsKey: 'listArtists.byGroup', + contentFunction: 'listArtistsByGroup', + featureFlag: 'enableGroupUI', + seeAlso: ['artists/by-name', 'artists/by-contribs'], +}); + +listingSpec.push({ directory: 'artists/by-latest', stringsKey: 'listArtists.byLatest', contentFunction: 'listArtistsByLatestContribution', diff --git a/src/strings-default.json b/src/strings-default.json index b6471bdf..6c841e72 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -350,6 +350,7 @@ "listingPage.target.other": "Other", "listingPage.listingsFor": "Listings for {TARGET}: {LISTINGS}", "listingPage.seeAlso": "Also check out: {LISTINGS}", + "listingPage.skipToSection": "Skip to a section:", "listingPage.listAlbums.byName.title": "Albums - by Name", "listingPage.listAlbums.byName.title.short": "...by Name", "listingPage.listAlbums.byName.item": "{ALBUM} ({TRACKS})", @@ -371,20 +372,30 @@ "listingPage.listArtists.byName.item": "{ARTIST} ({CONTRIBUTIONS})", "listingPage.listArtists.byContribs.title": "Artists - by Contributions", "listingPage.listArtists.byContribs.title.short": "...by Contributions", - "listingPage.listArtists.byContribs.item": "{ARTIST} ({CONTRIBUTIONS})", + "listingPage.listArtists.byContribs.chunk.title.trackContributors": "Contributed tracks:", + "listingPage.listArtists.byContribs.chunk.title.artContributors": "Contributed artworks:", + "listingPage.listArtists.byContribs.chunk.title.flashContributors": "Contributed to flashes & games:", + "listingPage.listArtists.byContribs.chunk.item": "{ARTIST} ({CONTRIBUTIONS})", "listingPage.listArtists.byCommentary.title": "Artists - by Commentary Entries", "listingPage.listArtists.byCommentary.title.short": "...by Commentary Entries", "listingPage.listArtists.byCommentary.item": "{ARTIST} ({ENTRIES})", "listingPage.listArtists.byDuration.title": "Artists - by Duration", "listingPage.listArtists.byDuration.title.short": "...by Duration", "listingPage.listArtists.byDuration.item": "{ARTIST} ({DURATION})", + "listingPage.listArtists.byGroup.title": "Artists - by Group", + "listingPage.listArtists.byGroup.title.short": "...by Group", + "listingPage.listArtists.byGroup.item": "{ARTIST} ({CONTRIBUTIONS})", + "listingPage.listArtists.byGroup.chunk.title": "Contributed to {GROUP}:", + "listingPage.listArtists.byGroup.chunk.item": "{ARTIST} ({CONTRIBUTIONS})", "listingPage.listArtists.byLatest.title": "Artists - by Latest Contribution", "listingPage.listArtists.byLatest.title.short": "...by Latest Contribution", "listingPage.listArtists.byLatest.chunk.title.album": "{ALBUM} ({DATE})", "listingPage.listArtists.byLatest.chunk.title.flash": "{FLASH} ({DATE})", + "listingPage.listArtists.byLatest.chunk.title.dateless": "These artists' contributions aren't dated:", "listingPage.listArtists.byLatest.chunk.item": "{ARTIST}", - "listingPage.listArtists.byLatest.dateless.title": "These artists' contributions aren't dated:", - "listingPage.listArtists.byLatest.dateless.item": "{ARTIST}", + "listingPage.listArtists.byLatest.chunk.item.tracks": "{ARTIST} (tracks)", + "listingPage.listArtists.byLatest.chunk.item.tracksAndArt": "{ARTIST} (tracks, art)", + "listingPage.listArtists.byLatest.chunk.item.art": "{ARTIST} (art)", "listingPage.listGroups.byName.title": "Groups - by Name", "listingPage.listGroups.byName.title.short": "...by Name", "listingPage.listGroups.byName.item": "{GROUP} ({GALLERY})", |