diff --git a/jig-core/src/main/resources/templates/assets/glossary.js b/jig-core/src/main/resources/templates/assets/glossary.js index d86800f61..13d4b8152 100644 --- a/jig-core/src/main/resources/templates/assets/glossary.js +++ b/jig-core/src/main/resources/templates/assets/glossary.js @@ -40,6 +40,25 @@ function updateArticleVisibility(controls) { } +// 文字列の比較は日本語を優先しつつ大小を無視する +const termCollator = new Intl.Collator("ja", {numeric: true, sensitivity: "base"}); + +function sortTerms(terms, sortKey) { + const keyMap = { + name: "title", + fqn: "fqn", + simple: "simpleText", + }; + const key = keyMap[sortKey] ?? "title"; + return [...terms].sort((left, right) => { + const leftValue = left?.[key] ?? ""; + const rightValue = right?.[key] ?? ""; + const primary = termCollator.compare(leftValue, rightValue); + if (primary !== 0) return primary; + return termCollator.compare(left?.fqn ?? "", right?.fqn ?? ""); + }); +} + function getFilteredTerms(terms, controls) { if (!controls) return terms; const showEmptyDescription = controls.showEmptyDescription.checked; @@ -167,13 +186,18 @@ function renderMarkdownDescriptions() { .forEach(node => node.innerHTML = marked.parse(node.innerHTML)); } +function renderFilteredTerms(terms, controls) { + const filteredTerms = getFilteredTerms(terms, controls); + const sortedTerms = sortTerms(filteredTerms, controls.sortOrder?.value); + renderGlossaryTerms(sortedTerms); + renderMarkdownDescriptions(); +} + if (typeof document !== "undefined") { document.addEventListener("DOMContentLoaded", function () { if (!document.body.classList.contains("glossary")) return; const terms = getGlossaryData(); - renderGlossaryTerms(terms); - renderMarkdownDescriptions(); const controls = { searchInput: document.getElementById("search-input"), @@ -182,9 +206,10 @@ if (typeof document !== "undefined") { showClass: document.getElementById("show-class"), showMethod: document.getElementById("show-method"), showField: document.getElementById("show-field"), + sortOrder: document.getElementById("sort-order"), }; - const updateArticles = () => updateArticleVisibility(controls); + const updateArticles = () => renderFilteredTerms(terms, controls); controls.searchInput.addEventListener("input", updateArticles); controls.showEmptyDescription.addEventListener("change", updateArticles); @@ -192,6 +217,9 @@ if (typeof document !== "undefined") { controls.showClass.addEventListener("change", updateArticles); controls.showMethod.addEventListener("change", updateArticles); controls.showField.addEventListener("change", updateArticles); + if (controls.sortOrder) { + controls.sortOrder.addEventListener("change", updateArticles); + } const exportButton = document.getElementById("export-csv"); if (exportButton) { exportButton.addEventListener("click", () => { @@ -209,11 +237,13 @@ if (typeof document !== "undefined") { if (typeof module !== "undefined" && module.exports) { module.exports = { updateArticleVisibility, + sortTerms, getFilteredTerms, getGlossaryData, escapeCsvValue, buildGlossaryCsv, renderGlossaryTerms, + renderFilteredTerms, renderMarkdownDescriptions, }; } diff --git a/jig-core/src/main/resources/templates/glossary.html b/jig-core/src/main/resources/templates/glossary.html index 9b306a30e..a6f957f86 100644 --- a/jig-core/src/main/resources/templates/glossary.html +++ b/jig-core/src/main/resources/templates/glossary.html @@ -28,6 +28,14 @@ +
+ + +
diff --git a/jig-core/src/test/js/glossary.test.js b/jig-core/src/test/js/glossary.test.js index 9893b6183..464f45906 100644 --- a/jig-core/src/test/js/glossary.test.js +++ b/jig-core/src/test/js/glossary.test.js @@ -92,6 +92,24 @@ test.describe('glossary.js', () => { }); }); + test.describe('ソート', () => { + test('名前・完全修飾名・単純名で並び替える', () => { + const terms = [ + {title: 'Order', simpleText: 'Order', fqn: 'app.Order'}, + {title: 'Account', simpleText: 'Account', fqn: 'app.Account'}, + {title: 'User', simpleText: 'User', fqn: 'app.domain.User'}, + ]; + + const byName = glossary.sortTerms(terms, 'name').map(term => term.title); + const byFqn = glossary.sortTerms(terms, 'fqn').map(term => term.fqn); + const bySimple = glossary.sortTerms(terms, 'simple').map(term => term.simpleText); + + assert.deepEqual(byName, ['Account', 'Order', 'User']); + assert.deepEqual(byFqn, ['app.Account', 'app.domain.User', 'app.Order']); + assert.deepEqual(bySimple, ['Account', 'Order', 'User']); + }); + }); + test.describe('データ読み込み', () => { test('glossary-dataから用語一覧を取得する', () => { const doc = setupDocument();