« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/content/dependencies/generateAlbumReleaseInfo.js88
-rw-r--r--src/content/dependencies/generateArtistInfoPage.js21
-rw-r--r--src/content/dependencies/generateExternalLinksLineOrList.js99
-rw-r--r--src/content/dependencies/generateListenLineOrList.js (renamed from src/content/dependencies/generateReleaseInfoListenLine.js)40
-rw-r--r--src/content/dependencies/generateReleaseInfoBlock.js64
-rw-r--r--src/content/dependencies/generateTrackReleaseInfo.js92
-rw-r--r--src/external-links.js2
-rw-r--r--src/strings-default.yaml13
8 files changed, 277 insertions, 142 deletions
diff --git a/src/content/dependencies/generateAlbumReleaseInfo.js b/src/content/dependencies/generateAlbumReleaseInfo.js
index 4cec4120..38cd1c05 100644
--- a/src/content/dependencies/generateAlbumReleaseInfo.js
+++ b/src/content/dependencies/generateAlbumReleaseInfo.js
@@ -1,17 +1,16 @@
 import {accumulateSum, empty} from '#sugar';
 
 export default {
-  relations(relation, album) {
-    const relations = {};
+  relations: (relation, album) => ({
+    block:
+      relation('generateReleaseInfoBlock'),
 
-    relations.artistContributionsLine =
-      relation('generateReleaseInfoContributionsLine', album.artistContribs);
+    artistContributionsLine:
+      relation('generateReleaseInfoContributionsLine', album.artistContribs),
 
-    relations.listenLine =
-      relation('generateReleaseInfoListenLine', album);
-
-    return relations;
-  },
+    listenLineOrList:
+      relation('generateListenLineOrList', album),
+  }),
 
   data(album) {
     const data = {};
@@ -45,44 +44,37 @@ export default {
   generate: (data, relations, {html, language}) =>
     language.encapsulate('releaseInfo', capsule =>
       html.tags([
-        html.tag('p',
-          {[html.onlyIfContent]: true},
-          {[html.joinChildren]: html.tag('br')},
-
-          [
-            relations.artistContributionsLine.slots({
-              stringKey: capsule + '.by',
-              featuringStringKey: capsule + '.by.featuring',
-              chronologyKind: 'album',
-            }),
-
-            language.$(capsule, 'released', {
-              [language.onlyIfOptions]: ['date'],
-              date: language.formatDate(data.date),
-            }),
-
-            language.$(capsule, 'duration', {
-              [language.onlyIfOptions]: ['duration'],
-              duration:
-                language.formatDuration(data.duration, {
-                  approximate: data.durationApproximate,
-                }),
-            }),
-          ]),
-
-        html.tag('p',
-          {[html.onlyIfContent]: true},
-
-          relations.listenLine.slots({
-            context: [
-              'album',
-
-              (data.numTracks === 0
-                ? 'albumNoTracks'
-             : data.numTracks === 1
-                ? 'albumOneTrack'
-                : 'albumMultipleTracks'),
-            ],
-          })),
+        relations.block.slot('items', [
+          relations.artistContributionsLine.slots({
+            stringKey: capsule + '.by',
+            featuringStringKey: capsule + '.by.featuring',
+            chronologyKind: 'album',
+          }),
+
+          language.$(capsule, 'released', {
+            [language.onlyIfOptions]: ['date'],
+            date: language.formatDate(data.date),
+          }),
+
+          language.$(capsule, 'duration', {
+            [language.onlyIfOptions]: ['duration'],
+            duration:
+              language.formatDuration(data.duration, {
+                approximate: data.durationApproximate,
+              }),
+          }),
+        ]),
+
+        relations.listenLineOrList.slots({
+          context: [
+            'album',
+
+            (data.numTracks === 0
+              ? 'albumNoTracks'
+           : data.numTracks === 1
+              ? 'albumOneTrack'
+              : 'albumMultipleTracks'),
+          ],
+        }),
       ])),
 };
diff --git a/src/content/dependencies/generateArtistInfoPage.js b/src/content/dependencies/generateArtistInfoPage.js
index 45b111ed..ae21b361 100644
--- a/src/content/dependencies/generateArtistInfoPage.js
+++ b/src/content/dependencies/generateArtistInfoPage.js
@@ -60,9 +60,8 @@ export default {
       query.aliasLinkedGroups
         .map(({group}) => relation('linkGroup', group)),
 
-    visitLinks:
-      artist.urls
-        .map(entry => relation('linkExternal', entry)),
+    externalLinksLineOrList:
+      relation('generateExternalLinksLineOrList', artist.urls),
 
     tracksChunkedList:
       relation('generateArtistInfoPageTracksChunkedList', artist),
@@ -182,17 +181,11 @@ export default {
               }),
             ])),
 
