diff --git a/jig-core/src/main/resources/templates/assets/glossary.js b/jig-core/src/main/resources/templates/assets/glossary.js index a41cd0b9f..d40726f33 100644 --- a/jig-core/src/main/resources/templates/assets/glossary.js +++ b/jig-core/src/main/resources/templates/assets/glossary.js @@ -95,6 +95,10 @@ function getGlossaryData() { return glossaryData.terms ?? []; } +function buildTermAnchorId(term, index) { + return term.fqn || `term-${index}`; +} + function escapeCsvValue(value) { const text = String(value ?? "") .replace(/\r\n/g, "\n") @@ -128,19 +132,35 @@ function downloadCsv(text, filename) { URL.revokeObjectURL(url); } +function renderTermSidebar(terms) { + const list = document.getElementById("term-sidebar-list"); + if (!list) return; + + list.innerHTML = ""; + if (terms.length === 0) return; + + const fragment = document.createDocumentFragment(); + terms.forEach((term, index) => { + const link = document.createElement("a"); + link.className = "term-sidebar__item"; + link.href = `#${buildTermAnchorId(term, index)}`; + link.textContent = term.title || ""; + fragment.appendChild(link); + }); + list.appendChild(fragment); +} + function renderGlossaryTerms(terms) { const list = document.getElementById("term-list"); if (!list) return; list.innerHTML = ""; const fragment = document.createDocumentFragment(); - terms.forEach(term => { + 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"; @@ -193,6 +213,7 @@ function renderMarkdownDescriptions() { function renderFilteredTerms(terms, controls) { const filteredTerms = getFilteredTerms(terms, controls); const sortedTerms = sortTerms(filteredTerms, controls.sortOrder?.value); + renderTermSidebar(sortedTerms); renderGlossaryTerms(sortedTerms); renderMarkdownDescriptions(); } @@ -244,8 +265,10 @@ if (typeof module !== "undefined" && module.exports) { sortTerms, getFilteredTerms, getGlossaryData, + buildTermAnchorId, escapeCsvValue, buildGlossaryCsv, + 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 @@ -
+
+ +
+