« get me outta code hell

htmlify: artist, commentary, flash, static, tag - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/page
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2022-07-07 02:07:52 -0300
committer(quasar) nebula <qznebula@protonmail.com>2022-07-07 02:11:22 -0300
commit1ad3694bf132604eb7044c5a47771637d2b5d56f (patch)
tree3a1c08ef8195677c03ec36dbd96813e1d72906f3 /src/page
parent202d6e2a7bccd58823dbdbe6cf4150a4bdad9f74 (diff)
htmlify: artist, commentary, flash, static, tag
Diffstat (limited to 'src/page')
-rw-r--r--src/page/album-commentary.js14
-rw-r--r--src/page/album.js49
-rw-r--r--src/page/artist.js750
-rw-r--r--src/page/flash.js418
-rw-r--r--src/page/static.js17
-rw-r--r--src/page/tag.js56
-rw-r--r--src/page/track.js24
7 files changed, 640 insertions, 688 deletions
diff --git a/src/page/album-commentary.js b/src/page/album-commentary.js
index 43b8c8d6..beddf5aa 100644
--- a/src/page/album-commentary.js
+++ b/src/page/album-commentary.js
@@ -2,13 +2,8 @@
 
 // Album commentary page and index specifications.
 
-// Imports
-
-import * as html from '../util/html.js';
 import {filterAlbumsByCommentary} from '../util/wiki-data.js';
 
-// Page exports
-
 export function condition({wikiData}) {
   return filterAlbumsByCommentary(wikiData.albumData).length;
 }
