« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/listing-spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/listing-spec.js')
-rw-r--r--src/listing-spec.js1623
1 files changed, 922 insertions, 701 deletions
diff --git a/src/listing-spec.js b/src/listing-spec.js
index df2b038..92b9d9d 100644
--- a/src/listing-spec.js
+++ b/src/listing-spec.js
@@ -1,771 +1,968 @@
-import fixWS from 'fix-whitespace';
+import fixWS from "fix-whitespace";
 
 import {
-    chunkByProperties,
-    getArtistNumContributions,
-    getTotalDuration,
-    sortAlphabetically,
-    sortChronologically,
-} from './util/wiki-data.js';
+  chunkByProperties,
+  getArtistNumContributions,
+  getTotalDuration,
+  sortAlphabetically,
+  sortChronologically,
+} from "./util/wiki-data.js";
 
 const listingSpec = [
-    {
-        directory: 'albums/by-name',
-        stringsKey: 'listAlbums.byName',
-
-        data({wikiData}) {
-            return sortAlphabetically(wikiData.albumData.slice());
-        },
-
-        row(album, {link, language}) {
-            return language.$('listingPage.listAlbums.byName.item', {
-                album: link.album(album),
-                tracks: language.countTracks(album.tracks.length, {unit: true})
-            });
-        }
-    },
-
-    {
-        directory: 'albums/by-tracks',
-        stringsKey: 'listAlbums.byTracks',
-
-        data({wikiData}) {
-            return wikiData.albumData.slice()
-                .sort((a, b) => b.tracks.length - a.tracks.length);
-        },
-
-        row(album, {link, language}) {
-            return language.$('listingPage.listAlbums.byTracks.item', {
-                album: link.album(album),
-                tracks: language.countTracks(album.tracks.length, {unit: true})
-            });
-        }
-    },
-
-    {
-        directory: 'albums/by-duration',
-        stringsKey: 'listAlbums.byDuration',
-
-        data({wikiData}) {
-            return wikiData.albumData
-                .map(album => ({album, duration: getTotalDuration(album.tracks)}))
-                .sort((a, b) => b.duration - a.duration);
-        },
-
-        row({album, duration}, {link, language}) {
-            return language.$('listingPage.listAlbums.byDuration.item', {
-                album: link.album(album),
-                duration: language.formatDuration(duration)
-            });
-        }
-    },
-
-    {
-        directory: 'albums/by-date',
-        stringsKey: 'listAlbums.byDate',
-
-        data({wikiData}) {
-            return sortChronologically(wikiData.albumData.filter(album => album.date));
-        },
-
-        row(album, {link, language}) {
-            return language.$('listingPage.listAlbums.byDate.item', {
-                album: link.album(album),
-                date: language.formatDate(album.date)
-            });
-        }
-    },
-
-    {
-        directory: 'albums/by-date-added',
-        stringsKey: 'listAlbums.byDateAdded',
-
-        data({wikiData}) {
-            return chunkByProperties(wikiData.albumData.filter(a => a.dateAddedToWiki).sort((a, b) => {
-                if (a.dateAddedToWiki < b.dateAddedToWiki) return -1;
-                if (a.dateAddedToWiki > b.dateAddedToWiki) return 1;
-            }), ['dateAddedToWiki']);
-        },
-
-        html(chunks, {link, language}) {
-            return fixWS`
+  {
+    directory: "albums/by-name",
+    stringsKey: "listAlbums.byName",
+
+    data({ wikiData }) {
+      return sortAlphabetically(wikiData.albumData.slice());
+    },
+
+    row(album, { link, language }) {
+      return language.$("listingPage.listAlbums.byName.item", {
+        album: link.album(album),
+        tracks: language.countTracks(album.tracks.length, { unit: true }),
+      });
+    },
+  },
+
+  {
+    directory: "albums/by-tracks",
+    stringsKey: "listAlbums.byTracks",
+
+    data({ wikiData }) {
+      return wikiData.albumData
+        .slice()
+        .sort((a, b) => b.tracks.length - a.tracks.length);
+    },
+
+    row(album, { link, language }) {
+      return language.$("listingPage.listAlbums.byTracks.item", {
+        album: link.album(album),
+        tracks: language.countTracks(album.tracks.length, { unit: true }),
+      });
+    },
+  },
+
+  {
+    directory: "albums/by-duration",
+    stringsKey: "listAlbums.byDuration",
+
+    data({ wikiData }) {
+      return wikiData.albumData
+        .map((album) => ({ album, duration: getTotalDuration(album.tracks) }))
+        .sort((a, b) => b.duration - a.duration);
+    },
+
+    row({ album, duration }, { link, language }) {
+      return language.$("listingPage.listAlbums.byDuration.item", {
+        album: link.album(album),
+        duration: language.formatDuration(duration),
+      });
+    },
+  },
+
+  {
+    directory: "albums/by-date",
+    stringsKey: "listAlbums.byDate",
+
+    data({ wikiData }) {
+      return sortChronologically(
+        wikiData.albumData.filter((album) => album.date)
+      );
+    },
+
+    row(album, { link, language }) {
+      return language.$("listingPage.listAlbums.byDate.item", {
+        album: link.album(album),
+        date: language.formatDate(album.date),
+      });
+    },
+  },
+
+  {
+    directory: "albums/by-date-added",
+    stringsKey: "listAlbums.byDateAdded",
+
+    data({ wikiData }) {
+      return chunkByProperties(
+        wikiData.albumData
+          .filter((a) => a.dateAddedToWiki)
+          .sort((a, b) => {
+            if (a.dateAddedToWiki < b.dateAddedToWiki) return -1;
+            if (a.dateAddedToWiki > b.dateAddedToWiki) return 1;
+          }),
+        ["dateAddedToWiki"]
+      );
+    },
+
+    html(chunks, { link, language }) {
+      return fixWS`
                 <dl>
-                    ${chunks.map(({dateAddedToWiki, chunk: albums}) => fixWS`
-                        <dt>${language.$('listingPage.listAlbums.byDateAdded.date', {
-                            date: language.formatDate(dateAddedToWiki)
-                        })}</dt>
+                    ${chunks
+                      .map(
+                        ({ dateAddedToWiki, chunk: albums }) => fixWS`
+                        <dt>${language.$(
+                          "listingPage.listAlbums.byDateAdded.date",
+                          {
+                            date: language.formatDate(dateAddedToWiki),
+                          }
+                        )}</dt>
                         <dd><ul>
-                            ${(albums
-                                .map(album => language.$('listingPage.listAlbums.byDateAdded.album', {
-                                    album: link.album(album)
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                            ${albums
+                              .map((album) =>
+                                language.$(
+                                  "listingPage.listAlbums.byDateAdded.album",
+                                  {
+                                    album: link.album(album),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </ul></dd>
-                    `).join('\n')}
+                    `
+                      )
+                      .join("\n")}
                 </dl>
             `;
-        }
-    },
-
-    {
-        directory: 'artists/by-name',
-        stringsKey: 'listArtists.byName',
-
-        data({wikiData}) {
-            return sortAlphabetically(wikiData.artistData.slice())
-                .map(artist => ({artist, contributions: getArtistNumContributions(artist)}));
-        },
-
-        row({artist, contributions}, {link, language}) {
-            return language.$('listingPage.listArtists.byName.item', {
-                artist: link.artist(artist),
-                contributions: language.countContributions(contributions, {unit: true})
-            });
-        }
-    },
-
-    {
-        directory: 'artists/by-contribs',
-        stringsKey: 'listArtists.byContribs',
-
-        data({wikiData}) {
-            return {
-                toTracks: (wikiData.artistData
-                    .map(artist => ({
-                        artist,
-                        contributions: (
-                            (artist.tracksAsContributor?.length ?? 0) +
-                            (artist.tracksAsArtist?.length ?? 0)
-                        )
-                    }))
-                    .sort((a, b) => b.contributions - a.contributions)
-                    .filter(({ contributions }) => contributions)),
-
-                toArtAndFlashes: (wikiData.artistData
-                    .map(artist => ({
-                        artist,
-                        contributions: (
-                            (artist.tracksAsCoverArtist?.length ?? 0) +
-                            (artist.albumsAsCoverArtist?.length ?? 0) +
-                            (artist.albumsAsWallpaperArtist?.length ?? 0) +
-                            (artist.albumsAsBannerArtist?.length ?? 0) +
-                            (wikiData.wikiInfo.enableFlashesAndGames
-                                ? (artist.flashesAsContributor?.length ?? 0)
-                                : 0)
-                        )
-                    }))
-                    .sort((a, b) => b.contributions - a.contributions)
-                    .filter(({ contributions }) => contributions)),
-
-                // This is a kinda naughty hack, 8ut like, it's the only place
-                // we'd 8e passing wikiData to html() otherwise, so like....
-                // (Ok we do do this again once later.)
-                showAsFlashes: wikiData.wikiInfo.enableFlashesAndGames
-            };
-        },
-
-        html({toTracks, toArtAndFlashes, showAsFlashes}, {link, language}) {
-            return fixWS`
+    },
+  },
+
+  {
+    directory: "artists/by-name",
+    stringsKey: "listArtists.byName",
+
+    data({ wikiData }) {
+      return sortAlphabetically(wikiData.artistData.slice()).map((artist) => ({
+        artist,
+        contributions: getArtistNumContributions(artist),
+      }));
+    },
+
+    row({ artist, contributions }, { link, language }) {
+      return language.$("listingPage.listArtists.byName.item", {
+        artist: link.artist(artist),
+        contributions: language.countContributions(contributions, {
+          unit: true,
+        }),
+      });
+    },
+  },
+
+  {
+    directory: "artists/by-contribs",
+    stringsKey: "listArtists.byContribs",
+
+    data({ wikiData }) {
+      return {
+        toTracks: wikiData.artistData
+          .map((artist) => ({
+            artist,
+            contributions:
+              (artist.tracksAsContributor?.length ?? 0) +
+              (artist.tracksAsArtist?.length ?? 0),
+          }))
+          .sort((a, b) => b.contributions - a.contributions)
+          .filter(({ contributions }) => contributions),
+
+        toArtAndFlashes: wikiData.artistData
+          .map((artist) => ({
+            artist,
+            contributions:
+              (artist.tracksAsCoverArtist?.length ?? 0) +
+              (artist.albumsAsCoverArtist?.length ?? 0) +
+              (artist.albumsAsWallpaperArtist?.length ?? 0) +
+              (artist.albumsAsBannerArtist?.length ?? 0) +
+              (wikiData.wikiInfo.enableFlashesAndGames
+                ? artist.flashesAsContributor?.length ?? 0
+                : 0),
+          }))
+          .sort((a, b) => b.contributions - a.contributions)
+          .filter(({ contributions }) => contributions),
+
+        // This is a kinda naughty hack, 8ut like, it's the only place
+        // we'd 8e passing wikiData to html() otherwise, so like....
+        // (Ok we do do this again once later.)
+        showAsFlashes: wikiData.wikiInfo.enableFlashesAndGames,
+      };
+    },
+
+    html({ toTracks, toArtAndFlashes, showAsFlashes }, { link, language }) {
+      return fixWS`
                 <div class="content-columns">
                     <div class="column">
-                        <h2>${language.$('listingPage.misc.trackContributors')}</h2>
+                        <h2>${language.$(
+                          "listingPage.misc.trackContributors"
+                        )}</h2>
                         <ul>
-                            ${(toTracks
-                                .map(({ artist, contributions }) => language.$('listingPage.listArtists.byContribs.item', {
+                            ${toTracks
+                              .map(({ artist, contributions }) =>
+                                language.$(
+                                  "listingPage.listArtists.byContribs.item",
+                                  {
                                     artist: link.artist(artist),
-                                    contributions: language.countContributions(contributions, {unit: true})
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                    contributions: language.countContributions(
+                                      contributions,
+                                      { unit: true }
+                                    ),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                          </ul>
                     </div>
                     <div class="column">
-                        <h2>${language.$('listingPage.misc' +
+                        <h2>${language.$(
+                          "listingPage.misc" +
                             (showAsFlashes
-                                ? '.artAndFlashContributors'
-                                : '.artContributors'))}</h2>
+                              ? ".artAndFlashContributors"
+                              : ".artContributors")
+                        )}</h2>
                         <ul>
-                            ${(toArtAndFlashes
-                                .map(({ artist, contributions }) => language.$('listingPage.listArtists.byContribs.item', {
+                            ${toArtAndFlashes
+                              .map(({ artist, contributions }) =>
+                                language.$(
+                                  "listingPage.listArtists.byContribs.item",
+                                  {
                                     artist: link.artist(artist),
-                                    contributions: language.countContributions(contributions, {unit: true})
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                    contributions: language.countContributions(
+                                      contributions,
+                                      { unit: true }
+                                    ),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </ul>
                     </div>
                 </div>
             `;
-        }
-    },
-
-    {
-        directory: 'artists/by-commentary',
-        stringsKey: 'listArtists.byCommentary',
-
-        data({wikiData}) {
-            return wikiData.artistData
-                .map(artist => ({artist, entries: (
-                    (artist.tracksAsCommentator?.length ?? 0) +
-                    (artist.albumsAsCommentator?.length ?? 0)
-                )}))
-                .filter(({ entries }) => entries)
-                .sort((a, b) => b.entries - a.entries);
-        },
-
-        row({artist, entries}, {link, language}) {
-            return language.$('listingPage.listArtists.byCommentary.item', {
-                artist: link.artist(artist),
-                entries: language.countCommentaryEntries(entries, {unit: true})
-            });
-        }
-    },
-
-    {
-        directory: 'artists/by-duration',
-        stringsKey: 'listArtists.byDuration',
-
-        data({wikiData}) {
-            return wikiData.artistData
-                .map(artist => ({
-                    artist,
-                    duration: getTotalDuration([
-                        ...artist.tracksAsArtist ?? [],
-                        ...artist.tracksAsContributor ?? []
-                    ])
-                }))
-                .filter(({ duration }) => duration > 0)
-                .sort((a, b) => b.duration - a.duration);
-        },
-
-        row({artist, duration}, {link, language}) {
-            return language.$('listingPage.listArtists.byDuration.item', {
-                artist: link.artist(artist),
-                duration: language.formatDuration(duration)
-            });
-        }
-    },
-
-    {
-        directory: 'artists/by-latest',
-        stringsKey: 'listArtists.byLatest',
-
-        data({wikiData}) {
-            const reversedTracks = sortChronologically(wikiData.trackData.filter(t => t.date)).reverse();
-            const reversedArtThings = sortChronologically([...wikiData.trackData, ...wikiData.albumData].filter(t => t.coverArtDate)).reverse();
-
-            return {
-                toTracks: sortChronologically(wikiData.artistData
-                    .map(artist => ({
-                        artist,
-                        directory: artist.directory,
-                        name: artist.name,
-                        date: reversedTracks.find(track => ([
-                            ...track.artistContribs ?? [],
-                            ...track.contributorContribs ?? []
-                        ].some(({ who }) => who === artist)))?.date
-                    }))
-                    .filter(({ date }) => date)).reverse(),
-
-                toArtAndFlashes: sortChronologically(wikiData.artistData
-                    .map(artist => {
-                        const thing = reversedArtThings.find(thing => ([
-                            ...thing.coverArtistContribs ?? [],
-                            ...!thing.album && thing.contributorContribs || []
-                        ].some(({ who }) => who === artist)));
-                        return thing && {
-                            artist,
-                            directory: artist.directory,
-                            name: artist.name,
-                            date: (thing.coverArtistContribs?.some(({ who }) => who === artist)
-                                ? thing.coverArtDate
-                                : thing.date)
-                        };
-                    })
-                    .filter(Boolean)
-                    .sort((a, b) => a.name < b.name ? 1 : a.name > b.name ? -1 : 0)
-                ).reverse(),
-
-                // (Ok we did it again.)
-                // This is a kinda naughty hack, 8ut like, it's the only place
-                // we'd 8e passing wikiData to html() otherwise, so like....
-                showAsFlashes: wikiData.wikiInfo.enableFlashesAndGames
-            };
-        },
-
-        html({toTracks, toArtAndFlashes, showAsFlashes}, {link, language}) {
-            return fixWS`
+    },
+  },
+
+  {
+    directory: "artists/by-commentary",
+    stringsKey: "listArtists.byCommentary",
+
+    data({ wikiData }) {
+      return wikiData.artistData
+        .map((artist) => ({
+          artist,
+          entries:
+            (artist.tracksAsCommentator?.length ?? 0) +
+            (artist.albumsAsCommentator?.length ?? 0),
+        }))
+        .filter(({ entries }) => entries)
+        .sort((a, b) => b.entries - a.entries);
+    },
+
+    row({ artist, entries }, { link, language }) {
+      return language.$("listingPage.listArtists.byCommentary.item", {
+        artist: link.artist(artist),
+        entries: language.countCommentaryEntries(entries, { unit: true }),
+      });
+    },
+  },
+
+  {
+    directory: "artists/by-duration",
+    stringsKey: "listArtists.byDuration",
+
+    data({ wikiData }) {
+      return wikiData.artistData
+        .map((artist) => ({
+          artist,
+          duration: getTotalDuration([
+            ...(artist.tracksAsArtist ?? []),
+            ...(artist.tracksAsContributor ?? []),
+          ]),
+        }))
+        .filter(({ duration }) => duration > 0)
+        .sort((a, b) => b.duration - a.duration);
+    },
+
+    row({ artist, duration }, { link, language }) {
+      return language.$("listingPage.listArtists.byDuration.item", {
+        artist: link.artist(artist),
+        duration: language.formatDuration(duration),
+      });
+    },
+  },
+
+  {
+    directory: "artists/by-latest",
+    stringsKey: "listArtists.byLatest",
+
+    data({ wikiData }) {
+      const reversedTracks = sortChronologically(
+        wikiData.trackData.filter((t) => t.date)
+      ).reverse();
+      const reversedArtThings = sortChronologically(
+        [...wikiData.trackData, ...wikiData.albumData].filter(
+          (t) => t.coverArtDate
+        )
+      ).reverse();
+
+      return {
+        toTracks: sortChronologically(
+          wikiData.artistData
+            .map((artist) => ({
+              artist,
+              directory: artist.directory,
+              name: artist.name,
+              date: reversedTracks.find((track) =>
+                [
+                  ...(track.artistContribs ?? []),
+                  ...(track.contributorContribs ?? []),
+                ].some(({ who }) => who === artist)
+              )?.date,
+            }))
+            .filter(({ date }) => date)
+        ).reverse(),
+
+        toArtAndFlashes: sortChronologically(
+          wikiData.artistData
+            .map((artist) => {
+              const thing = reversedArtThings.find((thing) =>
+                [
+                  ...(thing.coverArtistContribs ?? []),
+                  ...((!thing.album && thing.contributorContribs) || []),
+                ].some(({ who }) => who === artist)
+              );
+              return (
+                thing && {
+                  artist,
+                  directory: artist.directory,
+                  name: artist.name,
+                  date: thing.coverArtistContribs?.some(
+                    ({ who }) => who === artist
+                  )
+                    ? thing.coverArtDate
+                    : thing.date,
+                }
+              );
+            })
+            .filter(Boolean)
+            .sort((a, b) => (a.name < b.name ? 1 : a.name > b.name ? -1 : 0))
+        ).reverse(),
+
+        // (Ok we did it again.)
+        // This is a kinda naughty hack, 8ut like, it's the only place
+        // we'd 8e passing wikiData to html() otherwise, so like....
+        showAsFlashes: wikiData.wikiInfo.enableFlashesAndGames,
+      };
+    },
+
+    html({ toTracks, toArtAndFlashes, showAsFlashes }, { link, language }) {
+      return fixWS`
                 <div class="content-columns">
                     <div class="column">
-                        <h2>${language.$('listingPage.misc.trackContributors')}</h2>
+                        <h2>${language.$(
+                          "listingPage.misc.trackContributors"
+                        )}</h2>
                         <ul>
-                            ${(toTracks
-                                .map(({ artist, date }) => language.$('listingPage.listArtists.byLatest.item', {
+                            ${toTracks
+                              .map(({ artist, date }) =>
+                                language.$(
+                                  "listingPage.listArtists.byLatest.item",
+                                  {
                                     artist: link.artist(artist),
-                                    date: language.formatDate(date)
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                    date: language.formatDate(date),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </ul>
                     </div>
                     <div class="column">
-                        <h2>${language.$('listingPage.misc' +
+                        <h2>${language.$(
+                          "listingPage.misc" +
                             (showAsFlashes
-                                ? '.artAndFlashContributors'
-                                : '.artContributors'))}</h2>
+                              ? ".artAndFlashContributors"
+                              : ".artContributors")
+                        )}</h2>
                         <ul>
-                            ${(toArtAndFlashes
-                                .map(({ artist, date }) => language.$('listingPage.listArtists.byLatest.item', {
+                            ${toArtAndFlashes
+                              .map(({ artist, date }) =>
+                                language.$(
+                                  "listingPage.listArtists.byLatest.item",
+                                  {
                                     artist: link.artist(artist),
-                                    date: language.formatDate(date)
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                    date: language.formatDate(date),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </ul>
                     </div>
                 </div>
             `;
-        }
     },
-
-    {
-        directory: 'groups/by-name',
-        stringsKey: 'listGroups.byName',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableGroupUI,
-        data: ({wikiData}) => sortAlphabetically(wikiData.groupData.slice()),
-
-        row(group, {link, language}) {
-            return language.$('listingPage.listGroups.byCategory.group', {
-                group: link.groupInfo(group),
-                gallery: link.groupGallery(group, {
-                    text: language.$('listingPage.listGroups.byCategory.group.gallery')
-                })
-            });
-        }
+  },
+
+  {
+    directory: "groups/by-name",
+    stringsKey: "listGroups.byName",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableGroupUI,
+    data: ({ wikiData }) => sortAlphabetically(wikiData.groupData.slice()),
+
+    row(group, { link, language }) {
+      return language.$("listingPage.listGroups.byCategory.group", {
+        group: link.groupInfo(group),
+        gallery: link.groupGallery(group, {
+          text: language.$("listingPage.listGroups.byCategory.group.gallery"),
+        }),
+      });
     },
+  },
 
-    {
-        directory: 'groups/by-category',
-        stringsKey: 'listGroups.byCategory',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableGroupUI,
-        data: ({wikiData}) => wikiData.groupCategoryData,
+  {
+    directory: "groups/by-category",
+    stringsKey: "listGroups.byCategory",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableGroupUI,
+    data: ({ wikiData }) => wikiData.groupCategoryData,
 
-        html(groupCategoryData, {link, language}) {
-            return fixWS`
+    html(groupCategoryData, { link, language }) {
+      return fixWS`
                 <dl>
-                    ${groupCategoryData.map(category => fixWS`
-                        <dt>${language.$('listingPage.listGroups.byCategory.category', {
-                            category: link.groupInfo(category.groups[0], {text: category.name})
-                        })}</dt>
+                    ${groupCategoryData
+                      .map(
+                        (category) => fixWS`
+                        <dt>${language.$(
+                          "listingPage.listGroups.byCategory.category",
+                          {
+                            category: link.groupInfo(category.groups[0], {
+                              text: category.name,
+                            }),
+                          }
+                        )}</dt>
                         <dd><ul>
-                            ${(category.groups
-                                .map(group => language.$('listingPage.listGroups.byCategory.group', {
+                            ${category.groups
+                              .map((group) =>
+                                language.$(
+                                  "listingPage.listGroups.byCategory.group",
+                                  {
                                     group: link.groupInfo(group),
                                     gallery: link.groupGallery(group, {
-                                        text: language.$('listingPage.listGroups.byCategory.group.gallery')
-                                    })
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                      text: language.$(
+                                        "listingPage.listGroups.byCategory.group.gallery"
+                                      ),
+                                    }),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </ul></dd>
-                    `).join('\n')}
+                    `
+                      )
+                      .join("\n")}
                 </dl>
             `;
-        }
-    },
-
-    {
-        directory: 'groups/by-albums',
-        stringsKey: 'listGroups.byAlbums',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableGroupUI,
-
-        data({wikiData}) {
-            return wikiData.groupData
-                .map(group => ({group, albums: group.albums.length}))
-                .sort((a, b) => b.albums - a.albums);
-        },
-
-        row({group, albums}, {link, language}) {
-            return language.$('listingPage.listGroups.byAlbums.item', {
-                group: link.groupInfo(group),
-                albums: language.countAlbums(albums, {unit: true})
-            });
-        }
-    },
-
-    {
-        directory: 'groups/by-tracks',
-        stringsKey: 'listGroups.byTracks',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableGroupUI,
-
-        data({wikiData}) {
-            return wikiData.groupData
-                .map(group => ({group, tracks: group.albums.reduce((acc, album) => acc + album.tracks.length, 0)}))
-                .sort((a, b) => b.tracks - a.tracks);
-        },
-
-        row({group, tracks}, {link, language}) {
-            return language.$('listingPage.listGroups.byTracks.item', {
-                group: link.groupInfo(group),
-                tracks: language.countTracks(tracks, {unit: true})
-            });
-        }
-    },
-
-    {
-        directory: 'groups/by-duration',
-        stringsKey: 'listGroups.byDuration',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableGroupUI,
-
-        data({wikiData}) {
-            return wikiData.groupData
-                .map(group => ({group, duration: getTotalDuration(group.albums.flatMap(album => album.tracks))}))
-                .sort((a, b) => b.duration - a.duration);
-        },
-
-        row({group, duration}, {link, language}) {
-            return language.$('listingPage.listGroups.byDuration.item', {
-                group: link.groupInfo(group),
-                duration: language.formatDuration(duration)
-            });
-        }
-    },
-
-    {
-        directory: 'groups/by-latest-album',
-        stringsKey: 'listGroups.byLatest',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableGroupUI,
-
-        data({wikiData}) {
-            return sortChronologically(wikiData.groupData
-                .map(group => {
-                    const albums = group.albums.filter(a => a.date);
-                    return albums.length && {
-                        group,
-                        directory: group.directory,
-                        name: group.name,
-                        date: albums[albums.length - 1].date
-                    };
-                })
-                .filter(Boolean)
-                // 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 sortChronologically
-                // call surrounding this).
-                .reverse()).reverse()
-        },
-
-        row({group, date}, {link, language}) {
-            return language.$('listingPage.listGroups.byLatest.item', {
-                group: link.groupInfo(group),
-                date: language.formatDate(date)
-            });
-        }
-    },
-
-    {
-        directory: 'tracks/by-name',
-        stringsKey: 'listTracks.byName',
-
-        data({wikiData}) {
-            return sortAlphabetically(wikiData.trackData.slice());
-        },
-
-        row(track, {link, language}) {
-            return language.$('listingPage.listTracks.byName.item', {
-                track: link.track(track)
-            });
-        }
-    },
-
-    {
-        directory: 'tracks/by-album',
-        stringsKey: 'listTracks.byAlbum',
-        data: ({wikiData}) => wikiData.albumData,
-
-        html(albumData, {link, language}) {
-            return fixWS`
+    },
+  },
+
+  {
+    directory: "groups/by-albums",
+    stringsKey: "listGroups.byAlbums",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableGroupUI,
+
+    data({ wikiData }) {
+      return wikiData.groupData
+        .map((group) => ({ group, albums: group.albums.length }))
+        .sort((a, b) => b.albums - a.albums);
+    },
+
+    row({ group, albums }, { link, language }) {
+      return language.$("listingPage.listGroups.byAlbums.item", {
+        group: link.groupInfo(group),
+        albums: language.countAlbums(albums, { unit: true }),
+      });
+    },
+  },
+
+  {
+    directory: "groups/by-tracks",
+    stringsKey: "listGroups.byTracks",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableGroupUI,
+
+    data({ wikiData }) {
+      return wikiData.groupData
+        .map((group) => ({
+          group,
+          tracks: group.albums.reduce(
+            (acc, album) => acc + album.tracks.length,
+            0
+          ),
+        }))
+        .sort((a, b) => b.tracks - a.tracks);
+    },
+
+    row({ group, tracks }, { link, language }) {
+      return language.$("listingPage.listGroups.byTracks.item", {
+        group: link.groupInfo(group),
+        tracks: language.countTracks(tracks, { unit: true }),
+      });
+    },
+  },
+
+  {
+    directory: "groups/by-duration",
+    stringsKey: "listGroups.byDuration",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableGroupUI,
+
+    data({ wikiData }) {
+      return wikiData.groupData
+        .map((group) => ({
+          group,
+          duration: getTotalDuration(
+            group.albums.flatMap((album) => album.tracks)
+          ),
+        }))
+        .sort((a, b) => b.duration - a.duration);
+    },
+
+    row({ group, duration }, { link, language }) {
+      return language.$("listingPage.listGroups.byDuration.item", {
+        group: link.groupInfo(group),
+        duration: language.formatDuration(duration),
+      });
+    },
+  },
+
+  {
+    directory: "groups/by-latest-album",
+    stringsKey: "listGroups.byLatest",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableGroupUI,
+
+    data({ wikiData }) {
+      return sortChronologically(
+        wikiData.groupData
+          .map((group) => {
+            const albums = group.albums.filter((a) => a.date);
+            return (
+              albums.length && {
+                group,
+                directory: group.directory,
+                name: group.name,
+                date: albums[albums.length - 1].date,
+              }
+            );
+          })
+          .filter(Boolean)
+          // 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 sortChronologically
+          // call surrounding this).
+          .reverse()
+      ).reverse();
+    },
+
+    row({ group, date }, { link, language }) {
+      return language.$("listingPage.listGroups.byLatest.item", {
+        group: link.groupInfo(group),
+        date: language.formatDate(date),
+      });
+    },
+  },
+
+  {
+    directory: "tracks/by-name",
+    stringsKey: "listTracks.byName",
+
+    data({ wikiData }) {
+      return sortAlphabetically(wikiData.trackData.slice());
+    },
+
+    row(track, { link, language }) {
+      return language.$("listingPage.listTracks.byName.item", {
+        track: link.track(track),
+      });
+    },
+  },
+
+  {
+    directory: "tracks/by-album",
+    stringsKey: "listTracks.byAlbum",
+    data: ({ wikiData }) => wikiData.albumData,
+
+    html(albumData, { link, language }) {
+      return fixWS`
                 <dl>
-                    ${albumData.map(album => fixWS`
-                        <dt>${language.$('listingPage.listTracks.byAlbum.album', {
-                            album: link.album(album)
-                        })}</dt>
+                    ${albumData
+                      .map(
+                        (album) => fixWS`
+                        <dt>${language.$(
+                          "listingPage.listTracks.byAlbum.album",
+                          {
+                            album: link.album(album),
+                          }
+                        )}</dt>
                         <dd><ol>
-                            ${(album.tracks
-                                .map(track => language.$('listingPage.listTracks.byAlbum.track', {
-                                    track: link.track(track)
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                            ${album.tracks
+                              .map((track) =>
+                                language.$(
+                                  "listingPage.listTracks.byAlbum.track",
+                                  {
+                                    track: link.track(track),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </ol></dd>
-                    `).join('\n')}
+                    `
+                      )
+                      .join("\n")}
                 </dl>
             `;
-        }
     },
+  },
 
-    {
-        directory: 'tracks/by-date',
-        stringsKey: 'listTracks.byDate',
+  {
+    directory: "tracks/by-date",
+    stringsKey: "listTracks.byDate",
 
-        data({wikiData}) {
-            return chunkByProperties(
-                sortChronologically(wikiData.trackData.filter(t => t.date)),
-                ['album', 'date']
-            );
-        },
+    data({ wikiData }) {
+      return chunkByProperties(
+        sortChronologically(wikiData.trackData.filter((t) => t.date)),
+        ["album", "date"]
+      );
+    },
 
-        html(chunks, {link, language}) {
-            return fixWS`
+    html(chunks, { link, language }) {
+      return fixWS`
                 <dl>
-                    ${chunks.map(({album, date, chunk: tracks}) => fixWS`
-                        <dt>${language.$('listingPage.listTracks.byDate.album', {
+                    ${chunks
+                      .map(
+                        ({ album, date, chunk: tracks }) => fixWS`
+                        <dt>${language.$(
+                          "listingPage.listTracks.byDate.album",
+                          {
                             album: link.album(album),
-                            date: language.formatDate(date)
-                        })}</dt>
+                            date: language.formatDate(date),
+                          }
+                        )}</dt>
                         <dd><ul>
-                            ${(tracks
-                                .map(track => track.aka
-                                    ? `<li class="rerelease">${language.$('listingPage.listTracks.byDate.track.rerelease', {
-                                        track: link.track(track)
-                                    })}</li>`
-                                    : `<li>${language.$('listingPage.listTracks.byDate.track', {
-                                        track: link.track(track)
-                                    })}</li>`)
-                                .join('\n'))}
+                            ${tracks
+                              .map((track) =>
+                                track.aka
+                                  ? `<li class="rerelease">${language.$(
+                                      "listingPage.listTracks.byDate.track.rerelease",
+                                      {
+                                        track: link.track(track),
+                                      }
+                                    )}</li>`
+                                  : `<li>${language.$(
+                                      "listingPage.listTracks.byDate.track",
+                                      {
+                                        track: link.track(track),
+                                      }
+                                    )}</li>`
+                              )
+                              .join("\n")}
                         </ul></dd>
-                    `).join('\n')}
+                    `
+                      )
+                      .join("\n")}
                 </dl>
             `;
-        }
     },
+  },
 
-    {
-        directory: 'tracks/by-duration',
-        stringsKey: 'listTracks.byDuration',
+  {
+    directory: "tracks/by-duration",
+    stringsKey: "listTracks.byDuration",
 
-        data({wikiData}) {
-            return wikiData.trackData
-                .map(track => ({track, duration: track.duration}))
-                .filter(({ duration }) => duration > 0)
-                .sort((a, b) => b.duration - a.duration);
-        },
-
-        row({track, duration}, {link, language}) {
-            return language.$('listingPage.listTracks.byDuration.item', {
-                track: link.track(track),
-                duration: language.formatDuration(duration)
-            });
-        }
+    data({ wikiData }) {
+      return wikiData.trackData
+        .map((track) => ({ track, duration: track.duration }))
+        .filter(({ duration }) => duration > 0)
+        .sort((a, b) => b.duration - a.duration);
     },
 
-    {
-        directory: 'tracks/by-duration-in-album',
-        stringsKey: 'listTracks.byDurationInAlbum',
-
-        data({wikiData}) {
-            return wikiData.albumData.map(album => ({
-                album,
-                tracks: album.tracks.slice().sort((a, b) => (b.duration ?? 0) - (a.duration ?? 0))
-            }));
-        },
+    row({ track, duration }, { link, language }) {
+      return language.$("listingPage.listTracks.byDuration.item", {
+        track: link.track(track),
+        duration: language.formatDuration(duration),
+      });
+    },
+  },
+
+  {
+    directory: "tracks/by-duration-in-album",
+    stringsKey: "listTracks.byDurationInAlbum",
+
+    data({ wikiData }) {
+      return wikiData.albumData.map((album) => ({
+        album,
+        tracks: album.tracks
+          .slice()
+          .sort((a, b) => (b.duration ?? 0) - (a.duration ?? 0)),
+      }));
+    },
 
-        html(albums, {link, language}) {
-            return fixWS`
+    html(albums, { link, language }) {
+      return fixWS`
                 <dl>
-                    ${albums.map(({album, tracks}) => fixWS`
-                        <dt>${language.$('listingPage.listTracks.byDurationInAlbum.album', {
-                            album: link.album(album)
-                        })}</dt>
+                    ${albums
+                      .map(
+                        ({ album, tracks }) => fixWS`
+                        <dt>${language.$(
+                          "listingPage.listTracks.byDurationInAlbum.album",
+                          {
+                            album: link.album(album),
+                          }
+                        )}</dt>
                         <dd><ul>
-                            ${(tracks
-                                .map(track => language.$('listingPage.listTracks.byDurationInAlbum.track', {
+                            ${tracks
+                              .map((track) =>
+                                language.$(
+                                  "listingPage.listTracks.byDurationInAlbum.track",
+                                  {
                                     track: link.track(track),
-                                    duration: language.formatDuration(track.duration ?? 0)
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                    duration: language.formatDuration(
+                                      track.duration ?? 0
+                                    ),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </dd></ul>
-                    `).join('\n')}
+                    `
+                      )
+                      .join("\n")}
                 </dl>
             `;
-        }
     },
-
-    {
-        directory: 'tracks/by-times-referenced',
-        stringsKey: 'listTracks.byTimesReferenced',
-
-        data({wikiData}) {
-            return wikiData.trackData
-                .map(track => ({track, timesReferenced: track.referencedByTracks.length}))
-                .filter(({ timesReferenced }) => timesReferenced > 0)
-                .sort((a, b) => b.timesReferenced - a.timesReferenced);
-        },
-
-        row({track, timesReferenced}, {link, language}) {
-            return language.$('listingPage.listTracks.byTimesReferenced.item', {
-                track: link.track(track),
-                timesReferenced: language.countTimesReferenced(timesReferenced, {unit: true})
-            });
-        }
+  },
+
+  {
+    directory: "tracks/by-times-referenced",
+    stringsKey: "listTracks.byTimesReferenced",
+
+    data({ wikiData }) {
+      return wikiData.trackData
+        .map((track) => ({
+          track,
+          timesReferenced: track.referencedByTracks.length,
+        }))
+        .filter(({ timesReferenced }) => timesReferenced > 0)
+        .sort((a, b) => b.timesReferenced - a.timesReferenced);
     },
 
-    {
-        directory: 'tracks/in-flashes/by-album',
-        stringsKey: 'listTracks.inFlashes.byAlbum',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableFlashesAndGames,
-
-        data({wikiData}) {
-            return chunkByProperties(wikiData.trackData
-                .filter(t => t.featuredInFlashes?.length > 0), ['album']);
-        },
+    row({ track, timesReferenced }, { link, language }) {
+      return language.$("listingPage.listTracks.byTimesReferenced.item", {
+        track: link.track(track),
+        timesReferenced: language.countTimesReferenced(timesReferenced, {
+          unit: true,
+        }),
+      });
+    },
+  },
+
+  {
+    directory: "tracks/in-flashes/by-album",
+    stringsKey: "listTracks.inFlashes.byAlbum",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableFlashesAndGames,
+
+    data({ wikiData }) {
+      return chunkByProperties(
+        wikiData.trackData.filter((t) => t.featuredInFlashes?.length > 0),
+        ["album"]
+      );
+    },
 
-        html(chunks, {link, language}) {
-            return fixWS`
+    html(chunks, { link, language }) {
+      return fixWS`
                 <dl>
-                    ${chunks.map(({album, chunk: tracks}) => fixWS`
-                        <dt>${language.$('listingPage.listTracks.inFlashes.byAlbum.album', {
+                    ${chunks
+                      .map(
+                        ({ album, chunk: tracks }) => fixWS`
+                        <dt>${language.$(
+                          "listingPage.listTracks.inFlashes.byAlbum.album",
+                          {
                             album: link.album(album),
-                            date: language.formatDate(album.date)
-                        })}</dt>
+                            date: language.formatDate(album.date),
+                          }
+                        )}</dt>
                         <dd><ul>
-                            ${(tracks
-                                .map(track => language.$('listingPage.listTracks.inFlashes.byAlbum.track', {
+                            ${tracks
+                              .map((track) =>
+                                language.$(
+                                  "listingPage.listTracks.inFlashes.byAlbum.track",
+                                  {
                                     track: link.track(track),
-                                    flashes: language.formatConjunctionList(track.featuredInFlashes.map(link.flash))
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                    flashes: language.formatConjunctionList(
+                                      track.featuredInFlashes.map(link.flash)
+                                    ),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </dd></ul>
-                    `).join('\n')}
+                    `
+                      )
+                      .join("\n")}
                 </dl>
             `;
-        }
     },
+  },
 
-    {
-        directory: 'tracks/in-flashes/by-flash',
-        stringsKey: 'listTracks.inFlashes.byFlash',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableFlashesAndGames,
-        data: ({wikiData}) => wikiData.flashData,
+  {
+    directory: "tracks/in-flashes/by-flash",
+    stringsKey: "listTracks.inFlashes.byFlash",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableFlashesAndGames,
+    data: ({ wikiData }) => wikiData.flashData,
 
-        html(flashData, {link, language}) {
-            return fixWS`
+    html(flashData, { link, language }) {
+      return fixWS`
                 <dl>
-                    ${sortChronologically(flashData.slice()).map(flash => fixWS`
-                        <dt>${language.$('listingPage.listTracks.inFlashes.byFlash.flash', {
+                    ${sortChronologically(flashData.slice())
+                      .map(
+                        (flash) => fixWS`
+                        <dt>${language.$(
+                          "listingPage.listTracks.inFlashes.byFlash.flash",
+                          {
                             flash: link.flash(flash),
-                            date: language.formatDate(flash.date)
-                        })}</dt>
+                            date: language.formatDate(flash.date),
+                          }
+                        )}</dt>
                         <dd><ul>
-                            ${(flash.featuredTracks
-                                .map(track => language.$('listingPage.listTracks.inFlashes.byFlash.track', {
+                            ${flash.featuredTracks
+                              .map((track) =>
+                                language.$(
+                                  "listingPage.listTracks.inFlashes.byFlash.track",
+                                  {
                                     track: link.track(track),
-                                    album: link.album(track.album)
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                    album: link.album(track.album),
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </ul></dd>
-                    `).join('\n')}
+                    `
+                      )
+                      .join("\n")}
                 </dl>
             `;
-        }
+    },
+  },
+
+  {
+    directory: "tracks/with-lyrics",
+    stringsKey: "listTracks.withLyrics",
+
+    data({ wikiData }) {
+      return wikiData.albumData
+        .map((album) => ({
+          album,
+          tracks: album.tracks.filter((t) => t.lyrics),
+        }))
+        .filter(({ tracks }) => tracks.length > 0);
     },
 
-    {
-        directory: 'tracks/with-lyrics',
-        stringsKey: 'listTracks.withLyrics',
-
-        data({wikiData}) {
-            return wikiData.albumData.map(album => ({
-                album,
-                tracks: album.tracks.filter(t => t.lyrics)
-            })).filter(({ tracks }) => tracks.length > 0);
-        },
-
-        html(chunks, {link, language}) {
-            return fixWS`
+    html(chunks, { link, language }) {
+      return fixWS`
                 <dl>
-                    ${chunks.map(({album, tracks}) => fixWS`
-                        <dt>${language.$('listingPage.listTracks.withLyrics.album', {
+                    ${chunks
+                      .map(
+                        ({ album, tracks }) => fixWS`
+                        <dt>${language.$(
+                          "listingPage.listTracks.withLyrics.album",
+                          {
                             album: link.album(album),
-                            date: language.formatDate(album.date)
-                        })}</dt>
+                            date: language.formatDate(album.date),
+                          }
+                        )}</dt>
                         <dd><ul>
-                            ${(tracks
-                                .map(track => language.$('listingPage.listTracks.withLyrics.track', {
+                            ${tracks
+                              .map((track) =>
+                                language.$(
+                                  "listingPage.listTracks.withLyrics.track",
+                                  {
                                     track: link.track(track),
-                                }))
-                                .map(row => `<li>${row}</li>`)
-                                .join('\n'))}
+                                  }
+                                )
+                              )
+                              .map((row) => `<li>${row}</li>`)
+                              .join("\n")}
                         </dd></ul>
-                    `).join('\n')}
+                    `
+                      )
+                      .join("\n")}
                 </dl>
             `;
-        }
-    },
-
-    {
-        directory: 'tags/by-name',
-        stringsKey: 'listTags.byName',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableArtTagUI,
-
-        data({wikiData}) {
-            return sortAlphabetically(wikiData.artTagData.filter(tag => !tag.isContentWarning))
-                .map(tag => ({tag, timesUsed: tag.taggedInThings?.length}));
-        },
-
-        row({tag, timesUsed}, {link, language}) {
-            return language.$('listingPage.listTags.byName.item', {
-                tag: link.tag(tag),
-                timesUsed: language.countTimesUsed(timesUsed, {unit: true})
-            });
-        }
-    },
-
-    {
-        directory: 'tags/by-uses',
-        stringsKey: 'listTags.byUses',
-        condition: ({wikiData}) => wikiData.wikiInfo.enableArtTagUI,
-
-        data({wikiData}) {
-            return wikiData.artTagData
-                .filter(tag => !tag.isContentWarning)
-                .map(tag => ({tag, timesUsed: tag.taggedInThings?.length}))
-                .sort((a, b) => b.timesUsed - a.timesUsed);
-        },
-
-        row({tag, timesUsed}, {link, language}) {
-            return language.$('listingPage.listTags.byUses.item', {
-                tag: link.tag(tag),
-                timesUsed: language.countTimesUsed(timesUsed, {unit: true})
-            });
-        }
-    },
-
-    {
-        directory: 'random',
-        stringsKey: 'other.randomPages',
-
-        data: ({wikiData}) => ({
-            officialAlbumData: wikiData.officialAlbumData,
-            fandomAlbumData: wikiData.fandomAlbumData
-        }),
+    },
+  },
+
+  {
+    directory: "tags/by-name",
+    stringsKey: "listTags.byName",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableArtTagUI,
+
+    data({ wikiData }) {
+      return sortAlphabetically(
+        wikiData.artTagData.filter((tag) => !tag.isContentWarning)
+      ).map((tag) => ({ tag, timesUsed: tag.taggedInThings?.length }));
+    },
+
+    row({ tag, timesUsed }, { link, language }) {
+      return language.$("listingPage.listTags.byName.item", {
+        tag: link.tag(tag),
+        timesUsed: language.countTimesUsed(timesUsed, { unit: true }),
+      });
+    },
+  },
+
+  {
+    directory: "tags/by-uses",
+    stringsKey: "listTags.byUses",
+    condition: ({ wikiData }) => wikiData.wikiInfo.enableArtTagUI,
+
+    data({ wikiData }) {
+      return wikiData.artTagData
+        .filter((tag) => !tag.isContentWarning)
+        .map((tag) => ({ tag, timesUsed: tag.taggedInThings?.length }))
+        .sort((a, b) => b.timesUsed - a.timesUsed);
+    },
+
+    row({ tag, timesUsed }, { link, language }) {
+      return language.$("listingPage.listTags.byUses.item", {
+        tag: link.tag(tag),
+        timesUsed: language.countTimesUsed(timesUsed, { unit: true }),
+      });
+    },
+  },
+
+  {
+    directory: "random",
+    stringsKey: "other.randomPages",
+
+    data: ({ wikiData }) => ({
+      officialAlbumData: wikiData.officialAlbumData,
+      fandomAlbumData: wikiData.fandomAlbumData,
+    }),
 
-        html: ({officialAlbumData, fandomAlbumData}, {
-            getLinkThemeString,
-            language
-        }) => fixWS`
+    html: (
+      { officialAlbumData, fandomAlbumData },
+      { getLinkThemeString, language }
+    ) => fixWS`
             <p>Choose a link to go to a random page in that category or album! If your browser doesn't support relatively modern JavaScript or you've disabled it, these links won't work - sorry.</p>
             <p class="js-hide-once-data">(Data files are downloading in the background! Please wait for data to load.)</p>
             <p class="js-show-once-data">(Data files have finished being downloaded. The links should work!)</p>
@@ -780,49 +977,73 @@ const listingSpec = [
                     <li><a href="#" data-random="track">Random Track (whole site)</a></li>
                 </ul></dd>
                 ${[
-                    {name: 'Official', albumData: officialAlbumData, code: 'official'},
-                    {name: 'Fandom', albumData: fandomAlbumData, code: 'fandom'}
-                ].map(category => fixWS`
-                    <dt>${category.name}: (<a href="#" data-random="album-in-${category.code}">Random Album</a>, <a href="#" data-random="track-in-${category.code}">Random Track</a>)</dt>
-                    <dd><ul>${category.albumData.map(album => fixWS`
-                        <li><a style="${getLinkThemeString(album.color)}; --album-directory: ${album.directory}" href="#" data-random="track-in-album">${album.name}</a></li>
-                    `).join('\n')}</ul></dd>
-                `).join('\n')}
+                  {
+                    name: "Official",
+                    albumData: officialAlbumData,
+                    code: "official",
+                  },
+                  {
+                    name: "Fandom",
+                    albumData: fandomAlbumData,
+                    code: "fandom",
+                  },
+                ]
+                  .map(
+                    (category) => fixWS`
+                    <dt>${category.name}: (<a href="#" data-random="album-in-${
+                      category.code
+                    }">Random Album</a>, <a href="#" data-random="track-in-${
+                      category.code
+                    }">Random Track</a>)</dt>
+                    <dd><ul>${category.albumData
+                      .map(
+                        (album) => fixWS`
+                        <li><a style="${getLinkThemeString(
+                          album.color
+                        )}; --album-directory: ${
+                          album.directory
+                        }" href="#" data-random="track-in-album">${
+                          album.name
+                        }</a></li>
+                    `
+                      )
+                      .join("\n")}</ul></dd>
+                `
+                  )
+                  .join("\n")}
             </dl>
-        `
-    }
+        `,
+  },
 ];
 
-const filterListings = directoryPrefix => listingSpec
-    .filter(l => l.directory.startsWith(directoryPrefix));
+const filterListings = (directoryPrefix) =>
+  listingSpec.filter((l) => l.directory.startsWith(directoryPrefix));
 
 const listingTargetSpec = [
-    {
-        title: ({language}) => language.$('listingPage.target.album'),
-        listings: filterListings('album')
-    },
-    {
-        title: ({language}) => language.$('listingPage.target.artist'),
-        listings: filterListings('artist')
-    },
-    {
-        title: ({language}) => language.$('listingPage.target.group'),
-        listings: filterListings('group')
-    },
-    {
-        title: ({language}) => language.$('listingPage.target.track'),
-        listings: filterListings('track')
-    },
-    {
-        title: ({language}) => language.$('listingPage.target.tag'),
-        listings: filterListings('tag')
-    },
-    {
-        title: ({language}) => language.$('listingPage.target.other'),
-        listings: [
-            listingSpec.find(l => l.directory === 'random')
-        ]
-    }
+  {
+    title: ({ language }) => language.$("listingPage.target.album"),
+    listings: filterListings("album"),
+  },
+  {
+    title: ({ language }) => language.$("listingPage.target.artist"),
+    listings: filterListings("artist"),
+  },
+  {
+    title: ({ language }) => language.$("listingPage.target.group"),
+    listings: filterListings("group"),
+  },
+  {
+    title: ({ language }) => language.$("listingPage.target.track"),
+    listings: filterListings("track"),
+  },
+  {
+    title: ({ language }) => language.$("listingPage.target.tag"),
+    listings: filterListings("tag"),
+  },
+  {
+    title: ({ language }) => language.$("listingPage.target.other"),
+    listings: [listingSpec.find((l) => l.directory === "random")],
+  },
 ];
 
-export {listingSpec, listingTargetSpec};
+export { listingSpec, listingTargetSpec };