« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/content/dependencies/generateStaticPage.js11
-rw-r--r--src/content/dependencies/transformContent.js11
-rw-r--r--src/data/composite/things/album/withTrackSections.js4
-rw-r--r--src/data/composite/things/album/withTracks.js4
-rw-r--r--src/data/things/album.js5
-rw-r--r--src/data/things/static-page.js1
-rw-r--r--src/data/things/track.js13
-rw-r--r--src/data/yaml.js12
-rw-r--r--src/static/client3.js92
-rw-r--r--src/util/sugar.js35
-rw-r--r--tap-snapshots/test/snapshot/transformContent.js.test.cjs39
-rw-r--r--test/lib/wiki-data.js35
-rw-r--r--test/snapshot/transformContent.js39
-rw-r--r--test/unit/data/things/album.js20
-rw-r--r--test/unit/data/things/track.js16
15 files changed, 199 insertions, 138 deletions
diff --git a/src/content/dependencies/generateStaticPage.js b/src/content/dependencies/generateStaticPage.js
index 3e27fd43..226152c7 100644
--- a/src/content/dependencies/generateStaticPage.js
+++ b/src/content/dependencies/generateStaticPage.js
@@ -1,5 +1,6 @@
 export default {
   contentDependencies: ['generatePageLayout', 'transformContent'],
+  extraDependencies: ['html'],
 
   relations(relation, staticPage) {
     return {
@@ -12,10 +13,11 @@ export default {
     return {
       name: staticPage.name,
       stylesheet: staticPage.stylesheet,
+      script: staticPage.script,
     };
   },
 
-  generate(data, relations) {
+  generate(data, relations, {html}) {
     return relations.layout
       .slots({
         title: data.name,
@@ -27,7 +29,12 @@ export default {
             : []),
 
         mainClasses: ['long-content'],
-        mainContent: relations.content,
+        mainContent: [
+          relations.content,
+
+          data.script &&
+            html.tag('script', data.script),
+        ],
 
         navLinkStyle: 'hierarchical',
         navLinks: [
diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js
index b0a7796c..2002ebee 100644
--- a/src/content/dependencies/transformContent.js
+++ b/src/content/dependencies/transformContent.js
@@ -562,11 +562,14 @@ export default {
     const transformMultiline = () => {
       const markedInput =
         extractNonTextNodes()
-          // Compress multiple line breaks into single line breaks.
-          .replace(/\n{2,}/g, '\n')
+          // Compress multiple line breaks into single line breaks,
+          // except when they're preceding or following indented
+          // text (by at least two spaces).
+          .replace(/(?<!  .*)\n{2,}(?!^  )/gm, '\n') /* eslint-disable-line no-regex-spaces */
           // Expand line breaks which don't follow a list, quote,
-          // or <br> / "  ".
-          .replace(/(?<!^ *-.*|^>.*|  $|<br>$)\n+/gm, '\n\n') /* eslint-disable-line no-regex-spaces */
+          // or <br> / "  ", and which don't precede or follow
+          // indented text (by at least two spaces).
+          .replace(/(?<!^ *-.*|^>.*|^  .*\n*|  $|<br>$)\n+(?!  |\n)/gm, '\n\n') /* eslint-disable-line no-regex-spaces */
           // Expand line breaks which are at the end of a list.
           .replace(/(?<=^ *-.*)\n+(?!^ *-)/gm, '\n\n')
           // Expand line breaks which are at the end of a quote.
diff --git a/src/data/composite/things/album/withTrackSections.js b/src/data/composite/things/album/withTrackSections.js
index baa3cb4a..679a09fd 100644
--- a/src/data/composite/things/album/withTrackSections.js
+++ b/src/data/composite/things/album/withTrackSections.js
@@ -22,7 +22,7 @@ export default templateCompositeFrom({
 
   steps: () => [
     exitWithoutDependency({
-      dependency: 'trackData',
+      dependency: 'ownTrackData',
       value: input.value([]),
     }),
 
@@ -75,7 +75,7 @@ export default templateCompositeFrom({
 
     withResolvedReferenceList({
       list: '#trackRefs',
-      data: 'trackData',
+      data: 'ownTrackData',
       notFoundMode: input.value('null'),
       find: input.value(find.track),
     }).outputs({
diff --git a/src/data/composite/things/album/withTracks.js b/src/data/composite/things/album/withTracks.js
index dcea6593..fff3d5ae 100644
--- a/src/data/composite/things/album/withTracks.js
+++ b/src/data/composite/things/album/withTracks.js
@@ -12,7 +12,7 @@ export default templateCompositeFrom({
 
   steps: () => [
     exitWithoutDependency({
-      dependency: 'trackData',
+      dependency: 'ownTrackData',
       value: input.value([]),
     }),
 
@@ -35,7 +35,7 @@ export default templateCompositeFrom({
 
     withResolvedReferenceList({
       list: '#trackRefs',
-      data: 'trackData',
+      data: 'ownTrackData',
       find: input.value(find.track),
     }),
 
diff --git a/src/data/things/album.js b/src/data/things/album.js
index 63ec1140..a95ba354 100644
--- a/src/data/things/album.js
+++ b/src/data/things/album.js
@@ -133,7 +133,10 @@ export class Album extends Thing {
       class: input.value(Group),
     }),
 
-    trackData: wikiData({
+    // Only the tracks which belong to this album.
+    // Necessary for computing the track list, so provide this statically
+    // or keep it updated.
+    ownTrackData: wikiData({
       class: input.value(Track),
     }),
 
diff --git a/src/data/things/static-page.js b/src/data/things/static-page.js
index ab9c5f98..8a3fd10e 100644
--- a/src/data/things/static-page.js
+++ b/src/data/things/static-page.js
@@ -30,5 +30,6 @@ export class StaticPage extends Thing {
     directory: directory(),
     content: simpleString(),
     stylesheet: simpleString(),
+    script: simpleString(),
   });
 }
diff --git a/src/data/things/track.js b/src/data/things/track.js
index 08891719..e3fe0804 100644
--- a/src/data/things/track.js
+++ b/src/data/things/track.js
@@ -336,12 +336,21 @@ export class Track extends Thing {
     }
 
     let album;
-    if (depth >= 0 && (album = this.album ?? this.dataSourceAlbum)) {
+
+    if (depth >= 0) {
+      try {
+        album = this.album;
+      } catch (_error) {}
+
+      album ??= this.dataSourceAlbum;
+    }
+
+    if (album) {
       const albumName = album.name;
       const albumIndex = album.tracks.indexOf(this);
       const trackNum =
         (albumIndex === -1
-          ? '#?'
+          ? 'indeterminate position'
           : `#${albumIndex + 1}`);
       parts.push(` (${colors.yellow(trackNum)} in ${colors.green(albumName)})`);
     }
diff --git a/src/data/yaml.js b/src/data/yaml.js
index 2c600341..82b7faf2 100644
--- a/src/data/yaml.js
+++ b/src/data/yaml.js
@@ -646,6 +646,7 @@ export const processStaticPageDocument = makeProcessDocument(T.StaticPage, {
     directory: 'Directory',
 
     stylesheet: 'Style',
+    script: 'Script',
     content: 'Content',
   },
 });
@@ -935,6 +936,7 @@ export const dataSteps = [
         // an individual section before applying it, since those are just
         // generic objects; they aren't Things in and of themselves.)
         const trackSections = [];
+        const ownTrackData = [];
 
         let currentTrackSection = {
           name: `Default Track Section`,
@@ -969,13 +971,16 @@ export const dataSteps = [
 
           entry.dataSourceAlbum = albumRef;
 
+          ownTrackData.push(entry);
           currentTrackSection.tracks.push(Thing.getReference(entry));
         }
 
         closeCurrentTrackSection();
 
-        album.trackSections = trackSections;
         albumData.push(album);
+
+        album.trackSections = trackSections;
+        album.ownTrackData = ownTrackData;
       }
 
       return {albumData, trackData};
@@ -1550,7 +1555,7 @@ export function linkWikiDataArrays(wikiData, {
 
   assignWikiData([WD.wikiInfo], 'groupData');
 
-  assignWikiData(WD.albumData, 'artistData', 'artTagData', 'groupData', 'trackData');
+  assignWikiData(WD.albumData, 'artistData', 'artTagData', 'groupData');
   assignWikiData(WD.trackData, 'albumData', 'artistData', 'artTagData', 'flashData', 'trackData');
   assignWikiData(WD.artistData, 'albumData', 'artistData', 'flashData', 'trackData');
   assignWikiData(WD.groupData, 'albumData', 'groupCategoryData');
@@ -1624,8 +1629,7 @@ export function filterDuplicateDirectories(wikiData) {
         call(() => {
           throw new Error(
             `Duplicate directory ${colors.green(directory)}:\n` +
-              places.map((thing) => ` - ` + inspect(thing)).join('\n')
-          );
+            places.map(thing => ` - ` + inspect(thing)).join('\n'));
         });
       }
 
diff --git a/src/static/client3.js b/src/static/client3.js
index 86b5f985..1e64ebe1 100644
--- a/src/static/client3.js
+++ b/src/static/client3.js
@@ -2015,98 +2015,6 @@ function addExternalIconTooltipPageListeners() {
 clientSteps.getPageReferences.push(getExternalIconTooltipReferences);
 clientSteps.addPageListeners.push(addExternalIconTooltipPageListeners);
 
-/*
-const linkIconTooltipInfo =
-  Array.from(document.querySelectorAll('span.contribution.has-tooltip'))
-    .map(span => ({
-      mainLink: span.querySelector('a'),
-      iconsContainer: span.querySelector('span.icons-tooltip'),
-      iconLinks: span.querySelectorAll('span.icons-tooltip a'),
-    }));
-
-for (const info of linkIconTooltipInfo) {
-  const focusElements =
-    [info.mainLink, ...info.iconLinks];
-
-  const hoverElements =
-    [info.mainLink, info.iconsContainer];
-
-  let hidden = true;
-
-  const show = () => {
-    info.iconsContainer.classList.add('visible');
-    info.iconsContainer.inert = false;
-    hidden = false;
-  };
-
-  const hide = () => {
-    info.iconsContainer.classList.remove('visible');
-    info.iconsContainer.inert = true;
-    hidden = true;
-  };
-
-  const considerHiding = () => {
-    if (hoverElements.some(el => el.matches(':hover'))) {
-      return;
-    }
-
-    if (<document.activeElement is inside tooltip>) {
-      return;
-    }
-
-    if (justTouched) {
-      return;
-    }
-
-    hide();
-  };
-
-  // Hover (pointer)
-
-  let hoverTimeout;
-
-  info.mainLink.addEventListener('mouseenter', () => {
-    if (hidden) {
-      hoverTimeout = setTimeout(show, 250);
-    }
-  });
-
-  info.mainLink.addEventListener('mouseout', () => {
-    if (hidden) {
-      clearTimeout(hoverTimeout);
-    } else {
-      considerHiding();
-    }
-  });
-
-  info.iconsContainer.addEventListener('mouseout', () => {
-    if (!hidden) {
-      considerHiding();
-    }
-  });
-
-  // Focus (keyboard)
-
-  let focusTimeout;
-
-  info.mainLink.addEventListener('focus', () => {
-    focusTimeout = setTimeout(show, 750);
-  });
-
-  info.mainLink.addEventListener('blur', () => {
-    clearTimeout(focusTimeout);
-  });
-
-  info.iconsContainer.addEventListener('focusout', () => {
-    requestAnimationFrame(considerHiding);
-  });
-
-  info.mainLink.addEventListener('blur', () => {
-    requestAnimationFrame(considerHiding);
-  });
-}
-*/
-
 // Sticky commentary sidebar ------------------------------
 
 const albumCommentarySidebarInfo = clientInfo.albumCommentarySidebarInfo = {
diff --git a/src/util/sugar.js b/src/util/sugar.js
index eab44b75..cee3df12 100644
--- a/src/util/sugar.js
+++ b/src/util/sugar.js
@@ -589,6 +589,32 @@ export function _withAggregate(mode, aggregateOpts, fn) {
   }
 }
 
+export const unhelpfulStackLines = [
+  /sugar/,
+  /node:/,
+  /<anonymous>/,
+];
+
+export function getUsefulStackLine(stack) {
+  if (!stack) return '';
+
+  function isUseful(stackLine) {
+    const trimmed = stackLine.trim();
+
+    if (!trimmed.startsWith('at'))
+      return false;
+
+    if (unhelpfulStackLines.some(regex => regex.test(trimmed)))
+      return false;
+
+    return true;
+  }
+
+  const stackLines = stack.split('\n');
+  const usefulStackLine = stackLines.find(isUseful);
+  return usefulStackLine ?? '';
+}
+
 export function showAggregate(topError, {
   pathToFileURL = f => f,
   showTraces = true,
@@ -670,15 +696,8 @@ export function showAggregate(topError, {
         : messagePart);
 
     if (showTraces) {
-      const stackLines =
-        stack?.split('\n');
-
       const stackLine =
-        stackLines?.find(line =>
-          line.trim().startsWith('at') &&
-          !line.includes('sugar') &&
-          !line.includes('node:') &&
-          !line.includes('<anonymous>'));
+        getUsefulStackLine(stack);
 
       const tracePart =
         (stackLine
diff --git a/tap-snapshots/test/snapshot/transformContent.js.test.cjs b/tap-snapshots/test/snapshot/transformContent.js.test.cjs
index 85ee740f..9ab299e6 100644
--- a/tap-snapshots/test/snapshot/transformContent.js.test.cjs
+++ b/tap-snapshots/test/snapshot/transformContent.js.test.cjs
@@ -10,6 +10,45 @@ exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) >
 <p>Very nice: <time datetime="Fri, 25 Oct 2413 03:00:00 GMT">10/25/2413</time></p>
 `
 
+exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > hanging indent list 1`] = `
+<p>Hello!</p>
+<ul>
+<li><p>I am a list item and I
+go on and on and on
+and on and on and on.</p>
+</li>
+<li><p>I am another list item.
+Yeah.</p>
+</li>
+</ul>
+<p>In-between!</p>
+<ul>
+<li>Spooky,
+spooky, I say!</li>
+<li>Following list item.
+No empty line around me.</li>
+<li>Very cool.
+So, so cool.</li>
+</ul>
+<p>Goodbye!</p>
+`
+
+exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > indent on a directly following line 1`] = `
+<div>
+    <span>Wow!</span>
+</div>
+`
+
+exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > indent on an indierctly following line 1`] = `
+<p>Some text.</p>
+<p>Yes, some more text.</p>
+<pre><code>I am hax0rz!!
+All yor base r blong 2 us.
+</code></pre>
+<p>Aye.</p>
+<p>Aye aye aye.</p>
+`
+
 exports[`test/snapshot/transformContent.js > TAP > transformContent (snapshot) > inline images 1`] = `
 <p><img src="snooping.png"> as USUAL...</p>
 <p>What do you know? <img src="cowabunga.png" width="24" height="32"></p>
diff --git a/test/lib/wiki-data.js b/test/lib/wiki-data.js
index c4083a56..5433de29 100644
--- a/test/lib/wiki-data.js
+++ b/test/lib/wiki-data.js
@@ -1,7 +1,32 @@
+import CacheableObject from '#cacheable-object';
+import find from '#find';
 import {linkWikiDataArrays} from '#yaml';
 
-export function linkAndBindWikiData(wikiData) {
-  linkWikiDataArrays(wikiData);
+export function linkAndBindWikiData(wikiData, {
+  inferAlbumsOwnTrackData = true,
+} = {}) {
+  function customLinkWikiDataArrays(...args) {
+    linkWikiDataArrays(...args);
+
+    // If albumData is present, automatically set albums' ownTrackData values
+    // by resolving track sections' references against the full array. This is
+    // just a nicety for working with albums throughout tests.
+    if (inferAlbumsOwnTrackData && wikiData.albumData && wikiData.trackData) {
+      for (const album of wikiData.albumData) {
+        const trackSections =
+          CacheableObject.getUpdateValue(album, 'trackSections');
+
+        const trackRefs =
+          trackSections.flatMap(section => section.tracks);
+
+        album.ownTrackData =
+          trackRefs.map(ref =>
+            find.track(ref, wikiData.trackData, {mode: 'error'}));
+      }
+    }
+  }
+
+  customLinkWikiDataArrays(wikiData);
 
   return {
     // Mutate to make the below functions aware of new data objects, or of
@@ -13,12 +38,14 @@ export function linkAndBindWikiData(wikiData) {
     // It'll automatically relink everything on wikiData so all the objects
     // are caught up to date.
     linkWikiDataArrays:
-      linkWikiDataArrays.bind(null, wikiData),
+      customLinkWikiDataArrays
+        .bind(null, wikiData),
 
     // Use this if you HAVEN'T mutated wikiData and just need to decache
     // indirect dependencies on exposed properties of other data objects.
     // See documentation on linkWikiDataArarys (in yaml.js) for more info.
     XXX_decacheWikiData:
-      linkWikiDataArrays.bind(null, wikiData, {XXX_decacheWikiData: true}),
+      customLinkWikiDataArrays
+        .bind(null, wikiData, {XXX_decacheWikiData: true}),
   };
 }
diff --git a/test/snapshot/transformContent.js b/test/snapshot/transformContent.js
index b05beac1..740d94df 100644
--- a/test/snapshot/transformContent.js
+++ b/test/snapshot/transformContent.js
@@ -37,6 +37,45 @@ testContentFunctions(t, 'transformContent (snapshot)', async (t, evaluate) => {
       `That's right, [[album:cool-album]]!`);
 
   quickSnapshot(
+    'indent on a directly following line',
+      `<div>\n` +
+      `    <span>Wow!</span>\n` +
+      `</div>`);
+
+  quickSnapshot(
+    'indent on an indierctly following line',
+      `Some text.\n` +
+      `Yes, some more text.\n` +
+      `\n` +
+      `    I am hax0rz!!\n` +
+      `    All yor base r blong 2 us.\n` +
+      `\n` +
+      `Aye.\n` +
+      `Aye aye aye.`);
+
+  quickSnapshot(
+    'hanging indent list',
+      `Hello!\n` +
+      `\n` +
+      `* I am a list item and I\n` +
+      `  go on and on and on\n` +
+      `  and on and on and on.\n` +
+      `\n` +
+      `* I am another list item.\n` +
+      `  Yeah.\n` +
+      `\n` +
+      `In-between!\n` +
+      `\n` +
+      `* Spooky,\n` +
+      `  spooky, I say!\n` +
+      `* Following list item.\n` +
+      `  No empty line around me.\n` +
+      `* Very cool.\n` +
+      `  So, so cool.\n` +
+      `\n` +
+      `Goodbye!`);
+
+  quickSnapshot(
     'inline images',
       `<img src="snooping.png"> as USUAL...\n` +
       `What do you know? <img src="cowabunga.png" width="24" height="32">\n` +
diff --git a/test/unit/data/things/album.js b/test/unit/data/things/album.js
index 76a2b90f..5a1261a4 100644
--- a/test/unit/data/things/album.js
+++ b/test/unit/data/things/album.js
@@ -204,11 +204,13 @@ t.test(`Album.tracks`, t => {
   const track1 = stubTrack('track1');
   const track2 = stubTrack('track2');
   const track3 = stubTrack('track3');
+  const tracks = [track1, track2, track3];
 
-  linkAndBindWikiData({
-    albumData: [album],
-    trackData: [track1, track2, track3],
-  });
+  album.ownTrackData = tracks;
+
+  for (const track of tracks) {
+    track.albumData = [album];
+  }
 
   t.same(album.tracks, [],
     `Album.tracks #1: defaults to empty array`);
@@ -259,11 +261,13 @@ t.test(`Album.trackSections`, t => {
   const track2 = stubTrack('track2');
   const track3 = stubTrack('track3');
   const track4 = stubTrack('track4');
+  const tracks = [track1, track2, track3, track4];
 
-  linkAndBindWikiData({
-    albumData: [album],
-    trackData: [track1, track2, track3, track4],
-  });
+  album.ownTrackData = tracks;
+
+  for (const track of tracks) {
+    track.albumData = [album];
+  }
 
   album.trackSections = [
     {tracks: ['track:track1', 'track:track2']},
diff --git a/test/unit/data/things/track.js b/test/unit/data/things/track.js
index f84ba1cb..57bd4253 100644
--- a/test/unit/data/things/track.js
+++ b/test/unit/data/things/track.js
@@ -80,8 +80,8 @@ t.test(`Track.album`, t => {
 
   track1.albumData = [album1, album2];
   track2.albumData = [album1, album2];
-  album1.trackData = [track1, track2];
-  album2.trackData = [track1, track2];
+  album1.ownTrackData = [track1, track2];
+  album2.ownTrackData = [track1, track2];
   album1.trackSections = [{tracks: ['track:track1']}];
   album2.trackSections = [{tracks: ['track:track2']}];
 
@@ -98,13 +98,13 @@ t.test(`Track.album`, t => {
   t.equal(track1.album, null,
     `album #4: is null when track missing albumData`);
 
-  album1.trackData = [];
+  album1.ownTrackData = [];
   track1.albumData = [album1, album2];
 
   t.equal(track1.album, null,
-    `album #5: is null when album missing trackData`);
+    `album #5: is null when album missing ownTrackData`);
 
-  album1.trackData = [track1, track2];
+  album1.ownTrackData = [track1, track2];
   album1.trackSections = [{tracks: ['track:track2']}];
 
   // XXX_decacheWikiData
@@ -165,7 +165,7 @@ t.test(`Track.color`, t => {
 
   const {track, album} = stubTrackAndAlbum();
 
-  const {wikiData, linkWikiDataArrays, XXX_decacheWikiData} = linkAndBindWikiData({
+  const {XXX_decacheWikiData} = linkAndBindWikiData({
     albumData: [album],
     trackData: [track],
   });
@@ -188,7 +188,7 @@ t.test(`Track.color`, t => {
   // track's album will always have a corresponding track section. But if that
   // connection breaks for some future reason (with the album still present),
   // Track.color should still inherit directly from the album.
-  wikiData.albumData = [
+  track.albumData = [
     {
       constructor: {[Thing.referenceType]: 'album'},
       color: '#abcdef',
@@ -199,8 +199,6 @@ t.test(`Track.color`, t => {
     },
   ];
 
-  linkWikiDataArrays();
-
   t.equal(track.color, '#abcdef',
     `color #3: inherits from album without matching track section`);