@@ -30,8 +25,9 @@ export function write(album) {
       getAlbumStylesheet,
       getLinkThemeString,
       getThemeString,
-      link,
+      html,
       language,
+      link,
       transformMultiline,
     }) => ({
       title: language.$('albumCommentaryPage.title', {album: album.name}),
@@ -105,7 +101,11 @@ export function writeTargetless({wikiData}) {
   const page = {
     type: 'page',
     path: ['commentaryIndex'],
-    page: ({link, language}) => ({
+    page: ({
+      html,
+      language,
+      link,
+    }) => ({
       title: language.$('commentaryIndex.title'),
 
       main: {
diff --git a/src/page/album.js b/src/page/album.js
index 1cadde05..11c6da29 100644
--- a/src/page/album.js
+++ b/src/page/album.js
@@ -2,10 +2,6 @@
 
 // Album page specification.
 
-// Imports
-
-import * as html from '../util/html.js';
-
 import {bindOpts, compareArrays} from '../util/sugar.js';
 
 import {
@@ -14,17 +10,18 @@ import {
   getTotalDuration,
 } from '../util/wiki-data.js';
 
-// Page exports
-
 export function targets({wikiData}) {
   return wikiData.albumData;
 }
 
 export function write(album, {wikiData}) {
-  const unbound_trackToListItem = (
-    track,
-    {getArtistString, getLinkThemeString, link, language}
-  ) => {
+  const unbound_trackToListItem = (track, {
+    getArtistString,
+    getLinkThemeString,
+    html,
+    language,
+    link,
+  }) => {
     const itemOpts = {
       duration: language.formatDuration(track.duration ?? 0),
       track: link.track(track),
@@ -111,6 +108,7 @@ export function write(album, {wikiData}) {
       getLinkThemeString,
       getSizeOfAdditionalFile,
       getThemeString,
+      html,
       link,
       language,
       transformMultiline,
@@ -119,8 +117,9 @@ export function write(album, {wikiData}) {
       const trackToListItem = bindOpts(unbound_trackToListItem, {
         getArtistString,
         getLinkThemeString,
-        link,
+        html,
         language,
+        link,
       });
 
       const cover = getAlbumCover(album);
@@ -301,6 +300,7 @@ export function write(album, {wikiData}) {
         sidebarLeft: generateAlbumSidebar(album, null, {
           fancifyURL,
           getLinkThemeString,
+          html,
           link,
           language,
           transformMultiline,
@@ -320,13 +320,15 @@ export function write(album, {wikiData}) {
           bottomRowContent: generateAlbumNavLinks(album, null, {language}),
           content: generateAlbumChronologyLinks(album, null, {
             generateChronologyLinks,
+            html,
           }),
         },
 
         secondaryNav: generateAlbumSecondaryNav(album, null, {
+          getLinkThemeString,
+          html,
           language,
           link,
-          getLinkThemeString,
         }),
       };
     },
@@ -340,8 +342,9 @@ export function write(album, {wikiData}) {
 export function generateAlbumSidebar(album, currentTrack, {
   fancifyURL,
   getLinkThemeString,
-  link,
+  html,
   language,
+  link,
   transformMultiline,
 }) {
   const isAlbumPage = !currentTrack;
@@ -471,11 +474,12 @@ export function generateAlbumSidebar(album, currentTrack, {
   }
 }
 
-export function generateAlbumSecondaryNav(
-  album,
-  currentTrack,
-  {link, language, getLinkThemeString}
-) {
+export function generateAlbumSecondaryNav(album, currentTrack, {
+  getLinkThemeString,
+  html,
+  language,
+  link,
+}) {
   const isAlbumPage = !currentTrack;
 
   const {groups} = album;
@@ -551,11 +555,10 @@ export function generateAlbumNavLinks(
     : `<span class="js-hide-until-data">(${randomLink})</span>`;
 }
 
-export function generateAlbumChronologyLinks(
-  album,
-  currentTrack,
-  {generateChronologyLinks}
-) {
+export function generateAlbumChronologyLinks(album, currentTrack, {
+  generateChronologyLinks,
+  html,
+}) {
   const isTrackPage = !!currentTrack;
 
   return html.tag(
diff --git a/src/page/artist.js b/src/page/artist.js
index 481b4e36..28d73eb7 100644
--- a/src/page/artist.js
+++ b/src/page/artist.js
@@ -4,12 +4,6 @@
 //
 // NB: See artist-alias.js for artist alias redirect pages.
 
-// Imports
-
-import fixWS from 'fix-whitespace';
-
-import * as html from '../util/html.js';
-
 import {bindOpts, unique} from '../util/sugar.js';
 
 import {
@@ -19,8 +13,6 @@ import {
   sortChronologically,
 } from '../util/wiki-data.js';
 
-// Page exports
-
 export function targets({wikiData}) {
   return wikiData.artistData;
 }
@@ -37,16 +29,14 @@ export function write(artist, {wikiData}) {
       ...(artist.albumsAsBannerArtist ?? []),
       ...(artist.tracksAsCoverArtist ?? []),
     ]),
-    {getDate: (o) => o.coverArtDate}
-  );
+    {getDate: (o) => o.coverArtDate});
 
   const artThingsGallery = sortAlbumsTracksChronologically(
     [
       ...(artist.albumsAsCoverArtist ?? []),
       ...(artist.tracksAsCoverArtist ?? []),
     ],
-    {getDate: (o) => o.coverArtDate}
-  );
+    {getDate: (o) => o.coverArtDate});
 
   const commentaryThings = sortAlbumsTracksChronologically([
     ...(artist.albumsAsCommentator ?? []),
@@ -72,25 +62,21 @@ export function write(artist, {wikiData}) {
           track: thing.album ? thing : null,
           date: thing.date,
           ...props,
-        }))
-    ),
-    ['date', 'album']
-  );
+        }))),
+    ['date', 'album']);
 
   const commentaryListChunks = chunkByProperties(
     commentaryThings.map((thing) => ({
       album: thing.album || thing,
       track: thing.album ? thing : null,
     })),
-    ['album']
-  );
+    ['album']);
 
   const allTracks = sortAlbumsTracksChronologically(
     unique([
       ...(artist.tracksAsArtist ?? []),
       ...(artist.tracksAsContributor ?? []),
-    ])
-  );
+    ]));
 
   const chunkTracks = (tracks) =>
     chunkByProperties(
@@ -110,8 +96,8 @@ export function write(artist, {wikiData}) {
           ].filter(Boolean),
         },
       })),
-      ['date', 'album']
-    ).map(({date, album, chunk}) => ({
+      ['date', 'album'])
+    .map(({date, album, chunk}) => ({
       date,
       album,
       chunk,
@@ -123,12 +109,11 @@ export function write(artist, {wikiData}) {
 
   const countGroups = (things) => {
     const usedGroups = things.flatMap(
-      (thing) => thing.groups || thing.album?.groups || []
-    );
+      (thing) => thing.groups || thing.album?.groups || []);
     return groupData
       .map((group) => ({
         group,
-        contributions: usedGroups.filter((g) => g === group).length,
+        contributions: usedGroups.filter(g => g === group).length,
       }))
       .filter(({contributions}) => contributions > 0)
       .sort((a, b) => b.contributions - a.contributions);
@@ -195,76 +180,68 @@ export function write(artist, {wikiData}) {
         })
       : entry;
 
-  const unbound_generateTrackList = (
-    chunks,
-    {getArtistString, link, language}
-  ) => fixWS`
-        <dl>
-            ${chunks
-              .map(
-                ({date, album, chunk, duration}) => fixWS`
-                <dt>${
-                  date && duration
-                    ? language.$(
-                        'artistPage.creditList.album.withDate.withDuration',
-                        {
-                          album: link.album(album),
-                          date: language.formatDate(date),
-                          duration: language.formatDuration(duration, {
-                            approximate: true,
-                          }),
-                        }
-                      )
-                    : date
-                    ? language.$('artistPage.creditList.album.withDate', {
-                        album: link.album(album),
-                        date: language.formatDate(date),
-                      })
-                    : duration
-                    ? language.$('artistPage.creditList.album.withDuration', {
-                        album: link.album(album),
-                        duration: language.formatDuration(duration, {
-                          approximate: true,
-                        }),
-                      })
-                    : language.$('artistPage.creditList.album', {
-                        album: link.album(album),
-                      })
-                }</dt>
-                <dd><ul>
-                    ${chunk
-                      .map(({track, ...props}) => ({
-                        original: track.originalReleaseTrack,
-                        entry: language.$(
-                          'artistPage.creditList.entry.track.withDuration',
-                          {
-                            track: link.track(track),
-                            duration: language.formatDuration(
-                              track.duration ?? 0
-                            ),
-                          }
-                        ),
-                        ...props,
-                      }))
-                      .map(({original, ...opts}) =>
-                        html.tag(
-                          'li',
-                          {class: original && 'rerelease'},
-                          generateEntryAccents({
-                            getArtistString,
-                            language,
-                            original,
-                            ...opts,
-                          })
-                        )
-                      )
-                      .join('\n')}
-                </ul></dd>
-            `
-              )
-              .join('\n')}
-        </dl>
-    `;
+  const unbound_generateTrackList = (chunks, {
+    getArtistString,
+    html,
+    language,
+    link,
+  }) =>
+    html.tag('dl',
+      chunks.flatMap(({date, album, chunk, duration}) => [
+        html.tag('dt',
+          date && duration ?
+            language.$('artistPage.creditList.album.withDate.withDuration', {
+              album: link.album(album),
+              date: language.formatDate(date),
+              duration: language.formatDuration(duration, {
+                approximate: true,
+              }),
+            }) :
+
+          date ?
+            language.$('artistPage.creditList.album.withDate', {
+              album: link.album(album),
+              date: language.formatDate(date),
+            }) :
+
+          duration ?
+            language.$('artistPage.creditList.album.withDuration', {
+              album: link.album(album),
+              duration: language.formatDuration(duration, {
+                approximate: true,
+              }),
+            }) :
+
+          language.$('artistPage.creditList.album', {
+            album: link.album(album),
+          })),
+
+        html.tag('dd',
+          html.tag('ul',
+            chunk
+              .map(({track, ...props}) => ({
+                original: track.originalReleaseTrack,
+                entry: language.$('artistPage.creditList.entry.track.withDuration', {
+                  track: link.track(track),
+                  duration: language.formatDuration(
+                    track.duration ?? 0
+                  ),
+                }),
+                ...props,
+              }))
+              .map(({original, ...opts}) =>
+                html.tag(
+                  'li',
+                  {class: original && 'rerelease'},
+                  generateEntryAccents({
+                    getArtistString,
+                    language,
+                    original,
+                    ...opts,
+                  })
+                )
+              ))),
+      ]));
 
   const unbound_serializeArtistsAndContrib =
     (key, {serializeContribs, serializeLink}) =>
@@ -288,6 +265,15 @@ export function write(artist, {wikiData}) {
       })),
     }));
 
+  const jumpTo = {
+    tracks: allTracks.length > 0,
+    art: artThingsAll.length > 0,
+    flashes: wikiInfo.enableFlashesAndGames && flashes.length > 0,
+    commentary: commentaryThings.length > 0,
+  };
+
+  const showJumpTo = Object.values(jumpTo).includes(true);
+
   const data = {
     type: 'data',
     path: ['artist', artist.directory],
@@ -353,300 +339,258 @@ export function write(artist, {wikiData}) {
       generateInfoGalleryLinks,
       getArtistAvatar,
       getArtistString,
+      html,
       link,
       language,
       transformMultiline,
     }) => {
       const generateTrackList = bindOpts(unbound_generateTrackList, {
         getArtistString,
-        link,
+        html,
         language,
+        link,
       });
 
       return {
         title: language.$('artistPage.title', {artist: name}),
 
         main: {
-          content: fixWS`
-                        ${
-                          artist.hasAvatar &&
-                          generateCoverLink({
-                            src: getArtistAvatar(artist),
-                            alt: language.$('misc.alt.artistAvatar'),
-                          })
-                        }
-                        <h1>${language.$('artistPage.title', {
-                          artist: name,
-                        })}</h1>
-                        ${
-                          contextNotes &&
-                          fixWS`
-                            <p>${language.$('releaseInfo.note')}</p>
-                            <blockquote>
-                                ${transformMultiline(contextNotes)}
-                            </blockquote>
-                            <hr>
-                        `
-                        }
-                        ${
-                          urls?.length &&
-                          `<p>${language.$('releaseInfo.visitOn', {
-                            links: language.formatDisjunctionList(
-                              urls.map((url) => fancifyURL(url, {language}))
+          content: [
+            artist.hasAvatar &&
+              generateCoverLink({
+                src: getArtistAvatar(artist),
+                alt: language.$('misc.alt.artistAvatar'),
+              }),
+
+            html.tag('h1',
+              language.$('artistPage.title', {
+                artist: name,
+              })),
+
+            ...contextNotes ? [
+              html.tag('p',
+                language.$('releaseInfo.note')),
+
+              html.tag('blockquote',
+                transformMultiline(contextNotes)),
+
+              html.tag('hr'),
+            ] : [],
+
+            urls?.length &&
+              html.tag('p',
+                language.$('releaseInfo.visitOn', {
+                  links: language.formatDisjunctionList(
+                    urls.map((url) => fancifyURL(url, {language}))
+                  ),
+                })),
+
+            hasGallery &&
+              html.tag('p',
+                language.$('artistPage.viewArtGallery', {
+                  link: link.artistGallery(artist, {
+                    text: language.$('artistPage.viewArtGallery.link'),
+                  }),
+                })),
+
+            showJumpTo &&
+              html.tag('p',
+                language.$('misc.jumpTo.withLinks', {
+                  links: language.formatUnitList(
+                    [
+                      jumpTo.tracks &&
+                        html.tag('a',
+                          {href: '#tracks'},
+                          language.$('artistPage.trackList.title')),
+
+                      jumpTo.art &&
+                        html.tag('a',
+                          {href: '#art'},
+                          language.$('artistPage.artList.title')),
+
+                      jumpTo.flashes &&
+                        html.tag('a',
+                          {href: '#flashes'},
+                          language.$('artistPage.flashList.title')),
+
+                      jumpTo.commentary &&
+                        html.tag('a',
+                          {href: '#commentary'},
+                          language.$('artistPage.commentaryList.title')),
+                    ].filter(Boolean)),
+                })),
+
+            ...allTracks.length ? [
+              html.tag('h2',
+                {id: 'tracks'},
+                language.$('artistPage.trackList.title')),
+
+              totalDuration &&
+                html.tag('p',
+                  language.$('artistPage.contributedDurationLine', {
+                    artist: artist.name,
+                    duration: language.formatDuration(
+                      totalDuration,
+                      {
+                        approximate: true,
+                        unit: true,
+                      }
+                    ),
+                  })),
+
+              musicGroups.length &&
+                html.tag('p',
+                  language.$('artistPage.musicGroupsLine', {
+                    groups: language.formatUnitList(
+                      musicGroups.map(({group, contributions}) =>
+                        language.$('artistPage.groupsLine.item', {
+                          group: link.groupInfo(group),
+                          contributions:
+                            language.countContributions(
+                              contributions
                             ),
-                          })}</p>`
-                        }
-                        ${
-                          hasGallery &&
-                          `<p>${language.$('artistPage.viewArtGallery', {
-                            link: link.artistGallery(artist, {
-                              text: language.$(
-                                'artistPage.viewArtGallery.link'
-                              ),
-                            }),
-                          })}</p>`
-                        }
-                        <p>${language.$('misc.jumpTo.withLinks', {
-                          links: language.formatUnitList(
-                            [
-                              allTracks.length &&
-                                `<a href="#tracks">${language.$(
-                                  'artistPage.trackList.title'
-                                )}</a>`,
-                              artThingsAll.length &&
-                                `<a href="#art">${language.$(
-                                  'artistPage.artList.title'
-                                )}</a>`,
-                              wikiInfo.enableFlashesAndGames &&
-                                flashes.length &&
-                                `<a href="#flashes">${language.$(
-                                  'artistPage.flashList.title'
-                                )}</a>`,
-                              commentaryThings.length &&
-                                `<a href="#commentary">${language.$(
-                                  'artistPage.commentaryList.title'
-                                )}</a>`,
-                            ].filter(Boolean)
+                        })
+                      )
+                    ),
+                  })),
+
+              generateTrackList(trackListChunks),
+            ] : [],
+
+            ...artThingsAll.length ? [
+              html.tag('h2',
+                {id: 'art'},
+                language.$('artistPage.artList.title')),
+
+              hasGallery &&
+                html.tag('p',
+                  language.$('artistPage.viewArtGallery.orBrowseList', {
+                    link: link.artistGallery(artist, {
+                      text: language.$('artistPage.viewArtGallery.link'),
+                    })
+                  })),
+
+              artGroups.length &&
+                html.tag('p',
+                  language.$('artistPage.artGroupsLine', {
+                  groups: language.formatUnitList(
+                    artGroups.map(({group, contributions}) =>
+                      language.$('artistPage.groupsLine.item', {
+                        group: link.groupInfo(group),
+                        contributions:
+                          language.countContributions(
+                            contributions
                           ),
-                        })}</p>
-                        ${
-                          allTracks.length &&
-                          fixWS`
-                            <h2 id="tracks">${language.$(
-                              'artistPage.trackList.title'
-                            )}</h2>
-                            <p>${language.$(
-                              'artistPage.contributedDurationLine',
-                              {
-                                artist: artist.name,
-                                duration: language.formatDuration(
-                                  totalDuration,
-                                  {
-                                    approximate: true,
-                                    unit: true,
-                                  }
-                                ),
-                              }
-                            )}</p>
-                            <p>${language.$('artistPage.musicGroupsLine', {
-                              groups: language.formatUnitList(
-                                musicGroups.map(({group, contributions}) =>
-                                  language.$('artistPage.groupsLine.item', {
-                                    group: link.groupInfo(group),
-                                    contributions:
-                                      language.countContributions(
-                                        contributions
-                                      ),
-                                  })
-                                )
-                              ),
-                            })}</p>
-                            ${generateTrackList(trackListChunks)}
-                        `
-                        }
-                        ${
-                          artThingsAll.length &&
-                          fixWS`
-                            <h2 id="art">${language.$(
-                              'artistPage.artList.title'
-                            )}</h2>
-                            ${
-                              hasGallery &&
-                              `<p>${language.$(
-                                'artistPage.viewArtGallery.orBrowseList',
-                                {
-                                  link: link.artistGallery(artist, {
-                                    text: language.$(
-                                      'artistPage.viewArtGallery.link'
-                                    ),
-                                  }),
-                                }
-                              )}</p>`
-                            }
-                            <p>${language.$('artistPage.artGroupsLine', {
-                              groups: language.formatUnitList(
-                                artGroups.map(({group, contributions}) =>
-                                  language.$('artistPage.groupsLine.item', {
-                                    group: link.groupInfo(group),
-                                    contributions:
-                                      language.countContributions(
-                                        contributions
-                                      ),
-                                  })
-                                )
-                              ),
-                            })}</p>
-                            <dl>
-                                ${artListChunks
-                                  .map(
-                                    ({date, album, chunk}) => fixWS`
-                                    <dt>${language.$(
-                                      'artistPage.creditList.album.withDate',
-                                      {
-                                        album: link.album(album),
-                                        date: language.formatDate(date),
-                                      }
-                                    )}</dt>
-                                    <dd><ul>
-                                        ${chunk
-                                          .map(
-                                            ({
-                                              track,
-                                              key,
-                                              ...props
-                                            }) => ({
-                                              entry: track
-                                                ? language.$(
-                                                    'artistPage.creditList.entry.track',
-                                                    {
-                                                      track: link.track(track),
-                                                    }
-                                                  )
-                                                : `<i>${language.$(
-                                                    'artistPage.creditList.entry.album.' +
-                                                      {
-                                                        wallpaperArtistContribs:
-                                                          'wallpaperArt',
-                                                        bannerArtistContribs:
-                                                          'bannerArt',
-                                                        coverArtistContribs:
-                                                          'coverArt',
-                                                      }[key]
-                                                  )}</i>`,
-                                              ...props,
-                                            })
-                                          )
-                                          .map((opts) =>
-                                            generateEntryAccents({
-                                              getArtistString,
-                                              language,
-                                              ...opts,
-                                            })
-                                          )
-                                          .map((row) => `<li>${row}</li>`)
-                                          .join('\n')}
-                                    </ul></dd>
-                                `
-                                  )
-                                  .join('\n')}
-                            </dl>
-                        `
-                        }
-                        ${
-                          wikiInfo.enableFlashesAndGames &&
-                          flashes.length &&
-                          fixWS`
-                            <h2 id="flashes">${language.$(
-                              'artistPage.flashList.title'
-                            )}</h2>
-                            <dl>
-                                ${flashListChunks
-                                  .map(
-                                    ({
-                                      act,
-                                      chunk,
-                                      dateFirst,
-                                      dateLast,
-                                    }) => fixWS`
-                                    <dt>${language.$(
-                                      'artistPage.creditList.flashAct.withDateRange',
-                                      {
-                                        act: link.flash(chunk[0].flash, {
-                                          text: act.name,
-                                        }),
-                                        dateRange: language.formatDateRange(
-                                          dateFirst,
-                                          dateLast
-                                        ),
-                                      }
-                                    )}</dt>
-                                    <dd><ul>
-                                        ${chunk
-                                          .map(({flash, ...props}) => ({
-                                            entry: language.$(
-                                              'artistPage.creditList.entry.flash',
-                                              {
-                                                flash: link.flash(flash),
-                                              }
-                                            ),
-                                            ...props,
-                                          }))
-                                          .map((opts) =>
-                                            generateEntryAccents({
-                                              getArtistString,
-                                              language,
-                                              ...opts,
-                                            })
-                                          )
-                                          .map((row) => `<li>${row}</li>`)
-                                          .join('\n')}
-                                    </ul></dd>
-                                `
-                                  )
-                                  .join('\n')}
-                            </dl>
-                        `
-                        }
-                        ${
-                          commentaryThings.length &&
-                          fixWS`
-                            <h2 id="commentary">${language.$(
-                              'artistPage.commentaryList.title'
-                            )}</h2>
-                            <dl>
-                                ${commentaryListChunks
-                                  .map(
-                                    ({album, chunk}) => fixWS`
-                                    <dt>${language.$(
-                                      'artistPage.creditList.album',
-                                      {
-                                        album: link.album(album),
-                                      }
-                                    )}</dt>
-                                    <dd><ul>
-                                        ${chunk
-                                          .map(({track}) =>
-                                            track
-                                              ? language.$(
-                                                  'artistPage.creditList.entry.track',
-                                                  {
-                                                    track: link.track(track),
-                                                  }
-                                                )
-                                              : `<i>${language.$(
-                                                  'artistPage.creditList.entry.album.commentary'
-                                                )}</i>`
-                                          )
-                                          .map((row) => `<li>${row}</li>`)
-                                          .join('\n')}
-                                    </ul></dd>
-                                `
-                                  )
-                                  .join('\n')}
-                            </dl>
-                        `
-                        }
-                    `,
+                      })
+                    )
+                  ),
+                })),
+
+              html.tag('dl',
+                artListChunks.flatMap(({date, album, chunk}) => [
+                  html.tag('dt', language.$('artistPage.creditList.album.withDate', {
+                    album: link.album(album),
+                    date: language.formatDate(date),
+                  })),
+
+                  html.tag('dd',
+                    html.tag('ul',
+                      chunk
+                        .map(({track, key, ...props}) => ({
+                          ...props,
+                          entry:
+                            track
+                              ? language.$('artistPage.creditList.entry.track', {
+                                  track: link.track(track),
+                                })
+                              : html.tag('i',
+                                  language.$('artistPage.creditList.entry.album.' + {
+                                    wallpaperArtistContribs:
+                                      'wallpaperArt',
+                                    bannerArtistContribs:
+                                      'bannerArt',
+                                    coverArtistContribs:
+                                      'coverArt',
+                                  }[key])),
+                        }))
+                        .map((opts) => generateEntryAccents({
+                          getArtistString,
+                          language,
+                          ...opts,
+                        }))
+                        .map(row => html.tag('li', row)))),
+                ])),
+            ] : [],
+
+            ...(
+              wikiInfo.enableFlashesAndGames &&
+              flashes.length
+            ) ? [
+              html.tag('h2',
+                {id: 'flashes'},
+                language.$('artistPage.flashList.title')),
+
+              html.tag('dl',
+                flashListChunks.flatMap(({
+                  act,
+                  chunk,
+                  dateFirst,
+                  dateLast,
+                }) => [
+                  html.tag('dt',
+                    language.$('artistPage.creditList.flashAct.withDateRange', {
+                      act: link.flash(chunk[0].flash, {
+                        text: act.name,
+                      }),
+                      dateRange: language.formatDateRange(
+                        dateFirst,
+                        dateLast
+                      ),
+                    })),
+
+                  html.tag('dd',
+                    html.tag('ul',
+                      chunk
+                        .map(({flash, ...props}) => ({
+                          ...props,
+                          entry: language.$('artistPage.creditList.entry.flash', {
+                            flash: link.flash(flash),
+                          }),
+                        }))
+                        .map(opts => generateEntryAccents({
+                          getArtistString,
+                          language,
+                          ...opts,
+                        }))
+                        .map(row => html.tag('li', row)))),
+                ])),
+            ] : [],
+
+            ...commentaryThings.length ? [
+              html.tag('h2',
+                {id: 'commentary'},
+                language.$('artistPage.commentaryList.title')),
+
+              html.tag('dl',
+                commentaryListChunks.flatMap(({album, chunk}) => [
+                  html.tag('dt',
+                    language.$('artistPage.creditList.album', {
+                      album: link.album(album),
+                    })),
+
+                  html.tag('dd',
+                    html.tag('ul',
+                      chunk
+                        .map(({track}) => track
+                          ? language.$('artistPage.creditList.entry.track', {
+                              track: link.track(track),
+                            })
+                          : html.tag('i',
+                              language.$('artistPage.creditList.entry.album.commentary')))
+                        .map(row => html.tag('li', row)))),
+                ])),
+            ] : [],
+          ],
         },
 
         nav: generateNavForArtist(artist, false, hasGallery, {
@@ -667,6 +611,7 @@ export function write(artist, {wikiData}) {
       getAlbumCover,
       getGridHTML,
       getTrackCover,
+      html,
       link,
       language,
     }) => ({
@@ -674,35 +619,34 @@ export function write(artist, {wikiData}) {
 
       main: {
         classes: ['top-index'],
-        content: fixWS`
-                    <h1>${language.$('artistGalleryPage.title', {
-                      artist: name,
-                    })}</h1>
-                    <p class="quick-info">${language.$(
-                      'artistGalleryPage.infoLine',
-                      {
-                        coverArts: language.countCoverArts(
-                          artThingsGallery.length,
-                          {
-                            unit: true,
-                          }
-                        ),
-                      }
-                    )}</p>
-                    <div class="grid-listing">
-                        ${getGridHTML({
-                          entries: artThingsGallery.map((item) => ({item})),
-                          srcFn: (thing) =>
-                            thing.album
-                              ? getTrackCover(thing)
-                              : getAlbumCover(thing),
-                          linkFn: (thing, opts) =>
-                            thing.album
-                              ? link.track(thing, opts)
-                              : link.album(thing, opts),
-                        })}
-                    </div>
-                `,
+        content: [
+          html.tag('h1',
+            language.$('artistGalleryPage.title', {
+              artist: name,
+            })),
+
+          html.tag('p',
+            {class: 'quick-info'},
+            language.$('artistGalleryPage.infoLine', {
+              coverArts: language.countCoverArts(artThingsGallery.length, {
+                unit: true,
+              }),
+            })),
+
+          html.tag('div',
+            {class: 'grid-listing'},
+            getGridHTML({
+              entries: artThingsGallery.map((item) => ({item})),
+              srcFn: (thing) =>
+                thing.album
+                  ? getTrackCover(thing)
+                  : getAlbumCover(thing),
+              linkFn: (thing, opts) =>
+                thing.album
+                  ? link.track(thing, opts)
+                  : link.album(thing, opts),
+            })),
+        ],
       },
 
       nav: generateNavForArtist(artist, true, hasGallery, {
diff --git a/src/page/flash.js b/src/page/flash.js
index a4b3b9b0..3ce7646c 100644
--- a/src/page/flash.js
+++ b/src/page/flash.js
@@ -2,16 +2,8 @@
 
 // Flash page and index specifications.
 
-// Imports
-
-import fixWS from 'fix-whitespace';
-
-import * as html from '../util/html.js';
-
 import {getFlashLink} from '../util/wiki-data.js';
 
-// Page exports
-
 export function condition({wikiData}) {
   return wikiData.wikiInfo.enableFlashesAndGames;
 }
@@ -32,6 +24,7 @@ export function write(flash, {wikiData}) {
       getArtistString,
       getFlashCover,
       getThemeString,
+      html,
       link,
       language,
     }) => ({
@@ -41,79 +34,80 @@ export function write(flash, {wikiData}) {
       ]),
 
       main: {
-        content: fixWS`
-                    <h1>${language.$('flashPage.title', {
-                      flash: flash.name,
-                    })}</h1>
-                    ${generateCoverLink({
-                      src: getFlashCover(flash),
-                      alt: language.$('misc.alt.flashArt'),
-                    })}
-                    <p>${language.$('releaseInfo.released', {
-                      date: language.formatDate(flash.date),
-                    })}</p>
-                    ${
-                      (flash.page || flash.urls?.length) &&
-                      `<p>${language.$('releaseInfo.playOn', {
-                        links: language.formatDisjunctionList(
-                          [
-                            flash.page && getFlashLink(flash),
-                            ...(flash.urls ?? []),
-                          ].map((url) => fancifyFlashURL(url, flash))
+        content: [
+          html.tag('h1',
+            language.$('flashPage.title', {
+              flash: flash.name,
+            })),
+
+          generateCoverLink({
+            src: getFlashCover(flash),
+            alt: language.$('misc.alt.flashArt'),
+          }),
+
+          html.tag('p',
+            language.$('releaseInfo.released', {
+              date: language.formatDate(flash.date),
+            })),
+
+          (flash.page || flash.urls?.length) &&
+            html.tag('p',
+              language.$('releaseInfo.playOn', {
+                links: language.formatDisjunctionList(
+                  [
+                    flash.page && getFlashLink(flash),
+                    ...(flash.urls ?? []),
+                  ].map((url) => fancifyFlashURL(url, flash))
+                ),
+              })),
+
+          ...flash.featuredTracks?.length ? [
+            html.tag('p',
+              `Tracks featured in <i>${
+                flash.name.replace(/\.$/, '')
+              }</i>:`),
+
+            html.tag('ul',
+              flash.featuredTracks.map(track =>
+                html.tag('li',
+                  language.$('trackList.item.withArtists', {
+                    track: link.track(track),
+                    by: `<span class="by">${
+                      language.$('trackList.item.withArtists.by', {
+                        artists: getArtistString(
+                          track.artistContribs
                         ),
-                      })}</p>`
-                    }
-                    ${
-                      flash.featuredTracks &&
-                      fixWS`
-                        <p>Tracks featured in <i>${flash.name.replace(
-                          /\.$/,
-                          ''
-                        )}</i>:</p>
-                        <ul>
-                            ${flash.featuredTracks
-                              .map((track) =>
-                                language.$('trackList.item.withArtists', {
-                                  track: link.track(track),
-                                  by: `<span class="by">${language.$(
-                                    'trackList.item.withArtists.by',
-                                    {
-                                      artists: getArtistString(
-                                        track.artistContribs
-                                      ),
-                                    }
-                                  )}</span>`,
-                                })
-                              )
-                              .map((row) => `<li>${row}</li>`)
-                              .join('\n')}
-                        </ul>
-                    `
-                    }
-                    ${
-                      flash.contributorContribs.length &&
-                      fixWS`
-                        <p>${language.$('releaseInfo.contributors')}</p>
-                        <ul>
-                            ${flash.contributorContribs
-                              .map(
-                                (contrib) =>
-                                  `<li>${getArtistString([contrib], {
-                                    showContrib: true,
-                                    showIcons: true,
-                                  })}</li>`
-                              )
-                              .join('\n')}
-                        </ul>
-                    `
-                    }
-                `,
+                      })
+                    }</span>`,
+                  })))),
+          ] : [],
+
+          ...flash.contributorContribs.length ? [
+            html.tag('p',
+              language.$('releaseInfo.contributors')),
+
+            html.tag('ul',
+              flash.contributorContribs.map(contrib =>
+                html.tag('li',
+                  getArtistString([contrib], {
+                    showContrib: true,
+                    showIcons: true,
+                  })))),
+          ] : [],
+        ],
       },
 
-      sidebarLeft: generateSidebarForFlash(flash, {link, language, wikiData}),
+      sidebarLeft: generateSidebarForFlash(flash, {
+        html,
+        language,
+        link,
+        wikiData,
+      }),
+
       nav: generateNavForFlash(flash, {
         generateChronologyLinks,
         generatePreviousNextLinks,
+        html,
         link,
         language,
         wikiData,
@@ -124,54 +118,70 @@ export function write(flash, {wikiData}) {
   return [page];
 }
 
-export function writeTargetless({wikiData}) {
+export function writeTargetless({
+  wikiData,
+}) {
   const {flashActData} = wikiData;
 
   const page = {
     type: 'page',
     path: ['flashIndex'],
-    page: ({getFlashGridHTML, getLinkThemeString, link, language}) => ({
+    page: ({
+      getFlashGridHTML,
+      getLinkThemeString,
+      html,
+      language,
+      link,
+    }) => ({
       title: language.$('flashIndex.title'),
 
       main: {
         classes: ['flash-index'],
-        content: fixWS`
-                    <h1>${language.$('flashIndex.title')}</h1>
-                    <div class="long-content">
-                        <p class="quick-info">${language.$('misc.jumpTo')}</p>
-                        <ul class="quick-info">
-                            ${flashActData
-                              .filter((act) => act.jump)
-                              .map(
-                                ({anchor, jump, jumpColor}) => fixWS`
-                                <li><a href="#${anchor}" style="${getLinkThemeString(
-                                  jumpColor
-                                )}">${jump}</a></li>
-                            `
-                              )
-                              .join('\n')}
-                        </ul>
-                    </div>
-                    ${flashActData
-                      .map(
-                        (act, i) => fixWS`
-                        <h2 id="${act.anchor}" style="${getLinkThemeString(
-                          act.color
-                        )}">${link.flash(act.flashes[0], {
-                          text: act.name,
-                        })}</h2>
-                        <div class="grid-listing">
-                            ${getFlashGridHTML({
-                              entries: act.flashes.map((flash) => ({
-                                item: flash,
-                              })),
-                              lazy: i === 0 ? 4 : true,
-                            })}
-                        </div>
-                    `
-                      )
-                      .join('\n')}
-                `,
+        content: [
+          html.tag('h1',
+            language.$('flashIndex.title')),
+
+          html.tag('div',
+            {class: 'long-content'},
+            [
+              html.tag('p',
+                {class: 'quick-info'},
+                language.$('misc.jumpTo')),
+
+              html.tag('ul',
+                {class: 'quick-info'},
+                flashActData
+                  .filter(act => act.jump)
+                  .map(({anchor, jump, jumpColor}) =>
+                    html.tag('li',
+                      html.tag('a',
+                        {
+                          href: '#' + anchor,
+                          style: getLinkThemeString(jumpColor),
+                        },
+                        jump)))),
+            ]),
+
+          ...flashActData.flatMap((act, i) => [
+            html.tag('h2',
+              {
+                id: '#' + act.anchor,
+                style: getLinkThemeString(act.color),
+              },
+              link.flash(act.flashes[0], {
+                text: act.name,
+              })),
+
+            html.tag('div',
+              {class: 'grid-listing'},
+              getFlashGridHTML({
+                entries: act.flashes.map((flash) => ({
+                  item: flash,
+                })),
+                lazy: i === 0 ? 4 : true,
+              })),
+          ]),
+        ],
       },
 
       nav: {simple: true},
@@ -183,10 +193,14 @@ export function writeTargetless({wikiData}) {
 
 // Utility functions
 
-function generateNavForFlash(
-  flash,
-  {generateChronologyLinks, generatePreviousNextLinks, link, language, wikiData}
-) {
+function generateNavForFlash(flash, {
+  generateChronologyLinks,
+  generatePreviousNextLinks,
+  html,
+  language,
+  link,
+  wikiData
+}) {
   const {flashData} = wikiData;
 
   const previousNextLinks = generatePreviousNextLinks(flash, {
@@ -211,19 +225,21 @@ function generateNavForFlash(
 
     bottomRowContent: previousNextLinks && `(${previousNextLinks})`,
 
-    content: fixWS`
-            <div>
-                ${generateChronologyLinks(flash, {
-                  headingString: 'misc.chronology.heading.flash',
-                  contribKey: 'contributorContribs',
-                  getThings: (artist) => artist.flashesAsContributor,
-                })}
-            </div>
-        `,
+    content: html.tag('div',
+      generateChronologyLinks(flash, {
+        headingString: 'misc.chronology.heading.flash',
+        contribKey: 'contributorContribs',
+        getThings: (artist) => artist.flashesAsContributor,
+      })),
   };
 }
 
-function generateSidebarForFlash(flash, {link, language, wikiData}) {
+function generateSidebarForFlash(flash, {
+  html,
+  language,
+  link,
+  wikiData,
+}) {
   // all hard-coded, sorry :(
   // this doesnt have a super portable implementation/design...yet!!
 
@@ -244,82 +260,74 @@ function generateSidebarForFlash(flash, {link, language, wikiData}) {
   const currentAct = flash && flash.act;
 
   return {
-    content: fixWS`
-            <h1>${link.flashIndex('', {
-              text: language.$('flashIndex.title'),
-            })}</h1>
-            <dl>
-                ${flashActData
-                  .filter(
-                    (act) =>
-                      act.name.startsWith('Act 1') ||
-                      act.name.startsWith('Act 6 Act 1') ||
-                      act.name.startsWith('Hiveswap') ||
-                      // Sorry not sorry -Yiffy
-                      (({index = flashActData.indexOf(act)} = {}) =>
-                        index < act6
-                          ? side === 1
-                          : index < outsideCanon
-                          ? side === 2
-                          : true)()
-                  )
-                  .flatMap((act) => [
-                    (act.name.startsWith('Act 1') &&
-                      html.tag(
-                        'dt',
-                        {class: ['side', side === 1 && 'current']},
-                        link.flash(act.flashes[0], {
-                          color: '#4ac925',
-                          text: `Side 1 (Acts 1-5)`,
-                        })
-                      )) ||
-                      (act.name.startsWith('Act 6 Act 1') &&
-                        html.tag(
-                          'dt',
-                          {class: ['side', side === 2 && 'current']},
-                          link.flash(act.flashes[0], {
-                            color: '#1076a2',
-                            text: `Side 2 (Acts 6-7)`,
-                          })
-                        )) ||
-                      (act.name.startsWith('Hiveswap Act 1') &&
-                        html.tag(
-                          'dt',
-                          {class: ['side', side === 3 && 'current']},
-                          link.flash(act.flashes[0], {
-                            color: '#008282',
-                            text: `Outside Canon (Misc. Games)`,
-                          })
-                        )),
-                    (({index = flashActData.indexOf(act)} = {}) =>
-                      index < act6
-                        ? side === 1
-                        : index < outsideCanon
-                        ? side === 2
-                        : true)() &&
-                      html.tag(
-                        'dt',
-                        {class: act === currentAct && 'current'},
-                        link.flash(act.flashes[0], {text: act.name})
-                      ),
-                    act === currentAct &&
-                      fixWS`
-                        <dd><ul>
-                            ${act.flashes
-                              .map((f) =>
-                                html.tag(
-                                  'li',
-                                  {class: f === flash && 'current'},
-                                  link.flash(f)
-                                )
-                              )
-                              .join('\n')}
-                        </ul></dd>
-                    `,
-                  ])
-                  .filter(Boolean)
-                  .join('\n')}
-            </dl>
-        `,
+    content: [
+      html.tag('h1',
+        link.flashIndex('', {
+          text: language.$('flashIndex.title'),
+        })),
+
+      html.tag('dl',
+        flashActData
+          .filter(
+            (act) =>
+              act.name.startsWith('Act 1') ||
+              act.name.startsWith('Act 6 Act 1') ||
+              act.name.startsWith('Hiveswap') ||
+              // Sorry not sorry -Yiffy
+              (({index = flashActData.indexOf(act)} = {}) =>
+                index < act6
+                  ? side === 1
+                  : index < outsideCanon
+                  ? side === 2
+                  : true)())
+          .flatMap((act) => [
+            (act.name.startsWith('Act 1') &&
+              html.tag(
+                'dt',
+                {class: ['side', side === 1 && 'current']},
+                link.flash(act.flashes[0], {
+                  color: '#4ac925',
+                  text: `Side 1 (Acts 1-5)`,
+                })
+              )) ||
+              (act.name.startsWith('Act 6 Act 1') &&
+                html.tag(
+                  'dt',
+                  {class: ['side', side === 2 && 'current']},
+                  link.flash(act.flashes[0], {
+                    color: '#1076a2',
+                    text: `Side 2 (Acts 6-7)`,
+                  })
+                )) ||
+              (act.name.startsWith('Hiveswap Act 1') &&
+                html.tag(
+                  'dt',
+                  {class: ['side', side === 3 && 'current']},
+                  link.flash(act.flashes[0], {
+                    color: '#008282',
+                    text: `Outside Canon (Misc. Games)`,
+                  })
+                )),
+            (({index = flashActData.indexOf(act)} = {}) =>
+              index < act6
+                ? side === 1
+                : index < outsideCanon
+                ? side === 2
+                : true)() &&
+              html.tag(
+                'dt',
+                {class: act === currentAct && 'current'},
+                link.flash(act.flashes[0], {text: act.name})
+              ),
+            act === currentAct &&
+              html.tag('dd',
+                html.tag('ul',
+                  act.flashes
+                    .map(f =>
+                      html.tag('li',
+                        {class: f === flash && 'current'},
+                        link.flash(f))))),
+          ])),
+    ],
   };
 }
diff --git a/src/page/static.js b/src/page/static.js
index 2a49ff87..5f50333a 100644
--- a/src/page/static.js
+++ b/src/page/static.js
@@ -4,12 +4,6 @@
 // wiki data folder, used for a variety of purposes, e.g. wiki info,
 // changelog, and so on.)
 
-// Imports
-
-import fixWS from 'fix-whitespace';
-
-// Page exports
-
 export function targets({wikiData}) {
   return wikiData.staticPageData;
 }
@@ -18,17 +12,14 @@ export function write(staticPage) {
   const page = {
     type: 'page',
     path: ['staticPage', staticPage.directory],
-    page: ({transformMultiline}) => ({
+    page: ({html, transformMultiline}) => ({
       title: staticPage.name,
       stylesheet: staticPage.stylesheet,
 
       main: {
-        content: fixWS`
-                    <div class="long-content">
-                        <h1>${staticPage.name}</h1>
-                        ${transformMultiline(staticPage.content)}
-                    </div>
-                `,
+        content: html.tag('div',
+          {class: 'long-content'},
+          transformMultiline(staticPage.content)),
       },
 
       nav: {simple: true},
diff --git a/src/page/tag.js b/src/page/tag.js
index 38f7e213..faa0df22 100644
--- a/src/page/tag.js
+++ b/src/page/tag.js
@@ -2,12 +2,6 @@
 
 // Art tag page specification.
 
-// Imports
-
-import fixWS from 'fix-whitespace';
-
-// Page exports
-
 export function condition({wikiData}) {
   return wikiData.wikiInfo.enableArtTagUI;
 }
@@ -33,6 +27,7 @@ export function write(tag, {wikiData}) {
       getGridHTML,
       getThemeString,
       getTrackCover,
+      html,
       link,
       language,
     }) => ({
@@ -41,27 +36,34 @@ export function write(tag, {wikiData}) {
 
       main: {
         classes: ['top-index'],
-        content: fixWS`
-                    <h1>${language.$('tagPage.title', {tag: tag.name})}</h1>
-                    <p class="quick-info">${language.$('tagPage.infoLine', {
-                      coverArts: language.countCoverArts(things.length, {
-                        unit: true,
-                      }),
-                    })}</p>
-                    <div class="grid-listing">
-                        ${getGridHTML({
-                          entries,
-                          srcFn: (thing) =>
-                            thing.album
-                              ? getTrackCover(thing)
-                              : getAlbumCover(thing),
-                          linkFn: (thing, opts) =>
-                            thing.album
-                              ? link.track(thing, opts)
-                              : link.album(thing, opts),
-                        })}
-                    </div>
-                `,
+        content: [
+          html.tag('h1',
+            language.$('tagPage.title', {
+              tag: tag.name,
+            })),
+
+          html.tag('p',
+            {class: 'quick-info'},
+            language.$('tagPage.infoLine', {
+              coverArts: language.countCoverArts(things.length, {
+                unit: true,
+              }),
+            })),
+
+          html.tag('div',
+            {class: 'grid-listing'},
+            getGridHTML({
+              entries,
+              srcFn: (thing) =>
+                thing.album
+                  ? getTrackCover(thing)
+                  : getAlbumCover(thing),
+              linkFn: (thing, opts) =>
+                thing.album
+                  ? link.track(thing, opts)
+                  : link.album(thing, opts),
+            })),
+        ],
       },
 
       nav: generateTagNav(tag, {
diff --git a/src/page/track.js b/src/page/track.js
index 436205b4..258e11d3 100644
--- a/src/page/track.js
+++ b/src/page/track.js
@@ -2,8 +2,6 @@
 
 // Track page specification.
 
-// Imports
-
 import {
   generateAlbumChronologyLinks,
   generateAlbumNavLinks,
@@ -11,8 +9,6 @@ import {
   generateAlbumSidebar,
 } from './album.js';
 
-import * as html from '../util/html.js';
-
 import {bindOpts} from '../util/sugar.js';
 
 import {
@@ -21,8 +17,6 @@ import {
   sortChronologically,
 } from '../util/wiki-data.js';
 
-// Page exports
-
 export function targets({wikiData}) {
   return wikiData.trackData;
 }
@@ -48,7 +42,12 @@ export function write(track, {wikiData}) {
     );
   }
 
-  const unbound_getTrackItem = (track, {getArtistString, link, language}) =>
+  const unbound_getTrackItem = (track, {
+    getArtistString,
+    html,
+    language,
+    link,
+  }) =>
     html.tag('li',
       language.$('trackList.item.withArtists', {
         track: link.track(track),
@@ -159,6 +158,7 @@ export function write(track, {wikiData}) {
       getLinkThemeString,
       getThemeString,
       getTrackCover,
+      html,
       link,
       language,
       transformLyrics,
@@ -168,8 +168,9 @@ export function write(track, {wikiData}) {
     }) => {
       const getTrackItem = bindOpts(unbound_getTrackItem, {
         getArtistString,
-        link,
+        html,
         language,
+        link,
       });
       const cover = getTrackCover(track);
 
@@ -338,8 +339,9 @@ export function write(track, {wikiData}) {
         sidebarLeft: generateAlbumSidebar(album, track, {
           fancifyURL,
           getLinkThemeString,
-          link,
+          html,
           language,
+          link,
           transformMultiline,
           wikiData,
         }),
@@ -369,6 +371,7 @@ export function write(track, {wikiData}) {
 
           content: generateAlbumChronologyLinks(album, track, {
             generateChronologyLinks,
+            html,
           }),
 
           bottomRowContent:
@@ -380,9 +383,10 @@ export function write(track, {wikiData}) {
         },
 
         secondaryNav: generateAlbumSecondaryNav(album, track, {
+          getLinkThemeString,
+          html,
           language,
           link,
-          getLinkThemeString,
         }),
       };
     },