diff --git a/index.html b/index.html
index 04fa919..74ee93a 100644
--- a/index.html
+++ b/index.html
@@ -214,7 +214,7 @@
if (/^[\d.]*$/.test(searchString)) {
results = this.biblio.clauses
.filter(clause => clause.number.substring(0, searchString.length) === searchString)
- .map(clause => ({ entry: clause }));
+ .map(clause => ({ key: getKey(clause), entry: clause }));
} else {
results = [];
@@ -754,6 +754,7 @@
let $spacer = document.createElement('div');
$spacer.setAttribute('id', 'references-pane-spacer');
+ $spacer.classList.add('menu-spacer');
this.$pane = document.createElement('div');
this.$pane.setAttribute('id', 'references-pane');
@@ -902,6 +903,7 @@
e.preventDefault();
e.stopPropagation();
menu.togglePinEntry(this.entry.id);
+ this.$pinLink.textContent = menu._pinnedIds[this.entry.id] ? 'Unpin' : 'Pin';
});
this.$refsLink = document.createElement('a');
@@ -922,6 +924,7 @@
sdoBox.deactivate();
this.active = true;
this.entry = entry;
+ this.$pinLink.textContent = menu._pinnedIds[entry.id] ? 'Unpin' : 'Pin';
this.$outer.classList.add('active');
this.top = el.offsetTop - this.$outer.offsetHeight;
this.left = el.offsetLeft - 10;
@@ -1117,41 +1120,245 @@
referencePane.init();
});
+// preserve state during navigation
+
+function getTocPath(li) {
+ let path = [];
+ let pointer = li;
+ while (true) {
+ let parent = pointer.parentElement;
+ if (parent == null) {
+ return null;
+ }
+ let index = [].indexOf.call(parent.children, pointer);
+ if (index == -1) {
+ return null;
+ }
+ path.unshift(index);
+ pointer = parent.parentElement;
+ if (pointer == null) {
+ return null;
+ }
+ if (pointer.id === 'menu-toc') {
+ break;
+ }
+ if (pointer.tagName !== 'LI') {
+ return null;
+ }
+ }
+ return path;
+}
+
+function activateTocPath(path) {
+ try {
+ let pointer = document.getElementById('menu-toc');
+ for (let index of path) {
+ pointer = pointer.querySelector('ol').children[index];
+ }
+ pointer.classList.add('active');
+ } catch (e) {
+ // pass
+ }
+}
+
+function getActiveTocPaths() {
+ return [...menu.$menu.querySelectorAll('.active')].map(getTocPath).filter(p => p != null);
+}
+
+function loadStateFromSessionStorage() {
+ if (!window.sessionStorage || typeof menu === 'undefined' || window.navigating) {
+ return;
+ }
+ if (sessionStorage.referencePaneState != null) {
+ let state = JSON.parse(sessionStorage.referencePaneState);
+ if (state != null) {
+ if (state.type === 'ref') {
+ let entry = menu.search.biblio.byId[state.id];
+ if (entry != null) {
+ referencePane.showReferencesFor(entry);
+ }
+ } else if (state.type === 'sdo') {
+ let sdos = sdoMap[state.id];
+ if (sdos != null) {
+ referencePane.$headerText.innerHTML = state.html;
+ referencePane.showSDOsBody(sdos, state.id);
+ }
+ }
+ delete sessionStorage.referencePaneState;
+ }
+ }
+
+ if (sessionStorage.activeTocPaths != null) {
+ document
+ .getElementById('menu-toc')
+ .querySelectorAll('.active')
+ .forEach(e => {
+ e.classList.remove('active');
+ });
+ let active = JSON.parse(sessionStorage.activeTocPaths);
+ active.forEach(activateTocPath);
+ delete sessionStorage.activeTocPaths;
+ }
+
+ if (sessionStorage.searchValue != null) {
+ let value = JSON.parse(sessionStorage.searchValue);
+ menu.search.$searchBox.value = value;
+ menu.search.search(value);
+ delete sessionStorage.searchValue;
+ }
+
+ if (sessionStorage.tocScroll != null) {
+ let tocScroll = JSON.parse(sessionStorage.tocScroll);
+ menu.$toc.scrollTop = tocScroll;
+ delete sessionStorage.tocScroll;
+ }
+}
+
+document.addEventListener('DOMContentLoaded', loadStateFromSessionStorage);
+
+window.addEventListener('pageshow', loadStateFromSessionStorage);
+
+window.addEventListener('beforeunload', () => {
+ if (!window.sessionStorage || typeof menu === 'undefined') {
+ return;
+ }
+ sessionStorage.referencePaneState = JSON.stringify(referencePane.state || null);
+ sessionStorage.activeTocPaths = JSON.stringify(getActiveTocPaths());
+ sessionStorage.searchValue = JSON.stringify(menu.search.$searchBox.value);
+ sessionStorage.tocScroll = JSON.stringify(menu.$toc.scrollTop);
+});
+
'use strict';
-let decimalBullet = Array.from({ length: 100 }, (a, i) => '' + (i + 1));
-let alphaBullet = Array.from({ length: 26 }, (a, i) => String.fromCharCode('a'.charCodeAt(0) + i));
-
-// prettier-ignore
-let romanBullet = ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix', 'x', 'xi', 'xii', 'xiii', 'xiv', 'xv', 'xvi', 'xvii', 'xviii', 'xix', 'xx', 'xxi', 'xxii', 'xxiii', 'xxiv', 'xxv'];
-// prettier-ignore
-let bullets = [decimalBullet, alphaBullet, romanBullet, decimalBullet, alphaBullet, romanBullet];
-
-function addStepNumberText(ol, parentIndex) {
- for (let i = 0; i < ol.children.length; ++i) {
- let child = ol.children[i];
- let index = parentIndex.concat([i]);
- let applicable = bullets[Math.min(index.length - 1, 5)];
- let span = document.createElement('span');
- span.textContent = (applicable[i] || '?') + '. ';
- span.style.fontSize = '0';
- span.setAttribute('aria-hidden', 'true');
- child.prepend(span);
- let sublist = child.querySelector('ol');
- if (sublist != null) {
- addStepNumberText(sublist, index);
+
+// Manually prefix algorithm step list items with hidden counter representations
+// corresponding with their markers so they get selected and copied with content.
+// We read list-style-type to avoid divergence with the style sheet, but
+// for efficiency assume that all lists at the same nesting depth use the same
+// style (except for those associated with replacement steps).
+// We also precompute some initial items for each supported style type.
+// https://w3c.github.io/csswg-drafts/css-counter-styles/
+
+const lowerLetters = Array.from({ length: 26 }, (_, i) =>
+ String.fromCharCode('a'.charCodeAt(0) + i)
+);
+// Implement the lower-alpha 'alphabetic' algorithm,
+// adjusting for indexing from 0 rather than 1.
+// https://w3c.github.io/csswg-drafts/css-counter-styles/#simple-alphabetic
+// https://w3c.github.io/csswg-drafts/css-counter-styles/#alphabetic-system
+const lowerAlphaTextForIndex = i => {
+ let S = '';
+ for (const N = lowerLetters.length; i >= 0; i--) {
+ S = lowerLetters[i % N] + S;
+ i = Math.floor(i / N);
+ }
+ return S;
+};
+
+const weightedLowerRomanSymbols = Object.entries({
+ m: 1000,
+ cm: 900,
+ d: 500,
+ cd: 400,
+ c: 100,
+ xc: 90,
+ l: 50,
+ xl: 40,
+ x: 10,
+ ix: 9,
+ v: 5,
+ iv: 4,
+ i: 1,
+});
+// Implement the lower-roman 'additive' algorithm,
+// adjusting for indexing from 0 rather than 1.
+// https://w3c.github.io/csswg-drafts/css-counter-styles/#simple-numeric
+// https://w3c.github.io/csswg-drafts/css-counter-styles/#additive-system
+const lowerRomanTextForIndex = i => {
+ let value = i + 1;
+ let S = '';
+ for (const [symbol, weight] of weightedLowerRomanSymbols) {
+ if (!value) break;
+ if (weight > value) continue;
+ const reps = Math.floor(value / weight);
+ S += symbol.repeat(reps);
+ value -= weight * reps;
+ }
+ return S;
+};
+
+// Memoize pure index-to-text functions with an exposed cache for fast retrieval.
+const makeCounter = (pureGetTextForIndex, precomputeCount = 30) => {
+ const cache = Array.from({ length: precomputeCount }, (_, i) => pureGetTextForIndex(i));
+ const getTextForIndex = i => {
+ if (i >= cache.length) cache[i] = pureGetTextForIndex(i);
+ return cache[i];
+ };
+ return { getTextForIndex, cache };
+};
+
+const counterByStyle = {
+ __proto__: null,
+ decimal: makeCounter(i => String(i + 1)),
+ 'lower-alpha': makeCounter(lowerAlphaTextForIndex),
+ 'upper-alpha': makeCounter(i => lowerAlphaTextForIndex(i).toUpperCase()),
+ 'lower-roman': makeCounter(lowerRomanTextForIndex),
+ 'upper-roman': makeCounter(i => lowerRomanTextForIndex(i).toUpperCase()),
+};
+const fallbackCounter = makeCounter(() => '?');
+const counterByDepth = [];
+
+function addStepNumberText(
+ ol,
+ depth = 0,
+ special = [...ol.classList].some(c => c.startsWith('nested-'))
+) {
+ let counter = !special && counterByDepth[depth];
+ if (!counter) {
+ const counterStyle = getComputedStyle(ol)['list-style-type'];
+ counter = counterByStyle[counterStyle];
+ if (!counter) {
+ console.warn('unsupported list-style-type', {
+ ol,
+ counterStyle,
+ id: ol.closest('[id]')?.getAttribute('id'),
+ });
+ counterByStyle[counterStyle] = fallbackCounter;
+ counter = fallbackCounter;
+ }
+ if (!special) {
+ counterByDepth[depth] = counter;
}
}
+ const { cache, getTextForIndex } = counter;
+ let i = (Number(ol.getAttribute('start')) || 1) - 1;
+ for (const li of ol.children) {
+ const marker = document.createElement('span');
+ marker.textContent = `${i < cache.length ? cache[i] : getTextForIndex(i)}. `;
+ marker.setAttribute('aria-hidden', 'true');
+ const attributesContainer = li.querySelector('.attributes-tag');
+ if (attributesContainer == null) {
+ li.prepend(marker);
+ } else {
+ attributesContainer.insertAdjacentElement('afterend', marker);
+ }
+ for (const sublist of li.querySelectorAll(':scope > ol')) {
+ addStepNumberText(sublist, depth + 1, special);
+ }
+ i++;
+ }
}
+
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('emu-alg > ol').forEach(ol => {
- addStepNumberText(ol, []);
+ addStepNumberText(ol);
});
});
let sdoMap = JSON.parse(`{}`);
-let biblio = JSON.parse(`{"refsByClause":{"sec-object.groupby":["_ref_0","_ref_1","_ref_2"],"sec-map.groupby":["_ref_3","_ref_4","_ref_5"],"sec-group-by":["_ref_6","_ref_7","_ref_8","_ref_9","_ref_10","_ref_11","_ref_12","_ref_13","_ref_14","_ref_15","_ref_16","_ref_17"],"sec-add-value-to-keyed-group":["_ref_18"]},"entries":[{"type":"clause","id":"sec-scope","titleHTML":"Scope","number":"1"},{"type":"clause","id":"sec-object.groupby","title":"Object.groupBy ( items, callbackfn )","titleHTML":"Object.groupBy ( items, callbackfn )","number":"2.1"},{"type":"clause","id":"sec-properties-of-the-object-constructor","titleHTML":"Properties of the Object Constructor (20.1.2)","number":"2"},{"type":"clause","id":"sec-map.groupby","title":"Map.groupBy ( items, callbackfn )","titleHTML":"Map.groupBy ( items, callbackfn )","number":"3.1"},{"type":"clause","id":"sec-properties-of-the-map-constructor","titleHTML":"Properties of the Map Constructor (24.1.2)","number":"3"},{"type":"op","aoid":"GroupBy","refId":"sec-group-by"},{"type":"clause","id":"sec-group-by","title":"GroupBy ( items, callbackfn, coercion )","titleHTML":"GroupBy ( items, callbackfn, coercion )","number":"4.1","referencingIds":["_ref_0","_ref_3"]},{"type":"op","aoid":"AddValueToKeyedGroup","refId":"sec-add-value-to-keyed-group"},{"type":"clause","id":"sec-add-value-to-keyed-group","title":"AddValueToKeyedGroup ( groups, key, value )","titleHTML":"AddValueToKeyedGroup ( groups, key, value )","number":"4.2","referencingIds":["_ref_17"]},{"type":"clause","id":"sec-group-by-helpers","titleHTML":"Group By Helpers","number":"4"},{"type":"clause","id":"sec-copyright-and-software-license","title":"Copyright & Software License","titleHTML":"Copyright & Software License","number":"A"}]}`);
+let biblio = JSON.parse(`{"refsByClause":{"sec-object.groupby":["_ref_0"],"sec-map.groupby":["_ref_1"],"sec-group-by":["_ref_2"]},"entries":[{"type":"clause","id":"sec-scope","titleHTML":"Scope","number":"1"},{"type":"clause","id":"sec-object.groupby","title":"Object.groupBy ( items, callbackfn )","titleHTML":"Object.groupBy ( items, callbackfn )","number":"2.1"},{"type":"clause","id":"sec-properties-of-the-object-constructor","titleHTML":"Properties of the Object Constructor (20.1.2)","number":"2"},{"type":"clause","id":"sec-map.groupby","title":"Map.groupBy ( items, callbackfn )","titleHTML":"Map.groupBy ( items, callbackfn )","number":"3.1"},{"type":"clause","id":"sec-properties-of-the-map-constructor","titleHTML":"Properties of the Map Constructor (24.1.2)","number":"3"},{"type":"op","aoid":"GroupBy","refId":"sec-group-by"},{"type":"clause","id":"sec-group-by","title":"GroupBy ( items, callbackfn, coercion )","titleHTML":"GroupBy ( items, callbackfn, coercion )","number":"4.1","referencingIds":["_ref_0","_ref_1"]},{"type":"op","aoid":"AddValueToKeyedGroup","refId":"sec-add-value-to-keyed-group"},{"type":"clause","id":"sec-add-value-to-keyed-group","title":"AddValueToKeyedGroup ( groups, key, value )","titleHTML":"AddValueToKeyedGroup ( groups, key, value )","number":"4.2","referencingIds":["_ref_2"]},{"type":"clause","id":"sec-group-by-helpers","titleHTML":"Group By Helpers","number":"4"},{"type":"clause","id":"sec-copyright-and-software-license","title":"Copyright & Software License","titleHTML":"Copyright & Software License","number":"A"}]}`);
;let usesMultipage = false