@antoneeo/mkt-agentic-sdlc-skill 0.18.0 → 0.20.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/CHANGELOG.md +10 -0
- package/README.md +2 -0
- package/gemini-extension.json +1 -1
- package/package.json +6 -1
- package/skills/mkt-agentic-sdlc/SKILL.md +4 -3
- package/skills/mkt-agentic-sdlc/docs_site/app.js +1127 -0
- package/skills/mkt-agentic-sdlc/docs_site/index.html +17 -0
- package/skills/mkt-agentic-sdlc/docs_site/strings.js +323 -0
- package/skills/mkt-agentic-sdlc/docs_site/style.css +288 -0
- package/skills/mkt-agentic-sdlc/scripts/docs_site.py +1362 -0
- package/skills/mkt-agentic-sdlc/scripts/knowledge.py +20 -6
- package/skills/mkt-agentic-sdlc/scripts/mkt_check.py +9 -0
- package/skills/mkt-agentic-sdlc/scripts/sdlc_core.py +305 -0
- package/skills/mkt-agentic-sdlc/templates.md +12 -1
|
@@ -0,0 +1,1127 @@
|
|
|
1
|
+
/* Docs site — the page. A classic script: a page opened from disk cannot fetch,
|
|
2
|
+
so the generator writes its data as scripts this page loads. Every word comes
|
|
3
|
+
from strings.js; text from the project is inserted as text, and document
|
|
4
|
+
bodies arrive already formatted and sanitized by the generator. The only
|
|
5
|
+
action anywhere is opening a document: filters, search and view switches
|
|
6
|
+
change nothing. */
|
|
7
|
+
(function () {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
var S = window.DOCS_SITE_STRINGS || {};
|
|
11
|
+
var C = window.DOCS_SITE_CORE;
|
|
12
|
+
var D = null; // documents, loaded on first use
|
|
13
|
+
var K = null; // knowledge base, loaded on first use
|
|
14
|
+
var app = document.getElementById("app");
|
|
15
|
+
var ORIGINS = ["given", "elicited", "derived", "ruling", "imported"];
|
|
16
|
+
|
|
17
|
+
// ------------------------------------------------------------- helpers
|
|
18
|
+
|
|
19
|
+
function t(key, params) {
|
|
20
|
+
var s = Object.prototype.hasOwnProperty.call(S, key) ? S[key] : key;
|
|
21
|
+
if (params) {
|
|
22
|
+
Object.keys(params).forEach(function (k) {
|
|
23
|
+
s = s.split("{" + k + "}").join(String(params[k]));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return s;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function el(tag, attrs) {
|
|
30
|
+
var node = document.createElement(tag);
|
|
31
|
+
if (attrs) {
|
|
32
|
+
Object.keys(attrs).forEach(function (k) {
|
|
33
|
+
var v = attrs[k];
|
|
34
|
+
if (v === null || v === undefined || v === false) return;
|
|
35
|
+
if (k === "class") node.className = v;
|
|
36
|
+
else if (k === "text") node.textContent = v;
|
|
37
|
+
else if (k.slice(0, 2) === "on") node.addEventListener(k.slice(2), v);
|
|
38
|
+
else node.setAttribute(k, v === true ? "" : v);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
for (var i = 2; i < arguments.length; i++) add(node, arguments[i]);
|
|
42
|
+
return node;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function add(node, child) {
|
|
46
|
+
if (child === null || child === undefined || child === false) return;
|
|
47
|
+
if (Array.isArray(child)) { child.forEach(function (c) { add(node, c); }); return; }
|
|
48
|
+
node.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function docHref(path) { return "#/doc/" + encodeURI(path); }
|
|
52
|
+
function link(href, text, cls) { return el("a", { href: href, "class": cls }, text); }
|
|
53
|
+
function docLink(path, text) { return link(docHref(path), text || path); }
|
|
54
|
+
|
|
55
|
+
function normalize(s) {
|
|
56
|
+
s = String(s || "").normalize("NFKD").replace(/\p{Mn}/gu, "");
|
|
57
|
+
s = s.toLowerCase();
|
|
58
|
+
s = s.replace(/[‐-―−﹘﹣-]/g, "-");
|
|
59
|
+
s = s.replace(/[--]/g, "");
|
|
60
|
+
return s.replace(/\s+/g, " ").trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function written(v) { return v !== undefined && v !== null && String(v).trim() !== "" && String(v).trim() !== "-"; }
|
|
64
|
+
|
|
65
|
+
var pending = {};
|
|
66
|
+
|
|
67
|
+
function loadScript(src, ready) {
|
|
68
|
+
if (pending[src]) { pending[src].push(ready); return; }
|
|
69
|
+
pending[src] = [ready];
|
|
70
|
+
var s = document.createElement("script");
|
|
71
|
+
s.src = src;
|
|
72
|
+
s.onload = function () { var q = pending[src]; pending[src] = null; q.forEach(function (f) { f(); }); };
|
|
73
|
+
s.onerror = function () { fail(); };
|
|
74
|
+
document.body.appendChild(s);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function needDocs(then) {
|
|
78
|
+
if (D) return then();
|
|
79
|
+
main(el("p", { "class": "muted", text: t("loading_documents") }));
|
|
80
|
+
loadScript("data/docs.js", function () {
|
|
81
|
+
D = window.DOCS_SITE_DOCS;
|
|
82
|
+
if (!D) return fail();
|
|
83
|
+
then();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function needKb(then) {
|
|
88
|
+
if (K) return then();
|
|
89
|
+
if (!C.meta.has_kb) return notFound();
|
|
90
|
+
main(el("p", { "class": "muted", text: t("loading_knowledge") }));
|
|
91
|
+
loadScript("data/kb.js", function () {
|
|
92
|
+
K = window.DOCS_SITE_KB;
|
|
93
|
+
if (!K) return fail();
|
|
94
|
+
then();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function html(target, markup) {
|
|
99
|
+
target.innerHTML = markup; // produced by the generator's sanitizer only
|
|
100
|
+
Array.prototype.forEach.call(target.querySelectorAll("code.notfact"), function (c) {
|
|
101
|
+
c.title = t("not_fact_id");
|
|
102
|
+
});
|
|
103
|
+
Array.prototype.forEach.call(target.querySelectorAll('a[href^="#"]:not([href^="#/"])'), function (a) {
|
|
104
|
+
a.addEventListener("click", function (e) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
var id = decodeURIComponent(a.getAttribute("href").slice(1));
|
|
107
|
+
var dest = document.getElementById(id);
|
|
108
|
+
if (dest) dest.scrollIntoView({ behavior: "smooth" });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
return target;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function levelLabel(v) {
|
|
115
|
+
return Object.prototype.hasOwnProperty.call(S, "level_" + v) ? t("level_" + v) : v;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function statusPill(status, group) {
|
|
119
|
+
return el("span", { "class": "pill g" + group, title: t("group_" + group) }, status || t("group_" + group));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function originClass(o) { return ORIGINS.indexOf(o) >= 0 ? o : "other"; }
|
|
123
|
+
|
|
124
|
+
function originLabel(o) {
|
|
125
|
+
return ORIGINS.indexOf(o) >= 0 ? t("origin_" + o) : t("origin_other", { v: o });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function originWhy(o) { return ORIGINS.indexOf(o) >= 0 ? t("origin_" + o + "_why") : t("origin_other_why"); }
|
|
129
|
+
|
|
130
|
+
// ------------------------------------------------------------ the frame
|
|
131
|
+
|
|
132
|
+
var mainEl, panelEl = null, navLinks = {};
|
|
133
|
+
|
|
134
|
+
function frame() {
|
|
135
|
+
var nav = el("nav");
|
|
136
|
+
var items = [["home", "#/", "nav_home"], ["chronology", "#/chronology", "nav_chronology"],
|
|
137
|
+
["work", "#/work", "nav_work"]];
|
|
138
|
+
if (C.meta.units_page) items.push(["units", "#/units", "nav_" + C.meta.units_page]);
|
|
139
|
+
items.push(["rules", "#/rules", "nav_rules"], ["guides", "#/guides", "nav_guides"], ["documents", "#/documents", "nav_documents"]);
|
|
140
|
+
if (C.meta.has_kb) items.push(["map", "#/map", "nav_map"]);
|
|
141
|
+
if (C.meta.has_sources) items.push(["sources", "#/sources", "nav_sources"]);
|
|
142
|
+
items.forEach(function (it) {
|
|
143
|
+
navLinks[it[0]] = link(it[1], t(it[2]));
|
|
144
|
+
if (it[0] === "map") {
|
|
145
|
+
// the side bar's Map always opens the topics view
|
|
146
|
+
navLinks.map.addEventListener("click", function () {
|
|
147
|
+
mapView = "topics";
|
|
148
|
+
if (location.hash === "#/map") pageMap();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
nav.appendChild(navLinks[it[0]]);
|
|
152
|
+
});
|
|
153
|
+
var stamp = C.meta.git
|
|
154
|
+
? t("stamp_git", { date: C.meta.generated, commit: C.meta.commit }) +
|
|
155
|
+
(C.meta.changed === 1 ? t("stamp_changed_one")
|
|
156
|
+
: C.meta.changed > 1 ? t("stamp_changed_many", { n: C.meta.changed }) : "")
|
|
157
|
+
: t("stamp_no_git", { date: C.meta.generated });
|
|
158
|
+
var rail = el("aside", { "class": "rail" },
|
|
159
|
+
el("div", { "class": "brand" }, el("div", { "class": "kicker", text: t("site_name") }),
|
|
160
|
+
el("div", { "class": "name", text: C.home.title || t("site_name") })),
|
|
161
|
+
nav,
|
|
162
|
+
el("button", { "class": "searchbtn", type: "button", onclick: openSearch },
|
|
163
|
+
el("span", { text: t("nav_search") }), el("kbd", { text: t("search_hint") })),
|
|
164
|
+
el("div", { "class": "stamp", text: stamp }));
|
|
165
|
+
mainEl = el("main");
|
|
166
|
+
app.textContent = "";
|
|
167
|
+
add(app, [rail, mainEl]);
|
|
168
|
+
document.title = (C.home.title ? C.home.title + " — " : "") + t("site_name");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function main() {
|
|
172
|
+
mainEl.className = "";
|
|
173
|
+
mainEl.textContent = "";
|
|
174
|
+
for (var i = 0; i < arguments.length; i++) add(mainEl, arguments[i]);
|
|
175
|
+
window.scrollTo(0, 0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function setNav(key) {
|
|
179
|
+
Object.keys(navLinks).forEach(function (k) { navLinks[k].className = k === key ? "on" : ""; });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function fail() {
|
|
183
|
+
app.textContent = "";
|
|
184
|
+
add(app, el("main", null, el("p", { "class": "empty", text: t("incomplete") })));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function notFound() {
|
|
188
|
+
main(el("p", { "class": "empty" }, t("not_found"), " ", link("#/", t("back_home"))));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function heading(title, why) {
|
|
192
|
+
return [el("h1", { text: title }), why ? el("p", { "class": "why", text: why }) : null];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ------------------------------------------------------------- the pages
|
|
196
|
+
|
|
197
|
+
function eventList(events) {
|
|
198
|
+
return el("ul", { "class": "events" }, events.map(function (e) {
|
|
199
|
+
return el("li", { "class": "event" },
|
|
200
|
+
el("span", { "class": "date", text: e.date }),
|
|
201
|
+
el("span", null, el("div", { "class": "kind", text: t("event_" + e.kind) }),
|
|
202
|
+
e.source && C.meta.has_kb ? link("#/source/" + encodeURIComponent(e.source), e.label)
|
|
203
|
+
: e.path ? docLink(e.path, e.label) : e.label));
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function pageHome() {
|
|
208
|
+
var h = C.home;
|
|
209
|
+
setNav("home");
|
|
210
|
+
var northStar = h.vision && h.vision.north_star
|
|
211
|
+
? el("section", { "class": "north-star" },
|
|
212
|
+
el("h2", { text: h.vision.section_title }),
|
|
213
|
+
el("p", { "class": "why", text: t("home_north_star_why") }),
|
|
214
|
+
html(el("div", { "class": "prose" }), h.vision.section_html),
|
|
215
|
+
el("div", { "class": "links" },
|
|
216
|
+
el("span", { "class": "status", text: t("home_vision_status", { status: h.vision.status || "—" }) }),
|
|
217
|
+
docLink(h.vision.path, t("home_vision"))))
|
|
218
|
+
: null;
|
|
219
|
+
var project = el("section", { "class": "block" },
|
|
220
|
+
el("h2", { text: t("home_project") }),
|
|
221
|
+
el("p", { "class": "why", text: t("home_project_why") }),
|
|
222
|
+
h.vision
|
|
223
|
+
? el("div", { "class": "vision-box" },
|
|
224
|
+
el("div", { "class": "status", text: t("home_vision_status", { status: h.vision.status || "—" }) }),
|
|
225
|
+
!h.vision.north_star && h.vision.section_title ? el("h3", { text: h.vision.section_title }) : null,
|
|
226
|
+
!h.vision.north_star ? html(el("div", { "class": "prose" }), h.vision.section_html) : null,
|
|
227
|
+
docLink(h.vision.path, t("home_vision")))
|
|
228
|
+
: el("p", { "class": "muted", text: t("home_vision_none") }),
|
|
229
|
+
el("div", { "class": "links" },
|
|
230
|
+
h.links.readme ? docLink(h.links.readme, t("home_readme")) : null,
|
|
231
|
+
link("#/rules", t("home_rules")),
|
|
232
|
+
h.links.manifest ? docLink(h.links.manifest, t("home_manifest")) : null,
|
|
233
|
+
h.links.router ? docLink(h.links.router, t("home_router")) : null));
|
|
234
|
+
var lately = el("section", { "class": "block" },
|
|
235
|
+
el("h2", null, link("#/chronology", t("home_lately"))),
|
|
236
|
+
el("p", { "class": "why", text: t("home_lately_why") }),
|
|
237
|
+
h.lately.length ? eventList(h.lately) : el("p", { "class": "muted", text: t("chronology_empty") }),
|
|
238
|
+
link("#/chronology", t("home_all_chronology"), "more"));
|
|
239
|
+
var open = el("section", { "class": "block" },
|
|
240
|
+
el("h2", null, link("#/work", t("home_open"))),
|
|
241
|
+
el("p", { "class": "why", text: t("home_open_why") }),
|
|
242
|
+
h.open.length
|
|
243
|
+
? el("ul", null, h.open.map(function (c) {
|
|
244
|
+
return el("li", null, docLink(c.path, c.name), " ", c.status ? el("span", { "class": "pill", text: c.status }) : null,
|
|
245
|
+
c.next ? el("div", { "class": "muted", text: c.next }) : null);
|
|
246
|
+
}))
|
|
247
|
+
: el("p", { "class": "muted", text: t("work_empty") }),
|
|
248
|
+
el("div", { "class": "links" },
|
|
249
|
+
link("#/work", t("home_all_work")),
|
|
250
|
+
h.links.notes ? docLink(h.links.notes, t("home_notes")) : null,
|
|
251
|
+
h.links.register ? docLink(h.links.register, t("home_register")) : null));
|
|
252
|
+
var known = el("section", { "class": "block" },
|
|
253
|
+
el("h2", null, C.meta.has_kb ? link("#/map", t("home_known")) : t("home_known")),
|
|
254
|
+
el("p", { "class": "why", text: t("home_known_why") }),
|
|
255
|
+
h.known.length
|
|
256
|
+
? h.known.map(function (a) {
|
|
257
|
+
return el("div", { "class": "area-row" }, link("#/topic/" + encodeURIComponent(a.slug), a.name, "area"),
|
|
258
|
+
el("div", { "class": "chips" }, a.topics.map(function (s) {
|
|
259
|
+
return link("#/topic/" + encodeURIComponent(s), s, "chip");
|
|
260
|
+
})));
|
|
261
|
+
})
|
|
262
|
+
: el("p", { "class": "muted", text: t("home_known_none") }),
|
|
263
|
+
C.meta.has_kb ? link("#/map", t("home_map"), "more") : null);
|
|
264
|
+
var head = el("div", { "class": "home-head" },
|
|
265
|
+
el("h1", { text: h.title || t("home_no_title") }),
|
|
266
|
+
html(el("div", { "class": "intro" }), h.intro_html));
|
|
267
|
+
main(head, northStar, el("div", { "class": "quad" }, project, lately, open, known));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function pageChronology() {
|
|
271
|
+
setNav("chronology");
|
|
272
|
+
main(heading(t("chronology_title"), t("chronology_why")),
|
|
273
|
+
C.chronology.length ? eventList(C.chronology) : el("p", { "class": "empty", text: t("chronology_empty") }));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function pageWork() {
|
|
277
|
+
setNav("work");
|
|
278
|
+
var w = C.work;
|
|
279
|
+
var cards = w.cards.length
|
|
280
|
+
? el("div", { "class": "cards" }, w.cards.map(function (c) {
|
|
281
|
+
var rows = [];
|
|
282
|
+
function row(label, value) { if (written(value)) rows.push(el("dt", { text: t(label) }), el("dd", null, value)); }
|
|
283
|
+
row("work_status", c.status ? statusPill(c.status, c.group) : null);
|
|
284
|
+
row("work_level", c.level ? levelLabel(c.level) : null);
|
|
285
|
+
row("work_since", c.since);
|
|
286
|
+
row("work_updated", c.updated);
|
|
287
|
+
row("work_branch", c.branch ? el("code", { text: c.branch }) : null);
|
|
288
|
+
return el("article", { "class": "card" },
|
|
289
|
+
el("h3", { text: c.name }),
|
|
290
|
+
el("dl", null, rows),
|
|
291
|
+
c.next ? el("div", { "class": "next" }, el("b", { text: t("work_next") }), c.next) : null,
|
|
292
|
+
el("div", { "class": "links" }, docLink(c.path, t("work_resume"))));
|
|
293
|
+
}))
|
|
294
|
+
: el("p", { "class": "empty", text: t("work_empty") });
|
|
295
|
+
main(heading(t("work_title"), t("work_why")), cards,
|
|
296
|
+
w.notes ? [el("h2", { text: t("work_notes") }), el("p", { "class": "why", text: t("work_notes_why") }),
|
|
297
|
+
html(el("div", { "class": "prose prose-box" }), w.notes.html)] : null,
|
|
298
|
+
w.register ? el("p", null, docLink(w.register, t("work_register"))) : null);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
var unitsView = "columns", unitsLens = "";
|
|
302
|
+
|
|
303
|
+
function pageUnits() {
|
|
304
|
+
if (!C.meta.units_page) return notFound();
|
|
305
|
+
setNav("units");
|
|
306
|
+
var units = C.units || [];
|
|
307
|
+
var title = t("units_" + C.meta.units_page);
|
|
308
|
+
if (!units.length) return main(heading(title, t("units_why")), el("p", { "class": "empty", text: t("units_empty") }));
|
|
309
|
+
var lenses = units.map(function (u) { return u.lens; }).filter(function (v, i, a) { return a.indexOf(v) === i; }).sort();
|
|
310
|
+
var shown = units.filter(function (u) { return !unitsLens || u.lens === unitsLens; });
|
|
311
|
+
function lensName(l) { return Object.prototype.hasOwnProperty.call(S, "lens_" + l) ? t("lens_" + l) : l; }
|
|
312
|
+
function dates(u) { return [u.start, u.end].filter(written).join(" → "); }
|
|
313
|
+
var bar = el("div", { "class": "toolbar" },
|
|
314
|
+
el("span", { "class": "seg" },
|
|
315
|
+
el("button", { type: "button", "class": unitsView === "columns" ? "on" : "", onclick: function () { unitsView = "columns"; pageUnits(); } }, t("units_columns")),
|
|
316
|
+
el("button", { type: "button", "class": unitsView === "list" ? "on" : "", onclick: function () { unitsView = "list"; pageUnits(); } }, t("units_list"))),
|
|
317
|
+
lenses.length > 1
|
|
318
|
+
? el("label", null, t("units_lens"), " ",
|
|
319
|
+
el("select", { onchange: function (e) { unitsLens = e.target.value; pageUnits(); } },
|
|
320
|
+
el("option", { value: "", text: t("units_lens_all") }),
|
|
321
|
+
lenses.map(function (l) { return el("option", { value: l, text: lensName(l), selected: l === unitsLens }); })))
|
|
322
|
+
: null);
|
|
323
|
+
var body;
|
|
324
|
+
if (unitsView === "columns") {
|
|
325
|
+
var groups = [];
|
|
326
|
+
shown.forEach(function (u) { if (groups.indexOf(u.group) < 0) groups.push(u.group); });
|
|
327
|
+
groups.sort(function (a, b) { return a - b; });
|
|
328
|
+
body = el("div", { "class": "columns" }, groups.map(function (g) {
|
|
329
|
+
return el("section", { "class": "column" }, el("h3", { text: t("group_" + g) }),
|
|
330
|
+
shown.filter(function (u) { return u.group === g; }).map(function (u) {
|
|
331
|
+
return el("div", { "class": "unit" },
|
|
332
|
+
el("span", { "class": "id", text: u.id || "" }),
|
|
333
|
+
docLink(u.path, u.name),
|
|
334
|
+
el("div", { "class": "muted" }, [u.status, u.level ? levelLabel(u.level) : null, lensName(u.lens), dates(u)].filter(written).join(" · ")));
|
|
335
|
+
}));
|
|
336
|
+
}));
|
|
337
|
+
} else {
|
|
338
|
+
body = el("table", { "class": "list" },
|
|
339
|
+
el("thead", null, el("tr", null, [t("units_id"), t("units_name"), t("work_status"), t("work_level"), t("units_lens"), t("units_dates")].map(function (h) { return el("th", { text: h }); }))),
|
|
340
|
+
el("tbody", null, shown.map(function (u) {
|
|
341
|
+
return el("tr", null, el("td", null, el("code", { text: u.id || "" })), el("td", null, docLink(u.path, u.name)),
|
|
342
|
+
el("td", null, u.status ? statusPill(u.status, u.group) : ""), el("td", { text: u.level ? levelLabel(u.level) : "" }),
|
|
343
|
+
el("td", { text: lensName(u.lens) }), el("td", { text: dates(u) }));
|
|
344
|
+
})));
|
|
345
|
+
}
|
|
346
|
+
main(heading(title, t("units_why")), bar, body);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function ruleList(items, withStatus) {
|
|
350
|
+
return el("ul", null, items.map(function (r) {
|
|
351
|
+
return el("li", null, docLink(r.path, r.title),
|
|
352
|
+
withStatus && r.status ? [" ", el("span", { "class": "pill", text: r.status })] : null,
|
|
353
|
+
r.when ? el("div", { "class": "muted", text: withStatus ? r.when : t("rules_when", { when: r.when }) }) : null);
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function pageRules() {
|
|
358
|
+
setNav("rules");
|
|
359
|
+
var r = C.rules;
|
|
360
|
+
var any = r.vision.length || r.decisions.length;
|
|
361
|
+
main(heading(t("rules_title"), t("rules_why")),
|
|
362
|
+
any ? null : el("p", { "class": "empty", text: t("rules_empty") }),
|
|
363
|
+
r.vision.length ? [el("h2", { text: t("rules_vision") }), ruleList(r.vision, true)] : null,
|
|
364
|
+
r.decisions.length ? [el("h2", { text: t("rules_decisions") }), el("p", { "class": "why", text: t("rules_decisions_why") }), ruleList(r.decisions)] : null,
|
|
365
|
+
el("div", { "class": "links" }, r.manifest ? docLink(r.manifest, t("rules_manifest")) : null,
|
|
366
|
+
link("#/guides", t("rules_see_guides"))));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// The guides (R26): which guide to read before which task, the operative guides with
|
|
370
|
+
// when to read each and their status as declared, then the maps of complex parts.
|
|
371
|
+
function guideList(items) {
|
|
372
|
+
return el("ul", { "class": "guides" }, items.map(function (g) {
|
|
373
|
+
return el("li", null, docLink(g.path, g.title),
|
|
374
|
+
g.status ? [" ", el("span", { "class": "pill", text: g.status })] : null,
|
|
375
|
+
g.when ? el("div", { "class": "muted", text: t("rules_when", { when: g.when }) }) : null);
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function pageGuides() {
|
|
380
|
+
setNav("guides");
|
|
381
|
+
var g = C.guides;
|
|
382
|
+
var any = g.operative.length || g.comprehension.length;
|
|
383
|
+
main(heading(t("guides_title"), t("guides_why")),
|
|
384
|
+
g.router ? el("p", { "class": "router" }, docLink(g.router, t("guides_router")),
|
|
385
|
+
el("span", { "class": "why-line", text: t("guides_router_why") })) : null,
|
|
386
|
+
any ? null : el("p", { "class": "empty", text: t("guides_empty") }),
|
|
387
|
+
g.operative.length ? [el("h2", { text: t("guides_operative") }), el("p", { "class": "why", text: t("guides_operative_why") }), guideList(g.operative)] : null,
|
|
388
|
+
g.comprehension.length ? [el("h2", { text: t("guides_comprehension") }), el("p", { "class": "why", text: t("guides_comprehension_why") }), guideList(g.comprehension)] : null);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function folderName(f) {
|
|
392
|
+
var key = "folder_" + f.replace(/[\/\\]/g, "_");
|
|
393
|
+
return Object.prototype.hasOwnProperty.call(S, key) ? t(key) : f;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function pageDocuments() {
|
|
397
|
+
setNav("documents");
|
|
398
|
+
main(heading(t("documents_title"), t("documents_why")),
|
|
399
|
+
C.tree.length
|
|
400
|
+
? el("div", { "class": "tree" }, C.tree.map(function (f) {
|
|
401
|
+
return el("details", { open: C.tree.length < 8 },
|
|
402
|
+
el("summary", null, folderName(f.folder), f.folder ? el("span", { "class": "muted", text: f.folder + "/" }) : null),
|
|
403
|
+
el("ul", null, f.docs.map(function (d) { return el("li", null, docLink(d.path, d.title)); })));
|
|
404
|
+
}))
|
|
405
|
+
: el("p", { "class": "empty", text: t("documents_empty") }));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function docAt(path, id) { return docHref(path) + "?at=" + encodeURIComponent(id); }
|
|
409
|
+
|
|
410
|
+
function pageDoc(address) {
|
|
411
|
+
setNav("documents");
|
|
412
|
+
var at = address.split("?at=");
|
|
413
|
+
var path = at[0];
|
|
414
|
+
needDocs(function () {
|
|
415
|
+
var d = D[path];
|
|
416
|
+
if (!d) return notFound();
|
|
417
|
+
// the document's own first heading is its title; the body follows the chain
|
|
418
|
+
var body = d.html.replace(/^<h1[^>]*>[\s\S]*?<\/h1>/, "");
|
|
419
|
+
main(el("h1", { text: d.title }),
|
|
420
|
+
el("p", { "class": "muted" }, el("code", { text: path })),
|
|
421
|
+
d.meta.length ? el("div", { "class": "doc-meta", title: t("doc_about") }, d.meta.map(function (kv) {
|
|
422
|
+
return el("span", { "class": "label" }, el("b", { text: kv[0] }), kv[1]);
|
|
423
|
+
})) : null,
|
|
424
|
+
d.chain ? chainStrip(d.chain) : null,
|
|
425
|
+
html(el("article", { "class": "prose" }), body));
|
|
426
|
+
if (at[1]) {
|
|
427
|
+
var dest = document.getElementById(at[1]);
|
|
428
|
+
if (dest) dest.scrollIntoView();
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// An analysis read as the chain the agent works through (R24): one row per step,
|
|
434
|
+
// each linking to its sections as written; a step with none says so. Nothing here
|
|
435
|
+
// counts, scores or marks completeness.
|
|
436
|
+
function chainStrip(chain) {
|
|
437
|
+
function jump(id, label) {
|
|
438
|
+
return el("a", { href: "#" + id, onclick: function (e) {
|
|
439
|
+
e.preventDefault();
|
|
440
|
+
var dest = document.getElementById(id);
|
|
441
|
+
if (dest) dest.scrollIntoView({ behavior: "smooth" });
|
|
442
|
+
} }, label);
|
|
443
|
+
}
|
|
444
|
+
var c = chain.companions || {};
|
|
445
|
+
return el("nav", { "class": "chain", "aria-label": t("chain_title") },
|
|
446
|
+
el("h2", { text: t("chain_title") }),
|
|
447
|
+
el("ol", null, chain.steps.map(function (s) {
|
|
448
|
+
// what this analysis has for the step: its sections (for "merge and tests", the
|
|
449
|
+
// unit's own merge brief and E2E script). Only that decides "Not in this analysis";
|
|
450
|
+
// the project's North Star and the request record sit beside it, never in place of it.
|
|
451
|
+
var own = s.sections.map(function (sec) { return jump(sec.id, sec.title); });
|
|
452
|
+
if (s.step === "merge_tests") {
|
|
453
|
+
if (c.merge) own.push(docLink(c.merge, t("chain_merge")));
|
|
454
|
+
if (c.e2e) own.push(docLink(c.e2e, t("chain_e2e")));
|
|
455
|
+
}
|
|
456
|
+
var beside = [];
|
|
457
|
+
if (s.step === "vision" && chain.north_star) beside.push(link(docAt(chain.north_star.path, chain.north_star.id), t("chain_north_star")));
|
|
458
|
+
if (s.step === "vision" && c.request) beside.push(docLink(c.request, t("chain_request")));
|
|
459
|
+
return el("li", { "class": own.length ? "" : "absent" },
|
|
460
|
+
el("span", { "class": "step" }, el("b", { text: t("chain_step_" + s.step) }),
|
|
461
|
+
el("span", { "class": "why-line", text: t("chain_step_" + s.step + "_why") })),
|
|
462
|
+
el("span", { "class": "links" }, own.length ? own : el("span", { "class": "muted", text: t("chain_absent") }), beside));
|
|
463
|
+
})));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ----------------------------------------------------- what is known
|
|
467
|
+
|
|
468
|
+
// The one way a fact's text becomes a label or a tooltip: a fact resting on a
|
|
469
|
+
// person's statement says so wherever its text appears (criterion 6).
|
|
470
|
+
function factTitle(f) { return f.statement ? t("statement_prefix", { text: f.text }) : f.text; }
|
|
471
|
+
|
|
472
|
+
function factLine(id, opts) {
|
|
473
|
+
var f = K.facts[id];
|
|
474
|
+
if (!f) return el("li", { "class": "fact" }, el("span"), el("span", { "class": "muted", text: id + " — " + t("fact_unknown") }));
|
|
475
|
+
var oc = originClass(f.origin);
|
|
476
|
+
var marks = [el("span", { "class": "tag o-" + oc, title: originWhy(f.origin), text: originLabel(f.origin) })];
|
|
477
|
+
if (f.statement) marks.push(el("span", { "class": "tag statement", title: t("statement_notice"), text: t("statement_mark") }));
|
|
478
|
+
if (f.state === "superseded") marks.push(el("span", { "class": "tag replaced", title: t("state_superseded_why"), text: t("state_superseded") }));
|
|
479
|
+
if (f.state === "contested") marks.push(el("span", { "class": "tag conflict", title: t("state_contested_why"), text: t("state_contested") }));
|
|
480
|
+
if (opts && opts.topic) marks.push(link("#/topic/" + encodeURIComponent(f.topic), f.topic, "chip"));
|
|
481
|
+
return el("li", { "class": "fact" + (f.state === "superseded" ? " replaced" : ""), onclick: function () { location.hash = "#/fact/" + encodeURIComponent(id); } },
|
|
482
|
+
el("span", { "class": "dot bg-" + oc }),
|
|
483
|
+
el("span", null, el("span", { "class": "text", text: f.text }), el("div", { "class": "marks" }, marks)));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function topicPage(slug) {
|
|
487
|
+
needKb(function () {
|
|
488
|
+
var tp = K.topics[slug];
|
|
489
|
+
if (!tp) return notFound();
|
|
490
|
+
if (tp.merged_into) { location.replace("#/topic/" + encodeURIComponent(tp.merged_into)); return; }
|
|
491
|
+
setNav("map");
|
|
492
|
+
var filterText = "", filterOrigin = "";
|
|
493
|
+
var list = el("ul", { "class": "facts" });
|
|
494
|
+
function refill() {
|
|
495
|
+
list.textContent = "";
|
|
496
|
+
var q = normalize(filterText).split(" ").filter(Boolean);
|
|
497
|
+
var ids = tp.facts.filter(function (id) {
|
|
498
|
+
var f = K.facts[id];
|
|
499
|
+
if (!f) return true;
|
|
500
|
+
if (filterOrigin && f.origin !== filterOrigin) return false;
|
|
501
|
+
var n = normalize(f.text);
|
|
502
|
+
return q.every(function (w) { return n.indexOf(w) >= 0; });
|
|
503
|
+
});
|
|
504
|
+
add(list, ids.length ? ids.map(function (id) { return factLine(id); }) : el("li", { "class": "muted", text: t("topic_no_facts") }));
|
|
505
|
+
}
|
|
506
|
+
function chipsOf(slugs) {
|
|
507
|
+
return el("div", { "class": "chips" }, slugs.map(function (s) { return link("#/topic/" + encodeURIComponent(s), K.topics[s] ? K.topics[s].name : s, "chip"); }));
|
|
508
|
+
}
|
|
509
|
+
var dl = [];
|
|
510
|
+
function row(label, v) { if (v) dl.push(el("dt", { text: t(label) }), el("dd", null, v)); }
|
|
511
|
+
row("topic_area", tp.area ? link("#/topic/" + encodeURIComponent(tp.area), K.topics[tp.area].name) : el("span", { "class": "muted", text: t("topic_no_area") }));
|
|
512
|
+
row("topic_parents", tp.parents.length ? chipsOf(tp.parents) : null);
|
|
513
|
+
row("topic_aka", tp.aka.length ? tp.aka.join(" · ") : null);
|
|
514
|
+
row("topic_gaps", tp.gaps.length ? tp.gaps.join(" · ") : null);
|
|
515
|
+
row("topic_related", tp.related.length ? chipsOf(tp.related) : null);
|
|
516
|
+
row("topic_children", tp.children.length ? chipsOf(tp.children) : null);
|
|
517
|
+
var used = tp.facts.map(function (id) { return K.facts[id] && K.facts[id].origin; })
|
|
518
|
+
.filter(function (v, i, a) { return v && a.indexOf(v) === i; });
|
|
519
|
+
main(el("h1", { text: tp.name }),
|
|
520
|
+
el("p", { "class": "muted" }, el("code", { text: slug }), " · ",
|
|
521
|
+
tp.count === 1 ? t("topic_facts_one") : t("topic_facts_count", { n: tp.count }), " · ",
|
|
522
|
+
link("#/map/" + encodeURIComponent(slug), t("topic_on_map"))),
|
|
523
|
+
tp.description ? el("p", { "class": "intro", text: tp.description }) : null,
|
|
524
|
+
el("div", { "class": "panel-inline" }, el("dl", { "class": "card", style: null }, dl)),
|
|
525
|
+
el("h2", { text: t("topic_facts") }),
|
|
526
|
+
el("div", { "class": "toolbar" },
|
|
527
|
+
el("input", { type: "search", placeholder: t("topic_filter"), oninput: function (e) { filterText = e.target.value; refill(); } }),
|
|
528
|
+
used.length > 1 ? el("select", { onchange: function (e) { filterOrigin = e.target.value; refill(); } },
|
|
529
|
+
el("option", { value: "", text: t("topic_origin_all") }),
|
|
530
|
+
used.map(function (o) { return el("option", { value: o, text: originLabel(o) }); })) : null),
|
|
531
|
+
list);
|
|
532
|
+
refill();
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function whereText(w) {
|
|
537
|
+
if (!w) return "";
|
|
538
|
+
if (w.page) return t("fact_page", { n: w.page });
|
|
539
|
+
if (w.lines) return w.lines[0] === w.lines[1] ? t("fact_line", { a: w.lines[0] }) : t("fact_lines", { a: w.lines[0], b: w.lines[1] });
|
|
540
|
+
return "";
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function sourceBlock(s) {
|
|
544
|
+
var target = s.source
|
|
545
|
+
? link("#/source/" + encodeURIComponent(s.source), s.source)
|
|
546
|
+
: s.path.indexOf("corpus/notes/") === 0 ? docLink(s.path, s.path.split("/").pop()) : docLink(s.path);
|
|
547
|
+
var body = [];
|
|
548
|
+
if (s.passage !== undefined) {
|
|
549
|
+
body.push(el("div", { "class": "passage" }, s.before || "", el("mark", { text: s.passage }), s.after || ""));
|
|
550
|
+
} else if (s.reason) {
|
|
551
|
+
body.push(el("div", { "class": "reason", text: t("reason_" + s.reason, s.where || {}) }));
|
|
552
|
+
}
|
|
553
|
+
return el("div", { "class": "src" },
|
|
554
|
+
el("div", { "class": "head" }, target, el("span", { "class": "muted", text: whereText(s.where) })),
|
|
555
|
+
body);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function factPanel(id) {
|
|
559
|
+
needKb(function () {
|
|
560
|
+
closePanel();
|
|
561
|
+
var f0 = K.facts[id];
|
|
562
|
+
if (!lastPage) {
|
|
563
|
+
lastPage = f0 && K.topics[f0.topic] ? "#/topic/" + encodeURIComponent(f0.topic) : "#/";
|
|
564
|
+
if (f0 && K.topics[f0.topic]) topicPage(f0.topic); else pageHome();
|
|
565
|
+
}
|
|
566
|
+
var f = K.facts[id];
|
|
567
|
+
var box = el("aside", { "class": "panel" });
|
|
568
|
+
var close = el("button", { "class": "close", type: "button", onclick: leavePanel }, t("fact_close"));
|
|
569
|
+
if (!f) { add(box, [close, el("h2", { text: t("fact_title") }), el("p", { "class": "empty", text: t("fact_unknown") })]); return openPanel(box); }
|
|
570
|
+
var dl = [];
|
|
571
|
+
function row(label, v) { if (v) dl.push(el("dt", { text: t(label) }), el("dd", null, v)); }
|
|
572
|
+
row("fact_origin", el("span", null, el("span", { "class": "o-" + originClass(f.origin), text: originLabel(f.origin) }),
|
|
573
|
+
el("span", { "class": "why-line", text: originWhy(f.origin) })));
|
|
574
|
+
if (written(f.valid)) row("fact_valid", f.valid);
|
|
575
|
+
if (written(f.qty)) row("fact_qty", f.qty);
|
|
576
|
+
if (written(f.about)) row("fact_about", f.about);
|
|
577
|
+
row("fact_topic", link("#/topic/" + encodeURIComponent(f.topic), K.topics[f.topic] ? K.topics[f.topic].name : f.topic));
|
|
578
|
+
var parts = [];
|
|
579
|
+
if (f.state === "superseded") {
|
|
580
|
+
parts.push(el("h3", null, el("span", { "class": "tag replaced", text: t("state_superseded") }), " ", t("fact_replaced_by")),
|
|
581
|
+
el("p", { "class": "why-line", text: t("state_superseded_why") }),
|
|
582
|
+
el("ul", { "class": "facts" }, (f.successors || []).map(function (s) { return factLine(s, { topic: true }); })),
|
|
583
|
+
el("h3", { text: t("fact_current_at_end") }),
|
|
584
|
+
el("ul", { "class": "facts" }, (f.chain_ends || []).map(function (e) {
|
|
585
|
+
if (e.stop) return el("li", { "class": "reason", text: t("chain_" + e.stop, { id: e.id }) });
|
|
586
|
+
return factLine(e.id, { topic: true });
|
|
587
|
+
})));
|
|
588
|
+
}
|
|
589
|
+
if (f.state === "contested") {
|
|
590
|
+
parts.push(el("h3", null, el("span", { "class": "tag conflict", text: t("state_contested") }), " ", t("fact_in_conflict_with")),
|
|
591
|
+
el("p", { "class": "why-line", text: t("state_contested_why") }),
|
|
592
|
+
el("ul", { "class": "facts" }, (f.with || []).map(function (s) { return factLine(s, { topic: true }); })));
|
|
593
|
+
}
|
|
594
|
+
if (f.grounds) {
|
|
595
|
+
parts.push(el("h3", { text: t("fact_rests_on") }),
|
|
596
|
+
el("p", { "class": "why-line", text: t("ground_class_" + f.ground_class) }),
|
|
597
|
+
el("ul", null, f.grounds.map(function (g) {
|
|
598
|
+
var what = t("ground_" + g.kind);
|
|
599
|
+
var where = g.path ? (g.path.indexOf("corpus/given/") === 0
|
|
600
|
+
? link("#/source/" + encodeURIComponent(g.path.slice(13)), g.path.slice(13))
|
|
601
|
+
: docLink(g.path, g.path.split("/").pop())) : null;
|
|
602
|
+
return el("li", null, el("b", { text: what }), where ? [" — ", where] : null,
|
|
603
|
+
g.kind === "untraceable" ? el("div", { "class": "reason" }, t("fact_untraced"), g.entry ? [" ", el("code", { text: g.entry })] : null) : null);
|
|
604
|
+
})));
|
|
605
|
+
}
|
|
606
|
+
parts.push(el("h3", { text: t("fact_sources") }), f.sources.map(sourceBlock));
|
|
607
|
+
add(box, [close, el("div", { "class": "muted" }, el("code", { text: id })),
|
|
608
|
+
el("div", { "class": "ftext", text: f.text }),
|
|
609
|
+
f.statement ? el("div", { "class": "statement-notice" }, el("b", { text: t("statement_mark") + ": " }), t("statement_notice")) : null,
|
|
610
|
+
el("dl", null, dl), parts]);
|
|
611
|
+
openPanel(box);
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function leavePanel() { location.hash = lastPage || "#/"; }
|
|
616
|
+
|
|
617
|
+
function openPanel(box) { panelEl = box; document.body.appendChild(box); }
|
|
618
|
+
function closePanel() {
|
|
619
|
+
Array.prototype.forEach.call(document.querySelectorAll(".panel"), function (p) { p.remove(); });
|
|
620
|
+
panelEl = null;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function readText(r) {
|
|
624
|
+
if (!r) return "";
|
|
625
|
+
if (r.kind === "complete") return t("read_complete");
|
|
626
|
+
if (r.kind === "page") return r.of ? t("read_page_of", { n: r.n, m: r.of }) : t("read_page", { n: r.n });
|
|
627
|
+
if (r.kind === "line") return r.of ? t("read_line_of", { n: r.n, m: r.of }) : t("read_line", { n: r.n });
|
|
628
|
+
if (r.kind === "unreadable") return t("read_unreadable");
|
|
629
|
+
return t("read_unrecorded");
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function pageSources() {
|
|
633
|
+
needKb(function () {
|
|
634
|
+
setNav("sources");
|
|
635
|
+
var names = Object.keys(K.sources).sort();
|
|
636
|
+
if (!names.length) return main(heading(t("sources_title"), t("sources_why")), el("p", { "class": "empty", text: t("sources_empty") }));
|
|
637
|
+
main(heading(t("sources_title"), t("sources_why")),
|
|
638
|
+
el("table", { "class": "list" },
|
|
639
|
+
el("thead", null, el("tr", null, ["source_name", "source_kind", "source_date", "source_facts", "source_topics"].map(function (k) { return el("th", { text: t(k) }); }))),
|
|
640
|
+
el("tbody", null, names.map(function (n) {
|
|
641
|
+
var s = K.sources[n];
|
|
642
|
+
return el("tr", null, el("td", null, link("#/source/" + encodeURIComponent(n), n)), el("td", { text: s.kind }),
|
|
643
|
+
el("td", { text: s.date }), el("td", { text: String(s.facts) }),
|
|
644
|
+
el("td", null, el("div", { "class": "chips" }, s.topics.map(function (tp) { return link("#/topic/" + encodeURIComponent(tp), tp, "chip"); }))));
|
|
645
|
+
}))));
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function pageSource(name) {
|
|
650
|
+
needKb(function () {
|
|
651
|
+
var s = K.sources[name];
|
|
652
|
+
if (!s) return notFound();
|
|
653
|
+
setNav("sources");
|
|
654
|
+
var dl = [];
|
|
655
|
+
function row(label, v) { if (written(v) || (v && v.nodeType)) dl.push(el("dt", { text: t(label) }), el("dd", null, v)); }
|
|
656
|
+
row("source_kind", s.kind);
|
|
657
|
+
row("source_date", s.date);
|
|
658
|
+
row("source_facts", String(s.facts));
|
|
659
|
+
row("source_read", readText(s.read));
|
|
660
|
+
row("source_newer", s.older_of ? link("#/source/" + encodeURIComponent(s.older_of), s.older_of) : null);
|
|
661
|
+
row("source_older", s.newer_of ? (K.sources[s.newer_of] ? link("#/source/" + encodeURIComponent(s.newer_of), s.newer_of) : s.newer_of) : null);
|
|
662
|
+
row("source_topics", s.topics.length ? el("div", { "class": "chips" }, s.topics.map(function (tp) { return link("#/topic/" + encodeURIComponent(tp), tp, "chip"); })) : null);
|
|
663
|
+
row("source_record", docLink(s.sidecar, s.sidecar.split("/").pop()));
|
|
664
|
+
var ids = Object.keys(K.facts).filter(function (id) {
|
|
665
|
+
return K.facts[id].sources.some(function (x) { return x.source === name; });
|
|
666
|
+
}).sort();
|
|
667
|
+
main(el("h1", { text: name }), el("dl", { "class": "card" }, dl),
|
|
668
|
+
el("h2", { text: t("source_facts_list") }),
|
|
669
|
+
el("ul", { "class": "facts" }, ids.map(function (id) { return factLine(id, { topic: true }); })));
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ----------------------------------------------------------- the map
|
|
674
|
+
|
|
675
|
+
var mapView = "topics";
|
|
676
|
+
var hidden = {};
|
|
677
|
+
|
|
678
|
+
function areaColors() {
|
|
679
|
+
var areas = K.order.filter(function (s) { return K.topics[s].area === s; });
|
|
680
|
+
var colors = {};
|
|
681
|
+
areas.forEach(function (a, i) {
|
|
682
|
+
var hue = Math.round((i * 137.508 + 28) % 360);
|
|
683
|
+
colors[a] = "hsl(" + hue + ", 46%, 62%)";
|
|
684
|
+
});
|
|
685
|
+
return colors;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function originColor(o) {
|
|
689
|
+
return getComputedStyle(document.documentElement).getPropertyValue("--o-" + originClass(o)).trim() || "#888";
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function pageMap(slug) {
|
|
693
|
+
needKb(function () {
|
|
694
|
+
setNav("map");
|
|
695
|
+
if (slug !== undefined && !K.topics[slug]) return notFound();
|
|
696
|
+
if (slug && K.topics[slug].merged_into) { location.replace("#/map/" + encodeURIComponent(K.topics[slug].merged_into)); return; }
|
|
697
|
+
// the address decides the view: #/map/<slug> is one topic up close; #/map is
|
|
698
|
+
// the topics view, or everything when its button was pressed (the side bar's
|
|
699
|
+
// Map resets to topics)
|
|
700
|
+
if (slug) { mapView = "close"; lastFocus = slug; }
|
|
701
|
+
else if (mapView === "close") mapView = "topics";
|
|
702
|
+
var focus = slug || lastFocus || K.order.filter(function (s) { return K.topics[s].area === s; })[0] || K.order[0];
|
|
703
|
+
var ctl = null;
|
|
704
|
+
var views = el("span", { "class": "seg" },
|
|
705
|
+
["topics", "close", "every"].map(function (v) {
|
|
706
|
+
return el("button", { type: "button", "class": mapView === v ? "on" : "", disabled: v === "close" && !focus, onclick: function () {
|
|
707
|
+
if (v === "close") { location.hash = "#/map/" + encodeURIComponent(focus); return; }
|
|
708
|
+
mapView = v;
|
|
709
|
+
if (slug) location.hash = "#/map"; else pageMap();
|
|
710
|
+
} }, t("map_view_" + v));
|
|
711
|
+
}));
|
|
712
|
+
var zoom = el("span", { "class": "seg" },
|
|
713
|
+
el("button", { type: "button", title: t("map_zoom_out_why"), onclick: function () { if (ctl) ctl.zoom(1 / 1.4); } }, t("map_zoom_out")),
|
|
714
|
+
el("button", { type: "button", title: t("map_zoom_in_why"), onclick: function () { if (ctl) ctl.zoom(1.4); } }, t("map_zoom_in")),
|
|
715
|
+
el("button", { type: "button", onclick: function () { if (ctl) ctl.fit(); } }, t("map_fit")));
|
|
716
|
+
var wrap = el("div", { "class": "mapwrap" });
|
|
717
|
+
var canvas = el("canvas");
|
|
718
|
+
var tip = el("div", { "class": "tip" });
|
|
719
|
+
wrap.appendChild(canvas);
|
|
720
|
+
wrap.appendChild(tip);
|
|
721
|
+
wrap.appendChild(el("div", { "class": "maphint", text: t(mapView === "close" ? "map_hint_close" : "map_hint") }));
|
|
722
|
+
main(heading(t("map_title"), t(mapView === "close" ? "map_why_close" : mapView === "every" ? "map_why_every" : "map_why")),
|
|
723
|
+
el("div", { "class": "toolbar" }, views, zoom,
|
|
724
|
+
mapView === "close" ? link("#/topic/" + encodeURIComponent(focus), t("map_open_topic", { name: K.topics[focus].name })) : null), wrap);
|
|
725
|
+
mainEl.className = "wide";
|
|
726
|
+
if (!K.order.length) { wrap.textContent = ""; add(wrap, el("p", { "class": "empty", text: t("map_empty") })); return; }
|
|
727
|
+
var g = mapView === "close" ? closeGraph(focus) : mapView === "every" ? everyGraph() : topicGraph();
|
|
728
|
+
wrap.appendChild(legend(g, function () { if (ctl) ctl.draw(); }));
|
|
729
|
+
ctl = canvasGraph(canvas, tip, g);
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
var lastFocus = null;
|
|
734
|
+
|
|
735
|
+
function topicGraph() {
|
|
736
|
+
var colors = areaColors();
|
|
737
|
+
var nodes = K.map.nodes.map(function (n) {
|
|
738
|
+
return { id: n.id, x: n.x, y: n.y, r: 5 + 2.2 * Math.sqrt(n.count), kind: "topic",
|
|
739
|
+
color: n.area ? colors[n.area] : "#6c747d", label: K.topics[n.id].name,
|
|
740
|
+
href: "#/map/" + encodeURIComponent(n.id), tip: K.topics[n.id].name + (n.area ? "" : " · " + t("map_no_area")) };
|
|
741
|
+
});
|
|
742
|
+
var edges = K.map.edges.map(function (e) {
|
|
743
|
+
return { a: e.a, b: e.b, style: e.kind === "related" ? "dash" : e.kind === "shared" ? "thin" : "solid",
|
|
744
|
+
w: e.kind === "shared" ? Math.min(4, 0.6 + Math.log(1 + e.n)) : 1.4 };
|
|
745
|
+
});
|
|
746
|
+
var keys = [["solid", "map_edge_part"], ["dash", "map_edge_related"], ["thin", "map_edge_shared"]];
|
|
747
|
+
return { nodes: nodes, edges: edges, labels: true, edgeKeys: keys, kinds: ["topic"], origins: false };
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function closeGraph(slug) {
|
|
751
|
+
var tp = K.topics[slug];
|
|
752
|
+
var nodes = [], edges = [], byId = {};
|
|
753
|
+
function node(n) { if (!byId[n.id]) { byId[n.id] = n; nodes.push(n); } return byId[n.id]; }
|
|
754
|
+
node({ id: "t:" + slug, x: 0, y: 0, r: 14, kind: "topic", color: "#d9a441", label: tp.name, href: "#/topic/" + encodeURIComponent(slug), tip: tp.name });
|
|
755
|
+
var ids = tp.facts.slice();
|
|
756
|
+
tp.facts.forEach(function (id) {
|
|
757
|
+
var f = K.facts[id];
|
|
758
|
+
if (!f) return;
|
|
759
|
+
(f.successors || []).concat(f.with || []).forEach(function (o) { if (ids.indexOf(o) < 0 && K.facts[o]) ids.push(o); });
|
|
760
|
+
});
|
|
761
|
+
var ring = Math.max(170, ids.length * 9);
|
|
762
|
+
ids.forEach(function (id, i) {
|
|
763
|
+
var f = K.facts[id];
|
|
764
|
+
if (!f) return;
|
|
765
|
+
var a = 2 * Math.PI * i / Math.max(1, ids.length);
|
|
766
|
+
node({ id: "f:" + id, x: ring * Math.cos(a), y: ring * Math.sin(a), r: 6, kind: "fact", origin: f.origin,
|
|
767
|
+
color: originColor(f.origin), dim: f.state === "superseded", conflict: f.state === "contested",
|
|
768
|
+
statement: f.statement, label: factTitle(f), href: "#/fact/" + encodeURIComponent(id), tip: factTitle(f) });
|
|
769
|
+
if (f.topic === slug) edges.push({ a: "f:" + id, b: "t:" + slug, style: "thin", w: 1 });
|
|
770
|
+
});
|
|
771
|
+
var outer = [], outerSet = {};
|
|
772
|
+
ids.forEach(function (id) {
|
|
773
|
+
var f = K.facts[id];
|
|
774
|
+
if (!f) return;
|
|
775
|
+
f.sources.forEach(function (s) { var k = s.source ? "s:" + s.source : "n:" + s.path; if (!outerSet[k]) { outerSet[k] = 1; outer.push([k, s]); } });
|
|
776
|
+
(f.grounds || []).forEach(function (gr) { if (gr.path) { var k = gr.path.indexOf("corpus/given/") === 0 ? "s:" + gr.path.slice(13) : "n:" + gr.path; if (!outerSet[k]) { outerSet[k] = 1; outer.push([k, { path: gr.path, source: k.slice(0, 2) === "s:" ? gr.path.slice(13) : null }]); } } });
|
|
777
|
+
});
|
|
778
|
+
var ring2 = ring + 170;
|
|
779
|
+
outer.forEach(function (o, i) {
|
|
780
|
+
var a = 2 * Math.PI * (i + 0.5) / Math.max(1, outer.length);
|
|
781
|
+
var s = o[1], isSrc = o[0].slice(0, 2) === "s:";
|
|
782
|
+
node({ id: o[0], x: ring2 * Math.cos(a), y: ring2 * Math.sin(a), r: 7, kind: isSrc ? "source" : "note",
|
|
783
|
+
color: isSrc ? "#c9c2b2" : "#8b93a0", square: true, label: (isSrc ? s.source : s.path.split("/").pop()),
|
|
784
|
+
href: isSrc ? "#/source/" + encodeURIComponent(s.source) : docHref(s.path), tip: isSrc ? s.source : s.path });
|
|
785
|
+
});
|
|
786
|
+
ids.forEach(function (id) {
|
|
787
|
+
var f = K.facts[id];
|
|
788
|
+
if (!f) return;
|
|
789
|
+
f.sources.forEach(function (s) { var k = s.source ? "s:" + s.source : "n:" + s.path; if (byId[k]) edges.push({ a: "f:" + id, b: k, style: "solid", w: 1 }); });
|
|
790
|
+
(f.grounds || []).forEach(function (gr) { if (!gr.path) return; var k = gr.path.indexOf("corpus/given/") === 0 ? "s:" + gr.path.slice(13) : "n:" + gr.path; if (byId[k]) edges.push({ a: "f:" + id, b: k, style: "based", w: 1.2 }); });
|
|
791
|
+
(f.successors || []).forEach(function (o) { if (byId["f:" + o]) edges.push({ a: "f:" + id, b: "f:" + o, style: "dash", w: 1.2 }); });
|
|
792
|
+
(f.with || []).forEach(function (o) { if (byId["f:" + o]) edges.push({ a: "f:" + id, b: "f:" + o, style: "conflict", w: 1.4 }); });
|
|
793
|
+
});
|
|
794
|
+
Object.keys(K.sources).forEach(function (n) {
|
|
795
|
+
var s = K.sources[n];
|
|
796
|
+
if (s.newer_of && byId["s:" + n] && byId["s:" + s.newer_of]) edges.push({ a: "s:" + n, b: "s:" + s.newer_of, style: "newer", w: 1.2 });
|
|
797
|
+
});
|
|
798
|
+
var keys = [["thin", "topic_facts"], ["solid", "fact_sources"], ["based", "map_edge_based"], ["newer", "map_edge_newer"], ["dash", "map_edge_replaced"], ["conflict", "map_edge_conflict"]];
|
|
799
|
+
return { nodes: nodes, edges: edges, labels: true, edgeKeys: keys, kinds: ["topic", "fact", "source", "note"], origins: true };
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function everyGraph() {
|
|
803
|
+
var g = topicGraph();
|
|
804
|
+
g.edges = g.edges.filter(function (e) { return e.style !== "thin"; });
|
|
805
|
+
var topics = g.nodes;
|
|
806
|
+
g.nodes = [];
|
|
807
|
+
K.map.every.forEach(function (n) {
|
|
808
|
+
if (n.kind === "fact") {
|
|
809
|
+
var f = K.facts[n.id];
|
|
810
|
+
if (!f) return;
|
|
811
|
+
g.nodes.push({ id: "f:" + n.id, x: n.x, y: n.y, r: 2.2, kind: "fact", origin: f.origin, color: originColor(f.origin),
|
|
812
|
+
dim: f.state === "superseded", conflict: f.state === "contested", statement: f.statement, label: factTitle(f), href: "#/fact/" + encodeURIComponent(n.id), tip: factTitle(f) });
|
|
813
|
+
} else {
|
|
814
|
+
g.nodes.push({ id: "s:" + n.id, x: n.x, y: n.y, r: 3.5, kind: "source", color: "#c9c2b2", square: true,
|
|
815
|
+
label: n.id, href: "#/source/" + encodeURIComponent(n.id), tip: n.id });
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
g.nodes = g.nodes.concat(topics); // topics drawn last, on top of their facts
|
|
819
|
+
g.labels = true;
|
|
820
|
+
g.kinds = ["topic", "fact", "source"];
|
|
821
|
+
g.origins = true;
|
|
822
|
+
return g;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
var KIND_SWATCH = { topic: "#d9a441", fact: "#9aa3ad", source: "#c9c2b2", note: "#8b93a0" };
|
|
826
|
+
|
|
827
|
+
function legend(g, redraw) {
|
|
828
|
+
var box = el("details", { "class": "legend", open: window.innerWidth > 1000 });
|
|
829
|
+
add(box, el("summary", { text: t("map_legend") }));
|
|
830
|
+
function toggle(key, label, swatch) {
|
|
831
|
+
var cb = el("input", { type: "checkbox", checked: !hidden[key], onchange: function (e) { hidden[key] = !e.target.checked; redraw(); } });
|
|
832
|
+
return el("label", null, cb, swatch, label);
|
|
833
|
+
}
|
|
834
|
+
if (g.origins) {
|
|
835
|
+
add(box, el("h4", { text: t("fact_origin") }));
|
|
836
|
+
ORIGINS.forEach(function (o) {
|
|
837
|
+
add(box, toggle("o:" + o, originLabel(o), el("span", { "class": "sw bg-" + o })));
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
if (g.kinds.length > 1) {
|
|
841
|
+
add(box, el("h4", { text: t("map_kinds") }));
|
|
842
|
+
g.kinds.forEach(function (k) {
|
|
843
|
+
var sw = el("span", { "class": "sw" + (k === "source" || k === "note" ? " sq" : "") });
|
|
844
|
+
sw.style.background = KIND_SWATCH[k];
|
|
845
|
+
add(box, toggle("k:" + k, t("map_kind_" + k), sw));
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
add(box, el("h4", { text: t("map_lines") }));
|
|
849
|
+
g.edgeKeys.forEach(function (k) { add(box, el("label", null, el("span", { "class": "ln " + k[0] }), t(k[1]))); });
|
|
850
|
+
if (g.origins) add(box, el("label", { title: t("statement_notice") }, el("span", { "class": "sw ring" }), t("statement_mark")));
|
|
851
|
+
add(box, el("label", null, el("span", { "class": "tag replaced", text: t("state_superseded") })),
|
|
852
|
+
el("label", null, el("span", { "class": "tag conflict", text: t("state_contested") })));
|
|
853
|
+
return box;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function canvasGraph(canvas, tip, g) {
|
|
857
|
+
var ctx = canvas.getContext("2d");
|
|
858
|
+
var byId = {};
|
|
859
|
+
g.nodes.forEach(function (n) { byId[n.id] = n; });
|
|
860
|
+
var view = { s: 1, x: 0, y: 0 };
|
|
861
|
+
var W = 0, H = 0, dpr = window.devicePixelRatio || 1;
|
|
862
|
+
|
|
863
|
+
function visible(n) {
|
|
864
|
+
if (hidden["k:" + n.kind]) return false;
|
|
865
|
+
if (n.origin && hidden["o:" + originClass(n.origin)]) return false;
|
|
866
|
+
return true;
|
|
867
|
+
}
|
|
868
|
+
function fit() {
|
|
869
|
+
var r = canvas.getBoundingClientRect();
|
|
870
|
+
W = r.width; H = r.height;
|
|
871
|
+
canvas.width = Math.round(W * dpr); canvas.height = Math.round(H * dpr);
|
|
872
|
+
var xs = g.nodes.map(function (n) { return n.x; }), ys = g.nodes.map(function (n) { return n.y; });
|
|
873
|
+
var x0 = Math.min.apply(null, xs), x1 = Math.max.apply(null, xs), y0 = Math.min.apply(null, ys), y1 = Math.max.apply(null, ys);
|
|
874
|
+
var pad = 60;
|
|
875
|
+
view.s = Math.min((W - 2 * pad) / Math.max(1, x1 - x0), (H - 2 * pad) / Math.max(1, y1 - y0), 2.2);
|
|
876
|
+
view.x = W / 2 - view.s * (x0 + x1) / 2;
|
|
877
|
+
view.y = H / 2 - view.s * (y0 + y1) / 2;
|
|
878
|
+
}
|
|
879
|
+
function sx(x) { return view.x + view.s * x; }
|
|
880
|
+
function sy(y) { return view.y + view.s * y; }
|
|
881
|
+
function draw() {
|
|
882
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
883
|
+
ctx.clearRect(0, 0, W, H);
|
|
884
|
+
g.edges.forEach(function (e) {
|
|
885
|
+
var a = byId[e.a], b = byId[e.b];
|
|
886
|
+
if (!a || !b || !visible(a) || !visible(b)) return;
|
|
887
|
+
ctx.beginPath();
|
|
888
|
+
ctx.setLineDash(e.style === "dash" ? [6, 5] : e.style === "newer" ? [2, 4] : []);
|
|
889
|
+
ctx.strokeStyle = e.style === "thin" ? "rgba(120,132,145,0.35)" : e.style === "based" ? "rgba(169,155,224,0.7)"
|
|
890
|
+
: e.style === "newer" ? "rgba(111,183,168,0.8)" : e.style === "conflict" ? "rgba(224,96,90,0.85)" : "rgba(160,168,178,0.55)";
|
|
891
|
+
ctx.lineWidth = e.w;
|
|
892
|
+
ctx.moveTo(sx(a.x), sy(a.y));
|
|
893
|
+
ctx.lineTo(sx(b.x), sy(b.y));
|
|
894
|
+
ctx.stroke();
|
|
895
|
+
});
|
|
896
|
+
ctx.setLineDash([]);
|
|
897
|
+
var rs = Math.max(0.6, Math.min(1.6, Math.sqrt(view.s)));
|
|
898
|
+
g.nodes.forEach(function (n) {
|
|
899
|
+
if (!visible(n)) return;
|
|
900
|
+
var r = n.r * rs;
|
|
901
|
+
ctx.globalAlpha = n.dim ? 0.35 : 1;
|
|
902
|
+
ctx.fillStyle = n.color;
|
|
903
|
+
ctx.beginPath();
|
|
904
|
+
if (n.square) ctx.rect(sx(n.x) - r, sy(n.y) - r, 2 * r, 2 * r);
|
|
905
|
+
else ctx.arc(sx(n.x), sy(n.y), r, 0, 2 * Math.PI);
|
|
906
|
+
ctx.fill();
|
|
907
|
+
if (n.conflict) { ctx.strokeStyle = "#e0605a"; ctx.lineWidth = 2; ctx.stroke(); }
|
|
908
|
+
if (n.statement) {
|
|
909
|
+
ctx.beginPath();
|
|
910
|
+
ctx.arc(sx(n.x), sy(n.y), r + 2.5, 0, 2 * Math.PI);
|
|
911
|
+
ctx.strokeStyle = "#e0a24a"; ctx.lineWidth = 1.5; ctx.setLineDash([2, 2]); ctx.stroke(); ctx.setLineDash([]);
|
|
912
|
+
}
|
|
913
|
+
ctx.globalAlpha = 1;
|
|
914
|
+
});
|
|
915
|
+
if (g.labels) labels(rs);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Every node carries its label; a label is drawn only where it can be read
|
|
919
|
+
// whole without touching another one. Topics first, then documents and notes,
|
|
920
|
+
// then facts -- whose text is wrapped, never cut. Zooming in makes room, so
|
|
921
|
+
// more labels appear.
|
|
922
|
+
var PRIORITY = { topic: 0, source: 1, note: 1, fact: 2 };
|
|
923
|
+
var labelled = g.nodes.filter(function (n) { return n.label; }).sort(function (a, b) {
|
|
924
|
+
return (PRIORITY[a.kind] - PRIORITY[b.kind]) || (b.r - a.r) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
925
|
+
});
|
|
926
|
+
var family = null;
|
|
927
|
+
function fontOf(n) { return (n.kind === "topic" ? "600 12.5px " : n.kind === "fact" ? "11.5px " : "11px ") + family; }
|
|
928
|
+
function linesOf(n) {
|
|
929
|
+
if (n._lines) return n._lines;
|
|
930
|
+
ctx.font = fontOf(n);
|
|
931
|
+
var maxW = n.kind === "fact" ? 220 : Infinity, lines = [], cur = "";
|
|
932
|
+
String(n.label).split(/\s+/).forEach(function (w) {
|
|
933
|
+
var next = cur ? cur + " " + w : w;
|
|
934
|
+
if (cur && ctx.measureText(next).width > maxW) { lines.push(cur); cur = w; } else cur = next;
|
|
935
|
+
});
|
|
936
|
+
if (cur) lines.push(cur);
|
|
937
|
+
n._lines = lines;
|
|
938
|
+
n._w = Math.max.apply(null, lines.map(function (l) { return ctx.measureText(l).width; }));
|
|
939
|
+
n._lh = n.kind === "topic" ? 15 : 14;
|
|
940
|
+
return lines;
|
|
941
|
+
}
|
|
942
|
+
function labels(rs) {
|
|
943
|
+
family = family || getComputedStyle(document.body).fontFamily;
|
|
944
|
+
var CELL = 96, grid = {};
|
|
945
|
+
function keys(b) {
|
|
946
|
+
var out = [];
|
|
947
|
+
for (var gx = Math.floor(b.x0 / CELL); gx <= Math.floor(b.x1 / CELL); gx++)
|
|
948
|
+
for (var gy = Math.floor(b.y0 / CELL); gy <= Math.floor(b.y1 / CELL); gy++) out.push(gx + ":" + gy);
|
|
949
|
+
return out;
|
|
950
|
+
}
|
|
951
|
+
function free(b, ks) {
|
|
952
|
+
return ks.every(function (k) {
|
|
953
|
+
return !(grid[k] || []).some(function (o) { return b.x0 < o.x1 && o.x0 < b.x1 && b.y0 < o.y1 && o.y0 < b.y1; });
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
ctx.textAlign = "center";
|
|
957
|
+
ctx.textBaseline = "bottom";
|
|
958
|
+
ctx.lineJoin = "round";
|
|
959
|
+
labelled.forEach(function (n) {
|
|
960
|
+
if (!visible(n)) return;
|
|
961
|
+
var x = sx(n.x), y = sy(n.y);
|
|
962
|
+
if (x < -40 || x > W + 40 || y < -40 || y > H + 40) return;
|
|
963
|
+
var lines = linesOf(n), h = lines.length * n._lh, top = y - n.r * rs - 4 - h;
|
|
964
|
+
var b = { x0: x - n._w / 2 - 3, x1: x + n._w / 2 + 3, y0: top - 2, y1: top + h + 2 };
|
|
965
|
+
if (b.x0 < 0 || b.x1 > W || b.y0 < 0 || b.y1 > H) return;
|
|
966
|
+
var ks = keys(b);
|
|
967
|
+
if (!free(b, ks)) return;
|
|
968
|
+
ks.forEach(function (k) { (grid[k] = grid[k] || []).push(b); });
|
|
969
|
+
ctx.font = fontOf(n);
|
|
970
|
+
lines.forEach(function (ln, i) {
|
|
971
|
+
var yy = top + (i + 1) * n._lh;
|
|
972
|
+
ctx.strokeStyle = "rgba(12,15,19,0.92)";
|
|
973
|
+
ctx.lineWidth = 4;
|
|
974
|
+
ctx.strokeText(ln, x, yy);
|
|
975
|
+
ctx.fillStyle = n.kind === "topic" ? "#e8e2d4" : n.kind === "fact" ? "#d2ccbe" : "#b9b2a3";
|
|
976
|
+
ctx.fillText(ln, x, yy);
|
|
977
|
+
});
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
function at(px, py) {
|
|
981
|
+
var best = null, bd = 1e9;
|
|
982
|
+
g.nodes.forEach(function (n) {
|
|
983
|
+
if (!visible(n)) return;
|
|
984
|
+
var d = Math.hypot(sx(n.x) - px, sy(n.y) - py);
|
|
985
|
+
if (d < Math.max(8, n.r * 1.6) && d < bd) { bd = d; best = n; }
|
|
986
|
+
});
|
|
987
|
+
return best;
|
|
988
|
+
}
|
|
989
|
+
var drag = null, moved = false;
|
|
990
|
+
canvas.addEventListener("mousedown", function (e) { drag = { x: e.clientX, y: e.clientY, vx: view.x, vy: view.y }; moved = false; canvas.style.cursor = ""; canvas.className = "drag"; });
|
|
991
|
+
window.addEventListener("mouseup", function () { drag = null; canvas.className = ""; });
|
|
992
|
+
canvas.addEventListener("mousemove", function (e) {
|
|
993
|
+
var r = canvas.getBoundingClientRect();
|
|
994
|
+
if (drag) {
|
|
995
|
+
var dx = e.clientX - drag.x, dy = e.clientY - drag.y;
|
|
996
|
+
if (Math.abs(dx) + Math.abs(dy) > 3) moved = true;
|
|
997
|
+
view.x = drag.vx + dx; view.y = drag.vy + dy; draw(); return;
|
|
998
|
+
}
|
|
999
|
+
var n = at(e.clientX - r.left, e.clientY - r.top);
|
|
1000
|
+
if (n && n.tip) { tip.textContent = n.tip; tip.style.display = "block"; tip.style.left = (e.clientX - r.left + 14) + "px"; tip.style.top = (e.clientY - r.top + 10) + "px"; canvas.style.cursor = "pointer"; }
|
|
1001
|
+
else { tip.style.display = "none"; canvas.style.cursor = ""; }
|
|
1002
|
+
});
|
|
1003
|
+
canvas.addEventListener("click", function (e) {
|
|
1004
|
+
if (moved) return;
|
|
1005
|
+
var r = canvas.getBoundingClientRect();
|
|
1006
|
+
var n = at(e.clientX - r.left, e.clientY - r.top);
|
|
1007
|
+
if (n && n.href) location.hash = n.href;
|
|
1008
|
+
});
|
|
1009
|
+
canvas.addEventListener("wheel", function (e) {
|
|
1010
|
+
e.preventDefault();
|
|
1011
|
+
var r = canvas.getBoundingClientRect();
|
|
1012
|
+
var px = e.clientX - r.left, py = e.clientY - r.top;
|
|
1013
|
+
var k = Math.exp(-e.deltaY * 0.0015);
|
|
1014
|
+
k = Math.max(0.02, Math.min(60, view.s * k)) / view.s;
|
|
1015
|
+
view.x = px - (px - view.x) * k; view.y = py - (py - view.y) * k; view.s *= k;
|
|
1016
|
+
draw();
|
|
1017
|
+
}, { passive: false });
|
|
1018
|
+
window.addEventListener("resize", function () { if (canvas.isConnected) { fit(); draw(); } });
|
|
1019
|
+
fit();
|
|
1020
|
+
draw();
|
|
1021
|
+
return {
|
|
1022
|
+
draw: draw,
|
|
1023
|
+
fit: function () { fit(); draw(); },
|
|
1024
|
+
zoom: function (k) {
|
|
1025
|
+
var px = W / 2, py = H / 2;
|
|
1026
|
+
view.x = px - (px - view.x) * k; view.y = py - (py - view.y) * k; view.s *= k;
|
|
1027
|
+
draw();
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// ---------------------------------------------------------- search
|
|
1033
|
+
|
|
1034
|
+
var overlay = null;
|
|
1035
|
+
|
|
1036
|
+
function openSearch() {
|
|
1037
|
+
if (overlay) return;
|
|
1038
|
+
var input = el("input", { type: "search", placeholder: t(C.meta.has_kb ? "search_placeholder" : "search_placeholder_docs") });
|
|
1039
|
+
var results = el("div", { "class": "results" });
|
|
1040
|
+
overlay = el("div", { "class": "overlay", onclick: function (e) { if (e.target === overlay) closeSearch(); } },
|
|
1041
|
+
el("div", { "class": "searchbox" }, input, results, el("div", { "class": "keys", text: t("search_keys") })));
|
|
1042
|
+
document.body.appendChild(overlay);
|
|
1043
|
+
input.focus();
|
|
1044
|
+
var hits = [], sel = 0;
|
|
1045
|
+
function render() {
|
|
1046
|
+
results.textContent = "";
|
|
1047
|
+
var q = normalize(input.value).split(" ").filter(Boolean);
|
|
1048
|
+
if (!q.length) return;
|
|
1049
|
+
function match(e) { return q.every(function (w) { return e.n.indexOf(w) >= 0; }); }
|
|
1050
|
+
var groups = [];
|
|
1051
|
+
if (K) {
|
|
1052
|
+
groups.push(["search_group_facts", K.search.filter(function (e) { return e.k === "fact"; }).filter(match), function (e) { return "#/fact/" + encodeURIComponent(e.id); }]);
|
|
1053
|
+
groups.push(["search_group_topics", K.search.filter(function (e) { return e.k === "topic"; }).filter(match), function (e) { return "#/topic/" + encodeURIComponent(e.id); }]);
|
|
1054
|
+
}
|
|
1055
|
+
groups.push(["search_group_documents", C.search.filter(match), function (e) { return docHref(e.path); }]);
|
|
1056
|
+
hits = [];
|
|
1057
|
+
groups.forEach(function (gr) {
|
|
1058
|
+
if (!gr[1].length) return;
|
|
1059
|
+
add(results, el("div", { "class": "grp", text: t(gr[0]) }));
|
|
1060
|
+
gr[1].slice(0, 40).forEach(function (e) {
|
|
1061
|
+
var a = el("a", { "class": "res", href: gr[2](e), onclick: closeSearch }, e.title || e.path,
|
|
1062
|
+
e.statement ? [" ", el("span", { "class": "tag statement", title: t("statement_notice"), text: t("statement_mark") })] : null,
|
|
1063
|
+
el("small", { text: e.path || e.id }));
|
|
1064
|
+
hits.push(a);
|
|
1065
|
+
results.appendChild(a);
|
|
1066
|
+
});
|
|
1067
|
+
if (gr[1].length > 40) add(results, el("div", { "class": "none", text: t("search_more", { n: gr[1].length - 40 }) }));
|
|
1068
|
+
});
|
|
1069
|
+
if (!hits.length) add(results, el("div", { "class": "none", text: t(C.meta.has_kb ? "search_none_kb" : "search_none_docs") }));
|
|
1070
|
+
sel = 0;
|
|
1071
|
+
mark();
|
|
1072
|
+
}
|
|
1073
|
+
function mark() { hits.forEach(function (a, i) { a.className = "res" + (i === sel ? " sel" : ""); }); if (hits[sel]) hits[sel].scrollIntoView({ block: "nearest" }); }
|
|
1074
|
+
input.addEventListener("input", render);
|
|
1075
|
+
input.addEventListener("keydown", function (e) {
|
|
1076
|
+
if (e.key === "ArrowDown") { sel = Math.min(hits.length - 1, sel + 1); mark(); e.preventDefault(); }
|
|
1077
|
+
else if (e.key === "ArrowUp") { sel = Math.max(0, sel - 1); mark(); e.preventDefault(); }
|
|
1078
|
+
else if (e.key === "Enter" && hits[sel]) { location.hash = hits[sel].getAttribute("href"); closeSearch(); }
|
|
1079
|
+
else if (e.key === "Escape") closeSearch();
|
|
1080
|
+
});
|
|
1081
|
+
if (C.meta.has_kb && !K) {
|
|
1082
|
+
loadScript("data/kb.js", function () { K = window.DOCS_SITE_KB; render(); });
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function closeSearch() { if (overlay) { overlay.remove(); overlay = null; } }
|
|
1087
|
+
|
|
1088
|
+
document.addEventListener("keydown", function (e) {
|
|
1089
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); openSearch(); }
|
|
1090
|
+
else if (e.key === "Escape" && panelEl && !overlay) leavePanel();
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
// ---------------------------------------------------------- routing
|
|
1094
|
+
|
|
1095
|
+
var lastPage = "";
|
|
1096
|
+
|
|
1097
|
+
function route() {
|
|
1098
|
+
var h = location.hash || "#/";
|
|
1099
|
+
var parts = h.replace(/^#\/?/, "").split("/");
|
|
1100
|
+
var head = parts[0];
|
|
1101
|
+
var rest;
|
|
1102
|
+
try { rest = decodeURIComponent(parts.slice(1).join("/")); }
|
|
1103
|
+
catch (e) { closePanel(); notFound(); return; }
|
|
1104
|
+
closeSearch();
|
|
1105
|
+
if (head === "fact") { factPanel(rest); return; }
|
|
1106
|
+
closePanel();
|
|
1107
|
+
lastPage = h;
|
|
1108
|
+
if (head === "") pageHome();
|
|
1109
|
+
else if (head === "chronology") pageChronology();
|
|
1110
|
+
else if (head === "work") pageWork();
|
|
1111
|
+
else if (head === "units") pageUnits();
|
|
1112
|
+
else if (head === "rules") pageRules();
|
|
1113
|
+
else if (head === "guides") pageGuides();
|
|
1114
|
+
else if (head === "documents") pageDocuments();
|
|
1115
|
+
else if (head === "doc") pageDoc(rest);
|
|
1116
|
+
else if (head === "map") pageMap(parts.length > 1 && rest ? rest : undefined);
|
|
1117
|
+
else if (head === "topic") topicPage(rest);
|
|
1118
|
+
else if (head === "sources") pageSources();
|
|
1119
|
+
else if (head === "source") pageSource(rest);
|
|
1120
|
+
else notFound();
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if (!C || !C.meta || !C.home) { fail(); return; }
|
|
1124
|
+
frame();
|
|
1125
|
+
window.addEventListener("hashchange", route);
|
|
1126
|
+
route();
|
|
1127
|
+
})();
|