« get me outta code hell

content, client: generateIntrapageDotSwitcher - 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>2024-10-15 23:18:02 -0300
committer(quasar) nebula <qznebula@protonmail.com>2024-11-02 22:12:48 -0300
commit53eb9d679e9b43f59320b39a52421cb33a15f9de (patch)
tree25ee1742a8558494097f37d37222d59ca70a1140
parentb657f641c89ceee7676dc6d29fe4b9a168a97266 (diff)
content, client: generateIntrapageDotSwitcher
-rw-r--r--src/content/dependencies/generateIntrapageDotSwitcher.js47
-rw-r--r--src/static/js/client/index.js2
-rw-r--r--src/static/js/client/intrapage-dot-switcher.js82
3 files changed, 131 insertions, 0 deletions
diff --git a/src/content/dependencies/generateIntrapageDotSwitcher.js b/src/content/dependencies/generateIntrapageDotSwitcher.js
new file mode 100644
index 00000000..3f300676
--- /dev/null
+++ b/src/content/dependencies/generateIntrapageDotSwitcher.js
@@ -0,0 +1,47 @@
+import {stitchArrays} from '#sugar';
+
+export default {
+  contentDependencies: ['generateDotSwitcherTemplate'],
+  extraDependencies: ['html', 'language'],
+
+  relations: (relation) => ({
+    template:
+      relation('generateDotSwitcherTemplate'),
+  }),
+
+  slots: {
+    attributes: {
+      type: 'attributes',
+      mutable: false,
+    },
+
+    initialOptionIndex: {type: 'number'},
+
+    titles: {
+      validate: v => v.strictArrayOf(v.isHTML),
+    },
+
+    targetIDs: {
+      validate: v => v.strictArrayOf(v.isString),
+    },
+  },
+
+  generate: (relations, slots, {html, language}) =>
+    relations.template.slots({
+      attributes: [
+        {class: 'intrapage'},
+        slots.attributes,
+      ],
+
+      initialOptionIndex: slots.initialOptionIndex,
+
+      options:
+        stitchArrays({
+          title: slots.titles,
+          targetID: slots.targetIDs,
+        }).map(({title, targetID}) =>
+            html.tag('a', {href: '#'},
+              {'data-target-id': targetID},
+              language.sanitize(title))),
+    }),
+};
diff --git a/src/static/js/client/index.js b/src/static/js/client/index.js
index 8870a4d1..1c291bfc 100644
--- a/src/static/js/client/index.js
+++ b/src/static/js/client/index.js
@@ -10,6 +10,7 @@ import * as cssCompatibilityAssistantModule from './css-compatibility-assistant.
 import * as datetimestampTooltipModule from './datetimestamp-tooltip.js';
 import * as hashLinkModule from './hash-link.js';
 import * as hoverableTooltipModule from './hoverable-tooltip.js';
+import * as intrapageDotSwitcherModule from './intrapage-dot-switcher.js';
 import * as liveMousePositionModule from './live-mouse-position.js';
 import * as quickDescriptionModule from './quick-description.js';
 import * as scriptedLinkModule from './scripted-link.js';
@@ -27,6 +28,7 @@ export const modules = [
   datetimestampTooltipModule,
   hashLinkModule,
   hoverableTooltipModule,
+  intrapageDotSwitcherModule,
   liveMousePositionModule,
   quickDescriptionModule,
   scriptedLinkModule,
diff --git a/src/static/js/client/intrapage-dot-switcher.js b/src/static/js/client/intrapage-dot-switcher.js
new file mode 100644
index 00000000..d06bc5a6
--- /dev/null
+++ b/src/static/js/client/intrapage-dot-switcher.js
@@ -0,0 +1,82 @@
+/* eslint-env browser */
+
+import {stitchArrays} from '../../shared-util/sugar.js';
+
+import {cssProp} from '../client-util.js';
+
+export const info = {
+  id: 'intrapageDotSwitcherInfo',
+
+  // Each is a two-level array, by switcher.
+  // This is an evil data structure.
+  switcherSpans: null,
+  switcherLinks: null,
+  switcherTargets: null,
+};
+
+export function getPageReferences() {
+  const switchers =
+    Array.from(document.querySelectorAll('.dot-switcher.intrapage'));
+
+  info.switcherSpans =
+    switchers
+      .map(switcher => switcher.querySelectorAll(':scope > span'))
+      .map(spans => Array.from(spans));
+
+  info.switcherLinks =
+    info.switcherSpans
+      .map(spans => spans
+        .map(span => span.querySelector(':scope > a')));
+
+  info.switcherTargets =
+    info.switcherLinks
+      .map(links => links
+        .map(link => {
+          const targetID = link.getAttribute('data-target-id');
+          const target = document.getElementById(targetID);
+          if (target) {
+            return target;
+          } else {
+            console.warn(
+              `An intrapage dot switcher option is targetting an ID that doesn't exist, #${targetID}`,
+              link);
+            link.setAttribute('inert', '');
+            return null;
+          }
+        }));
+}
+
+export function addPageListeners() {
+  for (const {links, spans, targets} of stitchArrays({
+    spans: info.switcherSpans,
+    links: info.switcherLinks,
+    targets: info.switcherTargets,
+  })) {
+    for (const [index, {span, link, target}] of stitchArrays({
+      span: spans,
+      link: links,
+      target: targets,
+    }).entries()) {
+      const otherSpans =
+        [...spans.slice(0, index), ...spans.slice(index + 1)];
+
+      const otherTargets =
+        [...targets.slice(0, index), ...targets.slice(index + 1)];
+
+      link.addEventListener('click', domEvent => {
+        domEvent.preventDefault();
+
+        for (const otherSpan of otherSpans) {
+          otherSpan.classList.remove('current');
+        }
+
+        for (const otherTarget of otherTargets) {
+          cssProp(otherTarget, 'display', 'none');
+        }
+
+        span.classList.add('current');
+        cssProp(target, 'display', 'block');
+      });
+    }
+  }
+}