diff options
-rw-r--r-- | src/content/dependencies/generateIntrapageDotSwitcher.js | 47 | ||||
-rw-r--r-- | src/static/js/client/index.js | 2 | ||||
-rw-r--r-- | src/static/js/client/intrapage-dot-switcher.js | 82 |
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'); + }); + } + } +} |