-          html.tag('p',
-            {[html.onlyIfContent]: true},
-
-            language.$('releaseInfo.visitOn', {
-              [language.onlyIfOptions]: ['links'],
-
-              links:
-                language.formatDisjunctionList(
-                  relations.visitLinks
-                    .map(link => link.slot('context', 'artist'))),
-            })),
+          relations.externalLinksLineOrList.slots({
+            string: 'releaseInfo.visitOn',
+            maximumTotalEntriesInLine: 5,
+            maximumAnnotatedEntriesInLine: 3,
+          }),
 
           html.tag('p',
             {[html.onlyIfContent]: true},
diff --git a/src/content/dependencies/generateExternalLinksLineOrList.js b/src/content/dependencies/generateExternalLinksLineOrList.js
new file mode 100644
index 00000000..18922db6
--- /dev/null
+++ b/src/content/dependencies/generateExternalLinksLineOrList.js
@@ -0,0 +1,99 @@
+import {isExternalLinkContext} from '#external-links';
+import {empty, stitchArrays} from '#sugar';
+
+export default {
+  relations: (relation, urlEntries) => ({
+    externalLinks:
+      urlEntries.map(entry => relation('linkExternal', entry)),
+  }),
+
+  data: (urlEntries) => ({
+    totalEntries:
+      urlEntries.length,
+
+    annotatedEntries:
+      urlEntries.filter(entry => entry.annotation).length,
+  }),
+
+  slots: {
+    string: {type: 'string'},
+
+    context: {
+      validate: () => isExternalLinkContext,
+      default: 'generic',
+    },
+
+    contexts: {
+      validate: v => v.strictArrayOf(isExternalLinkContext),
+      default: [],
+    },
+
+    maximumTotalEntriesInLine: {type: 'number', default: 4},
+    maximumAnnotatedEntriesInLine: {type: 'number', default: 1},
+    maximumParenthesizedEntriesInLine: {type: 'number', default: 3},
+  },
+
+  generate(data, relations, slots, {html, language}) {
+    const {externalLinks} = relations;
+
+    if (empty(externalLinks)) {
+      return html.blank();
+    }
+
+    if (!html.isBlank(slots.contexts)) {
+      stitchArrays({
+        link: externalLinks,
+        linkContext: slots.contexts,
+      }).forEach(({link, linkContext}) => {
+          link.setSlot('context', [slots.context, linkContext].flat(Infinity));
+        });
+    } else {
+      externalLinks.forEach(link => {
+        link.setSlot('context', slots.context);
+      });
+    }
+
+    let style = 'line';
+
+    if (data.totalEntries > slots.maximumTotalEntriesInLine) {
+      style = 'list';
+    }
+
+    if (data.annotatedEntries > slots.maximumAnnotatedEntriesInLine) {
+      style = 'list';
+    }
+
+    let parenthesizedEntries = 0;
+    for (const item of externalLinks) {
+      const plainText = html.resolve(item, {normalize: 'plain'});
+      if (plainText.endsWith(')')) {
+        parenthesizedEntries++;
+      }
+    }
+
+    if (parenthesizedEntries > slots.maximumParenthesizedEntriesInLine) {
+      style = 'list';
+    }
+
+    switch (style) {
+      case 'line': return (
+        html.tag('p',
+          language.$(slots.string, {
+            links:
+              language.formatDisjunctionList(externalLinks),
+          }))
+      );
+
+      case 'list': return (
+        html.tags([
+          html.tag('p', language.$(slots.string, 'title')),
+          html.tag('ul',
+            externalLinks.map(link => html.tag('li', link))),
+        ])
+      );
+
+      default:
+        return html.blank();
+    }
+  },
+};
diff --git a/src/content/dependencies/generateReleaseInfoListenLine.js b/src/content/dependencies/generateListenLineOrList.js
index 54e7985f..f0d8dae9 100644
--- a/src/content/dependencies/generateReleaseInfoListenLine.js
+++ b/src/content/dependencies/generateListenLineOrList.js
@@ -1,5 +1,5 @@
 import {isExternalLinkContext} from '#external-links';
-import {empty, stitchArrays, unique} from '#sugar';
+import {empty, unique} from '#sugar';
 
 function getReleaseContext(urlString, {
   _artistURLs,
@@ -13,14 +13,14 @@ function getReleaseContext(urlString, {
   const url = new URL(urlString);
 
   if (url.hostname === 'homestuck.bandcamp.com') {
-    return 'officialRelease';
+    return ['officialRelease'];
   }
 
   if (composerBandcampDomains.includes(url.hostname)) {
-    return 'composerRelease';
+    return ['composerRelease'];
   }
 
-  return null;
+  return [];
 }
 
 export default {
@@ -63,9 +63,8 @@ export default {
   },
 
   relations: (relation, query, _thing) => ({
-    links:
-      query.urls
-        .map(entry => relation('linkExternal', entry)),
+    externalLinksLineOrList:
+      relation('generateExternalLinksLineOrList', query.urls),
   }),
 
   data(query, thing) {
@@ -73,6 +72,8 @@ export default {
 
     data.name = thing.name;
 
+    data.noLinks = empty(query.urls);
+
     const artistURLs =
       unique([
         ...query.artists.flatMap(artist => artist.urls),
@@ -108,7 +109,7 @@ export default {
       presentAlbumReleaseContexts.length <= 1
     ) {
       releaseContexts =
-        query.urls.map(() => null);
+        query.urls.map(() => []);
     }
 
     data.releaseContexts = releaseContexts;
@@ -130,28 +131,15 @@ export default {
 
   generate: (data, relations, slots, {html, language}) =>
     language.encapsulate('releaseInfo.listenOn', capsule =>
-      (empty(relations.links) && slots.visibleWithoutLinks
+      (data.noLinks && slots.visibleWithoutLinks
         ? language.$(capsule, 'noLinks', {
             name:
               html.tag('i', data.name),
           })
 
-        : language.$('releaseInfo.listenOn', {
-            [language.onlyIfOptions]: ['links'],
-
-            links:
-              language.formatDisjunctionList(
-                stitchArrays({
-                  link: relations.links,
-                  releaseContext: data.releaseContexts,
-                }).map(({link, releaseContext}) =>
-                    link.slot('context', [
-                      ...
-                      (Array.isArray(slots.context)
-                        ? slots.context
-                        : [slots.context]),
-
-                      releaseContext,
-                    ]))),
+        : relations.externalLinksLineOrList.slots({
+            string: capsule,
+            context: slots.context,
+            contexts: data.releaseContexts,
           }))),
 };
diff --git a/src/content/dependencies/generateReleaseInfoBlock.js b/src/content/dependencies/generateReleaseInfoBlock.js
new file mode 100644
index 00000000..93d889ab
--- /dev/null
+++ b/src/content/dependencies/generateReleaseInfoBlock.js
@@ -0,0 +1,64 @@
+import {empty} from '#sugar';
+
+export default {
+  slots: {
+    // This isn't mutable, but we will be inspecting items' contents.
+    items: {validate: v => v.looseArrayOf(v.isHTML)},
+  },
+
+  generate(slots, {html}) {
+    const tags = [];
+
+    let paragraphLines = [];
+    const closeParagraph = () => {
+      if (empty(paragraphLines)) return;
+
+      const paragraph =
+        html.tag('p',
+          {[html.joinChildren]: html.tag('br')},
+          {[html.onlyIfContent]: true},
+            paragraphLines);
+
+      tags.push(paragraph);
+      paragraphLines = [];
+    };
+
+    for (let item of slots.items) {
+      item = html.Template.resolve(item);
+
+      if (typeof item === 'string' && item.length) {
+        paragraphLines.push(item);
+        continue;
+      }
+
+      if (html.isBlank(item)) {
+        continue;
+      }
+
+      if (item.contentOnly) {
+        paragraphLines.push(item);
+        continue;
+      }
+
+      if (item.tagName === 'br') {
+        continue;
+      }
+
+      if (item.tagName === 'p') {
+        paragraphLines.push(item.content);
+        continue;
+      }
+
+      closeParagraph();
+      tags.push(item);
+    }
+
+    closeParagraph();
+
+    if (empty(tags)) {
+      return html.blank();
+    } else {
+      return html.tags(tags);
+    }
+  }
+};
diff --git a/src/content/dependencies/generateTrackReleaseInfo.js b/src/content/dependencies/generateTrackReleaseInfo.js
index 0207e574..001f5a54 100644
--- a/src/content/dependencies/generateTrackReleaseInfo.js
+++ b/src/content/dependencies/generateTrackReleaseInfo.js
@@ -1,22 +1,21 @@
 import {compareArrays} from '#sugar';
 
 export default {
-  relations(relation, track) {
-    const relations = {};
+  relations: (relation, track) => ({
+    block:
+      relation('generateReleaseInfoBlock'),
 
-    relations.artistContributionsLine =
+    artistContributionsLine:
       relation('generateReleaseInfoContributionsLine',
         track.artistContribs,
-        track.artistText);
+        track.artistText),
 
-    relations.listenLine =
-      relation('generateReleaseInfoListenLine', track);
+    listenLineOrList:
+      relation('generateListenLineOrList', track),
 
-    relations.albumLink =
-      relation('linkAlbum', track.album);
-
-    return relations;
-  },
+    albumLink:
+      relation('linkAlbum', track.album),
+  }),
 
   data(track) {
     const data = {};
@@ -48,43 +47,38 @@ export default {
   generate: (data, relations, {html, language}) =>
     language.encapsulate('releaseInfo', capsule =>
       html.tags([
-        html.tag('p',
-          {[html.onlyIfContent]: true},
-          {[html.joinChildren]: html.tag('br')},
-
-          [
-            language.encapsulate(capsule, 'by', capsule => {
-              const withAlbum =
-                (data.showAlbum ? '.withAlbum' : '');
-
-              const albumOptions =
-                (data.showAlbum ? {album: relations.albumLink} : {});
-
-              return relations.artistContributionsLine.slots({
-                stringKey: capsule + withAlbum,
-                featuringStringKey: capsule + '.featuring' + withAlbum,
-
-                additionalStringOptions: albumOptions,
-
-                chronologyKind: 'track',
-              });
-            }),
-
-            language.$(capsule, 'released', {
-              [language.onlyIfOptions]: ['date'],
-              date: language.formatDate(data.date),
-            }),
-
-            language.$(capsule, 'duration', {
-              [language.onlyIfOptions]: ['duration'],
-              duration: language.formatDuration(data.duration),
-            }),
-          ]),
-
-        html.tag('p',
-          relations.listenLine.slots({
-            visibleWithoutLinks: true,
-            context: ['track'],
-          })),
+        relations.block.slot('items', [
+          language.encapsulate(capsule, 'by', capsule => {
+            const withAlbum =
+              (data.showAlbum ? '.withAlbum' : '');
+
+            const albumOptions =
+              (data.showAlbum ? {album: relations.albumLink} : {});
+
+            return relations.artistContributionsLine.slots({
+              stringKey: capsule + withAlbum,
+              featuringStringKey: capsule + '.featuring' + withAlbum,
+
+              additionalStringOptions: albumOptions,
+
+              chronologyKind: 'track',
+            });
+          }),
+
+          language.$(capsule, 'released', {
+            [language.onlyIfOptions]: ['date'],
+            date: language.formatDate(data.date),
+          }),
+
+          language.$(capsule, 'duration', {
+            [language.onlyIfOptions]: ['duration'],
+            duration: language.formatDuration(data.duration),
+          }),
+        ]),
+
+        relations.listenLineOrList.slots({
+          visibleWithoutLinks: true,
+          context: 'track',
+        }),
       ])),
 };
diff --git a/src/external-links.js b/src/external-links.js
index 847970b8..409ff503 100644
--- a/src/external-links.js
+++ b/src/external-links.js
@@ -939,7 +939,7 @@ export function getExternalLinkStringOfStyleFromDescriptor(urlEntry, style, desc
 
     if (typeof descriptor.detail === 'string') {
       return language.$(prefix, descriptor.platform, descriptor.detail);
-    } else if (descriptor.detial.substring) {
+    } else if (descriptor.detail.substring) {
       const {substring, ...rest} = descriptor.detail;
 
       const opts =
diff --git a/src/strings-default.yaml b/src/strings-default.yaml
index 458e85e7..227bcac2 100644
--- a/src/strings-default.yaml
+++ b/src/strings-default.yaml
@@ -370,14 +370,19 @@ releaseInfo:
     sizeWarning: >-
       (Heads up! If you're on a mobile plan, this is a large download.)
 
-  listenOn:
-    _: "Listen on {LINKS}."
-    noLinks: >-
-      This wiki doesn't have any listening links for {NAME}.
+  listenOn: "Listen on {LINKS}."
+  listenOn.title: "Listen on:"
+  listenOn.noLinks: >-
+    This wiki doesn't have any listening links for {NAME}.
 
   visitOn: "Visit on {LINKS}."
+  visitOn.title: "Visit on:"
+
   playOn: "Play on {LINKS}."
+  playOn.title: "Play on:"
+
   watchOn: "Watch on {LINKS}."
+  watchOn.title: "Watch on:"
 
   readCommentary:
     _: "Read {LINK}."