« 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/common-util/wiki-data.js5
-rw-r--r--src/content/dependencies/generateIntrapageDotSwitcher.js2
-rw-r--r--src/content/dependencies/generateLyricsSection.js79
-rw-r--r--src/content/dependencies/generateLyricsSwitcher.js49
-rw-r--r--src/data/checks.js29
-rw-r--r--src/data/composite/wiki-data/withParsedLyricsEntries.js35
-rw-r--r--src/static/css/site.css1
-rw-r--r--src/validators.js12
8 files changed, 131 insertions, 81 deletions
diff --git a/src/common-util/wiki-data.js b/src/common-util/wiki-data.js
index 4bbef8ab..0aa18ddb 100644
--- a/src/common-util/wiki-data.js
+++ b/src/common-util/wiki-data.js
@@ -102,6 +102,11 @@ export const commentaryRegexCaseSensitive =
 export const commentaryRegexCaseSensitiveOneShot =
   new RegExp(commentaryRegexRaw);
 
+// The #validators function isOldStyleLyrics() describes
+// what this regular expression detects.
+export const oldStyleLyricsDetectionRegex =
+  /^<i>.*:<\/i>/m;
+
 export function filterAlbumsByCommentary(albums) {
   return albums
     .filter((album) => [album, ...album.tracks].some((x) => x.commentary));
diff --git a/src/content/dependencies/generateIntrapageDotSwitcher.js b/src/content/dependencies/generateIntrapageDotSwitcher.js
index 3f300676..1d58367d 100644
--- a/src/content/dependencies/generateIntrapageDotSwitcher.js
+++ b/src/content/dependencies/generateIntrapageDotSwitcher.js
@@ -42,6 +42,8 @@ export default {
         }).map(({title, targetID}) =>
             html.tag('a', {href: '#'},
               {'data-target-id': targetID},
+              {[html.onlyIfContent]: true},
+
               language.sanitize(title))),
     }),
 };
diff --git a/src/content/dependencies/generateLyricsSection.js b/src/content/dependencies/generateLyricsSection.js
index 7e7718c7..f6b719a9 100644
--- a/src/content/dependencies/generateLyricsSection.js
+++ b/src/content/dependencies/generateLyricsSection.js
@@ -1,8 +1,10 @@
+import {stitchArrays} from '#sugar';
+
 export default {
   contentDependencies: [
     'generateContentHeading',
+    'generateIntrapageDotSwitcher',
     'generateLyricsEntry',
-    'generateLyricsSwitcher',
     'transformContent',
   ],
 
@@ -13,30 +15,67 @@ export default {
       relation('generateContentHeading'),
 
     switcher:
-      relation('generateLyricsSwitcher', entries),
+      relation('generateIntrapageDotSwitcher'),
 
     entries:
       entries
         .map(entry => relation('generateLyricsEntry', entry)),
+
+    annotations:
+      entries
+        .map(entry => entry.annotation)
+        .map(annotation => relation('transformContent', annotation)),
   }),
 
