« get me outta code hell

data steps: contentFunction & explicit dependencies - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-03-06 22:00:00 -0400
committer(quasar) nebula <qznebula@protonmail.com>2023-03-06 22:00:00 -0400
commit82f1d4de61b3660e07353f44e6125ced019b18ad (patch)
treeb40664fc9c9ede6d59879996c0b7f77a3882b094
parentda4eda535893f1a26b095e5890658099e89d9457 (diff)
data steps: contentFunction & explicit dependencies
-rw-r--r--src/content-function.js177
-rw-r--r--src/misc-templates.js236
-rw-r--r--src/page/album.js767
3 files changed, 754 insertions, 426 deletions
diff --git a/src/content-function.js b/src/content-function.js
new file mode 100644
index 0000000..5138379
--- /dev/null
+++ b/src/content-function.js
@@ -0,0 +1,177 @@
+import {empty} from './util/sugar.js';
+
+export default function contentFunction({
+  contentDependencies = [],
+  extraDependencies = [],
+
+  data,
+  generate,
+}) {
+  return expectDependencies({
+    data,
+    generate,
+
+    expectedContentDependencyKeys: contentDependencies,
+    expectedExtraDependencyKeys: extraDependencies,
+    fulfilledDependencies: {},
+  });
+}
+
+contentFunction.identifyingSymbol = Symbol(`Is a content function?`);
+
+export function expectDependencies({
+  generate,
+  data,
+
+  expectedContentDependencyKeys,
+  expectedExtraDependencyKeys,
+  fulfilledDependencies,
+}) {
+  if (!generate) {
+    throw new Error(`Expected generate function`);
+  }
+
+  if (!data) {
+    throw new Error(`Expected data function`);
+  }
+
+  const fulfilledDependencyKeys = Object.keys(fulfilledDependencies);
+
+  const invalidatingDependencyKeys = Object.entries(fulfilledDependencies)
+    .filter(([key, value]) => value.fulfilled === false)
+    .map(([key]) => key);
+
+  const missingContentDependencyKeys = expectedContentDependencyKeys
+    .filter(key => !fulfilledDependencyKeys.includes(key));
+
+  const missingExtraDependencyKeys = expectedExtraDependencyKeys
+    .filter(key => !fulfilledDependencyKeys.includes(key));
+
+  let wrappedGenerate;
+
+  if (!empty(invalidatingDependencyKeys)) {
+    wrappedGenerate = function() {
+      throw new Error(`Generate invalidated because unfulfilled dependencies provided: ${invalidatingDependencyKeys.join(', ')}`);
+    };
+
+    wrappedGenerate.fulfilled ??= false;
+  }
+
+  if (empty(missingContentDependencyKeys) && empty(missingExtraDependencyKeys)) {
+    wrappedGenerate ??= function(data) {
+      return generate(data, fulfilledDependencies);
+    };
+
+    wrappedGenerate.fulfill = function() {
+      throw new Error(`All dependencies already fulfilled`);
+    };
+
+    wrappedGenerate.fulfilled ??= true;
+  }
+
+  wrappedGenerate ??= function() {
+    throw new Error(`Dependencies still needed: ${missingContentDependencyKeys.concat(missingExtraDependencyKeys).join(', ')}`);
+  };
+
+  wrappedGenerate.fulfilled ??= false;
+  wrappedGenerate[contentFunction.identifyingSymbol] = true;
+
+  if (empty(missingContentDependencyKeys)) {
+    const dataDependencies = {};
+
+    for (const key of expectedContentDependencyKeys) {
+      const wrappedDependency = function() {
+        throw new Error(`Expected call to this dependency's .data()`);
+      };
+
+      wrappedDependency.data = fulfilledDependencies[key].data;
+      dataDependencies[key] = wrappedDependency;
+    }
+
+    wrappedGenerate.data = function(...args) {
+      return data(...args, dataDependencies);
+    };
+  }
+
+  wrappedGenerate.data ??= function() {
+    throw new Error(`Dependencies still needed: ${missingContentDependencyKeys.join(', ')}`);
+  };
+
+  wrappedGenerate.fulfill ??= function(dependencies) {
+    return expectDependencies({
+      generate,
+      data,
+
+      expectedContentDependencyKeys,
+      expectedExtraDependencyKeys,
+
+      fulfilledDependencies: fulfillDependencies({
+        name: generate.name,
+        dependencies,
+
+        expectedContentDependencyKeys,
+        expectedExtraDependencyKeys,
+        fulfilledDependencies,
+      }),
+    });
+  };
+
+  return wrappedGenerate;
+}
+
+export function fulfillDependencies({
+  name,
+  dependencies,
+  expectedContentDependencyKeys,
+  expectedExtraDependencyKeys,
+  fulfilledDependencies,
+}) {
+  const newFulfilledDependencies = {...fulfilledDependencies};
+  const fulfilledDependencyKeys = Object.keys(fulfilledDependencies);
+
+  const errors = [];
+  let bail = false;
+
+  for (let [key, value] of Object.entries(dependencies)) {
+    if (key.startsWith('u_')) {
+      key = key.slice(2);
+    }
+
+    if (fulfilledDependencyKeys.includes(key)) {
+      errors.push(new Error(`Dependency ${key} is already fulfilled`));
+      bail = true;
+      continue;
+    }
+
+    const isContentKey = expectedContentDependencyKeys.includes(key);
+    const isExtraKey = expectedExtraDependencyKeys.includes(key);
+
+    if (!isContentKey && !isExtraKey) {
+      errors.push(new Error(`Dependency ${key} is not expected`));
+      bail = true;
+      continue;
+    }
+
+    if (isContentKey && !value[contentFunction.identifyingSymbol]) {
+      errors.push(new Error(`Content dependency ${key} is not a content function`));
+      bail = true;
+      continue;
+    }
+
+    if (isExtraKey && value[contentFunction.identifyingSymbol]) {
+      errors.push(new Error(`Extra dependency ${key} is a content function`));
+      bail = true;
+      continue;
+    }
+
+    if (!bail) {
+      newFulfilledDependencies[key] = value;
+    }
+  }
+
+  if (!empty(errors)) {
+    throw new AggregateError(errors, `Errors fulfilling dependencies for ${name}`);
+  }
+
+  return newFulfilledDependencies;
+}
diff --git a/src/misc-templates.js b/src/misc-templates.js
index 0d749d1..cbdedfe 100644
--- a/src/misc-templates.js
+++ b/src/misc-templates.js
@@ -18,6 +18,8 @@ import {
   sortChronologically,
 } from './util/wiki-data.js';
 
