« get me outta code hell

content: sprawl & transformContent - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/content
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-05-25 13:23:04 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-05-25 13:23:04 -0300
commit6d8fe82b5386af536ca96eb1d89150e201c603e9 (patch)
treec5d8cce46834facc82e66779e69e7cef67627d28 /src/content
parentbd0741dcf0c23489bf710249ab8fd9ba647db843 (diff)
content: sprawl & transformContent
Sprawling basically means tying a component to objects which
aren't directly passed to it. This is necessary for functions
like transformContent, which was *mostly* implemented here
(the multiline/lyrics modes are stubs, and a number of links
haven't been implemented yet).
Diffstat (limited to 'src/content')
-rw-r--r--src/content/dependencies/generatePageLayout.js34
-rw-r--r--src/content/dependencies/linkArtistGallery.js8
-rw-r--r--src/content/dependencies/linkFlash.js8
-rw-r--r--src/content/dependencies/linkGroupGallery.js8
-rw-r--r--src/content/dependencies/linkListing.js8
-rw-r--r--src/content/dependencies/linkNewsEntry.js8
-rw-r--r--src/content/dependencies/linkStaticPage.js8
-rw-r--r--src/content/dependencies/transformContent.js259
8 files changed, 332 insertions, 9 deletions
diff --git a/src/content/dependencies/generatePageLayout.js b/src/content/dependencies/generatePageLayout.js
index e9de61df..be61a6cd 100644
--- a/src/content/dependencies/generatePageLayout.js
+++ b/src/content/dependencies/generatePageLayout.js
@@ -4,6 +4,7 @@ export default {
   contentDependencies: [
     'generateFooterLocalizationLinks',
     'generateStickyHeadingContainer',
+    'transformContent',
   ],
 
   extraDependencies: [
@@ -12,10 +13,23 @@ export default {
     'language',
     'to',
     'transformMultiline',
-    'wikiInfo',
+    'wikiData',
   ],
 
-  relations(relation) {
+  sprawl({wikiInfo}) {
+    return {
+      footerContent: wikiInfo.footerContent,
+      wikiName: wikiInfo.nameShort,
+    };
+  },
+
+  data({wikiName}) {
+    return {
+      wikiName,
+    };
+  },
+
+  relations(relation, sprawl) {
     const relations = {};
 
     relations.footerLocalizationLinks =
@@ -24,16 +38,17 @@ export default {
     relations.stickyHeadingContainer =
       relation('generateStickyHeadingContainer');
 
+    relations.defaultFooterContent =
+      relation('transformContent', sprawl.footerContent);
+
     return relations;
   },
 
-  generate(relations, {
+  generate(data, relations, {
     cachebust,
     html,
     language,
     to,
-    transformMultiline,
-    wikiInfo,
   }) {
     const sidebarSlots = side => ({
       // Content is a flat HTML array. It'll generate one sidebar section
@@ -186,8 +201,9 @@ export default {
 
         let footerContent = slots.footerContent;
 
-        if (html.isBlank(footerContent) && wikiInfo.footerContent) {
-          footerContent = transformMultiline(wikiInfo.footerContent);
+        if (html.isBlank(footerContent)) {
+          footerContent = relations.defaultFooterContent
+            .slot('mode', 'multiline');
         }
 
         const mainHTML =
@@ -251,7 +267,7 @@ export default {
 
                   switch (cur.auto) {
                     case 'home':
-                      title = wikiInfo.nameShort;
+                      title = data.wikiName;
                       href = to('localized.home');
                       break;
                     case 'current':
@@ -400,7 +416,7 @@ export default {
                   showWikiNameInTitle
                     ? language.formatString('misc.pageTitle.withWikiName', {
                         title,
-                        wikiName: wikiInfo.nameShort,
+                        wikiName: data.wikiName,
                       })
                     : language.formatString('misc.pageTitle', {title})),
                 */
diff --git a/src/content/dependencies/linkArtistGallery.js b/src/content/dependencies/linkArtistGallery.js
new file mode 100644
index 00000000..66dc172d
--- /dev/null
+++ b/src/content/dependencies/linkArtistGallery.js
@@ -0,0 +1,8 @@
+export default {
+  contentDependencies: ['linkThing'],
+
+  relations: (relation, artist) =>
+    ({link: relation('linkThing', 'localized.artistGallery', artist)}),
+
+  generate: (relations) => relations.link,
+};
diff --git a/src/content/dependencies/linkFlash.js b/src/content/dependencies/linkFlash.js
new file mode 100644
index 00000000..93dd5a28
--- /dev/null
+++ b/src/content/dependencies/linkFlash.js
@@ -0,0 +1,8 @@
+export default {
+  contentDependencies: ['linkThing'],
+
+  relations: (relation, flash) =>
+    ({link: relation('linkThing', 'localized.flash', flash)}),
+
+  generate: (relations) => relations.link,
+};
diff --git a/src/content/dependencies/linkGroupGallery.js b/src/content/dependencies/linkGroupGallery.js
new file mode 100644
index 00000000..86c4a0f3
--- /dev/null
+++ b/src/content/dependencies/linkGroupGallery.js
@@ -0,0 +1,8 @@
+export default {
+  contentDependencies: ['linkThing'],
+
+  relations: (relation, group) =>
+    ({link: relation('linkThing', 'localized.groupGallery', group)}),
+
+  generate: (relations) => relations.link,
+};
diff --git a/src/content/dependencies/linkListing.js b/src/content/dependencies/linkListing.js
new file mode 100644
index 00000000..f27d93ac
--- /dev/null
+++ b/src/content/dependencies/linkListing.js
@@ -0,0 +1,8 @@
+export default {
+  contentDependencies: ['linkThing'],
+
+  relations: (relation, listing) =>
+    ({link: relation('linkThing', 'localized.listing', listing)}),
+
+  generate: (relations) => relations.link,
+};
diff --git a/src/content/dependencies/linkNewsEntry.js b/src/content/dependencies/linkNewsEntry.js
new file mode 100644
index 00000000..1fb32dd9
--- /dev/null
+++ b/src/content/dependencies/linkNewsEntry.js
@@ -0,0 +1,8 @@
+export default {
+  contentDependencies: ['linkThing'],
+
+  relations: (relation, newsEntry) =>
+    ({link: relation('linkThing', 'localized.newsEntry', newsEntry)}),
+
+  generate: (relations) => relations.link,
+};
diff --git a/src/content/dependencies/linkStaticPage.js b/src/content/dependencies/linkStaticPage.js
new file mode 100644
index 00000000..032af6c9
--- /dev/null
+++ b/src/content/dependencies/linkStaticPage.js
@@ -0,0 +1,8 @@
+export default {
+  contentDependencies: ['linkThing'],
+
+  relations: (relation, staticPage) =>
+    ({link: relation('linkThing', 'localized.staticPage', staticPage)}),
+
+  generate: (relations) => relations.link,
+};
diff --git a/src/content/dependencies/transformContent.js b/src/content/dependencies/transformContent.js
new file mode 100644
index 00000000..262c2982
--- /dev/null
+++ b/src/content/dependencies/transformContent.js
@@ -0,0 +1,259 @@
+import {bindFind} from '../../util/find.js';
+import {parseInput} from '../../util/replacer.js';
+import {replacerSpec} from '../../util/transform-content.js';
+
+const linkThingRelationMap = {
+  album: 'linkAlbum',
+  albumCommentary: 'linkAlbumCommentary',
+  albumGallery: 'linkAlbumGallery',
+  artist: 'linkArtist',
+  artistGallery: 'linkArtistGallery',
+  flash: 'linkFlash',
+  group: 'linkGroup',
+  groupGallery: 'linkGroupGallery',
+  listing: 'linkListing',
+  newsEntry: 'linkNewsEntry',
+  staticPage: 'linkStaticPage',
+  tag: 'linkArtTag',
+  track: 'linkTrack',
+};
+
+const linkValueRelationMap = {
+  // media: 'linkPathFromMedia',
+  // root: 'linkPathFromRoot',
+  // site: 'linkPathFromSite',
+};
+
+const linkIndexRelationMap = {
+  // commentaryIndex: 'linkCommentaryIndex',
+  // flashIndex: 'linkFlashIndex',
+  // home: 'linkHome',
+  // listingIndex: 'linkListingIndex',
+  // newsIndex: 'linkNewsIndex',
+};
+
+function getPlaceholder(node, content) {
+  return {type: 'text', data: content.slice(node.i, node.iEnd)};
+}
+
+export default {
+  contentDependencies: [
+    ...Object.values(linkThingRelationMap),
+    ...Object.values(linkValueRelationMap),
+    ...Object.values(linkIndexRelationMap),
+  ],
+
+  extraDependencies: ['html', 'language', 'wikiData'],
+
+  sprawl(wikiData, content) {
+    const find = bindFind(wikiData);
+
+    const parsedNodes = parseInput(content);
+
+    return {
+      nodes: parsedNodes
+        .map(node => {
+          if (node.type !== 'tag') {
+            return node;
+          }
+
+          const placeholder = getPlaceholder(node, content);
+
+          const replacerKeyImplied = !node.data.replacerKey;
+          const replacerKey = replacerKeyImplied ? 'track' : node.data.replacerKey.data;
+
+          // TODO: We don't support recursive nodes like before, at the moment. Sorry!
+          // const replacerValue = transformNodes(node.data.replacerValue, opts);
+          const replacerValue = node.data.replacerValue[0].data;
+
+          const spec = replacerSpec[replacerKey];
+
+          if (!spec) {
+            return placeholder;
+          }
+
+          if (spec.link) {
+            let data = {key: spec.link};
+
+            determineData: {
+              // No value at all: this is an index link.
+              if (!replacerValue) {
+                break determineData;
+              }
+
+              // Nothing to find: the link operates on a path or string, not a data object.
+              if (!spec.find) {
+                data.value = replacerValue;
+                break determineData;
+              }
+
+              const thing =
+                find[spec.find](
+                  (replacerKeyImplied
+                    ? replacerValue
+                    : replacerKey + `:` + replacerValue),
+                  wikiData);
+
+              // Nothing was found: this is unexpected, so return placeholder.
+              if (!thing) {
+                return placeholder;
+              }
+
+              // Something was found: the link operates on that thing.
+              data.thing = thing;
+            }
+
+            const {transformName} = spec;
+
+            // TODO: Again, no recursive nodes. Sorry!
+            // const enteredLabel = node.data.label && transformNode(node.data.label, opts);
+            const enteredLabel = node.data.label?.data;
+            const enteredHash = node.data.hash?.data;
+
+            data.label =
+              enteredLabel ??
+                (transformName && data.thing.name
+                  ? transformName(data.thing.name)
+                  : null);
+
+            data.hash = enteredHash ?? null;
+
+            return {i: node.i, iEnd: node.iEnd, type: 'link', data};
+          }
+
+          // This will be another {type: 'tag'} node which gets processed in
+          // generate.
+          return node;
+        }),
+    };
+  },
+
+  data(sprawl, content) {
+    return {
+      content,
+
+      nodes:
+        sprawl.nodes
+          .map(node => {
+            // Replace link nodes with a stub. It'll be replaced (by position)
+            // with an item from relations.
+            if (node.type === 'link') {
+              return {type: 'link'};
+            }
+
+            // Other nodes will get processed in generate.
+            return node;
+          }),
+    };
+  },
+
+  relations(relation, sprawl, content) {
+    const {nodes} = sprawl;
+
+    const relationOrPlaceholder =
+      (node, name, arg) =>
+        (name
+          ? {
+              link: relation(name, arg),
+              label: node.data.label,
+              hash: node.data.hash,
+            }
+          : getPlaceholder(node, content));
+
+    return {
+      links:
+        nodes
+          .filter(({type}) => type === 'link')
+          .map(node => {
+            const {key, thing, value} = node.data;
+
+            if (thing) {
+              return relationOrPlaceholder(node, linkThingRelationMap[key], thing);
+            } else if (value) {
+              return relationOrPlaceholder(node, linkValueRelationMap[key], value);
+            } else {
+              return relationOrPlaceholder(node, linkIndexRelationMap[key]);
+            }
+          }),
+    };
+  },
+
+  generate(data, relations, {html, language}) {
+    let linkIndex = 0;
+
+    // This array contains only straight text and link nodes, which are directly
+    // representable in html (so no further processing is needed on the level of
+    // individual nodes).
+    const contentFromNodes =
+      data.nodes.map(node => {
+        if (node.type === 'text') {
+          return {type: 'text', data: node.data};
+        }
+
+        if (node.type === 'link') {
+          const {link, label, hash} = relations.links[linkIndex++];
+          return {
+            type: 'link',
+            data: link.slots({content: label, hash}),
+          };
+        }
+
+        if (node.type === 'tag') {
+          const {replacerKey, replacerValue} = node.data;
+
+          const spec = replacerSpec[replacerKey];
+
+          if (!spec) {
+            return getPlaceholder(node, data.content);
+          }
+
+          const {value: valueFn, html: htmlFn} = spec;
+
+          const value =
+            (valueFn
+              ? valueFn(replacerValue)
+              : replacerValue);
+
+          const contents =
+            (htmlFn
+              ? htmlFn(value, {html, language})
+              : value);
+
+          return {type: 'text', data: contents};
+        }
+
+        return getPlaceholder(node, data.content);
+      });
+
+    return html.template({
+      annotation: `transformContent`,
+
+      slots: {
+        mode: {
+          validate: v => v.is('inline', 'multiline', 'lyrics'),
+          default: 'multiline',
+        },
+      },
+
+      content(slots) {
+        // In inline mode, no further processing is needed!
+
+        if (slots.mode === 'inline') {
+          return html.tags(contentFromNodes.map(node => node.data));
+        }
+
+        // In multiline mode...
+
+        if (slots.mode === 'multiline') {
+          return html.tags(contentFromNodes.map(node => node.data));
+        }
+
+        // In lyrics mode...
+
+        if (slots.mode === 'lyrics') {
+          return html.tags(contentFromNodes.map(node => node.data));
+        }
+      },
+    });
+  },
+}