-  generate: (relations, {html, language}) =>
-    html.tags([
-      relations.heading
-        .slots({
-          attributes: {id: 'lyrics'},
-          title: language.$('releaseInfo.lyrics'),
-        }),
-
-      relations.switcher,
-
-      relations.entries
-        .map((entry, index) =>
-          entry.slots({
-            attributes: [
-              index >= 1 &&
-                {style: 'display: none'},
-            ],
+  data: (entries) => ({
+    ids:
+      Array.from(
+        {length: entries.length},
+        (_, index) => 'lyrics-entry-' + index),
+  }),
+
+  generate: (data, relations, {html, language}) =>
+    language.encapsulate('releaseInfo.lyrics', capsule =>
+      html.tags([
+        relations.heading
+          .slots({
+            attributes: {id: 'lyrics'},
+            title: language.$(capsule),
+          }),
+
+        html.tag('p', {class: 'lyrics-switcher'},
+          {[html.onlyIfContent]: true},
+
+          language.$(capsule, 'switcher', {
+            [language.onlyIfOptions]: ['entries'],
+
+            entries:
+              relations.switcher.slots({
+                initialOptionIndex: 0,
+
+                titles:
+                  relations.annotations.map(annotation =>
+                    annotation.slots({
+                      mode: 'inline',
+                      textOnly: true,
+                    })),
+
+                targetIDs:
+                  data.ids,
+              }),
           })),
-    ]),
+
+        stitchArrays({
+          entry: relations.entries,
+          id: data.ids,
+        }).map(({entry, id}, index) =>
+            entry.slots({
+              attributes: [
+                {id},
+
+                index >= 1 &&
+                  {style: 'display: none'},
+              ],
+            })),
+      ])),
 };
diff --git a/src/content/dependencies/generateLyricsSwitcher.js b/src/content/dependencies/generateLyricsSwitcher.js
deleted file mode 100644
index 1c9ee6a3..00000000
--- a/src/content/dependencies/generateLyricsSwitcher.js
+++ /dev/null
@@ -1,49 +0,0 @@
-export default {
-  contentDependencies: ['transformContent'],
-  extraDependencies: ['html', 'language'],
-
-  relations: (relation, entries) => ({
-    annotations:
-      entries
-        .map(entry => entry.annotation)
-        .map(annotation => relation('transformContent', annotation)),
-  }),
-
-  slots: {
-    tag: {type: 'string', default: 'p'},
-  },
-
-  generate: (relations, slots, {html, language}) =>
-    html.tag(slots.tag, {class: 'lyrics-switcher'},
-      language.$('releaseInfo.lyrics.switcher', {
-        entries:
-          language.formatListWithoutSeparator(
-            relations.annotations
-              .map((annotation, index) =>
-                html.tag('span', {[html.joinChildren]: ''}, [
-                  html.tag('a',
-                    {href: '#'},
-
-                    index === 0 &&
-                      {style: 'display: none'},
-
-                    annotation
-                      .slots({
-                        mode: 'inline',
-                        textOnly: true,
-                      })),
-
-                  html.tag('a',
-                    {class: 'current'},
-
-                    index >= 1 &&
-                      {style: 'display: none'},
-
-                    annotation
-                      .slots({
-                        mode: 'inline',
-                        textOnly: true,
-                      })),
-                ]))),
-      })),
-};
diff --git a/src/data/checks.js b/src/data/checks.js
index b11b5d55..25863d2d 100644
--- a/src/data/checks.js
+++ b/src/data/checks.js
@@ -9,7 +9,6 @@ import {compareArrays, cut, cutStart, empty, getNestedProp, iterateMultiline}
   from '#sugar';
 import Thing from '#thing';
 import thingConstructors from '#things';
-import {combineWikiDataArrays, commentaryRegexCaseSensitive} from '#wiki-data';
 
 import {
   annotateErrorWithIndex,
@@ -20,6 +19,12 @@ import {
   withAggregate,
 } from '#aggregate';
 
+import {
+  combineWikiDataArrays,
+  commentaryRegexCaseSensitive,
+  oldStyleLyricsDetectionRegex,
+} from '#wiki-data';
+
 function inspect(value, opts = {}) {
   return nodeInspect(value, {colors: ENABLE_COLOR, ...opts});
 }
@@ -568,6 +573,12 @@ export function reportContentTextErrors(wikiData, {
     annotation: 'commentary annotation',
   };
 
+  const newStyleLyricsShape = {
+    body: 'lyrics body',
+    artistDisplayText: 'lyrics artist display text',
+    annotation: 'lyrics annotation',
+  };
+
   const contentTextSpec = [
     ['albumData', {
       additionalFiles: additionalFileShape,
@@ -614,7 +625,7 @@ export function reportContentTextErrors(wikiData, {
       additionalFiles: additionalFileShape,
       commentary: commentaryShape,
       creditSources: commentaryShape,
-      lyrics: '_content',
+      lyrics: '_lyrics',
       midiProjectFiles: additionalFileShape,
       sheetMusicFiles: additionalFileShape,
     }],
@@ -737,8 +748,9 @@ export function reportContentTextErrors(wikiData, {
         for (const thing of things) {
           nest({message: `Content text errors in ${inspect(thing)}`}, ({nest, push}) => {
 
-            for (const [property, shape] of Object.entries(propSpec)) {
-              const value = thing[property];
+            for (let [property, shape] of Object.entries(propSpec)) {
+              const rawValue = CacheableObject.getUpdateValue(thing, property);
+              let value = thing[property];
 
               if (value === undefined) {
                 push(new TypeError(`Property ${colors.red(property)} isn't valid for ${colors.green(thing.constructor.name)}`));
@@ -749,6 +761,15 @@ export function reportContentTextErrors(wikiData, {
                 continue;
               }
 
+              if (shape === '_lyrics') {
+                if (oldStyleLyricsDetectionRegex.test(rawValue)) {
+                  value = rawValue;
+                  shape = '_content';
+                } else {
+                  shape = newStyleLyricsShape;
+                }
+              }
+
               const fieldPropertyMessage =
                 getFieldPropertyMessage(
                   thing.constructor[Thing.yamlDocumentSpec],
diff --git a/src/data/composite/wiki-data/withParsedLyricsEntries.js b/src/data/composite/wiki-data/withParsedLyricsEntries.js
index 28e4c9b5..d13bfbaa 100644
--- a/src/data/composite/wiki-data/withParsedLyricsEntries.js
+++ b/src/data/composite/wiki-data/withParsedLyricsEntries.js
@@ -1,8 +1,8 @@
 import {input, templateCompositeFrom} from '#composite';
-import find from '#find';
 import {stitchArrays} from '#sugar';
 import {isLyrics} from '#validators';
-import {commentaryRegexCaseSensitive} from '#wiki-data';
+import {commentaryRegexCaseSensitive, oldStyleLyricsDetectionRegex}
+  from '#wiki-data';
 
 import {
   fillMissingListItems,
@@ -11,10 +11,25 @@ import {
   withUnflattenedList,
 } from '#composite/data';
 
+import inputSoupyFind from './inputSoupyFind.js';
 import processContentEntryDates from './processContentEntryDates.js';
 import withParsedContentEntries from './withParsedContentEntries.js';
 import withResolvedReferenceList from './withResolvedReferenceList.js';
 
+function constituteLyricsEntry(text) {
+  return {
+    artists: [],
+    artistDisplayText: null,
+    annotation: null,
+    date: null,
+    secondDate: null,
+    dateKind: null,
+    accessDate: null,
+    accessKind: null,
+    body: text,
+  };
+}
+
 export default templateCompositeFrom({
   annotation: `withParsedLyricsEntries`,
 
@@ -25,6 +40,19 @@ export default templateCompositeFrom({
   outputs: ['#parsedLyricsEntries'],
 
   steps: () => [
+    {
+      dependencies: [input('from')],
+      compute: (continuation, {
+        [input('from')]: lyrics,
+      }) =>
+        (oldStyleLyricsDetectionRegex.test(lyrics)
+          ? continuation()
+          : continuation.raiseOutput({
+              ['#parsedLyricsEntries']:
+                [constituteLyricsEntry(lyrics)],
+            })),
+    },
+
     withParsedContentEntries({
       from: input('from'),
       caseSensitiveRegex: input.value(commentaryRegexCaseSensitive),
@@ -65,8 +93,7 @@ export default templateCompositeFrom({
 
     withResolvedReferenceList({
       list: '#flattenedList',
-      data: 'artistData',
-      find: input.value(find.artist),
+      find: inputSoupyFind.input('artist'),
       notFoundMode: input.value('null'),
     }),
 
diff --git a/src/static/css/site.css b/src/static/css/site.css
index 6b61af72..a4139624 100644
--- a/src/static/css/site.css
+++ b/src/static/css/site.css
@@ -1621,6 +1621,7 @@ p.content-heading:has(+ .commentary-entry-heading.dated) {
 
 .lyrics-entry {
   padding-left: 40px;
+  max-width: 600px;
 }
 
 .js-hide,
diff --git a/src/validators.js b/src/validators.js
index 5300d4ad..6badc93a 100644
--- a/src/validators.js
+++ b/src/validators.js
@@ -3,8 +3,12 @@ import {inspect as nodeInspect} from 'node:util';
 import {openAggregate, withAggregate} from '#aggregate';
 import {colors, ENABLE_COLOR} from '#cli';
 import {cut, empty, matchMultiline, typeAppearance} from '#sugar';
-import {commentaryRegexCaseInsensitive, commentaryRegexCaseSensitiveOneShot}
-  from '#wiki-data';
+
+import {
+  commentaryRegexCaseInsensitive,
+  commentaryRegexCaseSensitiveOneShot,
+  oldStyleLyricsDetectionRegex,
+} from '#wiki-data';
 
 function inspect(value) {
   return nodeInspect(value, {colors: ENABLE_COLOR});
@@ -371,9 +375,9 @@ export const isCommentary =
 export function isOldStyleLyrics(content) {
   isContentString(content);
 
-  if (/^<i>/m.test(content)) {
+  if (oldStyleLyricsDetectionRegex.test(content)) {
     throw new TypeError(
-      `Expected old-style lyrics block not to include <i> at start of any line`);
+      `Expected old-style lyrics block not to include "<i> ... :</i>" at start of any line`);
   }
 
   return true;