@reqlan/analytical 0.4.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/out/analysis/analyser-registry.d.ts +21 -0
- package/out/analysis/analyser-registry.js +33 -0
- package/out/analysis/analyser-registry.js.map +1 -0
- package/out/analysis/completion-tracking-analyser.d.ts +3 -0
- package/out/analysis/completion-tracking-analyser.js +37 -0
- package/out/analysis/completion-tracking-analyser.js.map +1 -0
- package/out/analysis/deprecation-impact-analyser.d.ts +3 -0
- package/out/analysis/deprecation-impact-analyser.js +21 -0
- package/out/analysis/deprecation-impact-analyser.js.map +1 -0
- package/out/analysis/file-related-analyser.d.ts +5 -0
- package/out/analysis/file-related-analyser.js +41 -0
- package/out/analysis/file-related-analyser.js.map +1 -0
- package/out/analysis/git-dates-analyser.d.ts +9 -0
- package/out/analysis/git-dates-analyser.js +39 -0
- package/out/analysis/git-dates-analyser.js.map +1 -0
- package/out/analysis/list-ideas-analyser.d.ts +3 -0
- package/out/analysis/list-ideas-analyser.js +7 -0
- package/out/analysis/list-ideas-analyser.js.map +1 -0
- package/out/analysis/local-graph-analyser.d.ts +6 -0
- package/out/analysis/local-graph-analyser.js +42 -0
- package/out/analysis/local-graph-analyser.js.map +1 -0
- package/out/analysis/semantic-search-analyser.d.ts +8 -0
- package/out/analysis/semantic-search-analyser.js +51 -0
- package/out/analysis/semantic-search-analyser.js.map +1 -0
- package/out/analysis-api.d.ts +31 -0
- package/out/analysis-api.js +136 -0
- package/out/analysis-api.js.map +1 -0
- package/out/core/analytical-store.d.ts +78 -0
- package/out/core/analytical-store.js +141 -0
- package/out/core/analytical-store.js.map +1 -0
- package/out/core/context-model.d.ts +187 -0
- package/out/core/context-model.js +36 -0
- package/out/core/context-model.js.map +1 -0
- package/out/core/context-signals.d.ts +149 -0
- package/out/core/context-signals.js +397 -0
- package/out/core/context-signals.js.map +1 -0
- package/out/core/file-path-match.d.ts +7 -0
- package/out/core/file-path-match.js +39 -0
- package/out/core/file-path-match.js.map +1 -0
- package/out/core/file-reference-resolve.d.ts +8 -0
- package/out/core/file-reference-resolve.js +28 -0
- package/out/core/file-reference-resolve.js.map +1 -0
- package/out/core/idea-references.d.ts +13 -0
- package/out/core/idea-references.js +25 -0
- package/out/core/idea-references.js.map +1 -0
- package/out/core/index-error.d.ts +23 -0
- package/out/core/index-error.js +33 -0
- package/out/core/index-error.js.map +1 -0
- package/out/core/types.d.ts +172 -0
- package/out/core/types.js +48 -0
- package/out/core/types.js.map +1 -0
- package/out/core/workspace-paths.d.ts +4 -0
- package/out/core/workspace-paths.js +92 -0
- package/out/core/workspace-paths.js.map +1 -0
- package/out/create-runtime.d.ts +21 -0
- package/out/create-runtime.js +39 -0
- package/out/create-runtime.js.map +1 -0
- package/out/export/build-export-snapshot.d.ts +3 -0
- package/out/export/build-export-snapshot.js +573 -0
- package/out/export/build-export-snapshot.js.map +1 -0
- package/out/export/html-export-assets.d.ts +3 -0
- package/out/export/html-export-assets.js +1066 -0
- package/out/export/html-export-assets.js.map +1 -0
- package/out/export/html-export-template.d.ts +17 -0
- package/out/export/html-export-template.js +930 -0
- package/out/export/html-export-template.js.map +1 -0
- package/out/export/html-export-utils.d.ts +45 -0
- package/out/export/html-export-utils.js +262 -0
- package/out/export/html-export-utils.js.map +1 -0
- package/out/export/types.d.ts +187 -0
- package/out/export/types.js +2 -0
- package/out/export/types.js.map +1 -0
- package/out/export/write-html-export.d.ts +2 -0
- package/out/export/write-html-export.js +143 -0
- package/out/export/write-html-export.js.map +1 -0
- package/out/headless-index-service.d.ts +23 -0
- package/out/headless-index-service.js +171 -0
- package/out/headless-index-service.js.map +1 -0
- package/out/index-store/idea-extractor.d.ts +6 -0
- package/out/index-store/idea-extractor.js +271 -0
- package/out/index-store/idea-extractor.js.map +1 -0
- package/out/index-store/schema.d.ts +10 -0
- package/out/index-store/schema.js +54 -0
- package/out/index-store/schema.js.map +1 -0
- package/out/index-store/sqlite-store.d.ts +66 -0
- package/out/index-store/sqlite-store.js +964 -0
- package/out/index-store/sqlite-store.js.map +1 -0
- package/out/index-store/webview-graph-queries.d.ts +62 -0
- package/out/index-store/webview-graph-queries.js +256 -0
- package/out/index-store/webview-graph-queries.js.map +1 -0
- package/out/index-store/webview-table-queries.d.ts +57 -0
- package/out/index-store/webview-table-queries.js +199 -0
- package/out/index-store/webview-table-queries.js.map +1 -0
- package/out/index.d.ts +40 -0
- package/out/index.js +27 -0
- package/out/index.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1066 @@
|
|
|
1
|
+
export const SHARED_STYLES = `
|
|
2
|
+
:root {
|
|
3
|
+
color-scheme: dark;
|
|
4
|
+
--bg: #14100e;
|
|
5
|
+
--bg-elev: #1f1815;
|
|
6
|
+
--bg-soft: #1f1815;
|
|
7
|
+
--bg-raised: #2a211d;
|
|
8
|
+
--fg: #ebe4de;
|
|
9
|
+
--muted: #a89488;
|
|
10
|
+
--faint: #6e5f56;
|
|
11
|
+
--line: #3d2f28;
|
|
12
|
+
--accent: #07a0e5;
|
|
13
|
+
--accent-strong: #0bbefb;
|
|
14
|
+
--accent-dim: #0371c1;
|
|
15
|
+
--rust: #b85c38;
|
|
16
|
+
--rust-muted: #8a4530;
|
|
17
|
+
--good: #4ade80;
|
|
18
|
+
--warn: #fbbf24;
|
|
19
|
+
--bad: #fb7185;
|
|
20
|
+
font-family: Inter, system-ui, sans-serif;
|
|
21
|
+
}
|
|
22
|
+
* { box-sizing: border-box; }
|
|
23
|
+
body {
|
|
24
|
+
margin: 0;
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--fg);
|
|
27
|
+
line-height: 1.55;
|
|
28
|
+
}
|
|
29
|
+
a { color: var(--accent-strong); text-decoration: none; }
|
|
30
|
+
a:hover { color: var(--accent); }
|
|
31
|
+
main.layout { max-width: 1360px; margin: 0 auto; padding: 1.5rem; }
|
|
32
|
+
.topbar {
|
|
33
|
+
position: sticky; top: 0; z-index: 10;
|
|
34
|
+
background: var(--bg-elev); border-bottom: 1px solid var(--line); margin: 0 -1.5rem 1.5rem;
|
|
35
|
+
padding: 0.9rem 1.5rem;
|
|
36
|
+
}
|
|
37
|
+
.topbar-inner, .hero, .split, .grid, .detail-grid { display: grid; gap: 1rem; }
|
|
38
|
+
.topbar-inner { grid-template-columns: 1.3fr auto; align-items: center; }
|
|
39
|
+
.nav { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }
|
|
40
|
+
.nav a {
|
|
41
|
+
padding: 0.45rem 0.75rem; border-radius: 8px;
|
|
42
|
+
background: var(--bg-raised); color: var(--muted); border: 1px solid var(--line);
|
|
43
|
+
}
|
|
44
|
+
.nav a:hover { color: var(--fg); border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
|
|
45
|
+
.nav a.active {
|
|
46
|
+
background: color-mix(in srgb, var(--rust) 28%, var(--bg-raised));
|
|
47
|
+
color: var(--fg); border-color: var(--rust);
|
|
48
|
+
}
|
|
49
|
+
.hero { grid-template-columns: 2fr 1fr; margin-bottom: 1.5rem; }
|
|
50
|
+
.grid { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
|
|
51
|
+
.split, .detail-grid { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
|
|
52
|
+
.panel, .metric, .card, .table-shell, .graph-shell, .print-card, .search-result {
|
|
53
|
+
background: var(--bg-soft);
|
|
54
|
+
border: 1px solid var(--line);
|
|
55
|
+
border-radius: 10px;
|
|
56
|
+
}
|
|
57
|
+
.panel, .card, .table-shell, .graph-shell, .print-card, .search-result { padding: 1rem 1.1rem; }
|
|
58
|
+
.metric { padding: 0; overflow: hidden; }
|
|
59
|
+
.metric-link, .metric > span, .metric > strong {
|
|
60
|
+
display: flex; flex-direction: column; gap: 0.35rem; padding: 1rem; color: inherit;
|
|
61
|
+
}
|
|
62
|
+
.metric-link:hover { background: var(--bg-raised); }
|
|
63
|
+
.metric-label, .subtle, .eyebrow, .breadcrumbs { color: var(--muted); }
|
|
64
|
+
.metric-value { font-size: 1.75rem; color: var(--accent-strong); }
|
|
65
|
+
.eyebrow { text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.75rem; color: var(--rust); }
|
|
66
|
+
.page-header { margin-bottom: 1rem; }
|
|
67
|
+
.toolbar {
|
|
68
|
+
display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; justify-content: space-between;
|
|
69
|
+
margin-bottom: 0.9rem;
|
|
70
|
+
}
|
|
71
|
+
.toolbar .actions, .pill-row, .breadcrumbs { display: flex; gap: 0.55rem; flex-wrap: wrap; align-items: center; }
|
|
72
|
+
.chip, .pill {
|
|
73
|
+
display: inline-flex; align-items: center; gap: 0.35rem;
|
|
74
|
+
padding: 0.28rem 0.6rem; border-radius: 8px;
|
|
75
|
+
border: 1px solid var(--line); background: var(--bg-raised); color: var(--muted);
|
|
76
|
+
}
|
|
77
|
+
a.pill:hover { color: var(--accent-strong); border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
|
|
78
|
+
.graph-controls-bar {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-wrap: wrap;
|
|
81
|
+
gap: 0.65rem;
|
|
82
|
+
align-items: center;
|
|
83
|
+
margin-bottom: 0.9rem;
|
|
84
|
+
padding: 0.8rem;
|
|
85
|
+
border: 1px solid var(--line);
|
|
86
|
+
border-radius: 10px;
|
|
87
|
+
background: var(--bg-raised);
|
|
88
|
+
}
|
|
89
|
+
.graph-filter {
|
|
90
|
+
flex: 1 1 180px;
|
|
91
|
+
min-width: 160px;
|
|
92
|
+
}
|
|
93
|
+
.graph-action {
|
|
94
|
+
border: 1px solid var(--line);
|
|
95
|
+
background: var(--bg-soft);
|
|
96
|
+
color: var(--fg);
|
|
97
|
+
border-radius: 8px;
|
|
98
|
+
padding: 0.7rem 0.9rem;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
}
|
|
101
|
+
.graph-action:hover { border-color: var(--accent-dim); color: var(--accent-strong); }
|
|
102
|
+
.graph-action.is-active {
|
|
103
|
+
background: color-mix(in srgb, var(--rust) 28%, var(--bg-soft));
|
|
104
|
+
border-color: var(--rust);
|
|
105
|
+
color: var(--fg);
|
|
106
|
+
}
|
|
107
|
+
.graph-status {
|
|
108
|
+
color: var(--muted);
|
|
109
|
+
font-size: 0.92rem;
|
|
110
|
+
margin-left: auto;
|
|
111
|
+
}
|
|
112
|
+
.searchbar, select, input[type="search"] {
|
|
113
|
+
width: min(100%, 440px); background: var(--bg); color: var(--fg);
|
|
114
|
+
border: 1px solid var(--line); border-radius: 8px; padding: 0.75rem 0.9rem;
|
|
115
|
+
}
|
|
116
|
+
.searchbar:focus, select:focus, input[type="search"]:focus {
|
|
117
|
+
outline: none;
|
|
118
|
+
border-color: var(--accent);
|
|
119
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent);
|
|
120
|
+
}
|
|
121
|
+
table { width: 100%; border-collapse: collapse; }
|
|
122
|
+
th, td {
|
|
123
|
+
padding: 0.75rem 0.65rem; text-align: left; border-bottom: 1px solid var(--line); vertical-align: top;
|
|
124
|
+
}
|
|
125
|
+
th { color: var(--muted); font-size: 0.94rem; }
|
|
126
|
+
tbody tr:hover { background: var(--bg-raised); }
|
|
127
|
+
.table-shell { overflow-x: auto; }
|
|
128
|
+
.sortable-th { padding: 0.45rem 0.4rem; }
|
|
129
|
+
.sort-button {
|
|
130
|
+
display: inline-flex; align-items: center; gap: 0.35rem;
|
|
131
|
+
width: 100%; margin: 0; padding: 0.3rem 0.25rem;
|
|
132
|
+
border: 0; background: transparent; color: inherit;
|
|
133
|
+
font: inherit; text-align: left; cursor: pointer;
|
|
134
|
+
}
|
|
135
|
+
.sort-button:hover { color: var(--fg); }
|
|
136
|
+
.sort-button.sort-active { color: var(--accent-strong); }
|
|
137
|
+
.sort-indicator { color: var(--faint); font-size: 0.8em; min-width: 1em; }
|
|
138
|
+
.sort-button.sort-active .sort-indicator { color: var(--accent-strong); }
|
|
139
|
+
.column-filter-row th {
|
|
140
|
+
padding: 0.35rem 0.4rem 0.75rem; border-bottom: 1px solid var(--line); font-weight: 400;
|
|
141
|
+
}
|
|
142
|
+
.column-filter {
|
|
143
|
+
width: 100%; min-width: 4.5rem; background: var(--bg); color: var(--fg);
|
|
144
|
+
border: 1px solid var(--line); border-radius: 6px; padding: 0.4rem 0.55rem; font-size: 0.85rem;
|
|
145
|
+
}
|
|
146
|
+
.column-filter:focus {
|
|
147
|
+
outline: none;
|
|
148
|
+
border-color: var(--accent);
|
|
149
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent);
|
|
150
|
+
}
|
|
151
|
+
.entity-list { display: grid; gap: 0.85rem; }
|
|
152
|
+
.entity-card {
|
|
153
|
+
border: 1px solid var(--line); border-radius: 10px; padding: 0.9rem 1rem; background: var(--bg-raised);
|
|
154
|
+
color: inherit;
|
|
155
|
+
}
|
|
156
|
+
.entity-card:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
|
|
157
|
+
.entity-card h3, .panel h2, .table-shell h2, .graph-shell h2 { margin-top: 0; }
|
|
158
|
+
.rollup-list { display: grid; gap: 0.5rem; }
|
|
159
|
+
.rollup-list div {
|
|
160
|
+
display: flex; justify-content: space-between; gap: 1rem; border-bottom: 1px solid var(--line); padding-bottom: 0.4rem;
|
|
161
|
+
}
|
|
162
|
+
.rollup-list dd { margin: 0; }
|
|
163
|
+
.graph-root svg {
|
|
164
|
+
width: 100%; min-height: 620px; background: var(--bg); border-radius: 10px; border: 1px solid var(--line);
|
|
165
|
+
}
|
|
166
|
+
.graph-root line {
|
|
167
|
+
stroke: color-mix(in srgb, var(--line) 80%, transparent); stroke-width: 1.2;
|
|
168
|
+
opacity: 0.85; transition: opacity 180ms ease;
|
|
169
|
+
}
|
|
170
|
+
.graph-root .node { cursor: pointer; }
|
|
171
|
+
.graph-root circle {
|
|
172
|
+
fill: var(--accent); stroke: color-mix(in srgb, var(--fg) 35%, transparent); stroke-width: 1.25;
|
|
173
|
+
transition: fill 180ms ease, stroke 180ms ease, r 180ms ease;
|
|
174
|
+
}
|
|
175
|
+
.graph-root .external circle { fill: var(--rust-muted); stroke: color-mix(in srgb, var(--rust) 50%, transparent); }
|
|
176
|
+
.graph-root .subject circle {
|
|
177
|
+
fill: var(--rust);
|
|
178
|
+
stroke: var(--accent-strong);
|
|
179
|
+
stroke-width: 2.75;
|
|
180
|
+
animation: subject-pulse 2.4s ease-in-out infinite;
|
|
181
|
+
}
|
|
182
|
+
.graph-root text {
|
|
183
|
+
fill: var(--fg); font-size: 11px; paint-order: stroke;
|
|
184
|
+
stroke: var(--bg); stroke-width: 3px; stroke-linejoin: round;
|
|
185
|
+
pointer-events: none;
|
|
186
|
+
}
|
|
187
|
+
.graph-root .subject text {
|
|
188
|
+
fill: var(--accent-strong); font-size: 12.5px; font-weight: 600;
|
|
189
|
+
}
|
|
190
|
+
.graph-root .node:hover circle { stroke: var(--accent-strong); stroke-width: 2; }
|
|
191
|
+
.graph-root .graph-label {
|
|
192
|
+
width: 160px;
|
|
193
|
+
color: var(--fg);
|
|
194
|
+
font: 11px/1.3 Inter, system-ui, sans-serif;
|
|
195
|
+
text-align: center;
|
|
196
|
+
word-break: break-word;
|
|
197
|
+
overflow-wrap: anywhere;
|
|
198
|
+
pointer-events: none;
|
|
199
|
+
user-select: none;
|
|
200
|
+
}
|
|
201
|
+
.graph-root .graph-label strong { display: block; font-weight: 600; }
|
|
202
|
+
.graph-root .graph-label .meta { display: block; color: var(--muted); font-size: 10px; margin-top: 0.15rem; }
|
|
203
|
+
.graph-root .graph-label .attrs { display: block; color: var(--rust); font-size: 9.5px; margin-top: 0.1rem; }
|
|
204
|
+
.graph-root .subject .graph-label strong { color: var(--accent-strong); }
|
|
205
|
+
.graph-root svg { touch-action: none; cursor: grab; }
|
|
206
|
+
.graph-root svg.is-panning { cursor: grabbing; }
|
|
207
|
+
.graph-root .node { cursor: pointer; }
|
|
208
|
+
@keyframes subject-pulse {
|
|
209
|
+
0%, 100% { filter: drop-shadow(0 0 0 transparent); }
|
|
210
|
+
50% { filter: drop-shadow(0 0 8px color-mix(in srgb, var(--rust) 55%, transparent)); }
|
|
211
|
+
}
|
|
212
|
+
.search-results { display: grid; gap: 0.7rem; margin-top: 1rem; }
|
|
213
|
+
.search-result:hover { border-color: var(--accent-dim); }
|
|
214
|
+
.hidden { display: none !important; }
|
|
215
|
+
.breadcrumbs { margin-bottom: 0.65rem; }
|
|
216
|
+
.breadcrumbs a { color: var(--muted); }
|
|
217
|
+
.breadcrumbs a:hover { color: var(--accent-strong); }
|
|
218
|
+
.section-link { font-size: 0.92rem; }
|
|
219
|
+
.prose p { line-height: 1.55; }
|
|
220
|
+
.idea-summary { white-space: normal; }
|
|
221
|
+
.idea-ref {
|
|
222
|
+
display: inline;
|
|
223
|
+
padding: 0.05em 0.35em;
|
|
224
|
+
margin: 0 0.05em;
|
|
225
|
+
border-radius: 0.3em;
|
|
226
|
+
border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
|
|
227
|
+
background: color-mix(in srgb, var(--accent) 14%, var(--bg-raised));
|
|
228
|
+
color: var(--accent-strong);
|
|
229
|
+
font-weight: 600;
|
|
230
|
+
text-decoration: none;
|
|
231
|
+
white-space: nowrap;
|
|
232
|
+
}
|
|
233
|
+
a.idea-ref:hover {
|
|
234
|
+
color: var(--fg);
|
|
235
|
+
border-color: var(--accent);
|
|
236
|
+
background: color-mix(in srgb, var(--accent) 22%, var(--bg-raised));
|
|
237
|
+
}
|
|
238
|
+
.idea-ref--file {
|
|
239
|
+
border-color: color-mix(in srgb, var(--rust) 40%, var(--line));
|
|
240
|
+
background: color-mix(in srgb, var(--rust) 16%, var(--bg-raised));
|
|
241
|
+
color: #e8a07a;
|
|
242
|
+
}
|
|
243
|
+
a.idea-ref--file:hover {
|
|
244
|
+
color: var(--fg);
|
|
245
|
+
border-color: var(--rust);
|
|
246
|
+
}
|
|
247
|
+
.idea-ref--unresolved {
|
|
248
|
+
border-style: dashed;
|
|
249
|
+
border-color: color-mix(in srgb, var(--warn) 45%, var(--line));
|
|
250
|
+
background: color-mix(in srgb, var(--warn) 10%, var(--bg-raised));
|
|
251
|
+
color: var(--warn);
|
|
252
|
+
font-weight: 500;
|
|
253
|
+
}
|
|
254
|
+
pre.code-like {
|
|
255
|
+
white-space: pre-wrap; padding: 0.9rem; border-radius: 8px;
|
|
256
|
+
background: var(--bg); border: 1px solid var(--line);
|
|
257
|
+
}
|
|
258
|
+
@media (max-width: 900px) {
|
|
259
|
+
.hero, .topbar-inner { grid-template-columns: 1fr; }
|
|
260
|
+
}
|
|
261
|
+
@media print {
|
|
262
|
+
:root { color-scheme: light; }
|
|
263
|
+
body { background: white; color: black; }
|
|
264
|
+
.topbar, .searchbar, .toolbar .actions, .graph-shell .toolbar, .column-filter-row, .hide-on-print { display: none !important; }
|
|
265
|
+
.sort-button { cursor: default; }
|
|
266
|
+
.panel, .metric, .card, .table-shell, .graph-shell, .print-card, .search-result, .entity-card {
|
|
267
|
+
background: white; color: black; border-color: #cbd5e1; box-shadow: none;
|
|
268
|
+
}
|
|
269
|
+
a { color: black; text-decoration: underline; }
|
|
270
|
+
.print-break-avoid { break-inside: avoid; }
|
|
271
|
+
}
|
|
272
|
+
`;
|
|
273
|
+
export const APP_JS = `
|
|
274
|
+
const searchIndexByRoot = new WeakMap();
|
|
275
|
+
|
|
276
|
+
function exportRootPrefix(root) {
|
|
277
|
+
const searchUrl = root.dataset.searchIndex || '';
|
|
278
|
+
const dataIdx = searchUrl.lastIndexOf('data/');
|
|
279
|
+
return dataIdx >= 0 ? searchUrl.slice(0, dataIdx) : '';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function resolveExportUrl(root, url) {
|
|
283
|
+
const cleaned = String(url || '').replace(/^\\.\\//, '');
|
|
284
|
+
return \`\${exportRootPrefix(root)}\${cleaned}\`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function loadSearchIndex(root) {
|
|
288
|
+
if (searchIndexByRoot.has(root)) return searchIndexByRoot.get(root);
|
|
289
|
+
const embedded = typeof globalThis.__REQLAN_SEARCH_INDEX__ !== 'undefined'
|
|
290
|
+
? globalThis.__REQLAN_SEARCH_INDEX__
|
|
291
|
+
: null;
|
|
292
|
+
if (Array.isArray(embedded)) {
|
|
293
|
+
const ready = Promise.resolve(embedded);
|
|
294
|
+
searchIndexByRoot.set(root, ready);
|
|
295
|
+
return ready;
|
|
296
|
+
}
|
|
297
|
+
const url = root.dataset.searchIndex;
|
|
298
|
+
if (!url) return [];
|
|
299
|
+
const loaded = fetch(url).then(response => {
|
|
300
|
+
if (!response.ok) throw new Error('search index fetch failed');
|
|
301
|
+
return response.json();
|
|
302
|
+
}).catch(() => []);
|
|
303
|
+
searchIndexByRoot.set(root, loaded);
|
|
304
|
+
return loaded;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function lower(value) {
|
|
308
|
+
return String(value || '').toLowerCase();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function scoreDocument(doc, query) {
|
|
312
|
+
const haystack = [
|
|
313
|
+
doc.title,
|
|
314
|
+
doc.summary,
|
|
315
|
+
...(doc.tags || []),
|
|
316
|
+
...(doc.pathTokens || []),
|
|
317
|
+
...(doc.keywords || [])
|
|
318
|
+
].map(lower).join(' ');
|
|
319
|
+
return haystack.includes(query) ? haystack.indexOf(query) : -1;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function wireGlobalSearch(root) {
|
|
323
|
+
const input = root.querySelector('[data-global-search]');
|
|
324
|
+
const results = root.querySelector('[data-search-results]');
|
|
325
|
+
if (!input || !results) return;
|
|
326
|
+
const docs = await loadSearchIndex(root);
|
|
327
|
+
function render() {
|
|
328
|
+
const query = lower(input.value.trim());
|
|
329
|
+
if (!query) {
|
|
330
|
+
results.innerHTML = '';
|
|
331
|
+
results.classList.add('hidden');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const matches = docs
|
|
335
|
+
.map(doc => ({ doc, score: scoreDocument(doc, query) }))
|
|
336
|
+
.filter(item => item.score >= 0)
|
|
337
|
+
.sort((a, b) => a.score - b.score || a.doc.title.localeCompare(b.doc.title))
|
|
338
|
+
.slice(0, 18);
|
|
339
|
+
results.classList.remove('hidden');
|
|
340
|
+
results.innerHTML = matches.length === 0
|
|
341
|
+
? '<div class="search-result"><strong>No matches</strong><p class="subtle">Try idea names, tags, statuses, file paths, or cluster labels.</p></div>'
|
|
342
|
+
: matches.map(({ doc }) => \`
|
|
343
|
+
<a class="search-result" href="\${resolveExportUrl(root, doc.url)}">
|
|
344
|
+
<div class="pill-row">
|
|
345
|
+
<span class="pill">\${doc.kind}</span>
|
|
346
|
+
\${doc.status ? \`<span class="pill">\${doc.status}</span>\` : ''}
|
|
347
|
+
</div>
|
|
348
|
+
<strong>\${doc.title}</strong>
|
|
349
|
+
<p class="subtle">\${doc.summary || ''}</p>
|
|
350
|
+
</a>
|
|
351
|
+
\`).join('');
|
|
352
|
+
}
|
|
353
|
+
input.addEventListener('input', render);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function cellSortValue(cell) {
|
|
357
|
+
if (!cell) return '';
|
|
358
|
+
const explicit = cell.getAttribute('data-sort-value');
|
|
359
|
+
if (explicit != null) return explicit.trim();
|
|
360
|
+
return String(cell.textContent || '').replace(/\\s+/g, ' ').trim();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function compareSortValues(left, right) {
|
|
364
|
+
const leftNumber = Number(String(left).replace(/,/g, ''));
|
|
365
|
+
const rightNumber = Number(String(right).replace(/,/g, ''));
|
|
366
|
+
const bothNumeric = left !== '' && right !== ''
|
|
367
|
+
&& Number.isFinite(leftNumber)
|
|
368
|
+
&& Number.isFinite(rightNumber);
|
|
369
|
+
if (bothNumeric) {
|
|
370
|
+
return leftNumber - rightNumber;
|
|
371
|
+
}
|
|
372
|
+
return String(left).localeCompare(String(right), undefined, { numeric: true, sensitivity: 'base' });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function isPlaceholderRow(row) {
|
|
376
|
+
if (!row || row.cells.length === 0) return true;
|
|
377
|
+
return [...row.cells].some(cell => Number(cell.colSpan || 1) > 1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function wireTables(root) {
|
|
381
|
+
const tables = root.querySelectorAll('table');
|
|
382
|
+
const tableControllers = [];
|
|
383
|
+
|
|
384
|
+
for (const table of tables) {
|
|
385
|
+
const thead = table.tHead;
|
|
386
|
+
const tbody = table.tBodies[0];
|
|
387
|
+
if (!thead || !tbody || thead.rows.length === 0) continue;
|
|
388
|
+
const headerRow = thead.rows[0];
|
|
389
|
+
if (headerRow.dataset.enhanced === '1') continue;
|
|
390
|
+
headerRow.dataset.enhanced = '1';
|
|
391
|
+
|
|
392
|
+
const headers = [...headerRow.cells];
|
|
393
|
+
if (headers.length === 0) continue;
|
|
394
|
+
|
|
395
|
+
let sortIndex = -1;
|
|
396
|
+
let sortDir = 'asc';
|
|
397
|
+
const filterInputs = [];
|
|
398
|
+
const controller = {
|
|
399
|
+
table,
|
|
400
|
+
tbody,
|
|
401
|
+
filterInputs,
|
|
402
|
+
applyFilters() {
|
|
403
|
+
const sample = [...tbody.rows].find(row => row.hasAttribute('data-filter-row'));
|
|
404
|
+
const filterId = sample?.getAttribute('data-filter-row');
|
|
405
|
+
const globalInput = filterId
|
|
406
|
+
? root.querySelector(\`[data-filter-input="\${filterId}"]\`)
|
|
407
|
+
: null;
|
|
408
|
+
const globalQuery = lower(globalInput?.value.trim());
|
|
409
|
+
const columnQueries = filterInputs.map(input => lower(input.value.trim()));
|
|
410
|
+
for (const row of tbody.rows) {
|
|
411
|
+
if (isPlaceholderRow(row)) continue;
|
|
412
|
+
const globalText = lower(row.getAttribute('data-filter-text') || row.textContent);
|
|
413
|
+
const matchesGlobal = !globalQuery || globalText.includes(globalQuery);
|
|
414
|
+
const matchesColumns = columnQueries.every((query, index) => {
|
|
415
|
+
if (!query) return true;
|
|
416
|
+
return lower(cellSortValue(row.cells[index])).includes(query);
|
|
417
|
+
});
|
|
418
|
+
row.classList.toggle('hidden', !(matchesGlobal && matchesColumns));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
headers.forEach((th, index) => {
|
|
424
|
+
const label = String(th.textContent || '').trim() || \`Column \${index + 1}\`;
|
|
425
|
+
th.classList.add('sortable-th');
|
|
426
|
+
th.replaceChildren();
|
|
427
|
+
const button = document.createElement('button');
|
|
428
|
+
button.type = 'button';
|
|
429
|
+
button.className = 'sort-button';
|
|
430
|
+
button.dataset.sortCol = String(index);
|
|
431
|
+
button.setAttribute('aria-label', \`Sort by \${label}\`);
|
|
432
|
+
const labelNode = document.createElement('span');
|
|
433
|
+
labelNode.textContent = label;
|
|
434
|
+
const indicator = document.createElement('span');
|
|
435
|
+
indicator.className = 'sort-indicator';
|
|
436
|
+
indicator.setAttribute('aria-hidden', 'true');
|
|
437
|
+
button.append(labelNode, indicator);
|
|
438
|
+
button.addEventListener('click', () => {
|
|
439
|
+
if (sortIndex === index) {
|
|
440
|
+
sortDir = sortDir === 'asc' ? 'desc' : 'asc';
|
|
441
|
+
} else {
|
|
442
|
+
sortIndex = index;
|
|
443
|
+
sortDir = 'asc';
|
|
444
|
+
}
|
|
445
|
+
const dataRows = [...tbody.rows].filter(row => !isPlaceholderRow(row));
|
|
446
|
+
dataRows.sort((left, right) => {
|
|
447
|
+
const cmp = compareSortValues(
|
|
448
|
+
cellSortValue(left.cells[index]),
|
|
449
|
+
cellSortValue(right.cells[index])
|
|
450
|
+
);
|
|
451
|
+
return sortDir === 'asc' ? cmp : -cmp;
|
|
452
|
+
});
|
|
453
|
+
for (const row of dataRows) {
|
|
454
|
+
tbody.appendChild(row);
|
|
455
|
+
}
|
|
456
|
+
for (const header of headers) {
|
|
457
|
+
const sortButton = header.querySelector('.sort-button');
|
|
458
|
+
const mark = header.querySelector('.sort-indicator');
|
|
459
|
+
if (!sortButton || !mark) continue;
|
|
460
|
+
const active = Number(sortButton.dataset.sortCol) === sortIndex;
|
|
461
|
+
sortButton.classList.toggle('sort-active', active);
|
|
462
|
+
mark.textContent = active ? (sortDir === 'asc' ? '▲' : '▼') : '';
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
th.appendChild(button);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const filterRow = document.createElement('tr');
|
|
469
|
+
filterRow.className = 'column-filter-row';
|
|
470
|
+
headers.forEach((th, index) => {
|
|
471
|
+
const label = String(th.querySelector('.sort-button span')?.textContent || \`Column \${index + 1}\`);
|
|
472
|
+
const cell = document.createElement('th');
|
|
473
|
+
const input = document.createElement('input');
|
|
474
|
+
input.type = 'search';
|
|
475
|
+
input.className = 'column-filter';
|
|
476
|
+
input.placeholder = 'Filter…';
|
|
477
|
+
input.dataset.colFilter = String(index);
|
|
478
|
+
input.setAttribute('aria-label', \`Filter \${label}\`);
|
|
479
|
+
input.addEventListener('input', () => controller.applyFilters());
|
|
480
|
+
filterInputs.push(input);
|
|
481
|
+
cell.appendChild(input);
|
|
482
|
+
filterRow.appendChild(cell);
|
|
483
|
+
});
|
|
484
|
+
thead.appendChild(filterRow);
|
|
485
|
+
tableControllers.push(controller);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const inputs = root.querySelectorAll('[data-filter-input]');
|
|
489
|
+
for (const input of inputs) {
|
|
490
|
+
input.addEventListener('input', () => {
|
|
491
|
+
const targetId = input.dataset.filterInput;
|
|
492
|
+
for (const controller of tableControllers) {
|
|
493
|
+
const ownsRows = controller.tbody.querySelector(\`[data-filter-row="\${targetId}"]\`);
|
|
494
|
+
if (ownsRows) {
|
|
495
|
+
controller.applyFilters();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function hashAngle(seed) {
|
|
503
|
+
let hash = 0;
|
|
504
|
+
for (let index = 0; index < seed.length; index += 1) {
|
|
505
|
+
hash = (hash * 31 + seed.charCodeAt(index)) | 0;
|
|
506
|
+
}
|
|
507
|
+
return ((hash >>> 0) % 6283) / 1000;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Matches packages/extension/webviews/shared/graph/graph-physics.ts DEFAULT_PHYSICS_SETTINGS.
|
|
511
|
+
const EXPORT_PHYSICS_SETTINGS = {
|
|
512
|
+
gravity: 0.002,
|
|
513
|
+
repulsion: 20000,
|
|
514
|
+
linkStrength: 0.015,
|
|
515
|
+
linkDistance: 120,
|
|
516
|
+
damping: 0.5,
|
|
517
|
+
maxVelocity: 10,
|
|
518
|
+
minSeparation: 24,
|
|
519
|
+
restSpeed: 0.02,
|
|
520
|
+
restTicks: 90
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
function wireGraph(root) {
|
|
524
|
+
const graphRoots = root.querySelectorAll('[data-graph-json]');
|
|
525
|
+
for (const element of graphRoots) {
|
|
526
|
+
const scriptId = element.dataset.graphJson;
|
|
527
|
+
const dataElement = root.querySelector(\`#\${scriptId}\`);
|
|
528
|
+
if (!dataElement) continue;
|
|
529
|
+
let graph;
|
|
530
|
+
try {
|
|
531
|
+
graph = JSON.parse(dataElement.textContent || '{}');
|
|
532
|
+
} catch {
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
let controls = root.querySelector(\`[data-graph-controls="\${scriptId}"]\`);
|
|
536
|
+
if (!controls) {
|
|
537
|
+
controls = document.createElement('div');
|
|
538
|
+
controls.className = 'graph-controls-bar';
|
|
539
|
+
controls.dataset.graphControls = scriptId;
|
|
540
|
+
controls.innerHTML = '<button type="button" class="graph-action" data-graph-toggle-physics aria-pressed="false">Live physics</button><button type="button" class="graph-action" data-graph-fit>Fit</button><span class="graph-status" data-graph-status-text></span>';
|
|
541
|
+
element.parentElement?.insertBefore(controls, element);
|
|
542
|
+
}
|
|
543
|
+
const searchInput = controls.querySelector('[data-graph-search]');
|
|
544
|
+
const pathInput = controls.querySelector('[data-graph-path]');
|
|
545
|
+
const statusSelect = controls.querySelector('[data-graph-status]');
|
|
546
|
+
const tagSelect = controls.querySelector('[data-graph-tag]');
|
|
547
|
+
const toggleExternal = controls.querySelector('[data-graph-toggle-external]');
|
|
548
|
+
const togglePhysics = controls.querySelector('[data-graph-toggle-physics]');
|
|
549
|
+
const fitButton = controls.querySelector('[data-graph-fit]');
|
|
550
|
+
const resetButton = controls.querySelector('[data-graph-reset]');
|
|
551
|
+
const statusText = controls.querySelector('[data-graph-status-text]');
|
|
552
|
+
let hideExternal = false;
|
|
553
|
+
let livePhysics = false;
|
|
554
|
+
let animationFrame = 0;
|
|
555
|
+
let calmTicks = 0;
|
|
556
|
+
let simNodes = [];
|
|
557
|
+
let simEdges = [];
|
|
558
|
+
const positions = new Map();
|
|
559
|
+
const velocities = new Map();
|
|
560
|
+
let svg;
|
|
561
|
+
let edgeEls = [];
|
|
562
|
+
let nodeEls = [];
|
|
563
|
+
const pinnedIds = new Set();
|
|
564
|
+
let view = { x: 0, y: 0, w: 1200, h: 640 };
|
|
565
|
+
let userViewport = false;
|
|
566
|
+
let dragNodeId = null;
|
|
567
|
+
let dragMoved = false;
|
|
568
|
+
let panMode = false;
|
|
569
|
+
let panOrigin = null;
|
|
570
|
+
let suppressClickUntil = 0;
|
|
571
|
+
|
|
572
|
+
function resolveGraphNodeUrl(pageUrl) {
|
|
573
|
+
let cleaned = String(pageUrl || '').replace(/^\\.\\//, '');
|
|
574
|
+
if (cleaned && !cleaned.includes('/') && cleaned.endsWith('.html')) {
|
|
575
|
+
cleaned = \`ideas/\${cleaned}\`;
|
|
576
|
+
}
|
|
577
|
+
return resolveExportUrl(root, cleaned);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function escapeXml(value) {
|
|
581
|
+
return String(value || '')
|
|
582
|
+
.replace(/&/g, '&')
|
|
583
|
+
.replace(/</g, '<')
|
|
584
|
+
.replace(/>/g, '>')
|
|
585
|
+
.replace(/"/g, '"');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function formatNodeMeta(node) {
|
|
589
|
+
const bits = [];
|
|
590
|
+
if (node.status) bits.push(String(node.status));
|
|
591
|
+
if (Array.isArray(node.tags) && node.tags.length) bits.push(node.tags.join(', '));
|
|
592
|
+
return bits.join(' · ');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function formatNodeAttrs(node) {
|
|
596
|
+
const keys = Array.isArray(node.attributeKeys)
|
|
597
|
+
? node.attributeKeys
|
|
598
|
+
: Object.keys(node.attributes || {});
|
|
599
|
+
return keys.length ? keys.map(key => '@' + key).join(' ') : '';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function seedMissingPositions(nodes, width, height, centerId) {
|
|
603
|
+
const cx = width / 2;
|
|
604
|
+
const cy = height / 2;
|
|
605
|
+
const subject = nodes.find(node => node.isSubject || node.id === centerId);
|
|
606
|
+
const radius = Math.min(width, height) * 0.34;
|
|
607
|
+
nodes.forEach((node, index) => {
|
|
608
|
+
if (positions.has(node.id)) return;
|
|
609
|
+
if (subject && node.id === subject.id) {
|
|
610
|
+
positions.set(node.id, { x: cx, y: cy });
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const angle = (Math.PI * 2 * index) / Math.max(nodes.length, 1) - Math.PI / 2;
|
|
614
|
+
const ring = radius * (0.72 + (index % 3) * 0.14);
|
|
615
|
+
positions.set(node.id, {
|
|
616
|
+
x: cx + Math.cos(angle) * ring,
|
|
617
|
+
y: cy + Math.sin(angle) * ring
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function pruneState(validIds) {
|
|
623
|
+
for (const id of [...positions.keys()]) {
|
|
624
|
+
if (!validIds.has(id)) positions.delete(id);
|
|
625
|
+
}
|
|
626
|
+
for (const id of [...velocities.keys()]) {
|
|
627
|
+
if (!validIds.has(id)) velocities.delete(id);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function physicsStep(nodes, edges) {
|
|
632
|
+
const settings = EXPORT_PHYSICS_SETTINGS;
|
|
633
|
+
const count = nodes.length;
|
|
634
|
+
if (count === 0) return 0;
|
|
635
|
+
const ids = new Array(count);
|
|
636
|
+
const xs = new Float64Array(count);
|
|
637
|
+
const ys = new Float64Array(count);
|
|
638
|
+
const fxs = new Float64Array(count);
|
|
639
|
+
const fys = new Float64Array(count);
|
|
640
|
+
const indexById = new Map();
|
|
641
|
+
for (let i = 0; i < count; i += 1) {
|
|
642
|
+
const node = nodes[i];
|
|
643
|
+
const pos = positions.get(node.id) || { x: 0, y: 0 };
|
|
644
|
+
ids[i] = node.id;
|
|
645
|
+
xs[i] = pos.x;
|
|
646
|
+
ys[i] = pos.y;
|
|
647
|
+
indexById.set(node.id, i);
|
|
648
|
+
}
|
|
649
|
+
let centroidX = 0;
|
|
650
|
+
let centroidY = 0;
|
|
651
|
+
for (let i = 0; i < count; i += 1) {
|
|
652
|
+
centroidX += xs[i];
|
|
653
|
+
centroidY += ys[i];
|
|
654
|
+
}
|
|
655
|
+
centroidX /= count;
|
|
656
|
+
centroidY /= count;
|
|
657
|
+
for (let i = 0; i < count; i += 1) {
|
|
658
|
+
fxs[i] -= settings.gravity * (xs[i] - centroidX);
|
|
659
|
+
fys[i] -= settings.gravity * (ys[i] - centroidY);
|
|
660
|
+
}
|
|
661
|
+
const minSeparationSq = settings.minSeparation * settings.minSeparation;
|
|
662
|
+
for (let i = 0; i < count; i += 1) {
|
|
663
|
+
for (let j = i + 1; j < count; j += 1) {
|
|
664
|
+
let dx = xs[j] - xs[i];
|
|
665
|
+
let dy = ys[j] - ys[i];
|
|
666
|
+
let distSq = dx * dx + dy * dy;
|
|
667
|
+
if (distSq < 1e-6) {
|
|
668
|
+
const angle = hashAngle(ids[i] + ids[j]);
|
|
669
|
+
dx = Math.cos(angle);
|
|
670
|
+
dy = Math.sin(angle);
|
|
671
|
+
distSq = 1;
|
|
672
|
+
}
|
|
673
|
+
const clampedSq = Math.max(distSq, minSeparationSq);
|
|
674
|
+
const dist = Math.sqrt(distSq);
|
|
675
|
+
const force = settings.repulsion / clampedSq;
|
|
676
|
+
const fx = (dx / dist) * force;
|
|
677
|
+
const fy = (dy / dist) * force;
|
|
678
|
+
fxs[i] -= fx;
|
|
679
|
+
fys[i] -= fy;
|
|
680
|
+
fxs[j] += fx;
|
|
681
|
+
fys[j] += fy;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
for (const edge of edges) {
|
|
685
|
+
const sourceIndex = indexById.get(edge.sourceId);
|
|
686
|
+
const targetIndex = indexById.get(edge.targetId);
|
|
687
|
+
if (sourceIndex === undefined || targetIndex === undefined || sourceIndex === targetIndex) continue;
|
|
688
|
+
const dx = xs[targetIndex] - xs[sourceIndex];
|
|
689
|
+
const dy = ys[targetIndex] - ys[sourceIndex];
|
|
690
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
691
|
+
if (dist < 1e-3) continue;
|
|
692
|
+
const force = settings.linkStrength * (dist - settings.linkDistance);
|
|
693
|
+
const fx = (dx / dist) * force;
|
|
694
|
+
const fy = (dy / dist) * force;
|
|
695
|
+
fxs[sourceIndex] += fx;
|
|
696
|
+
fys[sourceIndex] += fy;
|
|
697
|
+
fxs[targetIndex] -= fx;
|
|
698
|
+
fys[targetIndex] -= fy;
|
|
699
|
+
}
|
|
700
|
+
let speedSum = 0;
|
|
701
|
+
let movingCount = 0;
|
|
702
|
+
for (let i = 0; i < count; i += 1) {
|
|
703
|
+
const id = ids[i];
|
|
704
|
+
if (pinnedIds.has(id)) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const velocity = velocities.get(id) || { vx: 0, vy: 0 };
|
|
708
|
+
let vx = (velocity.vx + fxs[i]) * settings.damping;
|
|
709
|
+
let vy = (velocity.vy + fys[i]) * settings.damping;
|
|
710
|
+
const speed = Math.sqrt(vx * vx + vy * vy);
|
|
711
|
+
if (speed > settings.maxVelocity) {
|
|
712
|
+
const scale = settings.maxVelocity / speed;
|
|
713
|
+
vx *= scale;
|
|
714
|
+
vy *= scale;
|
|
715
|
+
}
|
|
716
|
+
velocity.vx = vx;
|
|
717
|
+
velocity.vy = vy;
|
|
718
|
+
velocities.set(id, velocity);
|
|
719
|
+
positions.set(id, { x: xs[i] + vx, y: ys[i] + vy });
|
|
720
|
+
speedSum += Math.min(speed, settings.maxVelocity);
|
|
721
|
+
movingCount += 1;
|
|
722
|
+
}
|
|
723
|
+
return movingCount > 0 ? speedSum / movingCount : 0;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function contentBounds() {
|
|
727
|
+
let minX = Infinity;
|
|
728
|
+
let minY = Infinity;
|
|
729
|
+
let maxX = -Infinity;
|
|
730
|
+
let maxY = -Infinity;
|
|
731
|
+
for (const pos of positions.values()) {
|
|
732
|
+
minX = Math.min(minX, pos.x);
|
|
733
|
+
minY = Math.min(minY, pos.y);
|
|
734
|
+
maxX = Math.max(maxX, pos.x);
|
|
735
|
+
maxY = Math.max(maxY, pos.y);
|
|
736
|
+
}
|
|
737
|
+
if (!Number.isFinite(minX)) {
|
|
738
|
+
return { minX: 0, minY: 0, maxX: 1200, maxY: 640 };
|
|
739
|
+
}
|
|
740
|
+
return { minX, minY, maxX, maxY };
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function applyView() {
|
|
744
|
+
if (!svg) return;
|
|
745
|
+
svg.setAttribute('viewBox', \`\${view.x} \${view.y} \${view.w} \${view.h}\`);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function fitView(force = false) {
|
|
749
|
+
if (userViewport && !force) {
|
|
750
|
+
applyView();
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const bounds = contentBounds();
|
|
754
|
+
const pad = 80;
|
|
755
|
+
view = {
|
|
756
|
+
x: bounds.minX - pad,
|
|
757
|
+
y: bounds.minY - pad,
|
|
758
|
+
w: Math.max(360, bounds.maxX - bounds.minX + pad * 2),
|
|
759
|
+
h: Math.max(360, bounds.maxY - bounds.minY + pad * 2 + 70)
|
|
760
|
+
};
|
|
761
|
+
userViewport = false;
|
|
762
|
+
applyView();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function paint() {
|
|
766
|
+
if (!svg) return;
|
|
767
|
+
for (const { node, circle, label } of nodeEls) {
|
|
768
|
+
const pos = positions.get(node.id);
|
|
769
|
+
if (!pos) continue;
|
|
770
|
+
circle.setAttribute('cx', String(pos.x));
|
|
771
|
+
circle.setAttribute('cy', String(pos.y));
|
|
772
|
+
label.setAttribute('x', String(pos.x - 80));
|
|
773
|
+
label.setAttribute('y', String(pos.y + 18));
|
|
774
|
+
}
|
|
775
|
+
for (const { edge, line } of edgeEls) {
|
|
776
|
+
const source = positions.get(edge.sourceId);
|
|
777
|
+
const target = positions.get(edge.targetId);
|
|
778
|
+
if (!source || !target) continue;
|
|
779
|
+
line.setAttribute('x1', String(source.x));
|
|
780
|
+
line.setAttribute('y1', String(source.y));
|
|
781
|
+
line.setAttribute('x2', String(target.x));
|
|
782
|
+
line.setAttribute('y2', String(target.y));
|
|
783
|
+
line.setAttribute('opacity', '0.85');
|
|
784
|
+
}
|
|
785
|
+
if (!userViewport) {
|
|
786
|
+
fitView(false);
|
|
787
|
+
} else {
|
|
788
|
+
applyView();
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function stopLoop() {
|
|
793
|
+
if (animationFrame) {
|
|
794
|
+
cancelAnimationFrame(animationFrame);
|
|
795
|
+
animationFrame = 0;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function scheduleTick() {
|
|
800
|
+
if (animationFrame) return;
|
|
801
|
+
animationFrame = requestAnimationFrame(() => {
|
|
802
|
+
animationFrame = 0;
|
|
803
|
+
if (!livePhysics) return;
|
|
804
|
+
const averageSpeed = physicsStep(simNodes, simEdges);
|
|
805
|
+
paint();
|
|
806
|
+
if (averageSpeed < EXPORT_PHYSICS_SETTINGS.restSpeed) {
|
|
807
|
+
calmTicks += 1;
|
|
808
|
+
if (calmTicks >= EXPORT_PHYSICS_SETTINGS.restTicks) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
calmTicks = 0;
|
|
813
|
+
}
|
|
814
|
+
scheduleTick();
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function wake() {
|
|
819
|
+
calmTicks = 0;
|
|
820
|
+
if (livePhysics) scheduleTick();
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function batchSettle(nodes, edges) {
|
|
824
|
+
const iterations = Math.min(120, 48 + nodes.length);
|
|
825
|
+
for (let i = 0; i < iterations; i += 1) {
|
|
826
|
+
physicsStep(nodes, edges);
|
|
827
|
+
}
|
|
828
|
+
for (const velocity of velocities.values()) {
|
|
829
|
+
velocity.vx = 0;
|
|
830
|
+
velocity.vy = 0;
|
|
831
|
+
}
|
|
832
|
+
calmTicks = EXPORT_PHYSICS_SETTINGS.restTicks;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function mountGraph(filteredNodes, filteredEdges) {
|
|
836
|
+
stopLoop();
|
|
837
|
+
simNodes = filteredNodes;
|
|
838
|
+
simEdges = filteredEdges;
|
|
839
|
+
const validIds = new Set(filteredNodes.map(node => node.id));
|
|
840
|
+
pruneState(validIds);
|
|
841
|
+
const width = 1200;
|
|
842
|
+
const height = Math.max(640, Math.min(1400, 220 + filteredNodes.length * 28));
|
|
843
|
+
seedMissingPositions(filteredNodes, width, height, graph.centerId);
|
|
844
|
+
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
845
|
+
svg.setAttribute('viewBox', \`0 0 \${width} \${height}\`);
|
|
846
|
+
edgeEls = [];
|
|
847
|
+
for (const edge of filteredEdges) {
|
|
848
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
849
|
+
svg.appendChild(line);
|
|
850
|
+
edgeEls.push({ edge, line });
|
|
851
|
+
}
|
|
852
|
+
nodeEls = [];
|
|
853
|
+
for (const node of filteredNodes) {
|
|
854
|
+
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
855
|
+
const classes = ['node'];
|
|
856
|
+
if (node.isExternal) classes.push('external');
|
|
857
|
+
if (node.isSubject || node.id === graph.centerId) classes.push('subject');
|
|
858
|
+
group.setAttribute('class', classes.join(' '));
|
|
859
|
+
group.dataset.nodeId = node.id;
|
|
860
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
861
|
+
circle.setAttribute('r', node.isSubject || node.id === graph.centerId ? '18' : '13');
|
|
862
|
+
const label = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
|
|
863
|
+
label.setAttribute('width', '160');
|
|
864
|
+
label.setAttribute('height', '96');
|
|
865
|
+
const meta = formatNodeMeta(node);
|
|
866
|
+
const attrs = formatNodeAttrs(node);
|
|
867
|
+
label.innerHTML = \`<div xmlns="http://www.w3.org/1999/xhtml" class="graph-label"><strong>\${escapeXml(node.name)}</strong>\${meta ? \`<span class="meta">\${escapeXml(meta)}</span>\` : ''}\${attrs ? \`<span class="attrs">\${escapeXml(attrs)}</span>\` : ''}</div>\`;
|
|
868
|
+
const title = document.createElementNS('http://www.w3.org/2000/svg', 'title');
|
|
869
|
+
title.textContent = [node.name, meta, attrs].filter(Boolean).join('\\n');
|
|
870
|
+
group.appendChild(title);
|
|
871
|
+
group.appendChild(circle);
|
|
872
|
+
group.appendChild(label);
|
|
873
|
+
svg.appendChild(group);
|
|
874
|
+
nodeEls.push({ node, circle, label, group });
|
|
875
|
+
}
|
|
876
|
+
element.innerHTML = '';
|
|
877
|
+
element.appendChild(svg);
|
|
878
|
+
wireViewport(svg);
|
|
879
|
+
if (statusText) {
|
|
880
|
+
statusText.textContent = \`\${filteredNodes.length} nodes, \${filteredEdges.length} edges\`;
|
|
881
|
+
}
|
|
882
|
+
userViewport = false;
|
|
883
|
+
if (!livePhysics) {
|
|
884
|
+
batchSettle(filteredNodes, filteredEdges);
|
|
885
|
+
paint();
|
|
886
|
+
} else {
|
|
887
|
+
paint();
|
|
888
|
+
wake();
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function clientToGraph(event) {
|
|
893
|
+
const rect = svg.getBoundingClientRect();
|
|
894
|
+
const x = view.x + ((event.clientX - rect.left) / rect.width) * view.w;
|
|
895
|
+
const y = view.y + ((event.clientY - rect.top) / rect.height) * view.h;
|
|
896
|
+
return { x, y };
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function wireViewport(target) {
|
|
900
|
+
target.addEventListener('wheel', (event) => {
|
|
901
|
+
event.preventDefault();
|
|
902
|
+
const factor = event.deltaY < 0 ? 0.9 : 1.1;
|
|
903
|
+
const before = clientToGraph(event);
|
|
904
|
+
view.w = Math.min(8000, Math.max(180, view.w * factor));
|
|
905
|
+
view.h = Math.min(8000, Math.max(180, view.h * factor));
|
|
906
|
+
const after = clientToGraph(event);
|
|
907
|
+
view.x += before.x - after.x;
|
|
908
|
+
view.y += before.y - after.y;
|
|
909
|
+
userViewport = true;
|
|
910
|
+
applyView();
|
|
911
|
+
}, { passive: false });
|
|
912
|
+
|
|
913
|
+
target.addEventListener('pointerdown', (event) => {
|
|
914
|
+
const nodeGroup = event.target.closest?.('.node');
|
|
915
|
+
if (nodeGroup) {
|
|
916
|
+
const nodeId = nodeGroup.dataset.nodeId;
|
|
917
|
+
dragNodeId = nodeId;
|
|
918
|
+
dragMoved = false;
|
|
919
|
+
pinnedIds.add(nodeId);
|
|
920
|
+
velocities.set(nodeId, { vx: 0, vy: 0 });
|
|
921
|
+
target.setPointerCapture(event.pointerId);
|
|
922
|
+
event.preventDefault();
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
panMode = true;
|
|
926
|
+
panOrigin = { x: event.clientX, y: event.clientY, viewX: view.x, viewY: view.y };
|
|
927
|
+
target.classList.add('is-panning');
|
|
928
|
+
target.setPointerCapture(event.pointerId);
|
|
929
|
+
event.preventDefault();
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
target.addEventListener('pointermove', (event) => {
|
|
933
|
+
if (dragNodeId) {
|
|
934
|
+
const point = clientToGraph(event);
|
|
935
|
+
positions.set(dragNodeId, { x: point.x, y: point.y });
|
|
936
|
+
dragMoved = true;
|
|
937
|
+
wake();
|
|
938
|
+
paint();
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (panMode && panOrigin) {
|
|
942
|
+
const rect = target.getBoundingClientRect();
|
|
943
|
+
const dx = ((event.clientX - panOrigin.x) / rect.width) * view.w;
|
|
944
|
+
const dy = ((event.clientY - panOrigin.y) / rect.height) * view.h;
|
|
945
|
+
view.x = panOrigin.viewX - dx;
|
|
946
|
+
view.y = panOrigin.viewY - dy;
|
|
947
|
+
userViewport = true;
|
|
948
|
+
applyView();
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
target.addEventListener('pointerup', (event) => {
|
|
953
|
+
if (dragNodeId) {
|
|
954
|
+
const node = simNodes.find(item => item.id === dragNodeId);
|
|
955
|
+
const moved = dragMoved;
|
|
956
|
+
const nodeId = dragNodeId;
|
|
957
|
+
dragNodeId = null;
|
|
958
|
+
pinnedIds.delete(nodeId);
|
|
959
|
+
wake();
|
|
960
|
+
if (!moved && node?.pageUrl && Date.now() > suppressClickUntil) {
|
|
961
|
+
window.location.href = resolveGraphNodeUrl(node.pageUrl);
|
|
962
|
+
} else if (moved) {
|
|
963
|
+
suppressClickUntil = Date.now() + 250;
|
|
964
|
+
}
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (panMode) {
|
|
968
|
+
panMode = false;
|
|
969
|
+
panOrigin = null;
|
|
970
|
+
target.classList.remove('is-panning');
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
target.addEventListener('pointercancel', () => {
|
|
975
|
+
if (dragNodeId) {
|
|
976
|
+
pinnedIds.delete(dragNodeId);
|
|
977
|
+
dragNodeId = null;
|
|
978
|
+
wake();
|
|
979
|
+
}
|
|
980
|
+
panMode = false;
|
|
981
|
+
panOrigin = null;
|
|
982
|
+
target.classList.remove('is-panning');
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function matches(node) {
|
|
987
|
+
const query = lower(searchInput?.value?.trim());
|
|
988
|
+
const pathQuery = lower(pathInput?.value?.trim());
|
|
989
|
+
const statusQuery = lower(statusSelect?.value?.trim());
|
|
990
|
+
const tagQuery = lower(tagSelect?.value?.trim());
|
|
991
|
+
if (hideExternal && node.isExternal) return false;
|
|
992
|
+
const haystack = [node.name, node.kind, node.fileUri, node.status || '', ...(node.tags || [])].map(lower).join(' ');
|
|
993
|
+
if (query && !haystack.includes(query)) return false;
|
|
994
|
+
if (pathQuery && !lower(node.fileUri).includes(pathQuery)) return false;
|
|
995
|
+
if (statusQuery && lower(node.status || '') !== statusQuery) return false;
|
|
996
|
+
if (tagQuery && !(node.tags || []).some(tag => lower(tag) === tagQuery)) return false;
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function refresh() {
|
|
1001
|
+
const visibleNodes = (graph.nodes || []).filter(matches);
|
|
1002
|
+
const visibleIds = new Set(visibleNodes.map(node => node.id));
|
|
1003
|
+
const visibleEdges = (graph.edges || []).filter(edge =>
|
|
1004
|
+
visibleIds.has(edge.sourceId) && visibleIds.has(edge.targetId)
|
|
1005
|
+
);
|
|
1006
|
+
mountGraph(visibleNodes, visibleEdges);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
for (const control of [searchInput, pathInput, statusSelect, tagSelect]) {
|
|
1010
|
+
control?.addEventListener('input', refresh);
|
|
1011
|
+
control?.addEventListener('change', refresh);
|
|
1012
|
+
}
|
|
1013
|
+
toggleExternal?.addEventListener('click', () => {
|
|
1014
|
+
hideExternal = !hideExternal;
|
|
1015
|
+
toggleExternal.classList.toggle('is-active', hideExternal);
|
|
1016
|
+
toggleExternal.textContent = hideExternal ? 'Show external' : 'Hide external';
|
|
1017
|
+
refresh();
|
|
1018
|
+
});
|
|
1019
|
+
togglePhysics?.addEventListener('click', () => {
|
|
1020
|
+
livePhysics = !livePhysics;
|
|
1021
|
+
togglePhysics.classList.toggle('is-active', livePhysics);
|
|
1022
|
+
togglePhysics.setAttribute('aria-pressed', livePhysics ? 'true' : 'false');
|
|
1023
|
+
if (livePhysics) {
|
|
1024
|
+
wake();
|
|
1025
|
+
} else {
|
|
1026
|
+
stopLoop();
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
fitButton?.addEventListener('click', () => {
|
|
1030
|
+
fitView(true);
|
|
1031
|
+
});
|
|
1032
|
+
resetButton?.addEventListener('click', () => {
|
|
1033
|
+
if (searchInput) searchInput.value = '';
|
|
1034
|
+
if (pathInput) pathInput.value = '';
|
|
1035
|
+
if (statusSelect) statusSelect.value = '';
|
|
1036
|
+
if (tagSelect) tagSelect.value = '';
|
|
1037
|
+
hideExternal = false;
|
|
1038
|
+
toggleExternal?.classList.remove('is-active');
|
|
1039
|
+
if (toggleExternal) toggleExternal.textContent = 'Hide external';
|
|
1040
|
+
positions.clear();
|
|
1041
|
+
velocities.clear();
|
|
1042
|
+
pinnedIds.clear();
|
|
1043
|
+
userViewport = false;
|
|
1044
|
+
refresh();
|
|
1045
|
+
});
|
|
1046
|
+
refresh();
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function boot() {
|
|
1051
|
+
const root = document.body;
|
|
1052
|
+
wireTables(root);
|
|
1053
|
+
wireGraph(root);
|
|
1054
|
+
void wireGlobalSearch(root);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (document.readyState === 'loading') {
|
|
1058
|
+
document.addEventListener('DOMContentLoaded', boot);
|
|
1059
|
+
} else {
|
|
1060
|
+
boot();
|
|
1061
|
+
}
|
|
1062
|
+
`;
|
|
1063
|
+
export function buildSearchIndexScript(documents) {
|
|
1064
|
+
return `globalThis.__REQLAN_SEARCH_INDEX__ = ${JSON.stringify(documents).replace(/</g, '\\u003c')};\n`;
|
|
1065
|
+
}
|
|
1066
|
+
//# sourceMappingURL=html-export-assets.js.map
|