From df1473844f7001deb8cac84650faf1dad972c23f Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 8 Feb 2026 22:13:48 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E7=94=A8=E8=AA=9E=E9=9B=86?= =?UTF-8?q?=E3=81=AE=E7=B4=A2=E5=BC=95=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIG-DOCUMENT: Glossary --- .../resources/templates/assets/glossary.js | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/glossary.js b/jig-core/src/main/resources/templates/assets/glossary.js index a41cd0b9f..4bdc3e8b6 100644 --- a/jig-core/src/main/resources/templates/assets/glossary.js +++ b/jig-core/src/main/resources/templates/assets/glossary.js @@ -95,6 +95,35 @@ function getGlossaryData() { return glossaryData.terms ?? []; } +function getIndexKey(title) { + const text = (title || "").trim(); + if (!text) return "#"; + const firstChar = text[0]; + if (/[0-9]/.test(firstChar)) return "0-9"; + if (/[A-Za-z]/.test(firstChar)) return firstChar.toUpperCase(); + return firstChar; +} + +function buildIndexAnchorId(key) { + const normalized = Array.from(key) + .map(char => char.codePointAt(0).toString(16)) + .join("-"); + return `index-${normalized}`; +} + +function buildIndexEntries(terms) { + const entries = []; + let lastKey = null; + terms.forEach(term => { + const key = getIndexKey(term.title); + if (key !== lastKey) { + entries.push({key, anchorId: buildIndexAnchorId(key)}); + lastKey = key; + } + }); + return entries; +} + function escapeCsvValue(value) { const text = String(value ?? "") .replace(/\r\n/g, "\n") @@ -128,13 +157,56 @@ function downloadCsv(text, filename) { URL.revokeObjectURL(url); } -function renderGlossaryTerms(terms) { +function renderTermIndex(indexEntries) { + const list = document.getElementById("term-list"); + if (!list) return; + + let indexRoot = document.getElementById("term-index"); + if (!indexRoot) { + indexRoot = document.createElement("nav"); + indexRoot.id = "term-index"; + indexRoot.className = "term-index"; + list.parentNode.insertBefore(indexRoot, list); + } + + indexRoot.innerHTML = ""; + if (indexEntries.length === 0) { + indexRoot.style.display = "none"; + return; + } + + indexRoot.style.display = ""; + const fragment = document.createDocumentFragment(); + indexEntries.forEach(entry => { + const link = document.createElement("a"); + link.className = "term-index__item"; + link.href = `#${entry.anchorId}`; + link.textContent = entry.key; + fragment.appendChild(link); + }); + indexRoot.appendChild(fragment); +} + +function renderGlossaryTerms(terms, indexEntries = []) { const list = document.getElementById("term-list"); if (!list) return; list.innerHTML = ""; const fragment = document.createDocumentFragment(); + let indexCursor = 0; + let nextIndexEntry = indexEntries[indexCursor]; terms.forEach(term => { + const indexKey = getIndexKey(term.title); + if (nextIndexEntry && indexKey === nextIndexEntry.key) { + const anchor = document.createElement("div"); + anchor.className = "term-index-anchor"; + anchor.id = nextIndexEntry.anchorId; + anchor.textContent = nextIndexEntry.key; + fragment.appendChild(anchor); + indexCursor += 1; + nextIndexEntry = indexEntries[indexCursor]; + } + const article = document.createElement("article"); article.className = "term"; if (term.fqn) { @@ -193,7 +265,9 @@ function renderMarkdownDescriptions() { function renderFilteredTerms(terms, controls) { const filteredTerms = getFilteredTerms(terms, controls); const sortedTerms = sortTerms(filteredTerms, controls.sortOrder?.value); - renderGlossaryTerms(sortedTerms); + const indexEntries = buildIndexEntries(sortedTerms); + renderTermIndex(indexEntries); + renderGlossaryTerms(sortedTerms, indexEntries); renderMarkdownDescriptions(); } @@ -244,8 +318,12 @@ if (typeof module !== "undefined" && module.exports) { sortTerms, getFilteredTerms, getGlossaryData, + getIndexKey, + buildIndexAnchorId, + buildIndexEntries, escapeCsvValue, buildGlossaryCsv, + renderTermIndex, renderGlossaryTerms, renderFilteredTerms, renderMarkdownDescriptions, From 5b1c5d67a261e0281d2e1f942265c5bb5035c198 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 8 Feb 2026 22:21:50 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E7=94=A8=E8=AA=9E=E9=9B=86?= =?UTF-8?q?=E3=81=AE=E4=B8=80=E8=A6=A7=E3=82=B5=E3=82=A4=E3=83=89=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JIG-DOCUMENT: Glossary --- .../resources/templates/assets/glossary.js | 93 ++++--------------- .../main/resources/templates/assets/style.css | 57 ++++++++++++ .../main/resources/templates/glossary.html | 8 +- 3 files changed, 83 insertions(+), 75 deletions(-) diff --git a/jig-core/src/main/resources/templates/assets/glossary.js b/jig-core/src/main/resources/templates/assets/glossary.js index 4bdc3e8b6..d40726f33 100644 --- a/jig-core/src/main/resources/templates/assets/glossary.js +++ b/jig-core/src/main/resources/templates/assets/glossary.js @@ -95,33 +95,8 @@ function getGlossaryData() { return glossaryData.terms ?? []; } -function getIndexKey(title) { - const text = (title || "").trim(); - if (!text) return "#"; - const firstChar = text[0]; - if (/[0-9]/.test(firstChar)) return "0-9"; - if (/[A-Za-z]/.test(firstChar)) return firstChar.toUpperCase(); - return firstChar; -} - -function buildIndexAnchorId(key) { - const normalized = Array.from(key) - .map(char => char.codePointAt(0).toString(16)) - .join("-"); - return `index-${normalized}`; -} - -function buildIndexEntries(terms) { - const entries = []; - let lastKey = null; - terms.forEach(term => { - const key = getIndexKey(term.title); - if (key !== lastKey) { - entries.push({key, anchorId: buildIndexAnchorId(key)}); - lastKey = key; - } - }); - return entries; +function buildTermAnchorId(term, index) { + return term.fqn || `term-${index}`; } function escapeCsvValue(value) { @@ -157,62 +132,35 @@ function downloadCsv(text, filename) { URL.revokeObjectURL(url); } -function renderTermIndex(indexEntries) { - const list = document.getElementById("term-list"); +function renderTermSidebar(terms) { + const list = document.getElementById("term-sidebar-list"); if (!list) return; - let indexRoot = document.getElementById("term-index"); - if (!indexRoot) { - indexRoot = document.createElement("nav"); - indexRoot.id = "term-index"; - indexRoot.className = "term-index"; - list.parentNode.insertBefore(indexRoot, list); - } - - indexRoot.innerHTML = ""; - if (indexEntries.length === 0) { - indexRoot.style.display = "none"; - return; - } + list.innerHTML = ""; + if (terms.length === 0) return; - indexRoot.style.display = ""; const fragment = document.createDocumentFragment(); - indexEntries.forEach(entry => { + terms.forEach((term, index) => { const link = document.createElement("a"); - link.className = "term-index__item"; - link.href = `#${entry.anchorId}`; - link.textContent = entry.key; + link.className = "term-sidebar__item"; + link.href = `#${buildTermAnchorId(term, index)}`; + link.textContent = term.title || ""; fragment.appendChild(link); }); - indexRoot.appendChild(fragment); + list.appendChild(fragment); } -function renderGlossaryTerms(terms, indexEntries = []) { +function renderGlossaryTerms(terms) { const list = document.getElementById("term-list"); if (!list) return; list.innerHTML = ""; const fragment = document.createDocumentFragment(); - let indexCursor = 0; - let nextIndexEntry = indexEntries[indexCursor]; - terms.forEach(term => { - const indexKey = getIndexKey(term.title); - if (nextIndexEntry && indexKey === nextIndexEntry.key) { - const anchor = document.createElement("div"); - anchor.className = "term-index-anchor"; - anchor.id = nextIndexEntry.anchorId; - anchor.textContent = nextIndexEntry.key; - fragment.appendChild(anchor); - indexCursor += 1; - nextIndexEntry = indexEntries[indexCursor]; - } - + terms.forEach((term, index) => { const article = document.createElement("article"); article.className = "term"; - if (term.fqn) { - // 他ドキュメントからのリンク用にFQNをIDとして設定する - article.id = term.fqn; - } + // 他ドキュメントからのリンク用にFQNをIDとして設定する + article.id = buildTermAnchorId(term, index); const title = document.createElement("h2"); title.className = "term-title"; @@ -265,9 +213,8 @@ function renderMarkdownDescriptions() { function renderFilteredTerms(terms, controls) { const filteredTerms = getFilteredTerms(terms, controls); const sortedTerms = sortTerms(filteredTerms, controls.sortOrder?.value); - const indexEntries = buildIndexEntries(sortedTerms); - renderTermIndex(indexEntries); - renderGlossaryTerms(sortedTerms, indexEntries); + renderTermSidebar(sortedTerms); + renderGlossaryTerms(sortedTerms); renderMarkdownDescriptions(); } @@ -318,12 +265,10 @@ if (typeof module !== "undefined" && module.exports) { sortTerms, getFilteredTerms, getGlossaryData, - getIndexKey, - buildIndexAnchorId, - buildIndexEntries, + buildTermAnchorId, escapeCsvValue, buildGlossaryCsv, - renderTermIndex, + renderTermSidebar, renderGlossaryTerms, renderFilteredTerms, renderMarkdownDescriptions, diff --git a/jig-core/src/main/resources/templates/assets/style.css b/jig-core/src/main/resources/templates/assets/style.css index 26f699464..a9d886a1f 100644 --- a/jig-core/src/main/resources/templates/assets/style.css +++ b/jig-core/src/main/resources/templates/assets/style.css @@ -267,6 +267,63 @@ label { padding: 5px 0; } +.glossary .glossary-content { + display: grid; + grid-template-columns: minmax(180px, 260px) 1fr; + gap: 16px; + width: 90%; + max-width: 1200px; + margin: 0 auto; +} + +.glossary .term-sidebar { + border: 1px solid #ccc; + border-radius: 6px; + padding: 12px 14px; + background-color: #fafafa; + height: fit-content; + position: sticky; + top: 16px; + align-self: start; + max-height: calc(100vh - 32px); + overflow: auto; +} + +.glossary .term-sidebar h2 { + font-size: 16px; + margin: 0 0 8px 0; +} + +.glossary .term-sidebar__list { + display: block; +} + +.glossary .term-sidebar__item { + display: block; + padding: 3px 0; + color: #1a1a1a; + text-decoration: none; +} + +.glossary .term-sidebar__item:hover { + text-decoration: underline; +} + +.glossary .term-list { + width: 100%; +} + +@media (max-width: 900px) { + .glossary .glossary-content { + grid-template-columns: 1fr; + } + + .glossary .term-sidebar { + position: static; + max-height: none; + } +} + .glossary details.controls > div, .glossary details.controls > label { display: block; diff --git a/jig-core/src/main/resources/templates/glossary.html b/jig-core/src/main/resources/templates/glossary.html index a6f957f86..a747676d2 100644 --- a/jig-core/src/main/resources/templates/glossary.html +++ b/jig-core/src/main/resources/templates/glossary.html @@ -41,7 +41,13 @@ -
+
+ +
+