euparliamentmonitor 0.9.27 → 1.0.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/package.json +3 -3
- package/scripts/aggregator/html/localize-body.d.ts +28 -4
- package/scripts/aggregator/html/localize-body.js +79 -21
- package/scripts/aggregator/html/shell.js +8 -5
- package/scripts/aggregator/html/toc.js +4 -2
- package/scripts/aggregator/metadata/artifact-category-heading.js +8 -1
- package/scripts/aggregator/metadata/heading-rules.js +11 -0
- package/scripts/aggregator/metadata/seo-budgets.js +12 -9
- package/scripts/aggregator/progressive-disclosure.d.ts +2 -1
- package/scripts/aggregator/progressive-disclosure.js +8 -4
- package/scripts/aggregator/reader-friendly-transform.js +1 -1
- package/scripts/constants/languages.d.ts +2 -1
- package/scripts/constants/languages.js +1 -1
- package/scripts/constants/ui/index.d.ts +2 -0
- package/scripts/constants/ui/index.js +2 -0
- package/scripts/constants/ui/progressive-disclosure.d.ts +40 -0
- package/scripts/constants/ui/progressive-disclosure.js +150 -0
- package/scripts/discover-untranslated-briefs.js +296 -1
- package/scripts/generators/news-indexes/backfill-hreflang.d.ts +13 -0
- package/scripts/generators/news-indexes/backfill-hreflang.js +112 -0
- package/scripts/generators/news-indexes/backfill-reader-label.d.ts +47 -0
- package/scripts/generators/news-indexes/backfill-reader-label.js +86 -0
- package/scripts/generators/news-indexes/backfill.d.ts +19 -18
- package/scripts/generators/news-indexes/backfill.js +118 -111
- package/scripts/generators/news-indexes/per-language.js +2 -1
- package/scripts/generators/political-intelligence/html.js +2 -1
- package/scripts/generators/sitemap/html.js +2 -1
- package/scripts/generators/sitemap/index.d.ts +1 -1
- package/scripts/generators/sitemap/index.js +1 -1
- package/scripts/generators/sitemap/rss.d.ts +38 -2
- package/scripts/generators/sitemap/rss.js +54 -10
- package/scripts/generators/sitemap/xml.js +21 -6
- package/scripts/generators/sitemap.js +42 -9
- package/scripts/mcp/ep/error-classifier.d.ts +38 -0
- package/scripts/mcp/ep/error-classifier.js +49 -0
- package/scripts/mcp/ep/tools-feeds.js +27 -2
- package/scripts/templates/sections/footer.js +3 -1
- package/scripts/templates/sections/rss-discovery.d.ts +22 -0
- package/scripts/templates/sections/rss-discovery.js +48 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "euparliamentmonitor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
|
|
6
6
|
"main": "scripts/index.js",
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"clean-css": "^5.3.3",
|
|
168
168
|
"d3": "7.9.0",
|
|
169
169
|
"esbuild": "0.28.0",
|
|
170
|
-
"eslint": "10.4.
|
|
170
|
+
"eslint": "10.4.1",
|
|
171
171
|
"eslint-config-prettier": "10.1.8",
|
|
172
172
|
"eslint-plugin-jsdoc": "63.0.0",
|
|
173
173
|
"eslint-plugin-security": "4.0.0",
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
"husky": "9.1.7",
|
|
180
180
|
"jscpd": "4.2.4",
|
|
181
181
|
"knip": "^6.7.0",
|
|
182
|
-
"lint-staged": "17.0.
|
|
182
|
+
"lint-staged": "17.0.6",
|
|
183
183
|
"mermaid": "11.15.0",
|
|
184
184
|
"papaparse": "5.5.3",
|
|
185
185
|
"prettier": "3.8.3",
|
|
@@ -22,11 +22,32 @@ export declare function localizeArticleBody(bodyHtml: string, lang: LanguageCode
|
|
|
22
22
|
* @returns Modified string, or `haystack` unchanged when `needle` is absent
|
|
23
23
|
*/
|
|
24
24
|
export declare function replaceFirstStringIn(haystack: string, needle: string, replacement: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Locate the cut point that ends the Executive Brief body — the start of
|
|
27
|
+
* the next top-level boundary heading after `afterHeading`. A boundary is
|
|
28
|
+
* any `<h2>` whose `id` either starts with the canonical `section-` prefix
|
|
29
|
+
* or exactly matches one of {@link EXECUTIVE_BRIEF_BOUNDARY_ID_MARKERS}
|
|
30
|
+
* (Reader Guide / Tradecraft / Analysis Index / Supplementary appendices).
|
|
31
|
+
*
|
|
32
|
+
* Critically, this only matches **top-level** section anchors — never the
|
|
33
|
+
* brief's own internal `<h2>` sub-headings (`## BLUF`, `## 60-Second Read`,
|
|
34
|
+
* …), which carry slugified ids without the `section-` prefix. That is why
|
|
35
|
+
* we cannot simply look for the next `<h2`.
|
|
36
|
+
*
|
|
37
|
+
* Uses `indexOf`/`lastIndexOf` exclusively (no regex) to stay within
|
|
38
|
+
* CodeQL's safe-regex envelope.
|
|
39
|
+
*
|
|
40
|
+
* @param html - Full article body HTML
|
|
41
|
+
* @param afterHeading - Index immediately after the Executive Brief `</h2>`
|
|
42
|
+
* @returns Index of the next boundary `<h2`, or `-1` when the Executive
|
|
43
|
+
* Brief is the last block in the body.
|
|
44
|
+
*/
|
|
45
|
+
export declare function findExecutiveBriefSectionCut(html: string, afterHeading: number): number;
|
|
25
46
|
/**
|
|
26
47
|
* Replace the **inner body** of the Executive Brief section (the
|
|
27
48
|
* `<h2 id="section-executive-brief">…</h2>` heading and everything that
|
|
28
|
-
* follows it up to — but not including — the next
|
|
29
|
-
*
|
|
49
|
+
* follows it up to — but not including — the next top-level boundary
|
|
50
|
+
* heading) with the supplied replacement HTML. The Executive Brief
|
|
30
51
|
* heading itself is preserved by emitting it inline ahead of the
|
|
31
52
|
* replacement, so the in-page anchor (`#section-executive-brief`) and
|
|
32
53
|
* the table-of-contents link continue to work.
|
|
@@ -39,8 +60,11 @@ export declare function replaceFirstStringIn(haystack: string, needle: string, r
|
|
|
39
60
|
* `render-one.writeLanguageVariant`.
|
|
40
61
|
*
|
|
41
62
|
* Implementation uses `indexOf`/slice exclusively to stay within
|
|
42
|
-
* CodeQL's safe-regex envelope.
|
|
43
|
-
*
|
|
63
|
+
* CodeQL's safe-regex envelope. The replacement spans from the heading to
|
|
64
|
+
* the next top-level boundary (see {@link findExecutiveBriefSectionCut});
|
|
65
|
+
* when the Executive Brief is the last block in the body the replacement
|
|
66
|
+
* extends to end-of-body. Returns `html` unchanged only when the Executive
|
|
67
|
+
* Brief heading is absent or malformed.
|
|
44
68
|
*
|
|
45
69
|
* @param html - Full article body HTML
|
|
46
70
|
* @param localizedHeading - Localized text for the Executive Brief H2
|
|
@@ -12,6 +12,23 @@ import { TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOL
|
|
|
12
12
|
import { escapeHTML } from '../../utils/file-utils.js';
|
|
13
13
|
import { TRADECRAFT_SECTION_ID, MANIFEST_SECTION_ID, SUPPLEMENTARY_SECTION_ID, } from '../artifact-order.js';
|
|
14
14
|
import { KEY_TAKEAWAYS_SECTION_ID } from '../key-takeaways.js';
|
|
15
|
+
import { READER_GUIDE_SECTION_ID } from '../reader-guide-constants.js';
|
|
16
|
+
/**
|
|
17
|
+
* Top-level section anchors that mark the **end** of the Executive Brief
|
|
18
|
+
* body. Canonical analysis sections are matched by the shared
|
|
19
|
+
* `id="section-…"` prefix (see {@link findExecutiveBriefSectionCut});
|
|
20
|
+
* the appendix and reader-guide sections below carry bespoke ids that do
|
|
21
|
+
* **not** share that prefix, so they are matched explicitly. Including
|
|
22
|
+
* them ensures the localized brief splice also fires on sparse runs where
|
|
23
|
+
* the Executive Brief is the last canonical section and only appendix
|
|
24
|
+
* blocks follow it.
|
|
25
|
+
*/
|
|
26
|
+
const EXECUTIVE_BRIEF_BOUNDARY_ID_MARKERS = [
|
|
27
|
+
`id="${READER_GUIDE_SECTION_ID}"`,
|
|
28
|
+
`id="${TRADECRAFT_SECTION_ID}"`,
|
|
29
|
+
`id="${MANIFEST_SECTION_ID}"`,
|
|
30
|
+
`id="${SUPPLEMENTARY_SECTION_ID}"`,
|
|
31
|
+
];
|
|
15
32
|
/**
|
|
16
33
|
* Localize the Tradecraft References and Analysis Index sections in the
|
|
17
34
|
* rendered article body HTML. Replaces English headings, introductions,
|
|
@@ -102,11 +119,48 @@ export function replaceFirstStringIn(haystack, needle, replacement) {
|
|
|
102
119
|
return haystack;
|
|
103
120
|
return haystack.slice(0, idx) + replacement + haystack.slice(idx + needle.length);
|
|
104
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Locate the cut point that ends the Executive Brief body — the start of
|
|
124
|
+
* the next top-level boundary heading after `afterHeading`. A boundary is
|
|
125
|
+
* any `<h2>` whose `id` either starts with the canonical `section-` prefix
|
|
126
|
+
* or exactly matches one of {@link EXECUTIVE_BRIEF_BOUNDARY_ID_MARKERS}
|
|
127
|
+
* (Reader Guide / Tradecraft / Analysis Index / Supplementary appendices).
|
|
128
|
+
*
|
|
129
|
+
* Critically, this only matches **top-level** section anchors — never the
|
|
130
|
+
* brief's own internal `<h2>` sub-headings (`## BLUF`, `## 60-Second Read`,
|
|
131
|
+
* …), which carry slugified ids without the `section-` prefix. That is why
|
|
132
|
+
* we cannot simply look for the next `<h2`.
|
|
133
|
+
*
|
|
134
|
+
* Uses `indexOf`/`lastIndexOf` exclusively (no regex) to stay within
|
|
135
|
+
* CodeQL's safe-regex envelope.
|
|
136
|
+
*
|
|
137
|
+
* @param html - Full article body HTML
|
|
138
|
+
* @param afterHeading - Index immediately after the Executive Brief `</h2>`
|
|
139
|
+
* @returns Index of the next boundary `<h2`, or `-1` when the Executive
|
|
140
|
+
* Brief is the last block in the body.
|
|
141
|
+
*/
|
|
142
|
+
export function findExecutiveBriefSectionCut(html, afterHeading) {
|
|
143
|
+
let best = -1;
|
|
144
|
+
const consider = (markerIdx) => {
|
|
145
|
+
if (markerIdx === -1)
|
|
146
|
+
return;
|
|
147
|
+
const h2 = html.lastIndexOf('<h2', markerIdx);
|
|
148
|
+
if (h2 === -1 || h2 < afterHeading)
|
|
149
|
+
return;
|
|
150
|
+
if (best === -1 || h2 < best)
|
|
151
|
+
best = h2;
|
|
152
|
+
};
|
|
153
|
+
consider(html.indexOf('id="section-', afterHeading));
|
|
154
|
+
for (const marker of EXECUTIVE_BRIEF_BOUNDARY_ID_MARKERS) {
|
|
155
|
+
consider(html.indexOf(marker, afterHeading));
|
|
156
|
+
}
|
|
157
|
+
return best;
|
|
158
|
+
}
|
|
105
159
|
/**
|
|
106
160
|
* Replace the **inner body** of the Executive Brief section (the
|
|
107
161
|
* `<h2 id="section-executive-brief">…</h2>` heading and everything that
|
|
108
|
-
* follows it up to — but not including — the next
|
|
109
|
-
*
|
|
162
|
+
* follows it up to — but not including — the next top-level boundary
|
|
163
|
+
* heading) with the supplied replacement HTML. The Executive Brief
|
|
110
164
|
* heading itself is preserved by emitting it inline ahead of the
|
|
111
165
|
* replacement, so the in-page anchor (`#section-executive-brief`) and
|
|
112
166
|
* the table-of-contents link continue to work.
|
|
@@ -119,8 +173,11 @@ export function replaceFirstStringIn(haystack, needle, replacement) {
|
|
|
119
173
|
* `render-one.writeLanguageVariant`.
|
|
120
174
|
*
|
|
121
175
|
* Implementation uses `indexOf`/slice exclusively to stay within
|
|
122
|
-
* CodeQL's safe-regex envelope.
|
|
123
|
-
*
|
|
176
|
+
* CodeQL's safe-regex envelope. The replacement spans from the heading to
|
|
177
|
+
* the next top-level boundary (see {@link findExecutiveBriefSectionCut});
|
|
178
|
+
* when the Executive Brief is the last block in the body the replacement
|
|
179
|
+
* extends to end-of-body. Returns `html` unchanged only when the Executive
|
|
180
|
+
* Brief heading is absent or malformed.
|
|
124
181
|
*
|
|
125
182
|
* @param html - Full article body HTML
|
|
126
183
|
* @param localizedHeading - Localized text for the Executive Brief H2
|
|
@@ -147,23 +204,24 @@ export function replaceExecutiveBriefSection(html, localizedHeading, replacement
|
|
|
147
204
|
if (h2CloseTagIdx === -1)
|
|
148
205
|
return html;
|
|
149
206
|
const afterHeading = h2CloseTagIdx + '</h2>'.length;
|
|
150
|
-
// Find the next
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
207
|
+
// Find the next top-level boundary heading — the start of the following
|
|
208
|
+
// article section or appendix. When none exists the Executive Brief is
|
|
209
|
+
// the last block, so we replace through end-of-body. This guarantees the
|
|
210
|
+
// localized brief is spliced even on sparse runs (previously the splice
|
|
211
|
+
// bailed and non-English readers were stranded on the English brief).
|
|
212
|
+
const nextH2 = findExecutiveBriefSectionCut(html, afterHeading);
|
|
213
|
+
let cutEnd;
|
|
214
|
+
if (nextH2 === -1) {
|
|
215
|
+
cutEnd = html.length;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Start of the line containing the next `<h2` so we don't strip
|
|
219
|
+
// leading whitespace from the next section.
|
|
220
|
+
cutEnd = nextH2;
|
|
221
|
+
const prevNewline = html.lastIndexOf('\n', nextH2 - 1);
|
|
222
|
+
if (prevNewline !== -1 && prevNewline >= afterHeading) {
|
|
223
|
+
cutEnd = prevNewline + 1;
|
|
224
|
+
}
|
|
167
225
|
}
|
|
168
226
|
const newHeading = `<h2 id="section-executive-brief">${escapeHTML(localizedHeading)}</h2>\n`;
|
|
169
227
|
const trimmedReplacement = replacementBodyHtml.endsWith('\n')
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { BASE_URL, BUILD_SHORT, MERMAID_VERSION } from '../../constants/config.js';
|
|
14
14
|
import { buildHeadFreshnessTags } from '../../constants/build-info-meta.js';
|
|
15
|
-
import { ALL_LANGUAGES, PAGE_TITLES, SKIP_LINK_TEXTS, ARTICLE_NAV_LABELS, BACK_TO_NEWS_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, getLocalizedString, getTextDirection, } from '../../constants/languages.js';
|
|
15
|
+
import { ALL_LANGUAGES, PAGE_TITLES, SKIP_LINK_TEXTS, ARTICLE_NAV_LABELS, BACK_TO_NEWS_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, PROGRESSIVE_DISCLOSURE_LABELS, getLocalizedString, getTextDirection, } from '../../constants/languages.js';
|
|
16
16
|
import { buildOgLocaleTags } from '../../constants/og-locales.js';
|
|
17
17
|
import { ORG_SAME_AS, buildTwitterAttributionTags } from '../../constants/social-handles.js';
|
|
18
18
|
import { escapeHTML } from '../../utils/file-utils.js';
|
|
19
19
|
import { buildResponsiveIconLinks, buildResponsiveSocialImageMeta, buildSiteFooter, buildSiteHeader, buildPageBanner, } from '../../templates/section-builders.js';
|
|
20
20
|
import { getPoliticalIntelligenceFilename } from '../../generators/political-intelligence.js';
|
|
21
21
|
import { getSitemapFilename } from '../../generators/sitemap/index.js';
|
|
22
|
+
import { buildRssAlternateLink } from '../../templates/sections/rss-discovery.js';
|
|
22
23
|
import { truncateHeadline, getTitleSeparator, buildPageTitle, getLocalizedArticleType, getLocalizedArticleTypePlain, } from './headline.js';
|
|
23
24
|
import { clampForBudget } from '../metadata/seo-budgets.js';
|
|
24
25
|
import { getArticleFilename, buildArticleHreflangLinks, buildLanguageSwitcher, } from './hreflang.js';
|
|
@@ -186,7 +187,7 @@ export function wrapArticleHtml(options) {
|
|
|
186
187
|
const tocHtml = buildArticleToc(options.toc ?? [], safeLang);
|
|
187
188
|
const articleMainClass = tocHtml.length > 0 ? 'article-main--with-toc' : 'article-main--no-toc';
|
|
188
189
|
const articleSectionLabel = getLocalizedArticleTypePlain(options.articleType, safeLang);
|
|
189
|
-
const disclosureBody = buildProgressiveDisclosureBody(options.body);
|
|
190
|
+
const disclosureBody = buildProgressiveDisclosureBody(options.body, safeLang);
|
|
190
191
|
const transformedBodyHtml = options.readerFriendly === false
|
|
191
192
|
? disclosureBody.bodyHtml
|
|
192
193
|
: applyReaderFriendlyTransform(disclosureBody.bodyHtml);
|
|
@@ -199,7 +200,9 @@ export function wrapArticleHtml(options) {
|
|
|
199
200
|
disclosureBody.wordCounts.analysis +
|
|
200
201
|
disclosureBody.wordCounts.intelligence;
|
|
201
202
|
const readingTimes = options.readingTimes ?? buildLayerReadingTimes(disclosureBody.wordCounts);
|
|
202
|
-
const
|
|
203
|
+
const disclosureLabels = getLocalizedString(PROGRESSIVE_DISCLOSURE_LABELS, safeLang);
|
|
204
|
+
const min = disclosureLabels.minutesAbbr;
|
|
205
|
+
const readingTimeLine = `⏱️ ${disclosureLabels.quickRead}: ${readingTimes.quickRead}${min} · ${disclosureLabels.fullAnalysis}: ${readingTimes.fullAnalysis}${min} · ${disclosureLabels.completeIntelligence}: ${readingTimes.completeIntelligence}${min}`;
|
|
203
206
|
// Pre-compute the per-surface SEO-budget-clamped variants of title
|
|
204
207
|
// and description. Each surface gets its own clamp tuned to the
|
|
205
208
|
// documented platform envelope (Google/Bing SERP, Facebook/LinkedIn
|
|
@@ -350,7 +353,7 @@ ${keywordsMeta} <meta name="robots" content="index, follow, max-snippet:-1, max
|
|
|
350
353
|
<meta property="article:publisher" content="https://hack23.com">
|
|
351
354
|
<link rel="canonical" href="${canonicalUrl}">
|
|
352
355
|
${hreflangLinks}
|
|
353
|
-
|
|
356
|
+
${buildRssAlternateLink(safeLang, `${BASE_URL}/`)}
|
|
354
357
|
<link rel="preconnect" href="https://hack23.com" crossorigin>
|
|
355
358
|
<meta property="og:type" content="article">
|
|
356
359
|
<meta property="og:title" content="${escapeHTML(ogTitleClamped)}">
|
|
@@ -392,7 +395,7 @@ ${tocHtml} <article class="article-body" lang="${safeLang}">
|
|
|
392
395
|
<p class="article-kicker">${escapeHTML(getLocalizedArticleType(options.articleType, safeLang))}</p>
|
|
393
396
|
<h1>${escapeHTML(options.title)}</h1>
|
|
394
397
|
<p class="article-dek">${escapeHTML(options.description)}</p>
|
|
395
|
-
<p class="article-reading-times" aria-label="
|
|
398
|
+
<p class="article-reading-times" aria-label="${escapeHTML(disclosureLabels.readingTimeAria)}">${escapeHTML(readingTimeLine)}</p>
|
|
396
399
|
<p class="article-meta"><time datetime="${options.date}">${options.date}</time> · EU Parliament Monitor</p>
|
|
397
400
|
</header>
|
|
398
401
|
${sourceMdLink}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* that mirrors the Reader Intelligence Guide so the two navigation
|
|
9
9
|
* surfaces share a single visual vocabulary.
|
|
10
10
|
*/
|
|
11
|
-
import { TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, ANALYSIS_INDEX_HEADING_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, getLocalizedString, } from '../../constants/languages.js';
|
|
11
|
+
import { TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, ANALYSIS_INDEX_HEADING_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, PROGRESSIVE_DISCLOSURE_LABELS, getLocalizedString, } from '../../constants/languages.js';
|
|
12
12
|
import { escapeHTML } from '../../utils/file-utils.js';
|
|
13
13
|
import { READER_GUIDE_SECTION_ID } from '../reader-guide-constants.js';
|
|
14
14
|
import { READER_GUIDE_TITLE_LABELS, getReaderGuideSectionIcon, } from '../reader-intelligence-guide.js';
|
|
@@ -91,13 +91,15 @@ export function buildArticleToc(entries, lang) {
|
|
|
91
91
|
if (entries.length === 0)
|
|
92
92
|
return '';
|
|
93
93
|
const label = escapeHTML(getLocalizedString(TOC_ARIA_LABELS, lang));
|
|
94
|
+
const layerBadgeWord = getLocalizedString(PROGRESSIVE_DISCLOSURE_LABELS, lang).layerBadge;
|
|
94
95
|
const items = entries
|
|
95
96
|
.map((e) => {
|
|
96
97
|
const displayTitle = getLocalizedTocTitle(e.id, e.title, lang);
|
|
97
98
|
const icon = getTocSectionIcon(e.id);
|
|
98
99
|
const layer = resolveDisclosureLayer(e.id);
|
|
99
100
|
const layerBadge = layer === 'quick' ? 'L1' : layer === 'analysis' ? 'L2' : 'L3';
|
|
100
|
-
|
|
101
|
+
const layerAria = escapeHTML(`${layerBadgeWord} ${layerBadge}`);
|
|
102
|
+
return ` <li data-layer="${layer}"><a href="#${escapeHTML(e.id)}"><span class="article-toc-icon" aria-hidden="true">${icon}</span> <span class="article-toc-text">${escapeHTML(displayTitle)}</span><span class="article-toc-layer article-toc-layer--${layer}" aria-label="${layerAria}">${layerBadge}</span></a></li>`;
|
|
101
103
|
})
|
|
102
104
|
.join('\n');
|
|
103
105
|
return [
|
|
@@ -153,6 +153,13 @@ export const ARTIFACT_CATEGORY_PREFIXES = [
|
|
|
153
153
|
'voting patterns',
|
|
154
154
|
'weekly outlook',
|
|
155
155
|
'wildcards blackswans',
|
|
156
|
+
// CJK localized category prefixes (translations of "executive briefing")
|
|
157
|
+
'エグゼクティブ・ブリーフィング',
|
|
158
|
+
'エグゼクティブブリーフィング',
|
|
159
|
+
'エグゼクティブ・ブリーフ',
|
|
160
|
+
'행정 브리핑',
|
|
161
|
+
'执行简报',
|
|
162
|
+
'執行簡報',
|
|
156
163
|
];
|
|
157
164
|
/**
|
|
158
165
|
* Match a single calendar month name (English) with optional `-uary` /
|
|
@@ -211,7 +218,7 @@ function normaliseCategoryHeading(raw) {
|
|
|
211
218
|
return stripInlineMarkdown(raw)
|
|
212
219
|
.trim()
|
|
213
220
|
.toLowerCase()
|
|
214
|
-
.replace(/^[^a-z0-9]
|
|
221
|
+
.replace(/^[^a-z0-9\p{L}]+/u, '')
|
|
215
222
|
.replace(/\s+/g, ' ');
|
|
216
223
|
}
|
|
217
224
|
/**
|
|
@@ -158,6 +158,17 @@ const BARE_INSTITUTIONAL_HEADINGS = [
|
|
|
158
158
|
'briefing',
|
|
159
159
|
'intelligence brief',
|
|
160
160
|
'intelligence briefing',
|
|
161
|
+
// CJK / localized translations of generic headings
|
|
162
|
+
'エグゼクティブ・ブリーフィング',
|
|
163
|
+
'エグゼクティブブリーフィング',
|
|
164
|
+
'エグゼクティブ・ブリーフ',
|
|
165
|
+
'ブリーフィング',
|
|
166
|
+
'행정 브리핑',
|
|
167
|
+
'브리핑',
|
|
168
|
+
'执行简报',
|
|
169
|
+
'简报',
|
|
170
|
+
'執行簡報',
|
|
171
|
+
'簡報',
|
|
161
172
|
];
|
|
162
173
|
/**
|
|
163
174
|
* Return `true` when the heading is one of {@link BARE_INSTITUTIONAL_HEADINGS}
|
|
@@ -160,15 +160,18 @@ export function clampForBudget(text, lang, surface) {
|
|
|
160
160
|
if (cleaned.length >= softMin)
|
|
161
161
|
return cleaned;
|
|
162
162
|
}
|
|
163
|
-
// Whitespace-aware fallback.
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
// Whitespace-aware fallback. Runs for every script: an ASCII space
|
|
164
|
+
// past the soft minimum is a safe break that drops a partial trailing
|
|
165
|
+
// segment whole rather than slicing it mid-token. Chinese and Japanese
|
|
166
|
+
// prose has no inter-word spaces, so `lastIndexOf(' ')` returns -1 and
|
|
167
|
+
// this is a no-op for them — but composed SEO snippets join clauses
|
|
168
|
+
// (body, dateline, reader label) with ASCII spaces, so honouring that
|
|
169
|
+
// boundary prevents hard-cutting the reader label mid-word. Korean
|
|
170
|
+
// uses inter-word spaces natively and benefits the same way.
|
|
171
|
+
const lastSpace = window.lastIndexOf(' ');
|
|
172
|
+
if (lastSpace >= softMin) {
|
|
173
|
+
const safe = trimTrailingSeparators(window.slice(0, lastSpace));
|
|
174
|
+
return `${safe}…`;
|
|
172
175
|
}
|
|
173
176
|
const hardCut = trimTrailingSeparators(window);
|
|
174
177
|
return `${hardCut}…`;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LanguageCode } from '../types/index.js';
|
|
1
2
|
export type DisclosureLayer = 'quick' | 'analysis' | 'intelligence';
|
|
2
3
|
export interface LayerWordCounts {
|
|
3
4
|
readonly quick: number;
|
|
@@ -20,7 +21,7 @@ export declare function splitBodyIntoDisclosureLayers(bodyHtml: string): {
|
|
|
20
21
|
};
|
|
21
22
|
export declare function estimateReadingMinutes(wordCount: number): number;
|
|
22
23
|
export declare function buildLayerReadingTimes(words: LayerWordCounts): LayerReadingTimes;
|
|
23
|
-
export declare function buildProgressiveDisclosureBody(bodyHtml: string): {
|
|
24
|
+
export declare function buildProgressiveDisclosureBody(bodyHtml: string, lang?: LanguageCode): {
|
|
24
25
|
readonly bodyHtml: string;
|
|
25
26
|
readonly wordCounts: LayerWordCounts;
|
|
26
27
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { stripHtmlTags } from '../utils/html-sanitize.js';
|
|
4
|
+
import { escapeHTML } from '../utils/file-utils.js';
|
|
5
|
+
import { PROGRESSIVE_DISCLOSURE_LABELS } from '../constants/languages.js';
|
|
6
|
+
import { getLocalizedString } from '../constants/language-core.js';
|
|
4
7
|
export const QUICK_LAYER_SECTION_IDS = new Set([
|
|
5
8
|
'executive-brief',
|
|
6
9
|
'key-takeaways',
|
|
@@ -87,18 +90,19 @@ export function buildLayerReadingTimes(words) {
|
|
|
87
90
|
completeIntelligence: estimateReadingMinutes(complete),
|
|
88
91
|
};
|
|
89
92
|
}
|
|
90
|
-
export function buildProgressiveDisclosureBody(bodyHtml) {
|
|
93
|
+
export function buildProgressiveDisclosureBody(bodyHtml, lang = 'en') {
|
|
94
|
+
const labels = getLocalizedString(PROGRESSIVE_DISCLOSURE_LABELS, lang);
|
|
91
95
|
const layers = splitBodyIntoDisclosureLayers(bodyHtml);
|
|
92
96
|
const output = [
|
|
93
|
-
`<section class="article-layer article-layer--quick" data-disclosure-layer="quick" aria-label="
|
|
97
|
+
`<section class="article-layer article-layer--quick" data-disclosure-layer="quick" aria-label="${escapeHTML(labels.quickRead)}">`,
|
|
94
98
|
layers.quickHtml,
|
|
95
99
|
`</section>`,
|
|
96
100
|
];
|
|
97
101
|
if (layers.analysisHtml.trim().length > 0) {
|
|
98
|
-
output.push(`<details class="article-layer article-layer--analysis article-layer-details" data-disclosure-layer="analysis" id="article-layer-analysis">`, `<summary class="article-layer-summary"><span class="article-layer-summary__title"
|
|
102
|
+
output.push(`<details class="article-layer article-layer--analysis article-layer-details" data-disclosure-layer="analysis" id="article-layer-analysis">`, `<summary class="article-layer-summary"><span class="article-layer-summary__title">${escapeHTML(labels.expandAnalysis)} ↓</span></summary>`, `<section class="article-layer-content" aria-label="${escapeHTML(labels.fullAnalysis)}">`, layers.analysisHtml, `</section>`, `</details>`);
|
|
99
103
|
}
|
|
100
104
|
if (layers.intelligenceHtml.trim().length > 0) {
|
|
101
|
-
output.push(`<details class="article-layer article-layer--intelligence article-layer-details" data-disclosure-layer="intelligence" id="article-layer-intelligence">`, `<summary class="article-layer-summary"><span class="article-layer-summary__title"
|
|
105
|
+
output.push(`<details class="article-layer article-layer--intelligence article-layer-details" data-disclosure-layer="intelligence" id="article-layer-intelligence">`, `<summary class="article-layer-summary"><span class="article-layer-summary__title">${escapeHTML(labels.expandIntelligence)} ↓</span></summary>`, `<section class="article-layer-content" aria-label="${escapeHTML(labels.completeIntelligence)}">`, layers.intelligenceHtml, `</section>`, `</details>`);
|
|
102
106
|
}
|
|
103
107
|
return { bodyHtml: output.join('\n'), wordCounts: layers.wordCounts };
|
|
104
108
|
}
|
|
@@ -46,7 +46,7 @@ const ADMIRALTY_LABELS = {
|
|
|
46
46
|
export function applyReaderFriendlyTransform(html) {
|
|
47
47
|
const state = createInitialState(html);
|
|
48
48
|
const withGlossary = injectReaderGlossary(html);
|
|
49
|
-
const parts = withGlossary.split(/(<[
|
|
49
|
+
const parts = withGlossary.split(/(<[^<>]+>)/g);
|
|
50
50
|
for (let i = 0; i < parts.length; i++) {
|
|
51
51
|
const part = parts[i] ?? '';
|
|
52
52
|
if (part.startsWith('<')) {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* - **language-articles** — Article-type title generators and body-text strings
|
|
9
9
|
*/
|
|
10
10
|
export { ALL_LANGUAGES, LANGUAGE_PRESETS, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, isSupportedLanguage, getTextDirection, } from './language-core.js';
|
|
11
|
-
export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, ARTICLE_TYPE_ICONS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, HEADER_CTA_SPONSOR_LABELS, HEADER_CTA_BECOME_SPONSOR_LABELS, HEADER_CTA_SECURITY_LABELS, FOOTER_NEWS_LABELS, FOOTER_DASHBOARD_LABELS, FOOTER_ANALYSIS_REPORTS_LABELS, FOOTER_API_DOCS_LABELS, FOOTER_COMPANY_TAGLINE_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './language-ui.js';
|
|
11
|
+
export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, PROGRESSIVE_DISCLOSURE_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, ARTICLE_TYPE_ICONS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, HEADER_CTA_SPONSOR_LABELS, HEADER_CTA_BECOME_SPONSOR_LABELS, HEADER_CTA_SECURITY_LABELS, FOOTER_NEWS_LABELS, FOOTER_DASHBOARD_LABELS, FOOTER_ANALYSIS_REPORTS_LABELS, FOOTER_API_DOCS_LABELS, FOOTER_COMPANY_TAGLINE_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './language-ui.js';
|
|
12
12
|
export type { AISection, RelationshipLabels, RelatedAnalysisStrings } from './language-ui.js';
|
|
13
|
+
export type { ProgressiveDisclosureStrings } from './language-ui.js';
|
|
13
14
|
export { WEEK_AHEAD_TITLES, MONTH_AHEAD_TITLES, WEEKLY_REVIEW_TITLES, MONTHLY_REVIEW_TITLES, MOTIONS_TITLES, BREAKING_NEWS_TITLES, COMMITTEE_REPORTS_TITLES, PROPOSITIONS_TITLES, PROPOSITIONS_STRINGS, EDITORIAL_STRINGS, MOTIONS_STRINGS, WEEK_AHEAD_STRINGS, WEEK_AHEAD_STAKEHOLDER_STRINGS, BREAKING_STRINGS, DEEP_ANALYSIS_STRINGS, COMMITTEE_ANALYSIS_CONTENT_STRINGS, SWOT_STRINGS, DASHBOARD_STRINGS, SWOT_BUILDER_STRINGS, DASHBOARD_BUILDER_STRINGS, LOCALIZED_KEYWORDS, MONTH_IN_REVIEW_STRINGS, ANALYSIS_QUALITY_LABELS, ANALYSIS_INSIGHTS_HEADING, } from './language-articles.js';
|
|
14
15
|
//# sourceMappingURL=languages.d.ts.map
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
* - **language-articles** — Article-type title generators and body-text strings
|
|
11
11
|
*/
|
|
12
12
|
export { ALL_LANGUAGES, LANGUAGE_PRESETS, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, isSupportedLanguage, getTextDirection, } from './language-core.js';
|
|
13
|
-
export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, ARTICLE_TYPE_ICONS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, HEADER_CTA_SPONSOR_LABELS, HEADER_CTA_BECOME_SPONSOR_LABELS, HEADER_CTA_SECURITY_LABELS, FOOTER_NEWS_LABELS, FOOTER_DASHBOARD_LABELS, FOOTER_ANALYSIS_REPORTS_LABELS, FOOTER_API_DOCS_LABELS, FOOTER_COMPANY_TAGLINE_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './language-ui.js';
|
|
13
|
+
export { PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, ARTICLE_TYPE_LABELS, READ_TIME_LABELS, PROGRESSIVE_DISCLOSURE_LABELS, BACK_TO_NEWS_LABELS, ARTICLE_NAV_LABELS, RELATED_ARTICLES_NAV_LABELS, BREADCRUMB_HOME_LABELS, BREADCRUMB_NEWS_LABELS, TIMELINE_HEADINGS, COMPARISON_BEFORE_LABELS, COMPARISON_AFTER_LABELS, KEY_FIGURES_HEADINGS, AI_SECTION_CONTENT, FILTER_LABELS, SOURCES_HEADING_LABELS, HEADER_SUBTITLE_LABELS, THEME_TOGGLE_LABELS, FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LINKS_LABELS, FOOTER_BUILT_BY_LABELS, FOOTER_LANGUAGES_LABELS, FOOTER_HOME_LABELS, FOOTER_SITEMAP_LABELS, FOOTER_RSS_LABELS, FOOTER_GITHUB_REPO_LABELS, FOOTER_LICENSE_LABELS, FOOTER_EUROPARL_LABELS, FOOTER_LINKEDIN_LABELS, FOOTER_SECURITY_POLICY_LABELS, FOOTER_CONTACT_LABELS, FOOTER_DISCLAIMER_LABELS, FOOTER_REPORT_ISSUES_LABELS, FOOTER_ARTICLES_AVAILABLE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, TOC_ARIA_LABELS, TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, KEY_TAKEAWAYS_HEADING_LABELS, SUPPLEMENTARY_HEADING_LABELS, SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, ANALYSIS_TRANSPARENCY_LABELS, ANALYSIS_SUMMARY_LABELS, METHODOLOGY_LABELS, TRANSPARENCY_DISCLOSURE_LABELS, CLASSIFICATION_ANALYSIS_LABELS, THREAT_ASSESSMENT_LABELS, RISK_SCORING_LABELS, DEEP_ANALYSIS_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, ARTICLE_TYPE_ICONS, OPEN_SOURCE_NOTE_LABELS, AI_ANALYSIS_GUIDE_LABELS, SWOT_FRAMEWORK_LABELS, RISK_METHODOLOGY_LABELS, THREAT_FRAMEWORK_LABELS, CLASSIFICATION_GUIDE_LABELS, STYLE_GUIDE_LABELS, SIGNIFICANCE_CLASSIFICATION_LABELS, ACTOR_MAPPING_LABELS, FORCES_ANALYSIS_LABELS, IMPACT_MATRIX_LABELS, POLITICAL_THREAT_LANDSCAPE_LABELS, ACTOR_THREAT_PROFILING_LABELS, CONSEQUENCE_TREES_LABELS, LEGISLATIVE_DISRUPTION_LABELS, RISK_MATRIX_LABELS, QUANTITATIVE_SWOT_LABELS, POLITICAL_CAPITAL_RISK_LABELS, LEGISLATIVE_VELOCITY_RISK_LABELS, AGENT_RISK_WORKFLOW_LABELS, STAKEHOLDER_IMPACT_LABELS, COALITION_DYNAMICS_LABELS, VOTING_PATTERNS_LABELS, CROSS_SESSION_INTELLIGENCE_LABELS, SYNTHESIS_SUMMARY_LABELS, DOCUMENT_ANALYSIS_LABELS, SIGNIFICANCE_SCORING_LABELS, INSTALL_APP_LABELS, UPDATE_AVAILABLE_LABELS, UPDATE_REFRESH_CTA_LABELS, UPDATE_DISMISS_LABELS, OFFLINE_TITLE_LABELS, OFFLINE_BODY_LABELS, OFFLINE_RETRY_LABELS, BUILD_INFO_COMMIT_LABELS, BUILD_INFO_DEPLOYED_LABELS, HEADER_CTA_SPONSOR_LABELS, HEADER_CTA_BECOME_SPONSOR_LABELS, HEADER_CTA_SECURITY_LABELS, FOOTER_NEWS_LABELS, FOOTER_DASHBOARD_LABELS, FOOTER_ANALYSIS_REPORTS_LABELS, FOOTER_API_DOCS_LABELS, FOOTER_COMPANY_TAGLINE_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './language-ui.js';
|
|
14
14
|
export { WEEK_AHEAD_TITLES, MONTH_AHEAD_TITLES, WEEKLY_REVIEW_TITLES, MONTHLY_REVIEW_TITLES, MOTIONS_TITLES, BREAKING_NEWS_TITLES, COMMITTEE_REPORTS_TITLES, PROPOSITIONS_TITLES, PROPOSITIONS_STRINGS, EDITORIAL_STRINGS, MOTIONS_STRINGS, WEEK_AHEAD_STRINGS, WEEK_AHEAD_STAKEHOLDER_STRINGS, BREAKING_STRINGS, DEEP_ANALYSIS_STRINGS, COMMITTEE_ANALYSIS_CONTENT_STRINGS, SWOT_STRINGS, DASHBOARD_STRINGS, SWOT_BUILDER_STRINGS, DASHBOARD_BUILDER_STRINGS, LOCALIZED_KEYWORDS, MONTH_IN_REVIEW_STRINGS, ANALYSIS_QUALITY_LABELS, ANALYSIS_INSIGHTS_HEADING, } from './language-articles.js';
|
|
15
15
|
//# sourceMappingURL=languages.js.map
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - `article-category-labels.ts` — `ARTICLE_TYPE_LABELS`, `ARTICLE_TYPE_ICONS`
|
|
12
12
|
* - `accessibility.ts` — `SKIP_LINK_TEXTS`, `TOC_ARIA_LABELS`, language-switcher / footer trust-badge ARIA labels
|
|
13
13
|
* - `reading-time.ts` — `READ_TIME_LABELS` (per-language pluralization)
|
|
14
|
+
* - `progressive-disclosure.ts` — `PROGRESSIVE_DISCLOSURE_LABELS` (reading layers, expand CTAs, reading-time line, TOC layer badge)
|
|
14
15
|
* - `ai-content.ts` — `AI_SECTION_CONTENT` + `AISection` interface
|
|
15
16
|
* - `related-analysis.ts` — `SECTION_TITLE_LABELS`, `RELATED_ANALYSIS_LABELS`
|
|
16
17
|
* - `tradecraft-cards.ts` — tradecraft / analysis-index card labels
|
|
@@ -24,6 +25,7 @@ export { FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LIN
|
|
|
24
25
|
export { ARTICLE_TYPE_LABELS, ARTICLE_TYPE_ICONS, HE_DEEP_ANALYSIS, } from './article-category-labels.js';
|
|
25
26
|
export { SKIP_LINK_TEXTS, TOC_ARIA_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './accessibility.js';
|
|
26
27
|
export { READ_TIME_LABELS } from './reading-time.js';
|
|
28
|
+
export { PROGRESSIVE_DISCLOSURE_LABELS, type ProgressiveDisclosureStrings, } from './progressive-disclosure.js';
|
|
27
29
|
export { AI_SECTION_CONTENT, type AISection } from './ai-content.js';
|
|
28
30
|
export { SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, type RelationshipLabels, type RelatedAnalysisStrings, } from './related-analysis.js';
|
|
29
31
|
export { TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, OPEN_SOURCE_NOTE_LABELS, } from './tradecraft-cards.js';
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* - `article-category-labels.ts` — `ARTICLE_TYPE_LABELS`, `ARTICLE_TYPE_ICONS`
|
|
14
14
|
* - `accessibility.ts` — `SKIP_LINK_TEXTS`, `TOC_ARIA_LABELS`, language-switcher / footer trust-badge ARIA labels
|
|
15
15
|
* - `reading-time.ts` — `READ_TIME_LABELS` (per-language pluralization)
|
|
16
|
+
* - `progressive-disclosure.ts` — `PROGRESSIVE_DISCLOSURE_LABELS` (reading layers, expand CTAs, reading-time line, TOC layer badge)
|
|
16
17
|
* - `ai-content.ts` — `AI_SECTION_CONTENT` + `AISection` interface
|
|
17
18
|
* - `related-analysis.ts` — `SECTION_TITLE_LABELS`, `RELATED_ANALYSIS_LABELS`
|
|
18
19
|
* - `tradecraft-cards.ts` — tradecraft / analysis-index card labels
|
|
@@ -26,6 +27,7 @@ export { FOOTER_ABOUT_HEADING_LABELS, FOOTER_ABOUT_TEXT_LABELS, FOOTER_QUICK_LIN
|
|
|
26
27
|
export { ARTICLE_TYPE_LABELS, ARTICLE_TYPE_ICONS, HE_DEEP_ANALYSIS, } from './article-category-labels.js';
|
|
27
28
|
export { SKIP_LINK_TEXTS, TOC_ARIA_LABELS, LANGUAGE_SELECTION_ARIA_LABELS, FOOTER_TRUST_BADGES_ARIA_LABELS, } from './accessibility.js';
|
|
28
29
|
export { READ_TIME_LABELS } from './reading-time.js';
|
|
30
|
+
export { PROGRESSIVE_DISCLOSURE_LABELS, } from './progressive-disclosure.js';
|
|
29
31
|
export { AI_SECTION_CONTENT } from './ai-content.js';
|
|
30
32
|
export { SECTION_TITLE_LABELS, RELATED_ANALYSIS_LABELS, } from './related-analysis.js';
|
|
31
33
|
export { TRADECRAFT_HEADING_LABELS, TRADECRAFT_INTRO_LABELS, TRADECRAFT_METHODOLOGIES_LABELS, TRADECRAFT_TEMPLATES_LABELS, ANALYSIS_INDEX_HEADING_LABELS, ANALYSIS_INDEX_INTRO_LABELS, ANALYSIS_INDEX_COL_SECTION_LABELS, ANALYSIS_INDEX_COL_ARTIFACT_LABELS, ANALYSIS_INDEX_COL_PATH_LABELS, VIEW_SOURCE_LABELS, VIEW_SOURCE_MARKDOWN_LABELS, OPEN_SOURCE_NOTE_LABELS, } from './tradecraft-cards.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Constants/UI/ProgressiveDisclosure
|
|
3
|
+
* @description Localized chrome for the article progressive-disclosure
|
|
4
|
+
* surface — the three reading layers (quick / full analysis / complete
|
|
5
|
+
* intelligence), the visible `<summary>` expand CTAs, the reading-time
|
|
6
|
+
* estimate line, and the Table-of-Contents layer badge.
|
|
7
|
+
*
|
|
8
|
+
* A single `PROGRESSIVE_DISCLOSURE_LABELS` map is shared by every call
|
|
9
|
+
* site (the layer wrappers in `aggregator/progressive-disclosure.ts`, the
|
|
10
|
+
* reading-time line in `aggregator/html/shell.ts`, and the TOC badge in
|
|
11
|
+
* `aggregator/html/toc.ts`) so the layer vocabulary stays consistent and
|
|
12
|
+
* each phrase is translated exactly once.
|
|
13
|
+
*/
|
|
14
|
+
import type { LanguageMap } from '../../types/index.js';
|
|
15
|
+
/** Per-language UI strings for the article progressive-disclosure chrome. */
|
|
16
|
+
export interface ProgressiveDisclosureStrings {
|
|
17
|
+
/** Name of the always-visible quick-read layer (reading-time + aria). */
|
|
18
|
+
readonly quickRead: string;
|
|
19
|
+
/** Name of the cumulative full-analysis layer (reading-time + aria). */
|
|
20
|
+
readonly fullAnalysis: string;
|
|
21
|
+
/** Name of the cumulative complete-intelligence layer (reading-time + aria). */
|
|
22
|
+
readonly completeIntelligence: string;
|
|
23
|
+
/** Visible CTA on the analysis `<summary>` toggle (a `↓` glyph is appended in markup). */
|
|
24
|
+
readonly expandAnalysis: string;
|
|
25
|
+
/** Visible CTA on the intelligence `<summary>` toggle (a `↓` glyph is appended in markup). */
|
|
26
|
+
readonly expandIntelligence: string;
|
|
27
|
+
/** Abbreviation for minutes used in the reading-time line (includes leading space for non-CJK locales). */
|
|
28
|
+
readonly minutesAbbr: string;
|
|
29
|
+
/** Aria-label for the reading-time estimate paragraph. */
|
|
30
|
+
readonly readingTimeAria: string;
|
|
31
|
+
/** Aria-label prefix for the TOC layer badge (rendered as e.g. "Layer L1"). */
|
|
32
|
+
readonly layerBadge: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Localised progressive-disclosure strings across all 14 supported
|
|
36
|
+
* languages. Reused by the layer wrappers, the reading-time line, and the
|
|
37
|
+
* TOC layer badge so the layer vocabulary is translated only once.
|
|
38
|
+
*/
|
|
39
|
+
export declare const PROGRESSIVE_DISCLOSURE_LABELS: LanguageMap<ProgressiveDisclosureStrings>;
|
|
40
|
+
//# sourceMappingURL=progressive-disclosure.d.ts.map
|