spec-up-t 1.4.1-beta.1 → 1.4.1-beta.3
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/assets/compiled/head.css +2 -2
- package/assets/css/insert-trefs.css +1 -7
- package/assets/css/terms-and-definitions.css +9 -0
- package/package.json +1 -1
- package/src/freeze-spec-data.js +4 -0
- package/src/freeze.js +39 -0
- package/src/parsers/template-tag-parser-html-output.test.js +306 -0
- package/src/parsers/template-tag-parser.js +30 -17
- package/src/parsers/template-tag-parser.test.js +27 -0
- package/src/pipeline/references/external-references-service.js +48 -21
- package/src/pipeline/references/process-xtrefs-data.js +26 -4
- package/src/utils/logger.js +1 -1
package/assets/compiled/head.css
CHANGED
|
@@ -9,7 +9,7 @@ code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shad
|
|
|
9
9
|
@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}
|
|
10
10
|
#logo img{width:100px}.bi{display:inline-block;width:1.2rem;height:1.2rem}#header,#header *{background-color:var(--header-navbar-bg);color:var(--header-navbar-text)}[issue-count]:after{content:"(" attr(issue-count) ")";margin:0 0 0 .3em;padding:.1em 0 0}[issue-count=""][animate]{display:none;opacity:0}[issue-count][animate]:not([issue-count=""]){animation:display-show 1s}#header a:not(#logo),#header a:not(#logo) *,#header button,#header button *{background:#a9dde0}
|
|
11
11
|
.container{min-height:100vh}@media (min-width:768px){.sidebar{position:sticky;top:0;height:100vh;overflow-y:auto;z-index:100}}ul.toc{padding:1em 0 1.75em;font-size:.85em}ul.toc,ul.toc ul{margin:0;padding:0;list-style:none}ul.toc li a{display:block;padding:.4em .8em;text-decoration:none;color:var(--toc-text);transition:background .2s ease,padding-left .2s ease,border-left .2s ease;padding-left:.8em;border-left:3px solid transparent;border-bottom:1px solid rgba(0,0,0,.03)}ul.toc ul{margin-left:1.2em!important;margin-top:.3em}ul.toc a:hover{background:#f9f9f9;padding-left:1em;border-left:3px solid #007bff}ul.toc>li>ul>li>a{padding-left:1.5em}ul.toc>li>ul>li>ul>li>a{padding-left:2em}ul.toc a:focus{outline:2px solid #007bff;outline-offset:-2px;box-shadow:0 0 0 3px rgba(0,123,255,.25);position:relative;z-index:1}.spec-footer{font-size:.7em;text-align:center}ul.toc li a.active,ul.toc li a[aria-current=page]{border-left:3px solid #007bff;font-weight:500;background:#f0f0f0}ul.toc li:last-child a{border-bottom:none}#toc ul li{position:relative}#toc ul li.has-children{position:relative}.collapse-toggle{position:absolute;top:.35em;right:0;width:28px;height:28px;background:0 0;border:1px solid transparent;border-radius:3px;padding:0;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background-color .2s,border-color .2s;z-index:2}.collapse-toggle:hover{background-color:rgba(0,0,0,.05)}.collapse-toggle:focus{outline:2px solid #007bff;outline-offset:1px;border-color:#007bff;box-shadow:0 0 0 3px rgba(0,123,255,.25)}.collapse-toggle::after{content:"";border-style:solid;border-width:.15em .15em 0 0;display:inline-block;height:.5em;width:.5em;position:relative;transform:rotate(135deg);transition:transform .2s ease}.collapse-toggle.collapsed::after{transform:rotate(45deg)}#toc ul li.has-children>ul{max-height:1000px;overflow:hidden;transition:max-height .3s ease-in-out,opacity .3s ease-in-out;opacity:1}#toc ul li.has-children.collapsed>ul{max-height:0;overflow:hidden;opacity:.6}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.screen-reader-text:focus{width:auto;height:auto;clip:auto;display:block;padding:15px 23px 14px;background-color:#fff;color:#007bff;z-index:100000;font-size:1em;font-weight:700;text-decoration:none;line-height:normal;box-shadow:0 0 2px 2px rgba(0,0,0,.2)}
|
|
12
|
-
h2,h3,h4,h5,h6{margin-top:1.5em!important}#terminology-section-utility-container{margin:1em auto;width:100%}@media (min-width:576px){#terminology-section-utility-container{position:sticky;top:3.5em;z-index:10}}.toc-anchor{font-size:.875em;color:#73c2df;text-decoration:none;transition:opacity .3s ease}.toc-anchor:hover{opacity:1}dl.terms-and-definitions-list{margin:0;padding:0;position:relative;z-index:1}dl.terms-and-definitions-list>dt{font-weight:700;margin:0;background-color:var(--card-bg-dt);border:1px solid var(--card-border);padding:.5rem 1.25rem;border-radius:.375rem .375rem 0 0;color:var(--card-text);position:relative;line-height:1.5;font-size:1.05em;display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;min-height:2.5rem}dl.terms-and-definitions-list>dt>span{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:all .2s ease-in-out}dl.terms-and-definitions-list>dt:focus>span,dl.terms-and-definitions-list>dt:hover>span,dl.terms-and-definitions-list>dt>span:focus,dl.terms-and-definitions-list>dt>span:hover{white-space:normal;overflow:visible;text-overflow:initial;word-wrap:break-word;line-height:1.4}dl.terms-and-definitions-list>dt:active>span,dl.terms-and-definitions-list>dt>span:active{white-space:normal;overflow:visible;text-overflow:initial;word-wrap:break-word;line-height:1.4}dl.terms-and-definitions-list>dt .btn{font-size:1.2em!important}dl.terms-and-definitions-list>dt:hover{background-color:var(--card-hover-bg);transition:background-color .2s ease-in-out}dl.terms-and-definitions-list>dd{margin:0;background-color:var(--card-bg);border:1px solid var(--card-border);border-top:none;padding:.5rem 2rem;color:var(--card-text);width:100%;position:relative}dl.terms-and-definitions-list dd p{margin:0;color:var(--card-text);padding-left:.5rem}dl.terms-and-definitions-list>dd::before{display:none}dl.terms-and-definitions-list dd table{margin:.5em 0;width:100%;border-collapse:collapse}dl.terms-and-definitions-list dd table td,dl.terms-and-definitions-list dd table th{padding:.5em;border:1px solid #ddd}dl.terms-and-definitions-list dd table th{background-color:#f5f5f5}dl>dd:has(table).meta-info-content-wrapper{display:block;font-size:.7em;position:relative;max-height:none;height:auto;overflow:visible;transition:all .3s ease-out}dl>dd:has(table).meta-info-content-wrapper td,dl>dd:has(table).meta-info-content-wrapper th{padding:.3em}dl>dd:has(table).collapsed.meta-info-content-wrapper{max-height:0;height:0;padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;overflow:hidden;transition:all .3s ease-out;line-height:0}dl>dd:has(table).meta-info-content-wrapper>div{display:block;padding:inherit}.meta-info-toggle-button{position:relative;cursor:pointer}dl.terms-and-definitions-list dt+dd{border-bottom:none}dl.terms-and-definitions-list dd+dd{border-top:none;border-bottom:none}dl.terms-and-definitions-list dd+dt{margin-top:1rem}dl.terms-and-definitions-list dd.last-dd{border-radius:0 0 .375rem .375rem;border-bottom:1px solid var(--card-border)}html.dense-info dl.terms-and-definitions-list>dt{padding:.1rem 1.25rem;line-height:inherit;font-size:inherit;min-height:auto}html.dense-info dl.terms-and-definitions-list dd+dt{margin-top:.3rem}html.dense-info dl.terms-and-definitions-list>dt::before{display:none}html.dense-info dl.terms-and-definitions-list>dt>span{flex:1 1 auto;min-width:0}.term-external{position:relative}dl.terms-and-definitions-list>dd.term-external,dl.terms-and-definitions-list>dt.term-external{background:#a9dde03b}.term-local{position:relative}dl.terms-and-definitions-list>dd.term-local,dl.terms-and-definitions-list>dt.term-local{background:#f0f8ff}dl.terms-and-definitions-list .term-local-original-term{font-style:italic}dl.terms-and-definitions-list .term-local-parenthetical-terms{font-weight:400}.btn{font-variant:small-caps;text-transform:uppercase;position:relative;z-index:5}.transclusion-heading{font-size:1.3em;font-weight:700;margin:.2em 0 .2em}dl.terms-and-definitions-list>dd.term-external-embedded{position:relative;padding-left:2.5rem;padding-top:.3rem;padding-bottom:.3rem}dl.terms-and-definitions-list span.term-external::after{content:"";position:absolute;top:50%;transform:translateY(-50%);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' class='bi bi-box-arrow-right' viewBox='0 0 16 16' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z'/%3E%3Cpath fill-rule='evenodd' d='M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z'/%3E%3C/svg%3E");background-size:contain;background-repeat:no-repeat;background-position:center;width:.8rem;height:.8rem;left:.2rem}dl.terms-and-definitions-list span.term-external{padding-left:1.5rem;display:inline-block}.placeholder-tref{display:none}.no-xref-found-message{background:#f8c481;color:#03365f;margin:0 .5em;padding:0 .8em;border-radius:.25rem;font-size:.8rem;font-variant:small-caps;text-transform:uppercase}
|
|
12
|
+
h2,h3,h4,h5,h6{margin-top:1.5em!important}#terminology-section-utility-container{margin:1em auto;width:100%}@media (min-width:576px){#terminology-section-utility-container{position:sticky;top:3.5em;z-index:10}}.toc-anchor{font-size:.875em;color:#73c2df;text-decoration:none;transition:opacity .3s ease}.toc-anchor:hover{opacity:1}dl.terms-and-definitions-list{margin:0;padding:0;position:relative;z-index:1}dl.terms-and-definitions-list>dt{font-weight:700;margin:0;background-color:var(--card-bg-dt);border:1px solid var(--card-border);padding:.5rem 1.25rem;border-radius:.375rem .375rem 0 0;color:var(--card-text);position:relative;line-height:1.5;font-size:1.05em;display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;min-height:2.5rem}dl.terms-and-definitions-list>dt>span{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:all .2s ease-in-out}dl.terms-and-definitions-list>dt:focus>span,dl.terms-and-definitions-list>dt:hover>span,dl.terms-and-definitions-list>dt>span:focus,dl.terms-and-definitions-list>dt>span:hover{white-space:normal;overflow:visible;text-overflow:initial;word-wrap:break-word;line-height:1.4}dl.terms-and-definitions-list>dt:active>span,dl.terms-and-definitions-list>dt>span:active{white-space:normal;overflow:visible;text-overflow:initial;word-wrap:break-word;line-height:1.4}dl.terms-and-definitions-list>dt .btn{font-size:1.2em!important}dl.terms-and-definitions-list>dt:hover{background-color:var(--card-hover-bg);transition:background-color .2s ease-in-out}dl.terms-and-definitions-list>dd{margin:0;background-color:var(--card-bg);border:1px solid var(--card-border);border-top:none;padding:.5rem 2rem;color:var(--card-text);width:100%;position:relative}dl.terms-and-definitions-list dd p{margin:0;color:var(--card-text);padding-left:.5rem}dl.terms-and-definitions-list>dd::before{display:none}dl.terms-and-definitions-list dd table{margin:.5em 0;width:100%;border-collapse:collapse}dl.terms-and-definitions-list dd table td,dl.terms-and-definitions-list dd table th{padding:.5em;border:1px solid #ddd}dl.terms-and-definitions-list dd table th{background-color:#f5f5f5}dl>dd:has(table).meta-info-content-wrapper{display:block;font-size:.7em;position:relative;max-height:none;height:auto;overflow:visible;transition:all .3s ease-out}dl>dd:has(table).meta-info-content-wrapper td,dl>dd:has(table).meta-info-content-wrapper th{padding:.3em}dl>dd:has(table).collapsed.meta-info-content-wrapper{max-height:0;height:0;padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;overflow:hidden;transition:all .3s ease-out;line-height:0}dl>dd:has(table).meta-info-content-wrapper>div{display:block;padding:inherit}.meta-info-toggle-button{position:relative;cursor:pointer}dl.terms-and-definitions-list dt+dd{border-bottom:none}dl.terms-and-definitions-list dd+dd{border-top:none;border-bottom:none}dl.terms-and-definitions-list dd+dt{margin-top:1rem}dl.terms-and-definitions-list dd.last-dd{border-radius:0 0 .375rem .375rem;border-bottom:1px solid var(--card-border)}html.dense-info dl.terms-and-definitions-list>dt{padding:.1rem 1.25rem;line-height:inherit;font-size:inherit;min-height:auto}html.dense-info dl.terms-and-definitions-list dd+dt{margin-top:.3rem}html.dense-info dl.terms-and-definitions-list>dt::before{display:none}html.dense-info dl.terms-and-definitions-list>dt>span{flex:1 1 auto;min-width:0}.term-external{position:relative}dl.terms-and-definitions-list>dd.term-external,dl.terms-and-definitions-list>dt.term-external{background:#a9dde03b}.term-local{position:relative}dl.terms-and-definitions-list>dd.term-local,dl.terms-and-definitions-list>dt.term-local{background:#f0f8ff}dl.terms-and-definitions-list .term-original-term{font-style:italic}dl.terms-and-definitions-list .term-external-parenthetical-terms{font-weight:400}dl.terms-and-definitions-list .term-local-original-term{font-style:italic}dl.terms-and-definitions-list .term-local-parenthetical-terms{font-weight:400}.btn{font-variant:small-caps;text-transform:uppercase;position:relative;z-index:5}.transclusion-heading{font-size:1.3em;font-weight:700;margin:.2em 0 .2em}dl.terms-and-definitions-list>dd.term-external-embedded{position:relative;padding-left:2.5rem;padding-top:.3rem;padding-bottom:.3rem}dl.terms-and-definitions-list span.term-external::after{content:"";position:absolute;top:50%;transform:translateY(-50%);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' class='bi bi-box-arrow-right' viewBox='0 0 16 16' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z'/%3E%3Cpath fill-rule='evenodd' d='M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z'/%3E%3C/svg%3E");background-size:contain;background-repeat:no-repeat;background-position:center;width:.8rem;height:.8rem;left:.2rem}dl.terms-and-definitions-list span.term-external{padding-left:1.5rem;display:inline-block}.placeholder-tref{display:none}.no-xref-found-message{background:#f8c481;color:#03365f;margin:0 .5em;padding:0 .8em;border-radius:.25rem;font-size:.8rem;font-variant:small-caps;text-transform:uppercase}
|
|
13
13
|
.btn{font-size:.7em!important;margin:0 0 0 .5em!important}
|
|
14
14
|
.highlight-matches-search{font-size:1em;outline:3px solid #1d6dae39;outline-offset:5px;transition:outline .3s ease-in-out}.highlight-matches-search.active{outline:3px solid #1d6dae;transition:outline .3s ease-in-out}.highlight-matches-DIF-search{background-color:#1d6dae11}.highlight-matches-ToIP-search{background-color:#a9dde0b0}.highlight-matches-BTC-search{background-color:#f19019a1}.highlight-matches-KERI-search{background-color:#b5d070d5}.highlight-matches-SSI-search{background-color:#ff0}.highlight-matches-GLEIF-search{background-color:#52dac6a6}
|
|
15
15
|
.highlight-cfib41dyhcd99sm{background-color:#1d6dae11}
|
|
@@ -30,5 +30,5 @@ article a[href^="https://"]:not(.btn)::after,article a[href^=http]:not(.btn)::af
|
|
|
30
30
|
#content .figure:has(.scrollHintImage){padding:0;margin:0;width:auto;height:auto}
|
|
31
31
|
.highlight2{padding-left:1em;padding-right:1em;border:1px dashed #71bbe6;background:#a9dde03b}
|
|
32
32
|
#toc ul.toc{counter-reset:toc-section}#toc ul.toc>li{counter-reset:toc-subsection}#toc ul.toc>li>ul>li{counter-reset:toc-subsubsection}#toc ul.toc>li>a::before{counter-increment:toc-section;content:counter(toc-section) ". ";font-weight:700;margin-right:.5em}#toc ul.toc>li>ul>li>a::before{counter-increment:toc-subsection;content:counter(toc-section) "." counter(toc-subsection) " ";font-weight:700;margin-right:.5em}#toc ul.toc>li>ul>li>ul>li>a::before{counter-increment:toc-subsubsection;content:counter(toc-section) "." counter(toc-subsection) "." counter(toc-subsubsection) " ";font-weight:700;margin-right:.5em}main article{counter-reset:main-section}main article h2{counter-reset:main-subsection}main article h3{counter-reset:main-subsubsection}main article h2 .toc-anchor::after{counter-increment:main-section;content:" " counter(main-section) ".";font-weight:700}main article h3 .toc-anchor::after{counter-increment:main-subsection;content:" " counter(main-section) "." counter(main-subsection);font-weight:700}main article h4 .toc-anchor::after{counter-increment:main-subsubsection;content:" " counter(main-section) "." counter(main-subsection) "." counter(main-subsubsection);font-weight:700}main article h5::before,main article h6::before{font-weight:700;margin-right:.5em}@media print{#toc ul.toc>li>a::before,#toc ul.toc>li>ul>li>a::before,#toc ul.toc>li>ul>li>ul>li>a::before,main article h2 .toc-anchor::after,main article h3 .toc-anchor::after,main article h4 .toc-anchor::after{color:#000!important}}
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
:target{scroll-margin-top:calc(50vh)}body:not([hashscroll]) :target{animation:highlight-target 3.5s .25s ease}body:not([hashscroll]) dt:has(:target){animation:highlight-target-parent-dt 3.5s .25s ease}dl.terms-and-definitions-list>.highlight{background-color:#ff0!important;transition:background-color .3s ease-in-out}svg[icon]{width:1.25em;height:1.25em;vertical-align:text-top}@keyframes highlight-target{50%{background-color:#ff0}}@keyframes highlight-target-parent-dt{50%{background-color:#ff0;border:1px solid #00c8ff}}#svg{display:none}main *{overflow-wrap:anywhere;word-break:normal}.table-responsive{overflow-x:auto!important;-webkit-overflow-scrolling:touch;width:100%;max-width:100%}.table-responsive *{overflow-wrap:normal!important;word-break:normal!important;white-space:nowrap!important}dd td img{max-width:30px}.notice{margin:1em 0;padding:.5em .9em .55em .65em;border-left:.5em solid}.notice p{margin:.4em 0 0}.note{background:#e9fbe9;border-color:#52e052}.note .notice-link{display:block;color:#178217}.issue{background:#e9f0fb;border-color:#527fe0}.issue .notice-link:before{display:block;color:#1e4cae}.warning{background:#fbe9e9;border-color:#e05252}.warning .notice-link{display:block;color:#ae1e1e}.example{color:#cebe00;background:#1a1e23;border-left:.5em solid}.example .notice-link{display:block;color:inherit;font-size:1.1em;font-family:Heebo,sans-serif}.example pre[class*=language-]{padding:0;border-radius:0}.todo{background:#fbe4ff;border-color:#9700e2}.todo .notice-link{display:block;color:#6d00a2}.alert-primary{background-color:#f9fcff;border-color:#b2ebf2;color:#00838f}.alert-secondary{background-color:#f5f5f5;border-color:#e0e0e0;color:#616161}.alert-success{background-color:#e8f5e9;border-color:#c8e6c9;color:#388e3c}.alert-danger{background-color:#ffebee;border-color:#ef9a9a;color:#d32f2f}.alert-warning{background-color:#fffde7;border-color:#fff9c4;color:#f9a825}.alert-info{background-color:#e1f5fe;border-color:#b3e5fc;color:#0288d1}.alert-light{background-color:#f9f9f9;border-color:#eee;color:#424242}.alert-dark{background-color:#e0e0e0;border-color:#bdbdbd;color:#212121}.alert a{color:inherit;font-weight:700}.alert-primary a{color:#006064}.alert-success a{color:#2e7d32}.alert-danger a{color:#b71c1c}.alert-warning a{color:#f57f17}.alert-info a{color:#01579b}.alert-dark a{color:#000}[data-bs-theme=dark] .alert-primary{background-color:#004d40;border-color:#00695c;color:#b2dfdb}[data-bs-theme=dark] .alert-secondary{background-color:#424242;border-color:#616161;color:#e0e0e0}[data-bs-theme=dark] .alert-success{background-color:#1b5e20;border-color:#2e7d32;color:#a5d6a7}[data-bs-theme=dark] .alert-danger{background-color:#b71c1c;border-color:#c62828;color:#ef9a9a}[data-bs-theme=dark] .alert-warning{background-color:#f57f17;border-color:#ffb300;color:#ffe082}[data-bs-theme=dark] .alert-info{background-color:#01579b;border-color:#0277bd;color:#81d4fa}[data-bs-theme=dark] .alert-light{background-color:#303030;border-color:#424242;color:#e0e0e0}[data-bs-theme=dark] .alert-dark{background-color:#212121;border-color:#424242;color:#bdbdbd}[data-bs-theme=dark] .alert a{color:inherit;font-weight:700}[data-bs-theme=dark] .alert-primary a{color:#80cbc4}[data-bs-theme=dark] .alert-success a{color:#81c784}[data-bs-theme=dark] .alert-danger a{color:#ef5350}[data-bs-theme=dark] .alert-warning a{color:#ffca28}[data-bs-theme=dark] .alert-info a{color:#4fc3f7}[data-bs-theme=dark] .alert-dark a{color:#9e9e9e}#offcanvasSettings .btn-menu-item{border-radius:unset;width:100%;text-align:left;border:1px solid #dee2e6;background-color:var(--card-bg);color:var(--card-text);padding:10px 15px;margin-bottom:5px;transition:background-color .2s ease}#offcanvasSettings .btn-menu-item:hover{background-color:#e9ecef}
|
|
@@ -282,6 +282,15 @@ dl.terms-and-definitions-list>dd.term-local {
|
|
|
282
282
|
background: #f0f8ff;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/* Generic class for all original terms (both local and external) */
|
|
286
|
+
dl.terms-and-definitions-list .term-original-term {
|
|
287
|
+
font-style: italic;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
dl.terms-and-definitions-list .term-external-parenthetical-terms {
|
|
291
|
+
font-weight: normal;
|
|
292
|
+
}
|
|
293
|
+
|
|
285
294
|
/* Local term original term and parenthetical styling */
|
|
286
295
|
dl.terms-and-definitions-list .term-local-original-term {
|
|
287
296
|
font-style: italic;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-up-t",
|
|
3
|
-
"version": "1.4.1-beta.
|
|
3
|
+
"version": "1.4.1-beta.3",
|
|
4
4
|
"description": "Technical specification drafting tool that generates rich specification documents from markdown. Forked from https://github.com/decentralized-identity/spec-up by Daniel Buchner (https://github.com/csuwildcat)",
|
|
5
5
|
"main": "./index",
|
|
6
6
|
"repository": {
|
package/src/freeze-spec-data.js
CHANGED
|
@@ -44,3 +44,7 @@ const destFile = path.join(newVersionDir, 'index.html');
|
|
|
44
44
|
fs.copyFileSync(sourceFile, destFile);
|
|
45
45
|
|
|
46
46
|
Logger.success(`Created a freezed specification version in ${destFile}`);
|
|
47
|
+
|
|
48
|
+
// Update the versions index.html to include the newly created version
|
|
49
|
+
const createVersionsIndex = require('./pipeline/configuration/create-versions-index.js');
|
|
50
|
+
createVersionsIndex(outputPath);
|
package/src/freeze.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* freeze.js
|
|
4
|
+
*
|
|
5
|
+
* Legacy wrapper to run the existing src/freeze-spec-data.js script.
|
|
6
|
+
* This file intentionally keeps logic minimal: it simply requires the
|
|
7
|
+
* implementation file so older tooling or CI jobs that call `freeze.js`
|
|
8
|
+
* continue to work without changing behaviour.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node freeze.js
|
|
12
|
+
* or make it executable and run:
|
|
13
|
+
* ./freeze.js
|
|
14
|
+
*
|
|
15
|
+
* Rationale for keeping this file:
|
|
16
|
+
* - It provides a stable legacy entry point used by external consumers.
|
|
17
|
+
* - The real implementation remains in src/freeze-spec-data.js so maintenance
|
|
18
|
+
* and tests remain focused there.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
// The implementation `freeze-spec-data.js` now lives in the same `src` folder
|
|
24
|
+
// as this wrapper, so require it directly from __dirname.
|
|
25
|
+
const scriptPath = path.join(__dirname, 'freeze-spec-data.js');
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Require executes the implementation file. It is expected to run
|
|
29
|
+
// synchronously at top-level (the existing file performs its actions
|
|
30
|
+
// when loaded).
|
|
31
|
+
require(scriptPath);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
// Keep error reporting minimal and consistent with CLI usage.
|
|
34
|
+
// Use stderr for messages and exit with non-zero code on failure.
|
|
35
|
+
// This keeps behaviour predictable for callers.
|
|
36
|
+
// eslint-disable-next-line no-console
|
|
37
|
+
console.error('Failed to execute freeze script:', err && err.message ? err.message : err);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Tag Parser HTML Output Tests
|
|
3
|
+
*
|
|
4
|
+
* These tests verify that the HTML output contains all required CSS classes
|
|
5
|
+
* and structural elements that are essential for proper rendering and styling.
|
|
6
|
+
*
|
|
7
|
+
* These tests were added after discovering that shallow `.toContain()` assertions
|
|
8
|
+
* were insufficient to catch missing CSS classes that broke functionality.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
parseDef,
|
|
13
|
+
parseRef,
|
|
14
|
+
parseXref,
|
|
15
|
+
parseTref
|
|
16
|
+
} = require('./template-tag-parser');
|
|
17
|
+
|
|
18
|
+
describe('Template Tag Parser - HTML Output Validation', () => {
|
|
19
|
+
let mockConfig, mockGlobal;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
mockConfig = {
|
|
23
|
+
specs: [{
|
|
24
|
+
external_specs: [{
|
|
25
|
+
external_spec: 'test-spec',
|
|
26
|
+
gh_page: 'https://example.com/spec'
|
|
27
|
+
}]
|
|
28
|
+
}]
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
mockGlobal = {
|
|
32
|
+
definitions: [],
|
|
33
|
+
references: [],
|
|
34
|
+
specGroups: {},
|
|
35
|
+
noticeTitles: {},
|
|
36
|
+
currentFile: 'test.md'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
global.definitions = mockGlobal.definitions;
|
|
40
|
+
global.references = mockGlobal.references;
|
|
41
|
+
global.specGroups = mockGlobal.specGroups;
|
|
42
|
+
global.noticeTitles = mockGlobal.noticeTitles;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('parseDef - HTML structure validation', () => {
|
|
46
|
+
// Test: Does def output contain the required CSS class for styling?
|
|
47
|
+
test('should include term-original-term class when def has aliases', () => {
|
|
48
|
+
const mockToken = {
|
|
49
|
+
info: { args: ['test-term', 'alias1'] }
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = parseDef(mockGlobal, mockToken, 'Test Term', 'test.md');
|
|
53
|
+
|
|
54
|
+
// Critical: This class is required for CSS styling
|
|
55
|
+
expect(result).toContain('term-original-term');
|
|
56
|
+
expect(result).toContain('term-local-original-term');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should include term-original-term class when def has no aliases', () => {
|
|
60
|
+
const mockToken = {
|
|
61
|
+
info: { args: ['test-term'] }
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const result = parseDef(mockGlobal, mockToken, 'Test Term', 'test.md');
|
|
65
|
+
|
|
66
|
+
// Even without aliases, the original term should have proper class
|
|
67
|
+
expect(result).toContain('term-original-term');
|
|
68
|
+
expect(result).toContain('term-local-original-term');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('should include term-local-parenthetical-terms class for multiple aliases', () => {
|
|
72
|
+
const mockToken = {
|
|
73
|
+
info: { args: ['test-term', 'alias1', 'alias2', 'alias3'] }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = parseDef(mockGlobal, mockToken, 'Test Term', 'test.md');
|
|
77
|
+
|
|
78
|
+
expect(result).toContain('term-local-parenthetical-terms');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('should display primary alias first, then other aliases, then original term', () => {
|
|
82
|
+
const mockToken = {
|
|
83
|
+
info: { args: ['original-term', 'primary-alias', 'second-alias'] }
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const result = parseDef(mockGlobal, mockToken, 'Test Term', 'test.md');
|
|
87
|
+
|
|
88
|
+
// Primary alias should appear first (not in parentheses) in the innermost span
|
|
89
|
+
expect(result).toMatch(/>primary-alias <span class='term-local-parenthetical-terms'>/);
|
|
90
|
+
|
|
91
|
+
// Second alias should appear in parentheses
|
|
92
|
+
expect(result).toContain('second-alias');
|
|
93
|
+
|
|
94
|
+
// Original term should appear last in parentheses with proper class
|
|
95
|
+
expect(result).toContain('<span class=\'term-local-original-term term-original-term\' title=\'original term\'>original-term</span>');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('should have nested span structure with all IDs', () => {
|
|
99
|
+
const mockToken = {
|
|
100
|
+
info: { args: ['term1', 'alias1', 'alias2'] }
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const result = parseDef(mockGlobal, mockToken, 'Test Term', 'test.md');
|
|
104
|
+
|
|
105
|
+
// Check all IDs are present
|
|
106
|
+
expect(result).toContain('id="term:term1"');
|
|
107
|
+
expect(result).toContain('id="term:alias1"');
|
|
108
|
+
expect(result).toContain('id="term:alias2"');
|
|
109
|
+
|
|
110
|
+
// Check nested structure (outer span has last alias ID)
|
|
111
|
+
expect(result).toMatch(/^<span id="term:alias2">/);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('parseTref - HTML structure validation', () => {
|
|
116
|
+
// Test: Does tref output contain the required CSS classes for styling?
|
|
117
|
+
test('should include term-external-original-term and term-original-term classes when tref has aliases', () => {
|
|
118
|
+
const mockToken = {
|
|
119
|
+
info: { args: ['external-spec', 'test-term', 'alias1'] }
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const result = parseTref(mockToken);
|
|
123
|
+
|
|
124
|
+
// Critical: These classes are required for CSS styling
|
|
125
|
+
expect(result).toContain('term-original-term');
|
|
126
|
+
expect(result).toContain('term-external-original-term');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('should include term-original-term class when tref has no aliases', () => {
|
|
130
|
+
const mockToken = {
|
|
131
|
+
info: { args: ['external-spec', 'test-term'] }
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const result = parseTref(mockToken);
|
|
135
|
+
|
|
136
|
+
// Even without aliases, the original term should have proper class
|
|
137
|
+
expect(result).toContain('term-original-term');
|
|
138
|
+
expect(result).toContain('term-external-original-term');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('should include term-external-parenthetical-terms class for multiple aliases', () => {
|
|
142
|
+
const mockToken = {
|
|
143
|
+
info: { args: ['external-spec', 'test-term', 'alias1', 'alias2', 'alias3'] }
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const result = parseTref(mockToken);
|
|
147
|
+
|
|
148
|
+
expect(result).toContain('term-external-parenthetical-terms');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('should include term-external class on outer span', () => {
|
|
152
|
+
const mockToken = {
|
|
153
|
+
info: { args: ['external-spec', 'test-term', 'alias1'] }
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const result = parseTref(mockToken);
|
|
157
|
+
|
|
158
|
+
expect(result).toContain('class="term-external"');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should display primary alias first, then other aliases, then original term', () => {
|
|
162
|
+
const mockToken = {
|
|
163
|
+
info: { args: ['spec', 'original-term', 'primary-alias', 'second-alias'] }
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const result = parseTref(mockToken);
|
|
167
|
+
|
|
168
|
+
// Primary alias should appear first (not in parentheses)
|
|
169
|
+
expect(result).toContain('>primary-alias <span');
|
|
170
|
+
|
|
171
|
+
// Second alias should appear in parentheses
|
|
172
|
+
expect(result).toContain('second-alias');
|
|
173
|
+
|
|
174
|
+
// Original term should appear last in parentheses with proper class
|
|
175
|
+
expect(result).toContain('<span class=\'term-external-original-term term-original-term\' title=\'original term\'>original-term</span>');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('should have data-original-term attribute', () => {
|
|
179
|
+
const mockToken = {
|
|
180
|
+
info: { args: ['spec', 'test-term', 'alias1'] }
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const result = parseTref(mockToken);
|
|
184
|
+
|
|
185
|
+
expect(result).toContain('data-original-term="test-term"');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('should have proper title attribute on inner span when alias exists', () => {
|
|
189
|
+
const mockToken = {
|
|
190
|
+
info: { args: ['spec', 'test-term', 'alias1'] }
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const result = parseTref(mockToken);
|
|
194
|
+
|
|
195
|
+
expect(result).toContain('title="Externally defined as test-term"');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('parseXref - HTML structure validation', () => {
|
|
200
|
+
test('should include x-term-reference and term-reference classes', () => {
|
|
201
|
+
const mockToken = {
|
|
202
|
+
info: { args: ['test-spec', 'test-term'] }
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const result = parseXref(mockConfig, mockToken);
|
|
206
|
+
|
|
207
|
+
expect(result).toContain('class="x-term-reference term-reference"');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('should display primary alias instead of term when alias provided', () => {
|
|
211
|
+
const mockToken = {
|
|
212
|
+
info: { args: ['test-spec', 'test-term', 'my-alias'] }
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const result = parseXref(mockConfig, mockToken);
|
|
216
|
+
|
|
217
|
+
// Should show alias, not the term
|
|
218
|
+
expect(result).toContain('>my-alias</a>');
|
|
219
|
+
expect(result).not.toContain('>test-term</a>');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('should display term when no alias provided', () => {
|
|
223
|
+
const mockToken = {
|
|
224
|
+
info: { args: ['test-spec', 'test-term'] }
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const result = parseXref(mockConfig, mockToken);
|
|
228
|
+
|
|
229
|
+
expect(result).toContain('>test-term</a>');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('should have both data-local-href and href attributes', () => {
|
|
233
|
+
const mockToken = {
|
|
234
|
+
info: { args: ['test-spec', 'test-term'] }
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const result = parseXref(mockConfig, mockToken);
|
|
238
|
+
|
|
239
|
+
expect(result).toContain('data-local-href="#term:test-spec:test-term"');
|
|
240
|
+
expect(result).toContain('href="https://example.com/spec#term:test-term"');
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('parseRef - HTML structure validation', () => {
|
|
245
|
+
test('should include term-reference class', () => {
|
|
246
|
+
const result = parseRef(mockGlobal, 'test-term');
|
|
247
|
+
|
|
248
|
+
expect(result).toContain('class="term-reference"');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('should create proper href with term: prefix', () => {
|
|
252
|
+
const result = parseRef(mockGlobal, 'test-term');
|
|
253
|
+
|
|
254
|
+
expect(result).toContain('href="#term:test-term"');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('should display the term as link text', () => {
|
|
258
|
+
const result = parseRef(mockGlobal, 'my-test-term');
|
|
259
|
+
|
|
260
|
+
expect(result).toContain('>my-test-term</a>');
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('Regression tests - ensure critical classes are never removed', () => {
|
|
265
|
+
// These tests protect against the specific bug that was fixed
|
|
266
|
+
test('def: term-original-term class must always be present', () => {
|
|
267
|
+
const withAlias = { info: { args: ['term', 'alias'] } };
|
|
268
|
+
const withoutAlias = { info: { args: ['term'] } };
|
|
269
|
+
|
|
270
|
+
expect(parseDef(mockGlobal, withAlias, 'Test', 'test.md')).toContain('term-original-term');
|
|
271
|
+
expect(parseDef(mockGlobal, withoutAlias, 'Test', 'test.md')).toContain('term-original-term');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('def: term-local-original-term class must always be present', () => {
|
|
275
|
+
const withAlias = { info: { args: ['term', 'alias'] } };
|
|
276
|
+
const withoutAlias = { info: { args: ['term'] } };
|
|
277
|
+
|
|
278
|
+
expect(parseDef(mockGlobal, withAlias, 'Test', 'test.md')).toContain('term-local-original-term');
|
|
279
|
+
expect(parseDef(mockGlobal, withoutAlias, 'Test', 'test.md')).toContain('term-local-original-term');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('tref: term-original-term class must always be present', () => {
|
|
283
|
+
const withAlias = { info: { args: ['spec', 'term', 'alias'] } };
|
|
284
|
+
const withoutAlias = { info: { args: ['spec', 'term'] } };
|
|
285
|
+
|
|
286
|
+
expect(parseTref(withAlias)).toContain('term-original-term');
|
|
287
|
+
expect(parseTref(withoutAlias)).toContain('term-original-term');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('tref: term-external-original-term class must always be present', () => {
|
|
291
|
+
const withAlias = { info: { args: ['spec', 'term', 'alias'] } };
|
|
292
|
+
const withoutAlias = { info: { args: ['spec', 'term'] } };
|
|
293
|
+
|
|
294
|
+
expect(parseTref(withAlias)).toContain('term-external-original-term');
|
|
295
|
+
expect(parseTref(withoutAlias)).toContain('term-external-original-term');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('tref: term-external class must always be present on outer span', () => {
|
|
299
|
+
const withAlias = { info: { args: ['spec', 'term', 'alias'] } };
|
|
300
|
+
const withoutAlias = { info: { args: ['spec', 'term'] } };
|
|
301
|
+
|
|
302
|
+
expect(parseTref(withAlias)).toContain('class="term-external"');
|
|
303
|
+
expect(parseTref(withoutAlias)).toContain('class="term-external"');
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
@@ -89,15 +89,18 @@ function parseDef(globalState, token, primary, currentFile) {
|
|
|
89
89
|
parentheticalContent.push(...aliases.slice(1));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
92
|
+
// Always add the original term if there are aliases, with special styling
|
|
93
|
+
// This ensures consistent behavior regardless of whether alias1 equals the term
|
|
94
|
+
// The original term should always be visible in the parenthetical list
|
|
95
|
+
parentheticalContent.push(`<span class='term-local-original-term term-original-term' title='original term'>${termName}</span>`);
|
|
96
96
|
|
|
97
97
|
// Append parenthetical terms if any exist
|
|
98
98
|
if (parentheticalContent.length > 0) {
|
|
99
99
|
displayText += ` <span class='term-local-parenthetical-terms'>(${parentheticalContent.join(', ')})</span>`;
|
|
100
100
|
}
|
|
101
|
+
} else {
|
|
102
|
+
// No aliases: wrap the term name itself as the original term
|
|
103
|
+
displayText = `<span class='term-local-original-term term-original-term' title='original term'>${termName}</span>`;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
// Generate HTML spans for each term/alias combination
|
|
@@ -183,26 +186,36 @@ function parseTref(token) {
|
|
|
183
186
|
parentheticalContent.push(...aliases.slice(1));
|
|
184
187
|
}
|
|
185
188
|
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
189
|
+
// Always add the original term if there are aliases, with special styling
|
|
190
|
+
// This ensures consistent behavior regardless of whether alias1 equals the term
|
|
191
|
+
// The original term should always be visible in the parenthetical list
|
|
192
|
+
parentheticalContent.push(`<span class='term-external-original-term term-original-term' title='original term'>${termName}</span>`);
|
|
190
193
|
|
|
191
194
|
// Append parenthetical terms if any exist
|
|
192
195
|
if (parentheticalContent.length > 0) {
|
|
193
196
|
displayText += ` <span class='term-external-parenthetical-terms'>(${parentheticalContent.join(', ')})</span>`;
|
|
194
197
|
}
|
|
198
|
+
} else {
|
|
199
|
+
// No aliases: wrap the term name itself as the original term
|
|
200
|
+
displayText = `<span class='term-external-original-term term-original-term' title='original term'>${termName}</span>`;
|
|
195
201
|
}
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
|
|
203
|
+
// Generate HTML spans for each term/alias combination, similar to parseDef
|
|
204
|
+
// This creates anchor points that can be referenced by [[ref: ...]] links
|
|
205
|
+
// token.info.args for tref is [externalSpec, term, alias1, alias2, ...]
|
|
206
|
+
// We need to create IDs for the term and all aliases (skip the externalSpec at index 0)
|
|
207
|
+
const termsAndAliases = [termName, ...aliases];
|
|
199
208
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
return termsAndAliases.reduce((acc, syn, index) => {
|
|
210
|
+
// Generate a unique term ID by normalizing the synonym: replace whitespace with hyphens and convert to lowercase
|
|
211
|
+
const termId = `term:${syn.replace(whitespace.oneOrMore, '-').toLowerCase()}`;
|
|
212
|
+
// Add title attribute to the innermost span (first in the array, which wraps the display text directly)
|
|
213
|
+
// This provides a tooltip showing which external term this alias refers to
|
|
214
|
+
const titleAttr = index === 0 && aliases.length > 0 ? ` title="Externally defined as ${termName}"` : '';
|
|
215
|
+
// Add class and data attributes only to the outermost span (last one created)
|
|
216
|
+
const outerAttrs = index === termsAndAliases.length - 1 ? ` data-original-term="${termName}" class="term-external"` : '';
|
|
217
|
+
return `<span id="${termId}"${outerAttrs}${titleAttr}>${acc}</span>`;
|
|
218
|
+
}, displayText);
|
|
206
219
|
}
|
|
207
220
|
|
|
208
221
|
/**
|
|
@@ -231,7 +244,7 @@ function processXTrefObject(xtref) {
|
|
|
231
244
|
|
|
232
245
|
// Collect all aliases from parts after the term (index 1), trim and filter empties
|
|
233
246
|
const allAliases = parts.slice(2).map(p => p.trim()).filter(Boolean);
|
|
234
|
-
|
|
247
|
+
|
|
235
248
|
// Initialize both tref and xref alias arrays
|
|
236
249
|
xtrefObject.trefAliases = [];
|
|
237
250
|
xtrefObject.xrefAliases = [];
|
|
@@ -62,6 +62,33 @@ describe('Template Tag Parsing Functions', () => {
|
|
|
62
62
|
});
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
// Test: Does the parser handle the case when alias1 equals the term?
|
|
66
|
+
// Issue #194: When alias1 is the same as the term, the term should still appear in parentheses
|
|
67
|
+
test('should include term in parentheses even when alias1 equals term', () => {
|
|
68
|
+
const mockToken = {
|
|
69
|
+
info: { args: ['action', 'action', 'actions', 'act', 'acts'] }
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const result = parseDef(mockGlobal, mockToken, 'Action', 'test.md');
|
|
73
|
+
|
|
74
|
+
// Should show: action (actions, act, acts, action)
|
|
75
|
+
// The term "action" should appear both as primary and in parenthetical list
|
|
76
|
+
expect(result).toContain('action');
|
|
77
|
+
expect(result).toContain('actions');
|
|
78
|
+
expect(result).toContain('act');
|
|
79
|
+
expect(result).toContain('acts');
|
|
80
|
+
|
|
81
|
+
// The original term should be in the parenthetical content with special styling
|
|
82
|
+
expect(result).toContain('term-local-original-term');
|
|
83
|
+
expect(result).toContain('term-local-parenthetical-terms');
|
|
84
|
+
|
|
85
|
+
// Verify all term IDs are present
|
|
86
|
+
expect(result).toContain('id="term:action"');
|
|
87
|
+
expect(result).toContain('id="term:actions"');
|
|
88
|
+
expect(result).toContain('id="term:act"');
|
|
89
|
+
expect(result).toContain('id="term:acts"');
|
|
90
|
+
});
|
|
91
|
+
|
|
65
92
|
// Test: Can the system parse reference markup into proper links?
|
|
66
93
|
test('should parse reference correctly', () => {
|
|
67
94
|
const result = parseRef(mockGlobal, 'test-term');
|
|
@@ -118,22 +118,24 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
|
|
|
118
118
|
// Add xref terms to the allXTrefs structure
|
|
119
119
|
// Mark them with source: 'xref' to distinguish from tref entries
|
|
120
120
|
xrefTerms.forEach(xrefTerm => {
|
|
121
|
-
// Check if this term already exists (
|
|
121
|
+
// Check if this term already exists (match by externalSpec and term only)
|
|
122
|
+
// Don't filter by source because entries from markdown scanning don't have source field
|
|
122
123
|
const existingIndex = allXTrefs.xtrefs.findIndex(existing =>
|
|
123
124
|
existing.externalSpec === xrefTerm.externalSpec &&
|
|
124
|
-
existing.term === xrefTerm.term
|
|
125
|
-
existing.source === 'xref'
|
|
125
|
+
existing.term === xrefTerm.term
|
|
126
126
|
);
|
|
127
127
|
|
|
128
128
|
if (existingIndex >= 0) {
|
|
129
|
-
// Update existing entry
|
|
129
|
+
// Update existing entry - preserve the existing metadata but add/update content
|
|
130
130
|
allXTrefs.xtrefs[existingIndex] = {
|
|
131
131
|
...allXTrefs.xtrefs[existingIndex],
|
|
132
|
-
|
|
132
|
+
content: xrefTerm.content, // Update the content from fetched HTML
|
|
133
|
+
source: xrefTerm.source, // Add source field
|
|
134
|
+
termId: xrefTerm.termId, // Add termId if not present
|
|
133
135
|
lastUpdated: new Date().toISOString()
|
|
134
136
|
};
|
|
135
137
|
} else {
|
|
136
|
-
// Add new entry
|
|
138
|
+
// Add new entry (this term wasn't referenced in the markdown)
|
|
137
139
|
allXTrefs.xtrefs.push({
|
|
138
140
|
...xrefTerm,
|
|
139
141
|
lastUpdated: new Date().toISOString()
|
|
@@ -179,28 +181,53 @@ function extractTermsFromHtml(externalSpec, html) {
|
|
|
179
181
|
batch.each((index, termElement) => {
|
|
180
182
|
try {
|
|
181
183
|
const $termElement = $(termElement);
|
|
182
|
-
const termId = $termElement.attr('id');
|
|
183
184
|
|
|
184
|
-
//
|
|
185
|
-
|
|
185
|
+
// The id can be on the dt element itself OR on span(s) inside it
|
|
186
|
+
// Some terms have multiple nested spans with different IDs (e.g., aliases)
|
|
187
|
+
// We need to extract ALL term IDs from the dt element
|
|
188
|
+
const termIds = [];
|
|
189
|
+
|
|
190
|
+
// First check if dt itself has an id
|
|
191
|
+
const dtId = $termElement.attr('id');
|
|
192
|
+
if (dtId && dtId.includes('term:')) {
|
|
193
|
+
termIds.push(dtId.replace('term:', ''));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Then find all spans with ids that contain 'term:'
|
|
197
|
+
$termElement.find('span[id*="term:"]').each((i, span) => {
|
|
198
|
+
const spanId = $(span).attr('id');
|
|
199
|
+
if (spanId && spanId.includes('term:')) {
|
|
200
|
+
const termName = spanId.replace('term:', '');
|
|
201
|
+
if (!termIds.includes(termName)) {
|
|
202
|
+
termIds.push(termName);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Skip if no valid term IDs found
|
|
208
|
+
if (termIds.length === 0) {
|
|
186
209
|
return;
|
|
187
210
|
}
|
|
188
211
|
|
|
189
|
-
const termName = termId.replace('term:', '');
|
|
190
212
|
const dd = $termElement.next('dd');
|
|
191
213
|
|
|
192
214
|
if (dd.length > 0) {
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
215
|
+
const ddContent = $.html(dd); // Store the complete DD content once
|
|
216
|
+
|
|
217
|
+
// Create a term object for each ID found in this dt element
|
|
218
|
+
// This handles cases where one term definition has multiple aliases
|
|
219
|
+
termIds.forEach(termName => {
|
|
220
|
+
const termObj = {
|
|
221
|
+
externalSpec: externalSpec,
|
|
222
|
+
term: termName,
|
|
223
|
+
content: ddContent,
|
|
224
|
+
// Add metadata for consistency with tref structure
|
|
225
|
+
source: 'xref', // Distinguish from tref entries
|
|
226
|
+
termId: `term:${externalSpec}:${termName}`, // Fully qualified term ID
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
terms.push(termObj);
|
|
230
|
+
});
|
|
204
231
|
}
|
|
205
232
|
} catch (termError) {
|
|
206
233
|
Logger.warn(`Error processing term in ${externalSpec}:`, termError.message);
|
|
@@ -37,7 +37,10 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
37
37
|
|
|
38
38
|
for (const repoKey of Object.keys(xrefsByRepo)) {
|
|
39
39
|
const repoGroup = xrefsByRepo[repoKey];
|
|
40
|
-
|
|
40
|
+
// Build a repository URL for logging. Prefer an explicit repoUrl from
|
|
41
|
+
// an xtref, otherwise fall back to the canonical GitHub URL.
|
|
42
|
+
const repoUrl = repoGroup.xtrefs[0]?.repoUrl || `https://github.com/${repoKey}`;
|
|
43
|
+
Logger.process(`Processing repository: ${repoKey} (${repoGroup.xtrefs.length} terms) - ${repoUrl}`);
|
|
41
44
|
|
|
42
45
|
const ghPageUrl = repoGroup.xtrefs[0]?.ghPageUrl;
|
|
43
46
|
const allTermsData = await fetchAllTermsFromIndex(
|
|
@@ -48,7 +51,7 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
48
51
|
);
|
|
49
52
|
|
|
50
53
|
if (!allTermsData) {
|
|
51
|
-
Logger.error(`Could not fetch terms from repository ${repoKey}`);
|
|
54
|
+
Logger.error(`Could not fetch terms from repository ${repoKey} (${repoUrl})`);
|
|
52
55
|
repoGroup.xtrefs.forEach(xtref => {
|
|
53
56
|
xtref.commitHash = 'not found';
|
|
54
57
|
xtref.content = 'This term was not found in the external repository.';
|
|
@@ -71,11 +74,30 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
|
|
|
71
74
|
xtref.commitHash = 'not found';
|
|
72
75
|
xtref.content = 'This term was not found in the external repository.';
|
|
73
76
|
xtref.avatarUrl = null;
|
|
74
|
-
|
|
77
|
+
|
|
78
|
+
// Build a readable list of source files for the error message.
|
|
79
|
+
// Two possible data structures exist:
|
|
80
|
+
// 1. xtref.sourceFile is a STRING like "primitive.md"
|
|
81
|
+
// 2. xtref.sourceFiles is an ARRAY OF OBJECTS like [{file: "primitive.md", type: "xref"}]
|
|
82
|
+
//
|
|
83
|
+
// The ternary operator works as follows:
|
|
84
|
+
// - If xtref.sourceFile exists (legacy case) → use it directly (it's already a string)
|
|
85
|
+
// - Otherwise → extract file names from the sourceFiles array:
|
|
86
|
+
// - .map(sf => sf.file) extracts just the filename from each object
|
|
87
|
+
// - .join(', ') combines them into a comma-separated string
|
|
88
|
+
const sourceFilesList = xtref.sourceFile
|
|
89
|
+
? xtref.sourceFile
|
|
90
|
+
: (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
|
|
91
|
+
|
|
92
|
+
// Prefer an explicit repo URL if provided on the xtref, otherwise
|
|
93
|
+
// build a standard GitHub URL from the owner/repo.
|
|
94
|
+
const githubUrl = xtref.repoUrl || `https://github.com/${repoKey}`;
|
|
95
|
+
|
|
96
|
+
Logger.error(`Origin: ${sourceFilesList} 👉 No match found for term: ${xtref.term} in ${xtref.externalSpec} (${githubUrl})`);
|
|
75
97
|
}
|
|
76
98
|
}
|
|
77
99
|
|
|
78
|
-
Logger.success(`Finished processing repository: ${repoKey}`);
|
|
100
|
+
Logger.success(`Finished processing repository: ${repoKey} (${repoUrl})`);
|
|
79
101
|
Logger.separator();
|
|
80
102
|
}
|
|
81
103
|
|
package/src/utils/logger.js
CHANGED