+import contentFunction from './util/content-function.js';
+
 import u_link from './util/link.js';
 
 const BANDCAMP_DOMAINS = ['bc.s3m.us', 'music.solatrux.com'];
@@ -80,72 +82,74 @@ function unbound_generateAdditionalFilesList(additionalFiles, {
 
 // Artist strings
 
-unbound_generateContributionLinks.data = (contributions, {
-  showContribution = false,
-  showIcons = false,
-}) => {
-  return {
-    showContribution,
-    showIcons,
-
-    contributionData:
-      contributions.map(({who, what}) => ({
-        artistLinkData: u_link.artist.data(who),
+export const u_generateContributionLinks = contentFunction({
+  data: function(contributions, {
+    showContribution = false,
+    showIcons = false,
+  }) {
+    return {
+      showContribution,
+      showIcons,
 
-        hasContributionPart: !!(showContribution && what),
-        hasExternalPart: !!(showIcons && !empty(who.urls)),
+      contributionData:
+        contributions.map(({who, what}) => ({
+          artistLinkData: u_link.artist.data(who),
 
-        artistUrls: who.urls,
-        contribution: showContribution && what,
-      })),
-  };
-};
+          hasContributionPart: !!(showContribution && what),
+          hasExternalPart: !!(showIcons && !empty(who.urls)),
 
-function unbound_generateContributionLinks(data, {
-  html,
-  iconifyURL,
-  language,
-  link,
-}) {
-  return language.formatConjunctionList(
-    data.contributionData.map(({
-      artistLinkData,
-      hasContributionPart,
-      hasExternalPart,
-      artistUrls,
-      contribution,
-    }) => {
-      const artistLink = link.artist(artistLinkData);
-
-      const externalLinks = hasExternalPart &&
-        html.tag('span',
-          {[html.noEdgeWhitespace]: true, class: 'icons'},
-          language.formatUnitList(
-            artistUrls.map(url => iconifyURL(url, {language}))));
-
-      return (
-        (hasContributionPart
-          ? (hasExternalPart
-              ? language.$('misc.artistLink.withContribution.withExternalLinks', {
-                  artist: artistLink,
-                  contrib: contribution,
-                  links: externalLinks,
-                })
-              : language.$('misc.artistLink.withContribution', {
-                  artist: artistLink,
-                  contrib: contribution,
-                }))
-          : (hasExternalPart
-              ? language.$('misc.artistLink.withExternalLinks', {
-                  artist: artistLink,
-                  links: externalLinks,
-                })
-              : language.$('misc.artistLink', {
-                  artist: artistLink,
-                })))
-      );
-    }));
-}
+          artistUrls: who.urls,
+          contribution: showContribution && what,
+        })),
+    };
+  },
+
+  generate: function generateContributionLinks(data, {
+    html,
+    iconifyURL,
+    language,
+    link,
+  }) {
+    return language.formatConjunctionList(
+      data.contributionData.map(({
+        artistLinkData,
+        hasContributionPart,
+        hasExternalPart,
+        artistUrls,
+        contribution,
+      }) => {
+        const artistLink = link.artist(artistLinkData);
+
+        const externalLinks = hasExternalPart &&
+          html.tag('span',
+            {[html.noEdgeWhitespace]: true, class: 'icons'},
+            language.formatUnitList(
+              artistUrls.map(url => iconifyURL(url, {language}))));
+
+        return (
+          (hasContributionPart
+            ? (hasExternalPart
+                ? language.$('misc.artistLink.withContribution.withExternalLinks', {
+                    artist: artistLink,
+                    contrib: contribution,
+                    links: externalLinks,
+                  })
+                : language.$('misc.artistLink.withContribution', {
+                    artist: artistLink,
+                    contrib: contribution,
+                  }))
+            : (hasExternalPart
+                ? language.$('misc.artistLink.withExternalLinks', {
+                    artist: artistLink,
+                    links: externalLinks,
+                  })
+                : language.$('misc.artistLink', {
+                    artist: artistLink,
+                  })))
+        );
+      }));
+  },
+});
 
 // Chronology links
 
@@ -337,52 +341,65 @@ function unbound_getThemeString(color, {
   ].join('\n');
 }
 
-function unbound_getAlbumStylesheet(album, {
-  to,
-}) {
-  const hasWallpaper = album.wallpaperArtistContribs.length >= 1;
-  const hasWallpaperStyle = !!album.wallpaperStyle;
-  const hasBannerStyle = !!album.bannerStyle;
-
-  const wallpaperSource =
-    (hasWallpaper &&
-      to(
-        'media.albumWallpaper',
-        album.directory,
-        album.wallpaperFileExtension));
-
-  const wallpaperPart =
-    (hasWallpaper
-      ? [
-          `body::before {`,
-          `    background-image: url("${wallpaperSource}");`,
-          ...(hasWallpaperStyle
-            ? album.wallpaperStyle
-                .split('\n')
-                .map(line => `    ${line}`)
-            : []),
-          `}`,
-        ]
-      : []);
-
-  const bannerPart =
-    (hasBannerStyle
-      ? [
-          `#banner img {`,
-          ...album.bannerStyle
-            .split('\n')
-            .map(line => `    ${line}`),
-          `}`,
-        ]
-      : []);
+export const u_generateAlbumStylesheet = contentFunction({
+  extraDependencies: [
+    'to',
+  ],
 
-  return [
-    ...wallpaperPart,
-    ...bannerPart,
-  ]
-    .filter(Boolean)
-    .join('\n');
-}
+  data: function(album) {
+    const data = {};
+
+    data.hasWallpaper = !empty(album.wallpaperArtistContribs);
+    data.hasBanner = !empty(album.bannerArtistContribs);
+
+    if (data.hasWallpaper) {
+      data.hasWallpaperStyle = !!album.wallpaperStyle;
+      data.wallpaperPath = ['media.albumWallpaper', album.directory, album.wallpaperFileExtension];
+      data.wallpaperStyle = album.wallpaperStyle;
+    }
+
+    if (data.hasBanner) {
+      data.hasBannerStyle = !!album.bannerStyle;
+      data.bannerStyle = album.bannerStyle;
+    }
+
+    return data;
+  },
+
+  generate: function generateAlbumStylesheet(data, {to}) {
+    const wallpaperPart =
+      (data.hasWallpaper
+        ? [
+            `body::before {`,
+            `    background-image: url("${to(...data.wallpaperPath)}");`,
+            ...(data.hasWallpaperStyle
+              ? data.wallpaperStyle
+                  .split('\n')
+                  .map(line => `    ${line}`)
+              : []),
+            `}`,
+          ]
+        : []);
+
+    const bannerPart =
+      (data.hasBannerStyle
+        ? [
+            `#banner img {`,
+            ...data.bannerStyle
+              .split('\n')
+              .map(line => `    ${line}`),
+            `}`,
+          ]
+        : []);
+
+    return [
+      ...wallpaperPart,
+      ...bannerPart,
+    ]
+      .filter(Boolean)
+      .join('\n');
+  },
+});
 
 // Divided track lists
 
@@ -1057,8 +1074,6 @@ export {
   unbound_generateAdditionalFilesList as generateAdditionalFilesList,
   unbound_generateAdditionalFilesShortcut as generateAdditionalFilesShortcut,
 
-  unbound_generateContributionLinks as generateContributionLinks,
-
   unbound_generateChronologyLinks as generateChronologyLinks,
 
   unbound_getRevealStringFromContentWarningMessage as getRevealStringFromContentWarningMessage,
@@ -1067,7 +1082,6 @@ export {
   unbound_generateCoverLink as generateCoverLink,
 
   unbound_getThemeString as getThemeString,
-  unbound_getAlbumStylesheet as getAlbumStylesheet,
 
   unbound_generateTrackListDividedByGroups as generateTrackListDividedByGroups,
 
diff --git a/src/page/album.js b/src/page/album.js
index a266b91..ab1e1b2 100644
--- a/src/page/album.js
+++ b/src/page/album.js
@@ -13,10 +13,11 @@ import {
 } from '../util/wiki-data.js';
 
 import {
-  generateContributionLinks as u_generateContributionLinks,
+  u_generateAlbumStylesheet,
 } from '../misc-templates.js';
 
 import u_link from '../util/link.js';
+import contentFunction from '../util/content-function.js';
 
 export const description = `per-album info & track artwork gallery pages`;
 
@@ -27,7 +28,7 @@ export function targets({wikiData}) {
 export const dataSteps = {
   computePathsForTarget(data, album) {
     data.hasGalleryPage = album.tracks.some(t => t.hasUniqueCoverArt);
-    data.hasCommentaryPage = !!album.commentary || album.tracks.some(t => t.commentary);;
+    data.hasCommentaryPage = !!album.commentary || album.tracks.some(t => t.commentary);
 
     return [
       {
@@ -62,6 +63,16 @@ export const dataSteps = {
 
   computeDataForPageWrite: {
     album(data, album, _pathArgs) {
+      // TODO: We can't use content-unfulfilled functions here.
+      // But how do we express that these need to be fulfilled
+      // from within data steps?
+      data.socialEmbedData = u_generateAlbumSocialEmbed.data(album);
+      data.stylesheetData = u_generateAlbumStylesheet.data(album);
+
+      data.name = album.name;
+      data.color = album.color;
+      data.directory = album.directory;
+
       data.hasAdditionalFiles = !empty(album.additionalFiles);
       data.numAdditionalFiles = album.additionalFiles.flatMap((g) => g.files).length;
 
@@ -101,56 +112,455 @@ export const dataSteps = {
         link,
       });
 
+      const generateAlbumSocialEmbedDescription = u_generateAlbumSocialEmbedDescription.fulfill({
+        language,
+      });
+
+      const generateAlbumSocialEmbed = u_generateAlbumSocialEmbed.fulfill({
+        generateSocialEmbedDescription: generateAlbumSocialEmbedDescription,
+      });
+
       void generateTrackListItem;
+
+      return {
+        title: language.$('albumPage.title', {album: data.name}),
+        stylesheet: getAlbumStylesheet(data.stylesheetData),
+
+        themeColor: data.color,
+        theme:
+          getThemeString(data.color, {
+            additionalVariables: [
+              `--album-directory: ${data.directory}`,
+            ],
+          }),
+
+        socialEmbed: generateAlbumSocialEmbed(data.socialEmbedData, {
+          absoluteTo,
+          getAlbumCover,
+          getSocialEmbedDescription,
+          to,
+        }),
+      };
     },
   },
 };
 
-function u_generateTrackListItem(data, {
-  generateContributionLinks,
-  getLinkThemeString,
-  html,
-  language,
-  link,
-}) {
-  const stringOpts = {
-    duration: language.formatDuration(data.duration),
-    track: link.track(data.linkData),
-  };
+const u_generateAlbumSocialEmbedDescription = contentFunction({
+  extraDependencies: ['language'],
+
+  data: function(album) {
+    const data = {};
+
+    const duration = getTotalDuration(album);
+
+    data.hasDuration = duration > 0;
+    data.hasTracks = album.tracks.length > 0;
+    data.hasDate = !!album.date;
+    data.hasAny = (data.hasDuration || data.hasTracks || data.hasDuration);
+
+    if (!data.hasAny)
+      return data;
+
+    if (data.hasDuration)
+      data.duration = duration;
+
+    if (data.hasTracks)
+      data.tracks = album.tracks.length;
+
+    if (data.hasDate)
+      data.date = album.date;
+
+    return data;
+  },
+
+  generate: function generateAlbumSocialEmbedDescription(data, {
+    language,
+  }) {
+    return language.formatString(
+      'albumPage.socialEmbed.body' + [
+        data.hasDuration && '.withDuration',
+        data.hasTracks && '.withTracks',
+        data.hasDate && '.withReleaseDate',
+      ].filter(Boolean).join(''),
+
+      Object.fromEntries([
+        data.hasDuration &&
+          ['duration', language.formatDuration(data.duration)],
+        data.hasTracks &&
+          ['tracks', language.countTracks(data.tracks, {unit: true})],
+        data.hasDate &&
+          ['date', language.formatDate(data.date)],
+      ].filter(Boolean)));
+  },
+});
+
+const u_generateAlbumSocialEmbed = contentFunction({
+  contentDependencies: [
+    'generateSocialEmbedDescription',
+  ],
+
+  extraDependencies: [
+    'absoluteTo',
+    'language',
+    'to',
+    'urls',
+  ],
+
+  data: function(album, {
+    generateSocialEmbedDescription,
+  }) {
+    const data = {};
+
+    data.descriptionData = generateSocialEmbedDescription.data(album);
+
+    data.hasHeading = !empty(album.groups);
+
+    if (data.hasHeading) {
+      const firstGroup = album.groups[0];
+      data.headingGroupName = firstGroup.directory;
+      data.headingGroupDirectory = firstGroup.directory;
+    }
+
+    data.albumName = album.name;
+    data.albumColor = album.color;
+
+    return data;
+  },
+
+  generate: function generateAlbumSocialEmbed(data, {
+    generateSocialEmbedDescription,
+
+    absoluteTo,
+    language,
+    to,
+    urls,
+  }) {
+    const socialEmbed = {};
+
+    if (data.hasHeading) {
+      socialEmbed.heading =
+        language.$('albumPage.socialEmbed.heading', {
+          group: data.headingGroupName,
+        });
+
+      socialEmbed.headingLink =
+        absoluteTo('localized.album', data.headingGroupDirectory);
+    } else {
+      socialEmbed.heading = '';
+      socialEmbed.headingLink = null;
+    }
+
+    socialEmbed.title =
+      language.$('albumPage.socialEmbed.title', {
+        album: data.albumName,
+      });
+
+    socialEmbed.description = generateSocialEmbedDescription(data.descriptionData);
+
+    socialEmbed.image =
+      '/' + getAlbumCover(album, {to: urls.from('shared.root').to});
+
+    socialEmbed.color = data.albumColor;
+
+    return socialEmbed;
+  },
+});
+
+const u_generateTrackListItem = contentFunction({
+  contentDependencies: [
+    'generateContributionLinks',
+  ],
+
+  extraDependencies: [
+    'getLinkThemeString',
+    'html',
+    'language',
+    'link',
+  ],
+
+  data: function(track, {
+    generateContributionLinks,
+  }) {
+    return {
+      color: track.color,
+      duration: track.duration ?? 0,
+      linkData: u_link.track.data(track),
+
+      showArtists:
+        !compareArrays(
+          track.artistContribs.map(c => c.who),
+          track.album.artistContribs.map(c => c.who),
+          {checkOrder: false}),
+
+      contributionLinksData:
+        generateContributionLinks.data(track.artistContribs, {
+          showContribution: false,
+          showIcons: false,
+        }),
+    };
+  },
 
-  return html.tag('li',
-    {style: getLinkThemeString(data.color)},
-    (!data.showArtists
-      ? language.$('trackList.item.withDuration', stringOpts)
-      : language.$('trackList.item.withDuration.withArtists', {
-          ...stringOpts,
-          by:
-            html.tag('span', {class: 'by'},
-              language.$('trackList.item.withArtists.by', {
-                artists: generateContributionLinks(data.contributionLinksData),
+  generate: function generateTrackListItem(data, {
+    generateContributionLinks,
+
+    getLinkThemeString,
+    html,
+    language,
+    link,
+  }) {
+    const stringOpts = {
+      duration: language.formatDuration(data.duration),
+      track: link.track(data.linkData),
+    };
+
+    return html.tag('li',
+      {style: getLinkThemeString(data.color)},
+      (!data.showArtists
+        ? language.$('trackList.item.withDuration', stringOpts)
+        : language.$('trackList.item.withDuration.withArtists', {
+            ...stringOpts,
+            by:
+              html.tag('span', {class: 'by'},
+                language.$('trackList.item.withArtists.by', {
+                  artists: generateContributionLinks(data.contributionLinksData),
+                })),
+          })));
+  },
+});
+
+/*
+const infoPage = {
+  page: () => {
+    return {
+      banner: !empty(album.bannerArtistContribs) && {
+        dimensions: album.bannerDimensions,
+        path: [
+          'media.albumBanner',
+          album.directory,
+          album.bannerFileExtension,
+        ],
+        alt: language.$('misc.alt.albumBanner'),
+        position: 'top',
+      },
+
+      cover: {
+        src: getAlbumCover(album),
+        alt: language.$('misc.alt.albumCover'),
+        artTags: album.artTags,
+      },
+
+      main: {
+        headingMode: 'sticky',
+
+        content: [
+          html.tag('p',
+            {
+              [html.onlyIfContent]: true,
+              [html.joinChildren]: '<br>',
+            },
+            [
+              !empty(album.artistContribs) &&
+                language.$('releaseInfo.by', {
+                  artists: getArtistString(album.artistContribs, {
+                    showContrib: true,
+                    showIcons: true,
+                  }),
+                }),
+
+              !empty(album.coverArtistContribs) &&
+                language.$('releaseInfo.coverArtBy', {
+                  artists: getArtistString(album.coverArtistContribs, {
+                    showContrib: true,
+                    showIcons: true,
+                  }),
+                }),
+
+              !empty(album.wallpaperArtistContribs) &&
+                language.$('releaseInfo.wallpaperArtBy', {
+                  artists: getArtistString(album.wallpaperArtistContribs, {
+                    showContrib: true,
+                    showIcons: true,
+                  }),
+                }),
+
+              !empty(album.bannerArtistContribs) &&
+                language.$('releaseInfo.bannerArtBy', {
+                  artists: getArtistString(album.bannerArtistContribs, {
+                    showContrib: true,
+                    showIcons: true,
+                  }),
+                }),
+
+              album.date &&
+                language.$('releaseInfo.released', {
+                  date: language.formatDate(album.date),
+                }),
+
+              album.hasCoverArt &&
+              album.coverArtDate &&
+              +album.coverArtDate !== +album.date &&
+                language.$('releaseInfo.artReleased', {
+                  date: language.formatDate(album.coverArtDate),
+                }),
+
+              albumDuration > 0 &&
+                language.$('releaseInfo.duration', {
+                  duration: language.formatDuration(albumDuration, {
+                    approximate: album.tracks.length > 1,
+                  }),
+                }),
+            ]),
+
+          html.tag('p',
+            {
+              [html.onlyIfContent]: true,
+              [html.joinChildren]: '<br>',
+            },
+            [
+              hasAdditionalFiles &&
+                generateAdditionalFilesShortcut(album.additionalFiles),
+
+              checkGalleryPage(album) &&
+                language.$('releaseInfo.viewGallery', {
+                  link: link.albumGallery(album, {
+                    text: language.$('releaseInfo.viewGallery.link'),
+                  }),
+                }),
+
+              checkCommentaryPage(album) &&
+                language.$('releaseInfo.viewCommentary', {
+                  link: link.albumCommentary(album, {
+                    text: language.$('releaseInfo.viewCommentary.link'),
+                  }),
+                }),
+            ]),
+
+          !empty(album.urls) &&
+            html.tag('p',
+              language.$('releaseInfo.listenOn', {
+                links: language.formatDisjunctionList(
+                  album.urls.map(url => fancifyURL(url, {album: true}))
+                ),
               })),
-        })));
-}
 
-u_generateTrackListItem.data = track => {
-  return {
-    color: track.color,
-    duration: track.duration ?? 0,
-    linkData: u_link.track.data(track),
-
-    showArtists:
-      !compareArrays(
-        track.artistContribs.map((c) => c.who),
-        track.album.artistContribs.map((c) => c.who),
-        {checkOrder: false}),
-
-    contributionLinksData:
-      u_generateContributionLinks.data(track.artistContribs, {
-        showContribution: false,
-        showIcons: false,
+          displayTrackSections &&
+          !empty(album.trackSections) &&
+            html.tag('dl',
+              {class: 'album-group-list'},
+              album.trackSections.flatMap(({
+                name,
+                startIndex,
+                tracks,
+              }) => [
+                html.tag('dt',
+                  {class: ['content-heading']},
+                  language.$('trackList.section.withDuration', {
+                    duration: language.formatDuration(getTotalDuration(tracks), {
+                      approximate: tracks.length > 1,
+                    }),
+                    section: name,
+                  })),
+                html.tag('dd',
+                  html.tag(listTag,
+                    listTag === 'ol' ? {start: startIndex + 1} : {},
+                    tracks.map(trackToListItem))),
+              ])),
+
+          !displayTrackSections &&
+          !empty(album.tracks) &&
+            html.tag(listTag,
+              album.tracks.map(trackToListItem)),
+
+          html.tag('p',
+            {
+              [html.onlyIfContent]: true,
+              [html.joinChildren]: '<br>',
+            },
+            [
+              album.dateAddedToWiki &&
+                language.$('releaseInfo.addedToWiki', {
+                  date: language.formatDate(
+                    album.dateAddedToWiki
+                  ),
+                })
+            ]),
+
+          ...html.fragment(
+            hasAdditionalFiles && [
+              generateContentHeading({
+                id: 'additional-files',
+                title: language.$('releaseInfo.additionalFiles.heading', {
+                  additionalFiles: language.countAdditionalFiles(numAdditionalFiles, {
+                    unit: true,
+                  }),
+                }),
+              }),
+
+              generateAlbumAdditionalFilesList(album, album.additionalFiles, {
+                generateAdditionalFilesList,
+                getSizeOfAdditionalFile,
+                link,
+                urls,
+              }),
+            ]),
+
+          ...html.fragment(
+            album.commentary && [
+              generateContentHeading({
+                id: 'artist-commentary',
+                title: language.$('releaseInfo.artistCommentary'),
+              }),
+
+              html.tag('blockquote', transformMultiline(album.commentary)),
+            ]),
+        ],
+      },
+
+      sidebarLeft: generateAlbumSidebar(album, null, {
+        fancifyURL,
+        getLinkThemeString,
+        html,
+        link,
+        language,
+        transformMultiline,
+        wikiData,
       }),
-  };
+
+      nav: {
+        linkContainerClasses: ['nav-links-hierarchy'],
+        links: [
+          {toHome: true},
+          {
+            html: language.$('albumPage.nav.album', {
+              album: link.album(album, {class: 'current'}),
+            }),
+          },
+          {
+            divider: false,
+            html: generateAlbumNavLinks(album, null, {
+              generateNavigationLinks,
+              html,
+              language,
+              link,
+            }),
+          }
+        ],
+        content: generateAlbumChronologyLinks(album, null, {
+          generateChronologyLinks,
+          html,
+        }),
+      },
+
+      secondaryNav: generateAlbumSecondaryNav(album, null, {
+        getLinkThemeString,
+        html,
+        language,
+        link,
+      }),
+    };
+  },
 };
+*/
 
 /*
 export function write(album, {wikiData}) {
@@ -216,279 +626,6 @@ export function write(album, {wikiData}) {
     }),
   };
 
-  const infoPage = {
-    type: 'page',
-    path: ['album', album.directory],
-    page: ({
-    }) => {
-      const trackToListItem = bindOpts(unbound_trackToListItem, {
-        getArtistString,
-        getLinkThemeString,
-        html,
-        language,
-        link,
-      });
-
-      return {
-        title: language.$('albumPage.title', {album: album.name}),
-        stylesheet: getAlbumStylesheet(album),
-
-        themeColor: album.color,
-        theme:
-          getThemeString(album.color, {
-            additionalVariables: [
-              `--album-directory: ${album.directory}`,
-            ],
-          }),
-
-        socialEmbed: {
-          heading:
-            (empty(album.groups)
-              ? ''
-              : language.$('albumPage.socialEmbed.heading', {
-                  group: album.groups[0].name,
-                })),
-          headingLink:
-            (empty(album.groups)
-              ? null
-              : absoluteTo('localized.album', album.groups[0].directory)),
-          title: language.$('albumPage.socialEmbed.title', {
-            album: album.name,
-          }),
-          description: getSocialEmbedDescription({getArtistString, language}),
-          image: '/' + getAlbumCover(album, {to: urls.from('shared.root').to}),
-          color: album.color,
-        },
-
-        banner: !empty(album.bannerArtistContribs) && {
-          dimensions: album.bannerDimensions,
-          path: [
-            'media.albumBanner',
-            album.directory,
-            album.bannerFileExtension,
-          ],
-          alt: language.$('misc.alt.albumBanner'),
-          position: 'top',
-        },
-
-        cover: {
-          src: getAlbumCover(album),
-          alt: language.$('misc.alt.albumCover'),
-          artTags: album.artTags,
-        },
-
-        main: {
-          headingMode: 'sticky',
-
-          content: [
-            html.tag('p',
-              {
-                [html.onlyIfContent]: true,
-                [html.joinChildren]: '<br>',
-              },
-              [
-                !empty(album.artistContribs) &&
-                  language.$('releaseInfo.by', {
-                    artists: getArtistString(album.artistContribs, {
-                      showContrib: true,
-                      showIcons: true,
-                    }),
-                  }),
-
-                !empty(album.coverArtistContribs) &&
-                  language.$('releaseInfo.coverArtBy', {
-                    artists: getArtistString(album.coverArtistContribs, {
-                      showContrib: true,
-                      showIcons: true,
-                    }),
-                  }),
-
-                !empty(album.wallpaperArtistContribs) &&
-                  language.$('releaseInfo.wallpaperArtBy', {
-                    artists: getArtistString(album.wallpaperArtistContribs, {
-                      showContrib: true,
-                      showIcons: true,
-                    }),
-                  }),
-
-                !empty(album.bannerArtistContribs) &&
-                  language.$('releaseInfo.bannerArtBy', {
-                    artists: getArtistString(album.bannerArtistContribs, {
-                      showContrib: true,
-                      showIcons: true,
-                    }),
-                  }),
-
-                album.date &&
-                  language.$('releaseInfo.released', {
-                    date: language.formatDate(album.date),
-                  }),
-
-                album.hasCoverArt &&
-                album.coverArtDate &&
-                +album.coverArtDate !== +album.date &&
-                  language.$('releaseInfo.artReleased', {
-                    date: language.formatDate(album.coverArtDate),
-                  }),
-
-                albumDuration > 0 &&
-                  language.$('releaseInfo.duration', {
-                    duration: language.formatDuration(albumDuration, {
-                      approximate: album.tracks.length > 1,
-                    }),
-                  }),
-              ]),
-
-            html.tag('p',
-              {
-                [html.onlyIfContent]: true,
-                [html.joinChildren]: '<br>',
-              },
-              [
-                hasAdditionalFiles &&
-                  generateAdditionalFilesShortcut(album.additionalFiles),
-
-                checkGalleryPage(album) &&
-                  language.$('releaseInfo.viewGallery', {
-                    link: link.albumGallery(album, {
-                      text: language.$('releaseInfo.viewGallery.link'),
-                    }),
-                  }),
-
-                checkCommentaryPage(album) &&
-                  language.$('releaseInfo.viewCommentary', {
-                    link: link.albumCommentary(album, {
-                      text: language.$('releaseInfo.viewCommentary.link'),
-                    }),
-                  }),
-              ]),
-
-            !empty(album.urls) &&
-              html.tag('p',
-                language.$('releaseInfo.listenOn', {
-                  links: language.formatDisjunctionList(
-                    album.urls.map(url => fancifyURL(url, {album: true}))
-                  ),
-                })),
-
-            displayTrackSections &&
-            !empty(album.trackSections) &&
-              html.tag('dl',
-                {class: 'album-group-list'},
-                album.trackSections.flatMap(({
-                  name,
-                  startIndex,
-                  tracks,
-                }) => [
-                  html.tag('dt',
-                    {class: ['content-heading']},
-                    language.$('trackList.section.withDuration', {
-                      duration: language.formatDuration(getTotalDuration(tracks), {
-                        approximate: tracks.length > 1,
-                      }),
-                      section: name,
-                    })),
-                  html.tag('dd',
-                    html.tag(listTag,
-                      listTag === 'ol' ? {start: startIndex + 1} : {},
-                      tracks.map(trackToListItem))),
-                ])),
-
-            !displayTrackSections &&
-            !empty(album.tracks) &&
-              html.tag(listTag,
-                album.tracks.map(trackToListItem)),
-
-            html.tag('p',
-              {
-                [html.onlyIfContent]: true,
-                [html.joinChildren]: '<br>',
-              },
-              [
-                album.dateAddedToWiki &&
-                  language.$('releaseInfo.addedToWiki', {
-                    date: language.formatDate(
-                      album.dateAddedToWiki
-                    ),
-                  })
-              ]),
-
-            ...html.fragment(
-              hasAdditionalFiles && [
-                generateContentHeading({
-                  id: 'additional-files',
-                  title: language.$('releaseInfo.additionalFiles.heading', {
-                    additionalFiles: language.countAdditionalFiles(numAdditionalFiles, {
-                      unit: true,
-                    }),
-                  }),
-                }),
-
-                generateAlbumAdditionalFilesList(album, album.additionalFiles, {
-                  generateAdditionalFilesList,
-                  getSizeOfAdditionalFile,
-                  link,
-                  urls,
-                }),
-              ]),
-
-            ...html.fragment(
-              album.commentary && [
-                generateContentHeading({
-                  id: 'artist-commentary',
-                  title: language.$('releaseInfo.artistCommentary'),
-                }),
-
-                html.tag('blockquote', transformMultiline(album.commentary)),
-              ]),
-          ],
-        },
-
-        sidebarLeft: generateAlbumSidebar(album, null, {
-          fancifyURL,
-          getLinkThemeString,
-          html,
-          link,
-          language,
-          transformMultiline,
-          wikiData,
-        }),
-
-        nav: {
-          linkContainerClasses: ['nav-links-hierarchy'],
-          links: [
-            {toHome: true},
-            {
-              html: language.$('albumPage.nav.album', {
-                album: link.album(album, {class: 'current'}),
-              }),
-            },
-            {
-              divider: false,
-              html: generateAlbumNavLinks(album, null, {
-                generateNavigationLinks,
-                html,
-                language,
-                link,
-              }),
-            }
-          ],
-          content: generateAlbumChronologyLinks(album, null, {
-            generateChronologyLinks,
-            html,
-          }),
-        },
-
-        secondaryNav: generateAlbumSecondaryNav(album, null, {
-          getLinkThemeString,
-          html,
-          language,
-          link,
-        }),
-      };
-    },
-  };
-
   // TODO: only gen if there are any tracks with art
   const galleryPage = {
     type: 'page',