spec-up-t 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +13 -0
- package/assets/compiled/body.js +17 -11
- package/assets/compiled/head.css +6 -4
- package/assets/css/collapse-definitions.css +0 -1
- package/assets/css/create-pdf.css +4 -2
- package/assets/css/create-term-filter.css +4 -4
- package/assets/css/definition-buttons-container.css +60 -0
- package/assets/css/insert-trefs.css +7 -0
- package/assets/css/sidebar-toc.css +2 -1
- package/assets/css/terms-and-definitions.css +73 -22
- package/assets/js/add-href-to-snapshot-link.js +16 -9
- package/assets/js/addAnchorsToTerms.js +1 -1
- package/assets/js/charts.js +10 -0
- package/assets/js/collapse-definitions.js +13 -2
- package/assets/js/collapse-meta-info.js +11 -9
- package/assets/js/definition-button-container-utils.js +82 -0
- package/assets/js/edit-term-buttons.js +77 -20
- package/assets/js/github-issues.js +35 -0
- package/assets/js/github-repo-info.js +144 -0
- package/assets/js/highlight-heading-plus-sibling-nodes.test.js +18 -0
- package/assets/js/insert-trefs.js +62 -13
- package/assets/js/mermaid-diagrams.js +11 -0
- package/assets/js/terminology-section-utility-container/README.md +107 -0
- package/assets/js/terminology-section-utility-container/create-alphabet-index.js +17 -0
- package/assets/js/{create-term-filter.js → terminology-section-utility-container/create-term-filter.js} +11 -44
- package/assets/js/terminology-section-utility-container/hide-show-utility-container.js +21 -0
- package/assets/js/terminology-section-utility-container/search.js +203 -0
- package/assets/js/terminology-section-utility-container.js +203 -0
- package/assets/js/tooltips.js +283 -0
- package/config/asset-map.json +24 -16
- package/index.js +57 -390
- package/package.json +5 -2
- package/src/add-remove-xref-source.js +20 -21
- package/src/collect-external-references.js +8 -337
- package/src/collect-external-references.test.js +440 -33
- package/src/configure.js +8 -109
- package/src/create-docx.js +7 -6
- package/src/create-pdf.js +15 -14
- package/src/freeze-spec-data.js +46 -0
- package/src/git-info.test.js +76 -0
- package/src/health-check/destination-gitignore-checker.js +5 -3
- package/src/health-check/external-specs-checker.js +5 -4
- package/src/health-check/specs-configuration-checker.js +2 -1
- package/src/health-check/term-references-checker.js +5 -3
- package/src/health-check/terms-intro-checker.js +2 -1
- package/src/health-check/tref-term-checker.js +8 -7
- package/src/health-check.js +8 -7
- package/src/init.js +3 -2
- package/src/install-from-boilerplate/add-gitignore-entries.js +3 -2
- package/src/install-from-boilerplate/add-scripts-keys.js +5 -4
- package/src/install-from-boilerplate/boilerplate/README.md +1 -1
- package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +1 -1
- package/src/install-from-boilerplate/boilerplate/spec/spec-head.md +1 -1
- package/src/install-from-boilerplate/boilerplate/specs.json +2 -1
- package/src/install-from-boilerplate/config-scripts-keys.js +3 -3
- package/src/install-from-boilerplate/copy-boilerplate.js +2 -1
- package/src/install-from-boilerplate/copy-system-files.js +4 -3
- package/src/install-from-boilerplate/custom-update.js +12 -1
- package/src/install-from-boilerplate/help.txt +1 -1
- package/src/install-from-boilerplate/menu.sh +6 -6
- package/src/json-key-validator.js +17 -11
- package/src/markdown-it/README.md +207 -0
- package/src/markdown-it/definition-lists.js +397 -0
- package/src/markdown-it/index.js +83 -0
- package/src/markdown-it/link-enhancement.js +98 -0
- package/src/markdown-it/plugins.js +118 -0
- package/src/markdown-it/table-enhancement.js +97 -0
- package/src/markdown-it/template-tag-syntax.js +152 -0
- package/src/parsers/index.js +16 -0
- package/src/parsers/spec-parser.js +152 -0
- package/src/parsers/spec-parser.test.js +109 -0
- package/src/parsers/template-tag-parser.js +277 -0
- package/src/parsers/template-tag-parser.test.js +107 -0
- package/src/pipeline/configuration/configure-starterpack.js +200 -0
- package/src/{create-external-specs-list.js → pipeline/configuration/create-external-specs-list.js} +13 -12
- package/src/{create-term-index.js → pipeline/configuration/create-term-index.js} +19 -18
- package/src/{create-versions-index.js → pipeline/configuration/create-versions-index.js} +4 -3
- package/src/{insert-term-index.js → pipeline/configuration/insert-term-index.js} +2 -2
- package/src/pipeline/configuration/prepare-spec-configuration.js +70 -0
- package/src/pipeline/parsing/apply-markdown-it-extensions.js +35 -0
- package/src/pipeline/parsing/create-markdown-parser.js +94 -0
- package/src/pipeline/parsing/create-markdown-parser.test.js +49 -0
- package/src/{html-dom-processor.js → pipeline/postprocessing/definition-list-postprocessor.js} +69 -10
- package/src/{escape-handler.js → pipeline/preprocessing/escape-processor.js} +3 -1
- package/src/{fix-markdown-files.js → pipeline/preprocessing/normalize-terminology-markdown.js} +41 -31
- package/src/pipeline/references/collect-external-references.js +307 -0
- package/src/pipeline/references/external-references-service.js +231 -0
- package/src/pipeline/references/fetch-terms-from-index.js +198 -0
- package/src/pipeline/references/match-term.js +34 -0
- package/src/{collectExternalReferences/matchTerm.test.js → pipeline/references/match-term.test.js} +8 -2
- package/src/pipeline/references/process-xtrefs-data.js +94 -0
- package/src/pipeline/references/xtref-utils.js +166 -0
- package/src/pipeline/rendering/render-spec-document.js +146 -0
- package/src/pipeline/rendering/render-utils.js +154 -0
- package/src/utils/LOGGER.md +81 -0
- package/src/utils/{doesUrlExist.js → does-url-exist.js} +4 -3
- package/src/utils/fetch.js +5 -4
- package/src/utils/file-opener.js +3 -2
- package/src/utils/git-info.js +77 -0
- package/src/utils/logger.js +74 -0
- package/src/utils/regex-patterns.js +471 -0
- package/src/utils/regex-patterns.test.js +281 -0
- package/templates/template.html +56 -21
- package/assets/js/create-alphabet-index.js +0 -60
- package/assets/js/hide-show-utility-container.js +0 -16
- package/assets/js/index.js +0 -87
- package/assets/js/search.js +0 -365
- package/src/collectExternalReferences/fetchTermsFromIndex.js +0 -284
- package/src/collectExternalReferences/matchTerm.js +0 -32
- package/src/collectExternalReferences/processXTrefsData.js +0 -108
- package/src/freeze.js +0 -90
- package/src/markdown-it-extensions.js +0 -395
- package/src/references.js +0 -114
- /package/assets/css/{bootstrap.min.css → embedded-libraries/bootstrap.min.css} +0 -0
- /package/assets/css/{prism.css → embedded-libraries/prism.css} +0 -0
- /package/assets/css/{prism.dark.css → embedded-libraries/prism.dark.css} +0 -0
- /package/assets/css/{prism.default.css → embedded-libraries/prism.default.css} +0 -0
- /package/assets/js/{bootstrap.bundle.min.js → embedded-libraries/bootstrap.bundle.min.js} +0 -0
- /package/assets/js/{chart.js → embedded-libraries/chart.js} +0 -0
- /package/assets/js/{diff.min.js → embedded-libraries/diff.min.js} +0 -0
- /package/assets/js/{font-awesome.js → embedded-libraries/font-awesome.js} +0 -0
- /package/assets/js/{mermaid.js → embedded-libraries/mermaid.js} +0 -0
- /package/assets/js/{notyf.js → embedded-libraries/notyf.js} +0 -0
- /package/assets/js/{popper.js → embedded-libraries/popper.js} +0 -0
- /package/assets/js/{prism.dark.js → embedded-libraries/prism.dark.js} +0 -0
- /package/assets/js/{prism.default.js → embedded-libraries/prism.default.js} +0 -0
- /package/assets/js/{prism.js → embedded-libraries/prism.js} +0 -0
- /package/assets/js/{tippy.js → embedded-libraries/tippy.js} +0 -0
- /package/src/{escape-mechanism.js → pipeline/preprocessing/escape-placeholder-utils.js} +0 -0
- /package/src/utils/{isLineWithDefinition.js → is-line-with-definition.js} +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Search functionality for terminology section
|
|
3
|
+
* @author Kor Dwarshuis
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
* @since 2024-08-31
|
|
6
|
+
* @description Handles search logic and highlighting (DOM is constructed in main module)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Attaches search functionality to the provided DOM elements
|
|
11
|
+
* @param {HTMLInputElement} searchInput - The search input element
|
|
12
|
+
* @param {HTMLButtonElement} goToPreviousMatchButton - Previous match button
|
|
13
|
+
* @param {HTMLButtonElement} goToNextMatchButton - Next match button
|
|
14
|
+
* @param {HTMLSpanElement} totalMatchesSpan - Matches counter element
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
function attachSearchFunctionality(searchInput, goToPreviousMatchButton, goToNextMatchButton, totalMatchesSpan) {
|
|
18
|
+
// Check if the terms and definitions list exists
|
|
19
|
+
const termsListElement = document.querySelector(".terms-and-definitions-list");
|
|
20
|
+
const dtElements = termsListElement ? termsListElement.querySelectorAll("dt") : [];
|
|
21
|
+
|
|
22
|
+
if (dtElements.length === 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/*****************/
|
|
27
|
+
/* CONFIGURATION */
|
|
28
|
+
const matchesStyle = specConfig.searchHighlightStyle || 'ssi';
|
|
29
|
+
const debounceTime = 600;
|
|
30
|
+
const matches = 'matches';
|
|
31
|
+
const searchableContent = document.querySelector('.terms-and-definitions-list');
|
|
32
|
+
|
|
33
|
+
// Styling of search matches
|
|
34
|
+
const matchesStyleSelector = {
|
|
35
|
+
dif: 'highlight-matches-DIF-search',
|
|
36
|
+
toip: 'highlight-matches-ToIP-search',
|
|
37
|
+
btc: 'highlight-matches-BTC-search',
|
|
38
|
+
keri: 'highlight-matches-KERI-search',
|
|
39
|
+
ssi: 'highlight-matches-SSI-search',
|
|
40
|
+
gleif: 'highlight-matches-GLEIF-search'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const matchesClassName = "highlight-matches-search";
|
|
44
|
+
const matchesStyleSelectorClassName = matchesStyleSelector[matchesStyle.toLowerCase()];
|
|
45
|
+
|
|
46
|
+
let totalMatches = 0;
|
|
47
|
+
let activeMatchIndex = -1;
|
|
48
|
+
let debounceTimeout;
|
|
49
|
+
|
|
50
|
+
/* Helper functions */
|
|
51
|
+
function setTotalMatches() {
|
|
52
|
+
totalMatches = document.querySelectorAll('.' + matchesClassName).length;
|
|
53
|
+
totalMatchesSpan.innerHTML = `${totalMatches} ${matches}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handleBackAndForthButtonsDisabledState() {
|
|
57
|
+
const hasMatches = totalMatches > 0;
|
|
58
|
+
goToPreviousMatchButton.disabled = !hasMatches;
|
|
59
|
+
goToNextMatchButton.disabled = !hasMatches;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function scrollToElementCenter(element) {
|
|
63
|
+
if (element) {
|
|
64
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function removeHighlights() {
|
|
69
|
+
const highlighted = document.querySelectorAll('.' + matchesClassName);
|
|
70
|
+
highlighted.forEach(element => {
|
|
71
|
+
const parent = element.parentNode;
|
|
72
|
+
parent.replaceChild(document.createTextNode(element.textContent), element);
|
|
73
|
+
parent.normalize();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function highlightMatches(query) {
|
|
78
|
+
if (!query.trim()) {
|
|
79
|
+
removeHighlights();
|
|
80
|
+
setTotalMatches();
|
|
81
|
+
handleBackAndForthButtonsDisabledState();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
removeHighlights();
|
|
86
|
+
|
|
87
|
+
const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi');
|
|
88
|
+
|
|
89
|
+
function searchNodes(node) {
|
|
90
|
+
if (node.nodeType === 3) { // Text node
|
|
91
|
+
const text = node.textContent;
|
|
92
|
+
if (regex.test(text)) {
|
|
93
|
+
const fragments = document.createDocumentFragment();
|
|
94
|
+
let lastIndex = 0;
|
|
95
|
+
let match;
|
|
96
|
+
|
|
97
|
+
regex.lastIndex = 0; // Reset regex
|
|
98
|
+
while ((match = regex.exec(text)) !== null) {
|
|
99
|
+
// Add text before match
|
|
100
|
+
if (match.index > lastIndex) {
|
|
101
|
+
fragments.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Add highlighted match
|
|
105
|
+
const span = document.createElement('span');
|
|
106
|
+
span.className = `${matchesClassName} ${matchesStyleSelectorClassName}`;
|
|
107
|
+
span.textContent = match[0];
|
|
108
|
+
fragments.appendChild(span);
|
|
109
|
+
|
|
110
|
+
lastIndex = regex.lastIndex;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Add remaining text
|
|
114
|
+
if (lastIndex < text.length) {
|
|
115
|
+
fragments.appendChild(document.createTextNode(text.slice(lastIndex)));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
node.parentNode.replaceChild(fragments, node);
|
|
119
|
+
}
|
|
120
|
+
} else if (node.nodeType === 1) { // Element node
|
|
121
|
+
Array.from(node.childNodes).forEach(searchNodes);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
searchNodes(searchableContent);
|
|
126
|
+
setTotalMatches();
|
|
127
|
+
handleBackAndForthButtonsDisabledState();
|
|
128
|
+
activeMatchIndex = -1;
|
|
129
|
+
|
|
130
|
+
// Scroll to first match
|
|
131
|
+
const firstHighlight = document.querySelector('.' + matchesClassName);
|
|
132
|
+
if (firstHighlight) {
|
|
133
|
+
scrollToElementCenter(firstHighlight);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function debouncedSearchAndHighlight(query, shouldScrollToFirstMatch = false) {
|
|
138
|
+
clearTimeout(debounceTimeout);
|
|
139
|
+
debounceTimeout = setTimeout(() => {
|
|
140
|
+
highlightMatches(query);
|
|
141
|
+
}, debounceTime);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function navigateMatches(direction) {
|
|
145
|
+
const allMatches = document.querySelectorAll('.' + matchesClassName);
|
|
146
|
+
if (allMatches.length === 0) return;
|
|
147
|
+
|
|
148
|
+
if (direction === 'next') {
|
|
149
|
+
activeMatchIndex = (activeMatchIndex + 1) % allMatches.length;
|
|
150
|
+
} else {
|
|
151
|
+
activeMatchIndex = activeMatchIndex <= 0 ? allMatches.length - 1 : activeMatchIndex - 1;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Remove previous active styling
|
|
155
|
+
allMatches.forEach(match => match.classList.remove('active'));
|
|
156
|
+
|
|
157
|
+
// Add active styling and scroll to current match
|
|
158
|
+
allMatches[activeMatchIndex].classList.add('active');
|
|
159
|
+
scrollToElementCenter(allMatches[activeMatchIndex]);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Event listeners */
|
|
163
|
+
searchInput.addEventListener("input", function () {
|
|
164
|
+
debouncedSearchAndHighlight(searchInput.value, true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
goToNextMatchButton.addEventListener('click', () => navigateMatches('next'));
|
|
168
|
+
goToPreviousMatchButton.addEventListener('click', () => navigateMatches('prev'));
|
|
169
|
+
|
|
170
|
+
// Global keyboard navigation (Arrow keys work anywhere on the page)
|
|
171
|
+
document.addEventListener('keyup', (event) => {
|
|
172
|
+
if (totalMatches > 0) {
|
|
173
|
+
switch (event.key) {
|
|
174
|
+
case "ArrowRight":
|
|
175
|
+
goToNextMatchButton.click(); // Simulate a click on button
|
|
176
|
+
break;
|
|
177
|
+
case "ArrowLeft":
|
|
178
|
+
goToPreviousMatchButton.click(); // Simulate a click on button
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Keyboard navigation when search input is focused
|
|
185
|
+
document.addEventListener('keydown', function(event) {
|
|
186
|
+
if (document.activeElement === searchInput) {
|
|
187
|
+
if (event.key === 'ArrowDown' && totalMatches > 0) {
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
navigateMatches('next');
|
|
190
|
+
} else if (event.key === 'ArrowUp' && totalMatches > 0) {
|
|
191
|
+
event.preventDefault();
|
|
192
|
+
navigateMatches('prev');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Re-run search when definitions are collapsed/expanded
|
|
198
|
+
document.addEventListener('click', event => {
|
|
199
|
+
if (event.target.classList.contains('collapse-all-defs-button')) {
|
|
200
|
+
debouncedSearchAndHighlight(searchInput.value, true);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Main module for terminology section utility container functionality
|
|
3
|
+
* @author Kor Dwarshuis
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
* @since 2024-08-31
|
|
6
|
+
* @description Coordinates all terminology section utility container components
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Initializes the complete terminology section utility container
|
|
11
|
+
* This function coordinates all the components in the correct order
|
|
12
|
+
*/
|
|
13
|
+
function initializeTerminologyUtilityContainer() {
|
|
14
|
+
// Check if the terms and definitions list exists
|
|
15
|
+
const termsListElement = document.querySelector(".terms-and-definitions-list");
|
|
16
|
+
const dtElements = termsListElement ? termsListElement.querySelectorAll("dt") : [];
|
|
17
|
+
|
|
18
|
+
if (dtElements.length === 0) {
|
|
19
|
+
// Hide the container if no terms exist
|
|
20
|
+
hideShowUtilityContainer();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const terminologySectionUtilityContainer = document.getElementById("terminology-section-utility-container");
|
|
25
|
+
|
|
26
|
+
// Build alphabet index data
|
|
27
|
+
const alphabetIndex = {};
|
|
28
|
+
dtElements.forEach(dt => {
|
|
29
|
+
const span = dt.querySelector("span");
|
|
30
|
+
if (span?.id) {
|
|
31
|
+
const termId = span.id;
|
|
32
|
+
const firstChar = termId.charAt(termId.indexOf("term:") + 5).toUpperCase();
|
|
33
|
+
if (!alphabetIndex[firstChar]) {
|
|
34
|
+
alphabetIndex[firstChar] = span.id;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/*************************************************/
|
|
40
|
+
/* DOM CONSTRUCTION - COMPLETE LAYOUT STRUCTURE */
|
|
41
|
+
/*************************************************/
|
|
42
|
+
|
|
43
|
+
/* ===== ROW 1: ALPHABET INDEX ===== */
|
|
44
|
+
// ALPHABET INDEX TEMPORARILY DISABLED
|
|
45
|
+
// const alphabetRow = document.createElement("div");
|
|
46
|
+
// alphabetRow.className = "row mb-2";
|
|
47
|
+
//
|
|
48
|
+
// const alphabetCol = document.createElement("div");
|
|
49
|
+
// alphabetCol.className = "col-12";
|
|
50
|
+
//
|
|
51
|
+
// const alphabetIndexContainer = document.createElement("div");
|
|
52
|
+
// alphabetIndexContainer.className = "d-flex flex-wrap justify-content-center gap-2";
|
|
53
|
+
|
|
54
|
+
// // Create alphabet links
|
|
55
|
+
// Object.keys(alphabetIndex).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).forEach(char => {
|
|
56
|
+
// const link = document.createElement("a");
|
|
57
|
+
// link.href = `#${alphabetIndex[char]}`;
|
|
58
|
+
// link.textContent = char;
|
|
59
|
+
// link.className = "btn btn-outline-secondary btn-sm";
|
|
60
|
+
// alphabetIndexContainer.appendChild(link);
|
|
61
|
+
// });
|
|
62
|
+
|
|
63
|
+
// alphabetCol.appendChild(alphabetIndexContainer);
|
|
64
|
+
// alphabetRow.appendChild(alphabetCol);
|
|
65
|
+
|
|
66
|
+
/* ===== ROW 2: UTILITIES (TERM COUNT + FILTERS + SEARCH) ===== */
|
|
67
|
+
const utilityRow = document.createElement("div");
|
|
68
|
+
utilityRow.className = "row g-2";
|
|
69
|
+
utilityRow.id = "utility-row";
|
|
70
|
+
|
|
71
|
+
// Left column: Term count
|
|
72
|
+
const leftCol = document.createElement("div");
|
|
73
|
+
leftCol.className = "col-auto d-flex align-items-center";
|
|
74
|
+
|
|
75
|
+
// Term count
|
|
76
|
+
const numberOfTerms = document.createElement("small");
|
|
77
|
+
numberOfTerms.className = "text-muted mb-0";
|
|
78
|
+
numberOfTerms.textContent = `${dtElements.length} terms`;
|
|
79
|
+
leftCol.appendChild(numberOfTerms);
|
|
80
|
+
|
|
81
|
+
// Center column: Filters and Versions button
|
|
82
|
+
const centerCol = document.createElement("div");
|
|
83
|
+
centerCol.className = "col d-flex flex-wrap align-items-center gap-3";
|
|
84
|
+
|
|
85
|
+
// Filter checkboxes container
|
|
86
|
+
const checkboxesContainer = document.createElement('div');
|
|
87
|
+
checkboxesContainer.className = 'd-flex gap-3';
|
|
88
|
+
|
|
89
|
+
// Local terms checkbox
|
|
90
|
+
const localTermsCheckboxDiv = document.createElement('div');
|
|
91
|
+
localTermsCheckboxDiv.className = 'form-check';
|
|
92
|
+
localTermsCheckboxDiv.innerHTML = `
|
|
93
|
+
<input class="form-check-input" type="checkbox" id="showLocalTermsCheckbox" checked>
|
|
94
|
+
<label class="form-check-label" for="showLocalTermsCheckbox">
|
|
95
|
+
Local
|
|
96
|
+
</label>
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
// External terms checkbox
|
|
100
|
+
const externalTermsCheckboxDiv = document.createElement('div');
|
|
101
|
+
externalTermsCheckboxDiv.className = 'form-check';
|
|
102
|
+
externalTermsCheckboxDiv.innerHTML = `
|
|
103
|
+
<input class="form-check-input" type="checkbox" id="showExternalTermsCheckbox" checked>
|
|
104
|
+
<label class="form-check-label" for="showExternalTermsCheckbox">
|
|
105
|
+
Remote
|
|
106
|
+
</label>
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
checkboxesContainer.appendChild(localTermsCheckboxDiv);
|
|
110
|
+
checkboxesContainer.appendChild(externalTermsCheckboxDiv);
|
|
111
|
+
centerCol.appendChild(checkboxesContainer);
|
|
112
|
+
|
|
113
|
+
// Snapshot link
|
|
114
|
+
const snapshotLink = document.createElement('a');
|
|
115
|
+
snapshotLink.id = 'snapshot-link-in-content';
|
|
116
|
+
snapshotLink.className = 'btn btn-outline-primary btn-sm';
|
|
117
|
+
// snapshotLink.href = './versions/';
|
|
118
|
+
snapshotLink.href = '#';
|
|
119
|
+
snapshotLink.textContent = 'Versions';
|
|
120
|
+
centerCol.appendChild(snapshotLink);
|
|
121
|
+
|
|
122
|
+
// Right column: Search
|
|
123
|
+
const rightCol = document.createElement("div");
|
|
124
|
+
rightCol.className = "col-auto d-flex justify-content-end";
|
|
125
|
+
|
|
126
|
+
// Search container
|
|
127
|
+
const searchContainer = document.createElement("div");
|
|
128
|
+
searchContainer.setAttribute("id", "container-search");
|
|
129
|
+
searchContainer.classList.add("input-group", "input-group-sm");
|
|
130
|
+
searchContainer.setAttribute("role", "search");
|
|
131
|
+
|
|
132
|
+
// Search input
|
|
133
|
+
const searchInput = document.createElement("input");
|
|
134
|
+
searchInput.setAttribute("type", "text");
|
|
135
|
+
searchInput.setAttribute("id", "search");
|
|
136
|
+
searchInput.classList.add("form-control");
|
|
137
|
+
searchInput.setAttribute("placeholder", "🔍 (terms only)");
|
|
138
|
+
searchInput.setAttribute("aria-label", "Search terms");
|
|
139
|
+
searchInput.setAttribute("autocomplete", "off");
|
|
140
|
+
searchContainer.appendChild(searchInput);
|
|
141
|
+
|
|
142
|
+
// Search button group
|
|
143
|
+
const buttonGroup = document.createElement("div");
|
|
144
|
+
buttonGroup.classList.add("input-group-text", "p-0");
|
|
145
|
+
|
|
146
|
+
// Previous match button
|
|
147
|
+
const goToPreviousMatchButton = document.createElement("button");
|
|
148
|
+
goToPreviousMatchButton.setAttribute("id", "one-match-backward-search");
|
|
149
|
+
goToPreviousMatchButton.classList.add("btn", "btn-outline-secondary");
|
|
150
|
+
goToPreviousMatchButton.setAttribute("type", "button");
|
|
151
|
+
goToPreviousMatchButton.setAttribute("disabled", "true");
|
|
152
|
+
goToPreviousMatchButton.setAttribute("title", "Go to previous match (Left Arrow)");
|
|
153
|
+
goToPreviousMatchButton.setAttribute("aria-label", "Go to previous match");
|
|
154
|
+
goToPreviousMatchButton.innerHTML = '<span aria-hidden="true">▲</span>';
|
|
155
|
+
buttonGroup.appendChild(goToPreviousMatchButton);
|
|
156
|
+
|
|
157
|
+
// Next match button
|
|
158
|
+
const goToNextMatchButton = document.createElement("button");
|
|
159
|
+
goToNextMatchButton.setAttribute("id", "one-match-forward-search");
|
|
160
|
+
goToNextMatchButton.classList.add("btn", "btn-outline-secondary");
|
|
161
|
+
goToNextMatchButton.setAttribute("type", "button");
|
|
162
|
+
goToNextMatchButton.setAttribute("disabled", "true");
|
|
163
|
+
goToNextMatchButton.setAttribute("title", "Go to next match (Right Arrow)");
|
|
164
|
+
goToNextMatchButton.setAttribute("aria-label", "Go to next match");
|
|
165
|
+
goToNextMatchButton.innerHTML = '<span aria-hidden="true">▼</span>';
|
|
166
|
+
buttonGroup.appendChild(goToNextMatchButton);
|
|
167
|
+
|
|
168
|
+
// Matches counter
|
|
169
|
+
const totalMatchesSpan = document.createElement("span");
|
|
170
|
+
totalMatchesSpan.setAttribute("id", "total-matches-search");
|
|
171
|
+
totalMatchesSpan.classList.add("input-group-text");
|
|
172
|
+
totalMatchesSpan.innerHTML = "0 matches";
|
|
173
|
+
totalMatchesSpan.setAttribute("aria-live", "polite");
|
|
174
|
+
totalMatchesSpan.setAttribute("role", "status");
|
|
175
|
+
searchContainer.appendChild(totalMatchesSpan);
|
|
176
|
+
|
|
177
|
+
searchContainer.appendChild(buttonGroup);
|
|
178
|
+
rightCol.appendChild(searchContainer);
|
|
179
|
+
|
|
180
|
+
utilityRow.appendChild(leftCol);
|
|
181
|
+
utilityRow.appendChild(centerCol);
|
|
182
|
+
utilityRow.appendChild(rightCol);
|
|
183
|
+
|
|
184
|
+
/* ===== ASSEMBLE COMPLETE STRUCTURE ===== */
|
|
185
|
+
// ALPHABET INDEX TEMPORARILY DISABLED
|
|
186
|
+
// terminologySectionUtilityContainer.appendChild(alphabetRow);
|
|
187
|
+
terminologySectionUtilityContainer.appendChild(utilityRow);
|
|
188
|
+
|
|
189
|
+
/*****************************************/
|
|
190
|
+
/* INITIALIZE FUNCTIONALITY COMPONENTS */
|
|
191
|
+
/*****************************************/
|
|
192
|
+
|
|
193
|
+
// Initialize functionalities (these will attach to the DOM elements we just created)
|
|
194
|
+
// ALPHABET INDEX TEMPORARILY DISABLED
|
|
195
|
+
// attachAlphabetIndexFunctionality();
|
|
196
|
+
attachTermFilterFunctionality(checkboxesContainer);
|
|
197
|
+
attachSearchFunctionality(searchInput, goToPreviousMatchButton, goToNextMatchButton, totalMatchesSpan);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Initialize when DOM is ready
|
|
201
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
202
|
+
initializeTerminologyUtilityContainer();
|
|
203
|
+
});
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TOOLTIP SYSTEM OVERVIEW
|
|
6
|
+
* =======================
|
|
7
|
+
* This module creates interactive tooltips for term references in the specification.
|
|
8
|
+
* It handles two types of content sources:
|
|
9
|
+
*
|
|
10
|
+
* 1. LOCAL TERMS (tref and def): Content from the current document's DOM
|
|
11
|
+
* - These are definition lists <dt>/<dd> or table rows <tr>/<td>
|
|
12
|
+
* - Example: [[def: term, alias]] creates a local definition
|
|
13
|
+
* - Example: [[tref: ., term, alias]] references a local term
|
|
14
|
+
*
|
|
15
|
+
* 2. EXTERNAL TERMS (xref): Content from external specifications
|
|
16
|
+
* - These come from the global allXTrefs data structure
|
|
17
|
+
* - Example: [[tref: externalreference, term]] references an external term
|
|
18
|
+
* - The allXTrefs data is populated by src/pipeline/references/external-references-service.js during build time
|
|
19
|
+
* - External content is fetched from other GitHub-hosted specifications
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/* Tooltips WeakMap storage for performance optimization */
|
|
23
|
+
// WeakMap prevents memory leaks by automatically garbage collecting
|
|
24
|
+
// when DOM elements are removed
|
|
25
|
+
let tipMap = new WeakMap();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* EVENT DELEGATION FOR TOOLTIP TRIGGERS
|
|
29
|
+
* =====================================
|
|
30
|
+
* Uses delegateEvent (from utils.js) to efficiently handle hover events.
|
|
31
|
+
* Event delegation attaches a single listener to the document rather than
|
|
32
|
+
* individual listeners on each term reference, improving performance.
|
|
33
|
+
*
|
|
34
|
+
* TARGET SELECTORS:
|
|
35
|
+
* - '.term-reference': Generated for [[tref: ...]] and [[def: ...]] constructions
|
|
36
|
+
* - '.spec-reference': Generated for specification references
|
|
37
|
+
*
|
|
38
|
+
* TRIGGER EVENT: 'pointerover'
|
|
39
|
+
* - Fires when mouse enters the element (more reliable than 'mouseenter')
|
|
40
|
+
* - Works on both mouse and touch devices
|
|
41
|
+
*/
|
|
42
|
+
delegateEvent('pointerover', '.term-reference, .spec-reference', (e, anchor) => {
|
|
43
|
+
/**
|
|
44
|
+
* EXTRACT TARGET INFORMATION
|
|
45
|
+
* =========================
|
|
46
|
+
* The anchor element contains attributes that tell us what term to show:
|
|
47
|
+
*
|
|
48
|
+
* - data-local-href: For local references (format: #termname or #term:external:termname)
|
|
49
|
+
* - href: Fallback attribute for standard links
|
|
50
|
+
*
|
|
51
|
+
* Examples of generated markup:
|
|
52
|
+
* - [[def: myterm]] creates: <a class="term-reference" data-local-href="#myterm">myterm</a>
|
|
53
|
+
* - [[tref: ., myterm]] creates: <a class="term-reference" data-local-href="#myterm">myterm</a>
|
|
54
|
+
* - [[tref: external, myterm]] creates: <a class="term-reference" data-local-href="#term:external:myterm">myterm</a>
|
|
55
|
+
*/
|
|
56
|
+
const id = anchor.getAttribute('data-local-href') || anchor.getAttribute('href') || '';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* ATTEMPT TO FIND LOCAL DOM ELEMENT
|
|
60
|
+
* ================================
|
|
61
|
+
* Try to find a DOM element with the ID (removing the # prefix).
|
|
62
|
+
* This works for local definitions and some tref references.
|
|
63
|
+
*/
|
|
64
|
+
let term = document.getElementById(id.replace('#', ''));
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* TOOLTIP CONFIGURATION
|
|
68
|
+
* ====================
|
|
69
|
+
* Base configuration for Tippy.js tooltips:
|
|
70
|
+
* - allowHTML: Enables rich HTML content in tooltips
|
|
71
|
+
* - inlinePositioning: Positions tooltip relative to cursor for better UX
|
|
72
|
+
*/
|
|
73
|
+
let tip = {
|
|
74
|
+
allowHTML: true,
|
|
75
|
+
inlinePositioning: true
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* PREVENT DUPLICATE TOOLTIPS
|
|
80
|
+
* =========================
|
|
81
|
+
* Check if we've already created a tooltip for this anchor element.
|
|
82
|
+
* The WeakMap ensures efficient lookup and automatic cleanup.
|
|
83
|
+
*/
|
|
84
|
+
if (tipMap.has(anchor)) return;
|
|
85
|
+
|
|
86
|
+
if (term) {
|
|
87
|
+
/**
|
|
88
|
+
* LOCAL DOM-BASED TOOLTIP CONTENT
|
|
89
|
+
* ===============================
|
|
90
|
+
* When we find a matching DOM element, extract content from it.
|
|
91
|
+
* The spec-up system creates definitions in two main formats:
|
|
92
|
+
*
|
|
93
|
+
* 1. DEFINITION LIST FORMAT:
|
|
94
|
+
* <dt id="myterm">Term Name</dt>
|
|
95
|
+
* <dd>Term definition content goes here</dd>
|
|
96
|
+
*
|
|
97
|
+
* 2. TABLE FORMAT:
|
|
98
|
+
* <table>
|
|
99
|
+
* <thead><tr><th>Term</th><th>Definition</th><th>Source</th></tr></thead>
|
|
100
|
+
* <tbody><tr><td id="myterm">Term Name</td><td>Definition</td><td>Source</td></tr></tbody>
|
|
101
|
+
* </table>
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
// Find the container element that holds the term definition
|
|
105
|
+
// Look for either <dt> (definition term) or first <td> (table cell)
|
|
106
|
+
let container = term.closest('dt, td:first-child');
|
|
107
|
+
if (!container) return;
|
|
108
|
+
|
|
109
|
+
switch (container.tagName) {
|
|
110
|
+
case 'DT':
|
|
111
|
+
/**
|
|
112
|
+
* DEFINITION LIST PROCESSING
|
|
113
|
+
* =========================
|
|
114
|
+
* In a definition list, the <dt> contains the term name,
|
|
115
|
+
* and the next sibling <dd> contains the definition text.
|
|
116
|
+
* We extract the text content from the <dd> element.
|
|
117
|
+
* If the first <dd> has class 'meta-info-content-wrapper', skip it and use the next <dd>.
|
|
118
|
+
*/
|
|
119
|
+
let dd = container.nextElementSibling;
|
|
120
|
+
if (dd && dd.tagName === 'DD' && dd.classList.contains('meta-info-content-wrapper')) {
|
|
121
|
+
dd = dd.nextElementSibling;
|
|
122
|
+
}
|
|
123
|
+
if (dd && dd.tagName === 'DD') {
|
|
124
|
+
tip.content = dd.textContent;
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
case 'TD':
|
|
129
|
+
/**
|
|
130
|
+
* TABLE FORMAT PROCESSING
|
|
131
|
+
* ======================
|
|
132
|
+
* For table-based definitions, we create a mini table showing
|
|
133
|
+
* all the metadata associated with the term. This provides
|
|
134
|
+
* richer context than just the definition text.
|
|
135
|
+
*
|
|
136
|
+
* Process:
|
|
137
|
+
* 1. Find the parent table and get all header cells
|
|
138
|
+
* 2. Get all data cells from the current row
|
|
139
|
+
* 3. Create HTML table mapping headers to values
|
|
140
|
+
* 4. Skip the first column (which contains the term name itself)
|
|
141
|
+
*/
|
|
142
|
+
let table = container.closest('table');
|
|
143
|
+
let tds = Array.from(container.closest('tr').children);
|
|
144
|
+
tds.shift(); // Remove first cell (the term name)
|
|
145
|
+
|
|
146
|
+
if (table) {
|
|
147
|
+
let headings = Array.from(table.querySelectorAll('thead th'));
|
|
148
|
+
headings.shift(); // Remove first heading (matches the term name column)
|
|
149
|
+
|
|
150
|
+
if (headings.length) {
|
|
151
|
+
tip.content = `
|
|
152
|
+
<header>${container.textContent}</header>
|
|
153
|
+
<table>
|
|
154
|
+
${headings.map((th, i) => {
|
|
155
|
+
return `<tr><td>${th.textContent}:</td><td>${tds[i] ? tds[i].textContent : ''}</td></tr>`
|
|
156
|
+
}).join('')}
|
|
157
|
+
</table>`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
/**
|
|
164
|
+
* EXTERNAL REFERENCE TOOLTIP CONTENT
|
|
165
|
+
* ==================================
|
|
166
|
+
* When no local DOM element is found, this is likely an external reference (xref).
|
|
167
|
+
* External references have the format: #term:externalSpec:termName
|
|
168
|
+
*
|
|
169
|
+
* THE allXTrefs DATA SOURCE:
|
|
170
|
+
* -------------------------
|
|
171
|
+
* The allXTrefs global variable is created during the build process by:
|
|
172
|
+
* 1. src/pipeline/references/external-references-service.js - Orchestrates external reference collection
|
|
173
|
+
* 2. src/pipeline/references/ - Fetches content from external GitHub specs
|
|
174
|
+
* 3. The data is written to a JavaScript file and included in the page
|
|
175
|
+
*
|
|
176
|
+
* DATA STRUCTURE:
|
|
177
|
+
* allXTrefs = {
|
|
178
|
+
* xtrefs: [
|
|
179
|
+
* {
|
|
180
|
+
* externalSpec: "spec-name", // The external specification identifier
|
|
181
|
+
* term: "term-name", // The term being referenced
|
|
182
|
+
* content: "Definition text...", // The definition content from external spec
|
|
183
|
+
* url: "https://...", // Source URL
|
|
184
|
+
* // ... other metadata
|
|
185
|
+
* }
|
|
186
|
+
* ]
|
|
187
|
+
* }
|
|
188
|
+
*
|
|
189
|
+
* EXTERNAL SPEC FETCHING PROCESS:
|
|
190
|
+
* ------------------------------
|
|
191
|
+
* 1. External specs are defined in specs-configuration.yml
|
|
192
|
+
* 2. During build, the system fetches HTML from external GitHub Pages
|
|
193
|
+
* 3. Term definitions are extracted from the external HTML
|
|
194
|
+
* 4. Content is cached in JSON and JS files for runtime use
|
|
195
|
+
* 5. This allows tooltips to show definitions from other specifications
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
// Handle xref terms from allXTrefs data (when no DOM element found)
|
|
199
|
+
const href = anchor.getAttribute('data-local-href') || '';
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* PARSE EXTERNAL REFERENCE FORMAT
|
|
203
|
+
* ===============================
|
|
204
|
+
* Expected format: #term:externalSpec:termName
|
|
205
|
+
* Example: #term:KERI:delegator means:
|
|
206
|
+
* - externalSpec: "KERI"
|
|
207
|
+
* - termName: "delegator"
|
|
208
|
+
*/
|
|
209
|
+
const match = href.match(/#term:([^:]+):(.+)/);
|
|
210
|
+
if (match) {
|
|
211
|
+
const [, externalSpec, termName] = match;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* LOOKUP IN EXTERNAL REFERENCES DATA
|
|
215
|
+
* =================================
|
|
216
|
+
* Search the global allXTrefs data for matching term.
|
|
217
|
+
* Uses case-insensitive matching to handle inconsistencies
|
|
218
|
+
* between how terms are defined vs. referenced.
|
|
219
|
+
*/
|
|
220
|
+
if (typeof allXTrefs !== 'undefined' && allXTrefs.xtrefs && allXTrefs.xtrefs.length > 0) {
|
|
221
|
+
// Look for term with case-insensitive matching to handle case inconsistencies
|
|
222
|
+
const foundTerm = allXTrefs.xtrefs.find(xtref =>
|
|
223
|
+
xtref.externalSpec === externalSpec &&
|
|
224
|
+
xtref.term.toLowerCase() === termName.toLowerCase()
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
if (foundTerm && foundTerm.content) {
|
|
228
|
+
/**
|
|
229
|
+
* CLEAN HTML CONTENT FOR TOOLTIP
|
|
230
|
+
* ==============================
|
|
231
|
+
* The external content may contain HTML markup that's not suitable
|
|
232
|
+
* for tooltips. We create a temporary DOM element to extract
|
|
233
|
+
* clean text content while preserving structure.
|
|
234
|
+
*/
|
|
235
|
+
// Strip HTML tags for clean text tooltip
|
|
236
|
+
const tempDiv = document.createElement('div');
|
|
237
|
+
tempDiv.innerHTML = foundTerm.content;
|
|
238
|
+
tip.content = tempDiv.textContent || tempDiv.innerText || '';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* CREATE AND STORE TOOLTIP
|
|
246
|
+
* ========================
|
|
247
|
+
* If we successfully extracted content (from either local DOM or external data),
|
|
248
|
+
* create the actual tooltip using Tippy.js library and store it in our WeakMap
|
|
249
|
+
* for future reference and duplicate prevention.
|
|
250
|
+
*
|
|
251
|
+
* TIPPY.JS INTEGRATION:
|
|
252
|
+
* - tippy() creates the interactive tooltip element
|
|
253
|
+
* - WeakMap storage prevents memory leaks and duplicate creation
|
|
254
|
+
* - Tooltip appears on hover and includes rich formatting
|
|
255
|
+
*/
|
|
256
|
+
if (tip.content) {
|
|
257
|
+
tipMap.set(anchor, tippy(anchor, tip))
|
|
258
|
+
};
|
|
259
|
+
}, { passive: true }); // passive: true improves scroll performance
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* MODULE SUMMARY
|
|
263
|
+
* =============
|
|
264
|
+
* This tooltip system provides contextual help for specification terms by:
|
|
265
|
+
*
|
|
266
|
+
* 1. LISTENING: Uses event delegation to efficiently handle hover events
|
|
267
|
+
* 2. IDENTIFYING: Determines if term is local (DOM-based) or external (xref)
|
|
268
|
+
* 3. EXTRACTING: Gets content from appropriate source (DOM elements or allXTrefs data)
|
|
269
|
+
* 4. DISPLAYING: Creates rich tooltips with Tippy.js
|
|
270
|
+
* 5. CACHING: Prevents duplicate tooltip creation with WeakMap storage
|
|
271
|
+
*
|
|
272
|
+
* CONTENT SOURCES:
|
|
273
|
+
* - Local definitions: From <dt>/<dd> or table rows in current document
|
|
274
|
+
* - External references: From allXTrefs global data (fetched during build)
|
|
275
|
+
*
|
|
276
|
+
* MARKDOWN SYNTAX SUPPORTED:
|
|
277
|
+
* - [[def: term, alias]] - Creates local definition
|
|
278
|
+
* - [[tref: ., term, alias]] - References local term
|
|
279
|
+
* - [[tref: externalreference, term]] - References external term
|
|
280
|
+
* - [[xref: externalreference, term]] - External reference (alias for tref)
|
|
281
|
+
*/
|
|
282
|
+
|
|
283
|
+
})();
|