@reqlan/analytical 0.4.0 → 0.5.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/README.md +164 -0
- package/README.template.md +33 -0
- package/out/analysis-api.d.ts +8 -0
- package/out/analysis-api.js +21 -0
- package/out/analysis-api.js.map +1 -1
- package/out/core/application-memory.d.ts +11 -0
- package/out/core/application-memory.js +26 -0
- package/out/core/application-memory.js.map +1 -0
- package/out/create-runtime.d.ts +1 -0
- package/out/create-runtime.js +2 -3
- package/out/create-runtime.js.map +1 -1
- package/out/export/build-export-snapshot.js +52 -30
- package/out/export/build-export-snapshot.js.map +1 -1
- package/out/export/export-html.d.ts +4 -0
- package/out/export/export-html.js +8 -0
- package/out/export/export-html.js.map +1 -0
- package/out/export/html-export-assets.d.ts +2 -2
- package/out/export/html-export-assets.js +187 -17
- package/out/export/html-export-assets.js.map +1 -1
- package/out/export/html-export-template.d.ts +2 -1
- package/out/export/html-export-template.js +230 -120
- package/out/export/html-export-template.js.map +1 -1
- package/out/export/html-export-utils.d.ts +13 -2
- package/out/export/html-export-utils.js +167 -52
- package/out/export/html-export-utils.js.map +1 -1
- package/out/export/secret-rq.d.ts +2 -0
- package/out/export/secret-rq.js +9 -0
- package/out/export/secret-rq.js.map +1 -0
- package/out/export/types.d.ts +25 -0
- package/out/export/write-html-export.js +7 -1
- package/out/export/write-html-export.js.map +1 -1
- package/out/headless-index-service.js +2 -1
- package/out/headless-index-service.js.map +1 -1
- package/out/index-store/idea-extractor.js +42 -7
- package/out/index-store/idea-extractor.js.map +1 -1
- package/out/index-store/webview-graph-queries.d.ts +12 -0
- package/out/index-store/webview-graph-queries.js +37 -3
- package/out/index-store/webview-graph-queries.js.map +1 -1
- package/out/index.d.ts +5 -2
- package/out/index.js +4 -1
- package/out/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { escapeHtml, fileByIdea, filePageEnabled, formatAttributeValue, formatDate, hrefFor, ideaStatus, ideaTags, pageHref, relatedClusters, renderDefinitionList, renderIdeaSummaryHtml, renderMetric, renderOptionalStatusCell, renderOptionalStatusParagraph, renderOptionalTagsCell, renderOptionalTagsParagraph, resolveExportFilePage, slugAttributeKey, stringifyJson } from './html-export-utils.js';
|
|
1
|
+
import { escapeHtml, fileByIdea, filePageEnabled, formatAttributeValue, formatDate, hrefFor, ideaStatus, ideaTags, pageHref, relatedClusters, renderAttributeValueHtml, renderDefinitionList, renderIdeaSummaryHtml, renderMetric, renderOptionalStatusCell, renderOptionalStatusParagraph, renderOptionalTagsCell, renderOptionalTagsParagraph, renderTextWithRefsHtml, resolveExportFilePage, slugAttributeKey, stringifyJson } from './html-export-utils.js';
|
|
2
|
+
function exportHref(snapshot, currentPath, targetPath) {
|
|
3
|
+
return hrefFor(currentPath, targetPath, snapshot.urlBase);
|
|
4
|
+
}
|
|
5
|
+
function exportPageHref(snapshot, currentPath, pageInfo) {
|
|
6
|
+
return pageHref(currentPath, pageInfo, snapshot.urlBase);
|
|
7
|
+
}
|
|
2
8
|
export function renderHomePage(snapshot) {
|
|
3
9
|
const currentPath = snapshot.manifest.home.path;
|
|
4
10
|
const highlightClusters = snapshot.clusters.slice(0, 8).map(cluster => `
|
|
5
|
-
<a class="entity-card" href="${escapeHtml(
|
|
11
|
+
<a class="entity-card" href="${escapeHtml(exportPageHref(snapshot, currentPath, cluster.page))}">
|
|
6
12
|
<div class="pill-row">
|
|
7
13
|
<span class="pill">${escapeHtml(cluster.kind)}</span>
|
|
8
14
|
<span class="pill">${cluster.counts.ideas} ideas</span>
|
|
@@ -27,29 +33,29 @@ export function renderHomePage(snapshot) {
|
|
|
27
33
|
</div>
|
|
28
34
|
</header>
|
|
29
35
|
<section class="grid">
|
|
30
|
-
${renderMetric('Ideas', String(snapshot.counts.ideas),
|
|
31
|
-
${renderMetric('References', String(snapshot.counts.edges),
|
|
32
|
-
${renderMetric('Files', String(snapshot.counts.files),
|
|
33
|
-
${renderMetric('Code files', String(snapshot.codeFiles.length),
|
|
34
|
-
${renderMetric('Clusters', String(snapshot.counts.clusters),
|
|
35
|
-
${renderMetric('Attributes', String(snapshot.attributes.length),
|
|
36
|
+
${renderMetric('Ideas', String(snapshot.counts.ideas), exportPageHref(snapshot, currentPath, snapshot.manifest.ideasIndex))}
|
|
37
|
+
${renderMetric('References', String(snapshot.counts.edges), exportPageHref(snapshot, currentPath, snapshot.manifest.graph))}
|
|
38
|
+
${renderMetric('Files', String(snapshot.counts.files), exportPageHref(snapshot, currentPath, snapshot.manifest.filesIndex))}
|
|
39
|
+
${renderMetric('Code files', String(snapshot.codeFiles.length), exportPageHref(snapshot, currentPath, snapshot.manifest.codeFilesIndex))}
|
|
40
|
+
${renderMetric('Clusters', String(snapshot.counts.clusters), exportPageHref(snapshot, currentPath, snapshot.manifest.clustersIndex))}
|
|
41
|
+
${renderMetric('Attributes', String(snapshot.attributes.length), exportPageHref(snapshot, currentPath, snapshot.manifest.attributesIndex))}
|
|
36
42
|
</section>
|
|
37
43
|
<section class="split">
|
|
38
44
|
<div class="panel">
|
|
39
45
|
<h2>Status Rollup</h2>
|
|
40
|
-
|
|
46
|
+
<div class="scroll-window">${renderDefinitionList(snapshot.byStatus)}</div>
|
|
41
47
|
</div>
|
|
42
48
|
<div class="panel">
|
|
43
49
|
<h2>Tags</h2>
|
|
44
|
-
|
|
50
|
+
<div class="scroll-window">${renderDefinitionList(snapshot.byTag)}</div>
|
|
45
51
|
</div>
|
|
46
52
|
</section>
|
|
47
53
|
<section class="panel">
|
|
48
54
|
<div class="toolbar">
|
|
49
55
|
<h2>Highlighted Clusters</h2>
|
|
50
56
|
<div class="actions">
|
|
51
|
-
<a class="pill" href="${escapeHtml(
|
|
52
|
-
<a class="pill" href="${escapeHtml(
|
|
57
|
+
<a class="pill" href="${escapeHtml(exportPageHref(snapshot, currentPath, snapshot.manifest.printHome))}">Printable report</a>
|
|
58
|
+
<a class="pill" href="${escapeHtml(exportPageHref(snapshot, currentPath, snapshot.manifest.dataExport))}">Snapshot JSON</a>
|
|
53
59
|
</div>
|
|
54
60
|
</div>
|
|
55
61
|
<div class="entity-list">${highlightClusters}</div>
|
|
@@ -72,7 +78,7 @@ export function renderIdeasIndexPage(snapshot) {
|
|
|
72
78
|
<td>${escapeHtml(idea.fileUri)}</td>
|
|
73
79
|
<td>${renderOptionalStatusCell(idea)}</td>
|
|
74
80
|
<td>${renderOptionalTagsCell(idea)}</td>
|
|
75
|
-
<td>${
|
|
81
|
+
<td>${renderIdeaSummaryHtml(snapshot, currentPath, idea, '—')}</td>
|
|
76
82
|
<td>${idea.references.inbound.length}/${idea.references.outbound.length}</td>
|
|
77
83
|
</tr>
|
|
78
84
|
`).join('');
|
|
@@ -116,7 +122,7 @@ export function renderFilesIndexPage(snapshot) {
|
|
|
116
122
|
const currentPath = snapshot.manifest.filesIndex.path;
|
|
117
123
|
const rows = snapshot.files.map(file => `
|
|
118
124
|
<tr data-filter-row="files" data-filter-text="${escapeHtml([file.name, file.fileUri, Object.keys(file.tags).join(' ')].join(' '))}">
|
|
119
|
-
<td><strong>${snapshot.pageOptions.includeFilePages ? `<a href="${escapeHtml(
|
|
125
|
+
<td><strong>${snapshot.pageOptions.includeFilePages ? `<a href="${escapeHtml(exportPageHref(snapshot, currentPath, file.page))}">${escapeHtml(file.name)}</a>` : escapeHtml(file.name)}</strong></td>
|
|
120
126
|
<td>${escapeHtml(file.directory || '.')}</td>
|
|
121
127
|
<td>${file.ideas.length}</td>
|
|
122
128
|
<td>${file.edgeCount}</td>
|
|
@@ -156,7 +162,7 @@ export function renderCodeFilesIndexPage(snapshot) {
|
|
|
156
162
|
const currentPath = snapshot.manifest.codeFilesIndex.path;
|
|
157
163
|
const rows = snapshot.codeFiles.map(file => `
|
|
158
164
|
<tr data-filter-row="codeFiles" data-filter-text="${escapeHtml([file.name, file.fileUri, file.labels.join(' ')].join(' '))}">
|
|
159
|
-
<td><strong>${snapshot.pageOptions.includeCodeFilePages ? `<a href="${escapeHtml(
|
|
165
|
+
<td><strong>${snapshot.pageOptions.includeCodeFilePages ? `<a href="${escapeHtml(exportPageHref(snapshot, currentPath, file.page))}">${escapeHtml(file.name)}</a>` : escapeHtml(file.name)}</strong></td>
|
|
160
166
|
<td>${escapeHtml(file.directory || '.')}</td>
|
|
161
167
|
<td>${file.referencingIdeaIds.length}</td>
|
|
162
168
|
<td>${escapeHtml(file.labels.join(', ') || '—')}</td>
|
|
@@ -195,7 +201,7 @@ export function renderClustersIndexPage(snapshot) {
|
|
|
195
201
|
const currentPath = snapshot.manifest.clustersIndex.path;
|
|
196
202
|
const rows = snapshot.clusters.map(cluster => `
|
|
197
203
|
<tr data-filter-row="clusters" data-filter-text="${escapeHtml([cluster.label, cluster.kind, cluster.description].join(' '))}">
|
|
198
|
-
<td><strong>${snapshot.pageOptions.includeClusterPages ? `<a href="${escapeHtml(
|
|
204
|
+
<td><strong>${snapshot.pageOptions.includeClusterPages ? `<a href="${escapeHtml(exportPageHref(snapshot, currentPath, cluster.page))}">${escapeHtml(cluster.label)}</a>` : escapeHtml(cluster.label)}</strong></td>
|
|
199
205
|
<td>${escapeHtml(cluster.kind)}</td>
|
|
200
206
|
<td>${cluster.counts.ideas}</td>
|
|
201
207
|
<td>${cluster.counts.files}</td>
|
|
@@ -239,22 +245,15 @@ export function renderAttributesIndexPage(snapshot) {
|
|
|
239
245
|
.slice(0, 6)
|
|
240
246
|
.map(entry => `${entry.value} (${entry.count})`)
|
|
241
247
|
.join('; ');
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
return '';
|
|
246
|
-
}
|
|
247
|
-
return snapshot.pageOptions.includeIdeaPages
|
|
248
|
-
? `<a href="${escapeHtml(pageHref(currentPath, idea.page))}">${escapeHtml(idea.name)}</a>`
|
|
249
|
-
: escapeHtml(idea.name);
|
|
250
|
-
}).filter(Boolean).join(', ');
|
|
248
|
+
const keyCell = snapshot.pageOptions.includeAttributePages
|
|
249
|
+
? `<a href="${escapeHtml(exportPageHref(snapshot, currentPath, attribute.page))}">${escapeHtml(attribute.key)}</a>`
|
|
250
|
+
: escapeHtml(attribute.key);
|
|
251
251
|
return `
|
|
252
252
|
<tr id="${escapeHtml(anchor)}" data-filter-row="attributes" data-filter-text="${escapeHtml([attribute.key, valueSummary, attribute.ideaIds.join(' ')].join(' '))}">
|
|
253
|
-
<td><strong>${
|
|
253
|
+
<td><strong>${keyCell}</strong></td>
|
|
254
254
|
<td>${attribute.ideaCount}</td>
|
|
255
255
|
<td>${attribute.values.length}</td>
|
|
256
256
|
<td>${escapeHtml(valueSummary || '—')}</td>
|
|
257
|
-
<td>${ideaLinks || '—'}${attribute.ideaIds.length > 8 ? ` <span class="subtle">+${attribute.ideaIds.length - 8} more</span>` : ''}</td>
|
|
258
257
|
</tr>`;
|
|
259
258
|
}).join('');
|
|
260
259
|
return renderShell({
|
|
@@ -267,20 +266,103 @@ export function renderAttributesIndexPage(snapshot) {
|
|
|
267
266
|
<header class="page-header">
|
|
268
267
|
<p class="eyebrow">Attributes</p>
|
|
269
268
|
<h1>${escapeHtml(snapshot.title)}</h1>
|
|
270
|
-
<p class="subtle">Every attribute key in this export with values and
|
|
269
|
+
<p class="subtle">Every attribute key in this export with values and idea counts.</p>
|
|
271
270
|
</header>
|
|
272
271
|
<section class="table-shell">
|
|
273
272
|
<div class="toolbar">
|
|
274
273
|
<h2>Attributes</h2>
|
|
275
274
|
<div class="actions">
|
|
276
|
-
<input class="searchbar" type="search" placeholder="Filter attributes by key
|
|
275
|
+
<input class="searchbar" type="search" placeholder="Filter attributes by key or value" data-filter-input="attributes" />
|
|
277
276
|
</div>
|
|
278
277
|
</div>
|
|
279
278
|
<table>
|
|
280
279
|
<thead>
|
|
281
|
-
<tr><th>Attribute</th><th>Ideas</th><th>Values</th><th>Value summary</th
|
|
280
|
+
<tr><th>Attribute</th><th>Ideas</th><th>Values</th><th>Value summary</th></tr>
|
|
282
281
|
</thead>
|
|
283
|
-
<tbody>${rows || '<tr><td colspan="
|
|
282
|
+
<tbody>${rows || '<tr><td colspan="4" class="subtle">No attributes declared.</td></tr>'}</tbody>
|
|
283
|
+
</table>
|
|
284
|
+
</section>
|
|
285
|
+
`
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
export function renderAttributeDetailPage(snapshot, attribute) {
|
|
289
|
+
const currentPath = attribute.page.path;
|
|
290
|
+
const ideas = attribute.ideaIds
|
|
291
|
+
.map(ideaId => snapshot.ideasById[ideaId])
|
|
292
|
+
.filter((idea) => Boolean(idea));
|
|
293
|
+
const totalIdeas = attribute.ideaCount || 1;
|
|
294
|
+
const valueRows = attribute.values.map(entry => {
|
|
295
|
+
const percent = Math.round((entry.count / totalIdeas) * 1000) / 10;
|
|
296
|
+
const width = Math.max(entry.count > 0 ? 1.5 : 0, Math.min(100, (entry.count / totalIdeas) * 100));
|
|
297
|
+
const filterText = [entry.value, String(entry.count), `${percent}%`, ...entry.ideaIds].join(' ');
|
|
298
|
+
return `
|
|
299
|
+
<tr data-filter-row="attributeValues" data-filter-text="${escapeHtml(filterText)}">
|
|
300
|
+
<td>${escapeHtml(entry.value)}</td>
|
|
301
|
+
<td>
|
|
302
|
+
<div class="distribution-track" title="${escapeHtml(`${entry.count} of ${attribute.ideaCount} ideas (${percent}%)`)}" role="img" aria-label="${escapeHtml(`${entry.value}: ${entry.count} ideas, ${percent}%`)}">
|
|
303
|
+
<span class="distribution-fill" style="width: ${width}%"></span>
|
|
304
|
+
</div>
|
|
305
|
+
</td>
|
|
306
|
+
<td>${entry.count}</td>
|
|
307
|
+
<td>${percent}%</td>
|
|
308
|
+
</tr>`;
|
|
309
|
+
}).join('');
|
|
310
|
+
const ideaRows = ideas.map(idea => {
|
|
311
|
+
const value = formatAttributeValue(idea.attributes[attribute.key]) || '(empty)';
|
|
312
|
+
const filterText = [idea.name, value, ideaStatus(idea) ?? '', ideaTags(idea).join(' '), idea.summary].join(' ');
|
|
313
|
+
return `
|
|
314
|
+
<tr data-filter-row="attributeIdeas" data-filter-text="${escapeHtml(filterText)}">
|
|
315
|
+
<td>${renderIdeaAnchor(snapshot, currentPath, idea, idea.name)}</td>
|
|
316
|
+
<td>${escapeHtml(value)}</td>
|
|
317
|
+
<td>${renderOptionalStatusCell(idea)}</td>
|
|
318
|
+
<td>${renderOptionalTagsCell(idea)}</td>
|
|
319
|
+
<td>${escapeHtml(idea.summary)}</td>
|
|
320
|
+
</tr>`;
|
|
321
|
+
}).join('');
|
|
322
|
+
return renderShell({
|
|
323
|
+
currentPath,
|
|
324
|
+
activeNav: 'attributes',
|
|
325
|
+
title: `${snapshot.title} - ${attribute.key}`,
|
|
326
|
+
snapshot,
|
|
327
|
+
includeGlobalSearch: snapshot.runtimeMode === 'interactive',
|
|
328
|
+
breadcrumbs: [
|
|
329
|
+
{ label: 'Attributes', href: exportPageHref(snapshot, currentPath, snapshot.manifest.attributesIndex) },
|
|
330
|
+
{ label: attribute.key }
|
|
331
|
+
],
|
|
332
|
+
body: `
|
|
333
|
+
<header class="page-header">
|
|
334
|
+
<p class="eyebrow">Attribute detail</p>
|
|
335
|
+
<h1>${escapeHtml(attribute.key)}</h1>
|
|
336
|
+
<p class="subtle">${attribute.ideaCount} ideas · ${attribute.values.length} distinct values</p>
|
|
337
|
+
</header>
|
|
338
|
+
<section class="grid">
|
|
339
|
+
${renderMetric('Ideas', String(attribute.ideaCount))}
|
|
340
|
+
${renderMetric('Values', String(attribute.values.length))}
|
|
341
|
+
</section>
|
|
342
|
+
<section class="table-shell">
|
|
343
|
+
<div class="toolbar">
|
|
344
|
+
<h2>Value distribution</h2>
|
|
345
|
+
<div class="actions">
|
|
346
|
+
<input class="searchbar" type="search" placeholder="Filter values" data-filter-input="attributeValues" />
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
<div class="scroll-window">
|
|
350
|
+
<table>
|
|
351
|
+
<thead><tr><th>Value</th><th>Distribution</th><th>Ideas</th><th>Share</th></tr></thead>
|
|
352
|
+
<tbody>${valueRows || '<tr><td colspan="4" class="subtle">No values.</td></tr>'}</tbody>
|
|
353
|
+
</table>
|
|
354
|
+
</div>
|
|
355
|
+
</section>
|
|
356
|
+
<section class="table-shell">
|
|
357
|
+
<div class="toolbar">
|
|
358
|
+
<h2>Ideas with this attribute</h2>
|
|
359
|
+
<div class="actions">
|
|
360
|
+
<input class="searchbar" type="search" placeholder="Filter ideas by name, value, status, or tags" data-filter-input="attributeIdeas" />
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
<table>
|
|
364
|
+
<thead><tr><th>Idea</th><th>Value</th><th>Status</th><th>Tags</th><th>Summary</th></tr></thead>
|
|
365
|
+
<tbody>${ideaRows || '<tr><td colspan="5" class="subtle">No ideas declare this attribute.</td></tr>'}</tbody>
|
|
284
366
|
</table>
|
|
285
367
|
</section>
|
|
286
368
|
`
|
|
@@ -292,7 +374,7 @@ export function renderGraphPage(snapshot) {
|
|
|
292
374
|
const statuses = Object.keys(snapshot.byStatus).sort();
|
|
293
375
|
const tags = Object.keys(snapshot.byTag).sort();
|
|
294
376
|
const clusters = snapshot.clusters.slice(0, 16).map(cluster => snapshot.pageOptions.includeClusterPages
|
|
295
|
-
? `<a class="pill" href="${escapeHtml(
|
|
377
|
+
? `<a class="pill" href="${escapeHtml(exportPageHref(snapshot, currentPath, cluster.page))}">${escapeHtml(cluster.label)}</a>`
|
|
296
378
|
: `<span class="pill">${escapeHtml(cluster.label)}</span>`).join('');
|
|
297
379
|
return renderShell({
|
|
298
380
|
currentPath,
|
|
@@ -310,7 +392,7 @@ export function renderGraphPage(snapshot) {
|
|
|
310
392
|
<div class="toolbar">
|
|
311
393
|
<h2>Reference graph</h2>
|
|
312
394
|
<div class="actions">
|
|
313
|
-
<a class="pill" href="${escapeHtml(
|
|
395
|
+
<a class="pill" href="${escapeHtml(exportPageHref(snapshot, currentPath, snapshot.manifest.dataGraph))}">Graph JSON</a>
|
|
314
396
|
</div>
|
|
315
397
|
</div>
|
|
316
398
|
<div class="graph-controls-bar" data-graph-controls="graph-data">
|
|
@@ -325,6 +407,7 @@ export function renderGraphPage(snapshot) {
|
|
|
325
407
|
${tags.map(tag => `<option value="${escapeHtml(tag)}">${escapeHtml(tag)}</option>`).join('')}
|
|
326
408
|
</select>
|
|
327
409
|
<button type="button" class="graph-action" data-graph-toggle-external="true">Hide external</button>
|
|
410
|
+
<button type="button" class="graph-action" data-graph-toggle-ideasets="true">Hide ideasets</button>
|
|
328
411
|
<button type="button" class="graph-action" data-graph-toggle-physics aria-pressed="false">Live physics</button>
|
|
329
412
|
<button type="button" class="graph-action" data-graph-fit>Fit</button>
|
|
330
413
|
<button type="button" class="graph-action" data-graph-reset>Reset</button>
|
|
@@ -349,7 +432,7 @@ export function renderIdeaDetailPage(snapshot, idea) {
|
|
|
349
432
|
snapshot,
|
|
350
433
|
includeGlobalSearch: snapshot.runtimeMode === 'interactive',
|
|
351
434
|
breadcrumbs: [
|
|
352
|
-
{ label: 'Ideas', href:
|
|
435
|
+
{ label: 'Ideas', href: exportPageHref(snapshot, currentPath, snapshot.manifest.ideasIndex) },
|
|
353
436
|
{ label: idea.name }
|
|
354
437
|
],
|
|
355
438
|
body: `
|
|
@@ -373,9 +456,9 @@ export function renderIdeaDetailPage(snapshot, idea) {
|
|
|
373
456
|
<div class="panel print-break-avoid">
|
|
374
457
|
<h2>Navigation</h2>
|
|
375
458
|
<div class="entity-list">
|
|
376
|
-
${parentFile ? renderMaybeLinkedCard(snapshot.pageOptions.includeFilePages,
|
|
377
|
-
${clusters.map(cluster => renderMaybeLinkedCard(snapshot.pageOptions.includeClusterPages,
|
|
378
|
-
${idea.page.printablePath && snapshot.pageOptions.includePrintPages ? `<a class="entity-card" href="${escapeHtml(
|
|
459
|
+
${parentFile ? renderMaybeLinkedCard(snapshot.pageOptions.includeFilePages, exportPageHref(snapshot, currentPath, parentFile.page), 'Source file', parentFile.fileUri) : ''}
|
|
460
|
+
${clusters.map(cluster => renderMaybeLinkedCard(snapshot.pageOptions.includeClusterPages, exportPageHref(snapshot, currentPath, cluster.page), cluster.label, `${cluster.kind} cluster`)).join('')}
|
|
461
|
+
${idea.page.printablePath && snapshot.pageOptions.includePrintPages ? `<a class="entity-card" href="${escapeHtml(exportHref(snapshot, currentPath, idea.page.printablePath))}"><strong>Printable page</strong><p class="subtle">Static print-friendly idea sheet.</p></a>` : ''}
|
|
379
462
|
</div>
|
|
380
463
|
</div>
|
|
381
464
|
</section>
|
|
@@ -386,13 +469,15 @@ export function renderIdeaDetailPage(snapshot, idea) {
|
|
|
386
469
|
</div>
|
|
387
470
|
<div class="panel print-break-avoid">
|
|
388
471
|
<h2>Ancestor context</h2>
|
|
389
|
-
<div class="
|
|
390
|
-
|
|
472
|
+
<div class="scroll-window">
|
|
473
|
+
<div class="entity-list">
|
|
474
|
+
${idea.ancestors.ancestors.map(ancestor => {
|
|
391
475
|
const linked = snapshot.ideasById[ancestor.id];
|
|
392
476
|
return linked
|
|
393
|
-
? renderMaybeLinkedCard(snapshot.pageOptions.includeIdeaPages,
|
|
477
|
+
? renderMaybeLinkedCard(snapshot.pageOptions.includeIdeaPages, exportPageHref(snapshot, currentPath, linked.page), ancestor.name, ancestor.summary)
|
|
394
478
|
: '';
|
|
395
479
|
}).join('') || '<p class="subtle">No ancestor chain recorded.</p>'}
|
|
480
|
+
</div>
|
|
396
481
|
</div>
|
|
397
482
|
</div>
|
|
398
483
|
</section>
|
|
@@ -423,7 +508,7 @@ export function renderFileDetailPage(snapshot, file) {
|
|
|
423
508
|
snapshot,
|
|
424
509
|
includeGlobalSearch: snapshot.runtimeMode === 'interactive',
|
|
425
510
|
breadcrumbs: [
|
|
426
|
-
{ label: 'Files', href:
|
|
511
|
+
{ label: 'Files', href: exportPageHref(snapshot, currentPath, snapshot.manifest.filesIndex) },
|
|
427
512
|
{ label: file.name }
|
|
428
513
|
],
|
|
429
514
|
body: `
|
|
@@ -446,24 +531,28 @@ export function renderFileDetailPage(snapshot, file) {
|
|
|
446
531
|
<input class="searchbar" type="search" placeholder="Filter file ideas" data-filter-input="fileIdeas" />
|
|
447
532
|
</div>
|
|
448
533
|
</div>
|
|
449
|
-
<
|
|
450
|
-
<
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
<
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
534
|
+
<div class="scroll-window">
|
|
535
|
+
<table>
|
|
536
|
+
<thead><tr><th>Idea</th><th>Status</th><th>Tags</th><th>Summary</th></tr></thead>
|
|
537
|
+
<tbody>
|
|
538
|
+
${file.ideas.map(idea => `
|
|
539
|
+
<tr data-filter-row="fileIdeas" data-filter-text="${escapeHtml([idea.name, idea.summary, ideaStatus(idea) ?? '', ideaTags(idea).join(' ')].join(' '))}">
|
|
540
|
+
<td>${renderIdeaAnchor(snapshot, currentPath, idea, idea.name)}</td>
|
|
541
|
+
<td>${renderOptionalStatusCell(idea)}</td>
|
|
542
|
+
<td>${renderOptionalTagsCell(idea)}</td>
|
|
543
|
+
<td>${escapeHtml(idea.summary)}</td>
|
|
544
|
+
</tr>
|
|
545
|
+
`).join('')}
|
|
546
|
+
</tbody>
|
|
547
|
+
</table>
|
|
548
|
+
</div>
|
|
462
549
|
</div>
|
|
463
550
|
<div class="panel">
|
|
464
551
|
<h2>Related clusters</h2>
|
|
465
|
-
<div class="
|
|
466
|
-
|
|
552
|
+
<div class="scroll-window">
|
|
553
|
+
<div class="entity-list">
|
|
554
|
+
${clusterLinks.map(cluster => renderMaybeLinkedCard(snapshot.pageOptions.includeClusterPages, exportPageHref(snapshot, currentPath, cluster.page), cluster.label, cluster.kind)).join('') || '<p class="subtle">No related clusters.</p>'}
|
|
555
|
+
</div>
|
|
467
556
|
</div>
|
|
468
557
|
</div>
|
|
469
558
|
</section>
|
|
@@ -492,7 +581,7 @@ export function renderCodeFileDetailPage(snapshot, file) {
|
|
|
492
581
|
snapshot,
|
|
493
582
|
includeGlobalSearch: snapshot.runtimeMode === 'interactive',
|
|
494
583
|
breadcrumbs: [
|
|
495
|
-
{ label: 'Code files', href:
|
|
584
|
+
{ label: 'Code files', href: exportPageHref(snapshot, currentPath, snapshot.manifest.codeFilesIndex) },
|
|
496
585
|
{ label: file.name }
|
|
497
586
|
],
|
|
498
587
|
body: `
|
|
@@ -512,19 +601,21 @@ export function renderCodeFileDetailPage(snapshot, file) {
|
|
|
512
601
|
<input class="searchbar" type="search" placeholder="Filter referencing ideas" data-filter-input="codeFileIdeas" />
|
|
513
602
|
</div>
|
|
514
603
|
</div>
|
|
515
|
-
<
|
|
516
|
-
<
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
<
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
604
|
+
<div class="scroll-window">
|
|
605
|
+
<table>
|
|
606
|
+
<thead><tr><th>Idea</th><th>Status</th><th>Tags</th><th>Summary</th></tr></thead>
|
|
607
|
+
<tbody>
|
|
608
|
+
${referencingIdeas.map(idea => `
|
|
609
|
+
<tr data-filter-row="codeFileIdeas" data-filter-text="${escapeHtml([idea.name, idea.summary, ideaStatus(idea) ?? '', ideaTags(idea).join(' ')].join(' '))}">
|
|
610
|
+
<td>${renderIdeaAnchor(snapshot, currentPath, idea, idea.name)}</td>
|
|
611
|
+
<td>${renderOptionalStatusCell(idea)}</td>
|
|
612
|
+
<td>${renderOptionalTagsCell(idea)}</td>
|
|
613
|
+
<td>${escapeHtml(idea.summary)}</td>
|
|
614
|
+
</tr>
|
|
615
|
+
`).join('') || '<tr><td colspan="4" class="subtle">No referencing ideas.</td></tr>'}
|
|
616
|
+
</tbody>
|
|
617
|
+
</table>
|
|
618
|
+
</div>
|
|
528
619
|
</section>
|
|
529
620
|
${file.labels.length > 0 ? `
|
|
530
621
|
<section class="panel">
|
|
@@ -548,7 +639,7 @@ export function renderClusterDetailPage(snapshot, cluster) {
|
|
|
548
639
|
snapshot,
|
|
549
640
|
includeGlobalSearch: snapshot.runtimeMode === 'interactive',
|
|
550
641
|
breadcrumbs: [
|
|
551
|
-
{ label: 'Clusters', href:
|
|
642
|
+
{ label: 'Clusters', href: exportPageHref(snapshot, currentPath, snapshot.manifest.clustersIndex) },
|
|
552
643
|
{ label: cluster.label }
|
|
553
644
|
],
|
|
554
645
|
body: `
|
|
@@ -569,19 +660,21 @@ export function renderClusterDetailPage(snapshot, cluster) {
|
|
|
569
660
|
<input class="searchbar" type="search" placeholder="Filter cluster members" data-filter-input="clusterIdeas" />
|
|
570
661
|
</div>
|
|
571
662
|
</div>
|
|
572
|
-
<
|
|
573
|
-
<
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
<
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
663
|
+
<div class="scroll-window">
|
|
664
|
+
<table>
|
|
665
|
+
<thead><tr><th>Idea</th><th>File</th><th>Status</th><th>Summary</th></tr></thead>
|
|
666
|
+
<tbody>
|
|
667
|
+
${members.map(idea => `
|
|
668
|
+
<tr data-filter-row="clusterIdeas" data-filter-text="${escapeHtml([idea.name, idea.fileUri, ideaStatus(idea) ?? '', idea.summary].join(' '))}">
|
|
669
|
+
<td>${renderIdeaAnchor(snapshot, currentPath, idea, idea.name)}</td>
|
|
670
|
+
<td>${escapeHtml(idea.fileUri)}</td>
|
|
671
|
+
<td>${renderOptionalStatusCell(idea)}</td>
|
|
672
|
+
<td>${escapeHtml(idea.summary)}</td>
|
|
673
|
+
</tr>
|
|
674
|
+
`).join('')}
|
|
675
|
+
</tbody>
|
|
676
|
+
</table>
|
|
677
|
+
</div>
|
|
585
678
|
</section>
|
|
586
679
|
<section class="graph-shell print-break-avoid">
|
|
587
680
|
<div class="toolbar"><h2>Cluster graph</h2></div>
|
|
@@ -600,7 +693,7 @@ export function renderPrintHomePage(snapshot) {
|
|
|
600
693
|
const currentPath = snapshot.manifest.printHome.path;
|
|
601
694
|
const cards = snapshot.ideas.map(idea => `
|
|
602
695
|
<article class="print-card print-break-avoid">
|
|
603
|
-
<h3>${snapshot.pageOptions.includeIdeaPages ? `<a href="${escapeHtml(
|
|
696
|
+
<h3>${snapshot.pageOptions.includeIdeaPages ? `<a href="${escapeHtml(exportHref(snapshot, currentPath, idea.page.path))}">${escapeHtml(idea.name)}</a>` : escapeHtml(idea.name)}</h3>
|
|
604
697
|
<p class="subtle">${escapeHtml(idea.fileUri)}</p>
|
|
605
698
|
<p>${renderIdeaSummaryHtml(snapshot, currentPath, idea, '—')}</p>
|
|
606
699
|
${renderOptionalStatusParagraph(idea)}
|
|
@@ -626,7 +719,7 @@ export function renderPrintHomePage(snapshot) {
|
|
|
626
719
|
</section>
|
|
627
720
|
<section class="panel print-break-avoid">
|
|
628
721
|
<h2>Status rollup</h2>
|
|
629
|
-
|
|
722
|
+
<div class="scroll-window">${renderDefinitionList(snapshot.byStatus)}</div>
|
|
630
723
|
</section>
|
|
631
724
|
<section class="entity-list">${cards}</section>
|
|
632
725
|
`
|
|
@@ -649,7 +742,7 @@ export function renderPrintIdeaPage(snapshot, idea) {
|
|
|
649
742
|
<p class="idea-summary">${renderIdeaSummaryHtml(snapshot, currentPath, idea)}</p>
|
|
650
743
|
${renderOptionalStatusParagraph(idea)}
|
|
651
744
|
${renderOptionalTagsParagraph(idea)}
|
|
652
|
-
${snapshot.pageOptions.includeIdeaPages ? `<p><strong>Interactive page:</strong> <a href="${escapeHtml(
|
|
745
|
+
${snapshot.pageOptions.includeIdeaPages ? `<p><strong>Interactive page:</strong> <a href="${escapeHtml(exportHref(snapshot, currentPath, idea.page.path))}">${escapeHtml(idea.name)}</a></p>` : ''}
|
|
653
746
|
</section>
|
|
654
747
|
`
|
|
655
748
|
});
|
|
@@ -673,7 +766,7 @@ export function renderPrintFilePage(snapshot, file) {
|
|
|
673
766
|
<tbody>
|
|
674
767
|
${file.ideas.map(idea => `
|
|
675
768
|
<tr>
|
|
676
|
-
<td>${snapshot.pageOptions.includeIdeaPages ? `<a href="${escapeHtml(
|
|
769
|
+
<td>${snapshot.pageOptions.includeIdeaPages ? `<a href="${escapeHtml(exportHref(snapshot, currentPath, idea.page.path))}">${escapeHtml(idea.name)}</a>` : escapeHtml(idea.name)}</td>
|
|
677
770
|
<td>${renderOptionalStatusCell(idea)}</td>
|
|
678
771
|
<td>${renderOptionalTagsCell(idea)}</td>
|
|
679
772
|
<td>${escapeHtml(idea.summary)}</td>
|
|
@@ -707,7 +800,7 @@ export function renderPrintCodeFilePage(snapshot, file) {
|
|
|
707
800
|
<tbody>
|
|
708
801
|
${referencingIdeas.map(idea => `
|
|
709
802
|
<tr>
|
|
710
|
-
<td>${snapshot.pageOptions.includeIdeaPages ? `<a href="${escapeHtml(
|
|
803
|
+
<td>${snapshot.pageOptions.includeIdeaPages ? `<a href="${escapeHtml(exportHref(snapshot, currentPath, idea.page.path))}">${escapeHtml(idea.name)}</a>` : escapeHtml(idea.name)}</td>
|
|
711
804
|
<td>${renderOptionalStatusCell(idea)}</td>
|
|
712
805
|
<td>${renderOptionalTagsCell(idea)}</td>
|
|
713
806
|
<td>${escapeHtml(idea.summary)}</td>
|
|
@@ -741,7 +834,7 @@ export function renderPrintClusterPage(snapshot, cluster) {
|
|
|
741
834
|
<tbody>
|
|
742
835
|
${members.map(idea => `
|
|
743
836
|
<tr>
|
|
744
|
-
<td>${snapshot.pageOptions.includeIdeaPages ? `<a href="${escapeHtml(
|
|
837
|
+
<td>${snapshot.pageOptions.includeIdeaPages ? `<a href="${escapeHtml(exportHref(snapshot, currentPath, idea.page.path))}">${escapeHtml(idea.name)}</a>` : escapeHtml(idea.name)}</td>
|
|
745
838
|
<td>${escapeHtml(idea.fileUri)}</td>
|
|
746
839
|
<td>${renderOptionalStatusCell(idea)}</td>
|
|
747
840
|
<td>${escapeHtml(idea.summary)}</td>
|
|
@@ -757,10 +850,11 @@ function renderReferenceSection(snapshot, currentPath, id, title, rows) {
|
|
|
757
850
|
return `
|
|
758
851
|
<section class="table-shell print-break-avoid" id="${escapeHtml(id)}">
|
|
759
852
|
<div class="toolbar"><h2>${escapeHtml(title)}</h2></div>
|
|
760
|
-
<
|
|
761
|
-
<
|
|
762
|
-
|
|
763
|
-
|
|
853
|
+
<div class="scroll-window">
|
|
854
|
+
<table>
|
|
855
|
+
<thead><tr><th>Kind</th><th>Idea</th><th>Path</th><th>Snippet</th></tr></thead>
|
|
856
|
+
<tbody>
|
|
857
|
+
${rows.map(row => {
|
|
764
858
|
const linked = row.direction === 'inbound'
|
|
765
859
|
? snapshot.ideasById[row.sourceIdeaId]
|
|
766
860
|
: (row.targetIdeaId ? snapshot.ideasById[row.targetIdeaId] : undefined);
|
|
@@ -769,19 +863,27 @@ function renderReferenceSection(snapshot, currentPath, id, title, rows) {
|
|
|
769
863
|
? resolveExportFilePage(snapshot, { fileUri: row.targetPath })
|
|
770
864
|
: undefined;
|
|
771
865
|
const pathCell = fileTarget && filePageEnabled(snapshot, fileTarget.kind)
|
|
772
|
-
? `<a href="${escapeHtml(
|
|
866
|
+
? `<a href="${escapeHtml(exportPageHref(snapshot, currentPath, fileTarget.page))}">${escapeHtml(row.targetPath)}</a>`
|
|
773
867
|
: escapeHtml(row.targetPath);
|
|
868
|
+
const ideaCell = linked
|
|
869
|
+
? renderIdeaAnchor(snapshot, currentPath, linked, linked.name)
|
|
870
|
+
: (fileTarget && filePageEnabled(snapshot, fileTarget.kind)
|
|
871
|
+
? `<a class="idea-ref idea-ref--file" href="${escapeHtml(exportPageHref(snapshot, currentPath, fileTarget.page))}">${escapeHtml(label)}</a>`
|
|
872
|
+
: escapeHtml(label));
|
|
873
|
+
const snippetCell = row.snippet
|
|
874
|
+
? renderTextWithRefsHtml(snapshot, currentPath, row.snippet, { idea: snapshot.ideasById[row.sourceIdeaId] })
|
|
875
|
+
: '—';
|
|
774
876
|
return `
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
</
|
|
784
|
-
</
|
|
877
|
+
<tr>
|
|
878
|
+
<td>${escapeHtml(row.kind)}</td>
|
|
879
|
+
<td>${ideaCell}</td>
|
|
880
|
+
<td>${pathCell}</td>
|
|
881
|
+
<td>${snippetCell}</td>
|
|
882
|
+
</tr>`;
|
|
883
|
+
}).join('') || '<tr><td colspan="4" class="subtle">None</td></tr>'}
|
|
884
|
+
</tbody>
|
|
885
|
+
</table>
|
|
886
|
+
</div>
|
|
785
887
|
</section>
|
|
786
888
|
`;
|
|
787
889
|
}
|
|
@@ -843,27 +945,35 @@ function renderIdeaAttributes(snapshot, currentPath, idea) {
|
|
|
843
945
|
return '<p class="subtle">No attributes declared.</p>';
|
|
844
946
|
}
|
|
845
947
|
const rows = entries.map(([key, value]) => {
|
|
846
|
-
const
|
|
948
|
+
const attribute = snapshot.attributesByKey[key];
|
|
949
|
+
const href = attribute && snapshot.pageOptions.includeAttributePages
|
|
950
|
+
? exportPageHref(snapshot, currentPath, attribute.page)
|
|
951
|
+
: `${exportPageHref(snapshot, currentPath, snapshot.manifest.attributesIndex)}#attr-${slugAttributeKey(key)}`;
|
|
847
952
|
return `
|
|
848
953
|
<tr>
|
|
849
954
|
<td><a href="${escapeHtml(href)}">${escapeHtml(key)}</a></td>
|
|
850
|
-
<td>${
|
|
955
|
+
<td>${renderAttributeValueHtml(snapshot, currentPath, value, idea)}</td>
|
|
851
956
|
</tr>`;
|
|
852
957
|
}).join('');
|
|
853
958
|
return `
|
|
854
|
-
<
|
|
855
|
-
<
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
959
|
+
<div class="scroll-window">
|
|
960
|
+
<table>
|
|
961
|
+
<thead><tr><th>Key</th><th>Value</th></tr></thead>
|
|
962
|
+
<tbody>${rows}</tbody>
|
|
963
|
+
</table>
|
|
964
|
+
</div>
|
|
965
|
+
<p class="subtle"><a href="${escapeHtml(exportPageHref(snapshot, currentPath, snapshot.manifest.attributesIndex))}">Browse all attributes</a></p>
|
|
859
966
|
`;
|
|
860
967
|
}
|
|
861
968
|
function renderShell(options) {
|
|
862
|
-
const stylesheetHref =
|
|
863
|
-
const appHref =
|
|
864
|
-
const searchIndexScriptHref =
|
|
865
|
-
const searchHref =
|
|
969
|
+
const stylesheetHref = exportHref(options.snapshot, options.currentPath, 'assets/styles.css');
|
|
970
|
+
const appHref = exportHref(options.snapshot, options.currentPath, 'assets/app.js');
|
|
971
|
+
const searchIndexScriptHref = exportHref(options.snapshot, options.currentPath, 'assets/search-index.js');
|
|
972
|
+
const searchHref = exportHref(options.snapshot, options.currentPath, options.snapshot.manifest.dataSearch.path);
|
|
866
973
|
const includeGlobalSearch = options.includeGlobalSearch && options.snapshot.runtimeMode === 'interactive';
|
|
974
|
+
const headerLink = options.snapshot.headerLink
|
|
975
|
+
? `<a class="brand-link" href="${escapeHtml(options.snapshot.headerLink.href)}">${escapeHtml(options.snapshot.headerLink.label)}</a>`
|
|
976
|
+
: '';
|
|
867
977
|
const navLinks = [
|
|
868
978
|
navLink(options, 'overview', options.snapshot.manifest.home, true),
|
|
869
979
|
navLink(options, 'ideas', options.snapshot.manifest.ideasIndex, true),
|
|
@@ -890,11 +1000,11 @@ function renderShell(options) {
|
|
|
890
1000
|
<title>${escapeHtml(options.title)}</title>
|
|
891
1001
|
<link rel="stylesheet" href="${escapeHtml(stylesheetHref)}" />
|
|
892
1002
|
</head>
|
|
893
|
-
<body data-search-index="${escapeHtml(searchHref)}">
|
|
1003
|
+
<body data-runtime-mode="${escapeHtml(options.snapshot.runtimeMode)}" data-search-index="${escapeHtml(searchHref)}">
|
|
894
1004
|
<main class="layout">
|
|
895
1005
|
<header class="topbar">
|
|
896
1006
|
<div class="topbar-inner">
|
|
897
|
-
<div class="nav">${navLinks}</div>
|
|
1007
|
+
<div class="nav">${headerLink}${navLinks}</div>
|
|
898
1008
|
${includeGlobalSearch ? `
|
|
899
1009
|
<div>
|
|
900
1010
|
<input class="searchbar" type="search" placeholder="Search ideas, files, clusters, tags, statuses" data-global-search />
|
|
@@ -913,7 +1023,7 @@ function navLink(options, navId, page, enabled) {
|
|
|
913
1023
|
if (!enabled) {
|
|
914
1024
|
return '';
|
|
915
1025
|
}
|
|
916
|
-
const href =
|
|
1026
|
+
const href = exportPageHref(options.snapshot, options.currentPath, page);
|
|
917
1027
|
const activeClass = options.activeNav === navId ? 'active' : '';
|
|
918
1028
|
return `<a class="${activeClass}" href="${escapeHtml(href)}">${escapeHtml(page.title)}</a>`;
|
|
919
1029
|
}
|
|
@@ -924,7 +1034,7 @@ function renderMaybeLinkedCard(enabled, href, title, subtitle) {
|
|
|
924
1034
|
}
|
|
925
1035
|
function renderIdeaAnchor(snapshot, currentPath, idea, label) {
|
|
926
1036
|
return snapshot.pageOptions.includeIdeaPages
|
|
927
|
-
? `<a href="${escapeHtml(
|
|
1037
|
+
? `<a href="${escapeHtml(exportPageHref(snapshot, currentPath, idea.page))}">${escapeHtml(label)}</a>`
|
|
928
1038
|
: escapeHtml(label);
|
|
929
1039
|
}
|
|
930
1040
|
//# sourceMappingURL=html-export-template.js.map
|