@takagaki/cortex-decisions-viewer 0.12.146 → 0.12.147
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/dist/build.js +24 -1
- package/dist/render.js +36 -1
- package/dist/story.js +95 -3
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -127,6 +127,27 @@ const DEFAULT_EXCLUDE = ["README.md", "template.md"];
|
|
|
127
127
|
* `supersedes` の写しで、描くと同じ置き換えが逆向きにもう 1 本出る。frontmatter のパネルには今までどおり出る
|
|
128
128
|
*/
|
|
129
129
|
exports.MIRROR_RELS = new Set(["superseded_by"]);
|
|
130
|
+
/**
|
|
131
|
+
* ストーリーの名前のリンクの材料(`story.screen-name-links`)。関係者・用語・ルールの記録ごとに、表示名と別名・同義語を集める。
|
|
132
|
+
* 画面に載る記録(`sections` に残った物)だけなので、取り下げ・削除された記録の id は表に無く、リンクにならない。
|
|
133
|
+
* 関係者の題は姓名の間に空白があることがあるので、空白を除いた形も名前に足す
|
|
134
|
+
*/
|
|
135
|
+
function storyNameSources(sections) {
|
|
136
|
+
const out = new Map();
|
|
137
|
+
const list = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === "string") : []);
|
|
138
|
+
for (const sec of sections) {
|
|
139
|
+
for (const r of sec.records) {
|
|
140
|
+
const kind = r.id.split(":")[0];
|
|
141
|
+
if (kind !== "member" && kind !== "term" && kind !== "rule")
|
|
142
|
+
continue;
|
|
143
|
+
const names = [r.title, ...list(r.fm.aliases), ...list(r.fm.synonyms)];
|
|
144
|
+
if (kind === "member")
|
|
145
|
+
names.push(r.title.replace(/\s+/g, ""));
|
|
146
|
+
out.set(r.id, { id: r.id, names });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
130
151
|
/** frontmatterのrelationsを安全に正規化する */
|
|
131
152
|
function toRelations(value) {
|
|
132
153
|
if (!Array.isArray(value))
|
|
@@ -2517,7 +2538,9 @@ async function buildSite(opts, views) {
|
|
|
2517
2538
|
// ストーリーの日々の動き(#901 V2-5)。帳簿で on の日のファイルが無ければ null(キーごと出さない)
|
|
2518
2539
|
const story = await (0, story_1.readStory)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null,
|
|
2519
2540
|
// 動きのない日を埋める終点=ビルドの時刻の日本時間の日付(時計を注入できる)
|
|
2520
|
-
storyToday, ledgerDir
|
|
2541
|
+
storyToday, ledgerDir,
|
|
2542
|
+
// 名前のリンク(story.screen-name-links): 日の relations の関係者・用語・ルールを、画面にある記録の表示名・別名・同義語で本文に当てる
|
|
2543
|
+
(targets) => (0, story_1.storyNameLinks)(targets, storyNameSources(sections)));
|
|
2521
2544
|
// 週・月のあらすじ(#901)。週・月のファイルが無ければ null(キーごと出さない=今までと同じ画面)
|
|
2522
2545
|
const storyDigest = await (0, story_1.readStoryDigest)(repoRoot, renderGroundingSection, (p) => repo.baseUrl ? `${repo.baseUrl}/edit/${repo.branch}/${encodePath(p)}` : null, storyToday, ledgerDir);
|
|
2523
2546
|
// 案件アイコン(#348)。Cortex構成でなければ undefined(旧レイアウトには置き場が無い)
|
package/dist/render.js
CHANGED
|
@@ -89,6 +89,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
89
89
|
const google_chat_guide_images_1 = require("./google-chat-guide-images");
|
|
90
90
|
const meet_guide_images_1 = require("./meet-guide-images");
|
|
91
91
|
const figma_guide_images_1 = require("./figma-guide-images");
|
|
92
|
+
const story_1 = require("./story");
|
|
92
93
|
// 「人が確認したか/機械が触ってよいか」の口(#699 F2b)。正本は engine の confirmed.mjs・判定は流し込むだけ
|
|
93
94
|
const confirmed_1 = require("./confirmed");
|
|
94
95
|
// フロントマターの「つながっている ID」の欄名(#686)。**綴りはここ 1 本**——読む側(画面)も
|
|
@@ -6537,6 +6538,8 @@ ${calendarAddUrl.toString()}
|
|
|
6537
6538
|
try { return elx ? JSON.parse(elx.textContent) : null; } catch (e) { return null; }
|
|
6538
6539
|
})();
|
|
6539
6540
|
|
|
6541
|
+
// ストーリーの名前のリンクの切り分け(story.ts の純関数をそのまま埋め込む)
|
|
6542
|
+
${story_1.splitByNameLinks.toString()}
|
|
6540
6543
|
// ---------- インデックス構築 ----------
|
|
6541
6544
|
var byId = {};
|
|
6542
6545
|
SITE.decisions.forEach(function (d) { byId[d.id] = d; d._kind = "decision"; });
|
|
@@ -6565,6 +6568,38 @@ ${calendarAddUrl.toString()}
|
|
|
6565
6568
|
return '<a class="wikilink" data-go="' + attr + '">' + disp + "</a>";
|
|
6566
6569
|
});
|
|
6567
6570
|
}
|
|
6571
|
+
// ストーリーの本文の名前を記録へのリンクにする(story.screen-name-links)。
|
|
6572
|
+
// 🔴 本文の DOM を作った後に**テキストノードだけ**を走査する(HTML の文字列は触らない=タグ・属性は壊れない)。
|
|
6573
|
+
// a・code・pre・既存のウィキリンクの中は当てない。飛び先(byId)が無い id は当てない(素の文字のまま=空リンク・行き止まりを出さない)。
|
|
6574
|
+
// 名前は textContent で入れる(表示名を HTML として混ぜない)
|
|
6575
|
+
function linkifyStoryNames(root, links) {
|
|
6576
|
+
if (!root || !links || !links.length) return;
|
|
6577
|
+
var usable = links.filter(function (l) { return l && l.name && l.id && byId[l.id] && !byId[l.id].deleted; });
|
|
6578
|
+
if (!usable.length) return;
|
|
6579
|
+
var skip = { A: 1, CODE: 1, PRE: 1, SCRIPT: 1, STYLE: 1 };
|
|
6580
|
+
var nodes = [];
|
|
6581
|
+
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
6582
|
+
acceptNode: function (n) {
|
|
6583
|
+
for (var p = n.parentNode; p && p !== root; p = p.parentNode) { if (skip[p.tagName]) return NodeFilter.FILTER_REJECT; }
|
|
6584
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
6585
|
+
}
|
|
6586
|
+
});
|
|
6587
|
+
while (walker.nextNode()) nodes.push(walker.currentNode);
|
|
6588
|
+
nodes.forEach(function (n) {
|
|
6589
|
+
var parts = splitByNameLinks(n.nodeValue, usable);
|
|
6590
|
+
if (parts.length === 1 && !parts[0].id) return;
|
|
6591
|
+
var frag = document.createDocumentFragment();
|
|
6592
|
+
parts.forEach(function (part) {
|
|
6593
|
+
if (!part.id) { frag.appendChild(document.createTextNode(part.text)); return; }
|
|
6594
|
+
var a = document.createElement("a");
|
|
6595
|
+
a.className = "wikilink";
|
|
6596
|
+
a.setAttribute("data-go", part.id);
|
|
6597
|
+
a.textContent = part.text;
|
|
6598
|
+
frag.appendChild(a);
|
|
6599
|
+
});
|
|
6600
|
+
n.parentNode.replaceChild(frag, n);
|
|
6601
|
+
});
|
|
6602
|
+
}
|
|
6568
6603
|
// 本文は bodies/ 以下へ外出しされ詳細表示時に fetch されるため、linkify は loadBody 側で適用する
|
|
6569
6604
|
// (ここで inline bodyHtml に適用すると検索テキストにHTMLが混ざるため適用しない)。
|
|
6570
6605
|
|
|
@@ -8692,7 +8727,7 @@ ${calendarAddUrl.toString()}
|
|
|
8692
8727
|
}
|
|
8693
8728
|
list.appendChild(el("section", { class: "story-card" }, [
|
|
8694
8729
|
el("h3", { class: "story-card-heading", text: d.heading || d.date }),
|
|
8695
|
-
d.bodyHtml ? el("div", { class: "body", html: d.bodyHtml }) : null,
|
|
8730
|
+
d.bodyHtml ? (function () { var b = el("div", { class: "body", html: d.bodyHtml }); linkifyStoryNames(b, d.links); return b; })() : null,
|
|
8696
8731
|
// その日のファイルを GitHub で開く(表示の文字にファイル名を出さない)。URL が無い案件は出さない
|
|
8697
8732
|
storyEditRow(STORY_DAYS_BASE + d.date.slice(0, 7) + "/" + d.date + ".md", d.editUrl),
|
|
8698
8733
|
]));
|
package/dist/story.js
CHANGED
|
@@ -3,13 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.STORY_DIGEST_EMPTY_LINE = exports.STORY_MAX_MONTHS = exports.STORY_MAX_WEEKS = exports.STORY_MONTHS_DIR = exports.STORY_WEEKS_DIR = exports.STORY_MAX_FILE_BYTES = exports.STORY_COLLAPSE_QUIET_DAYS = exports.STORY_EMPTY_LINE = exports.STORY_MAX_DAYS = exports.STORY_DAYS_DIR = exports.STORY_LEDGER_FILE = void 0;
|
|
6
|
+
exports.STORY_DIGEST_EMPTY_LINE = exports.STORY_MAX_MONTHS = exports.STORY_MAX_WEEKS = exports.STORY_MONTHS_DIR = exports.STORY_WEEKS_DIR = exports.STORY_LINK_MIN_NAME = exports.STORY_LINK_KINDS = exports.STORY_MAX_FILE_BYTES = exports.STORY_COLLAPSE_QUIET_DAYS = exports.STORY_EMPTY_LINE = exports.STORY_MAX_DAYS = exports.STORY_DAYS_DIR = exports.STORY_LEDGER_FILE = void 0;
|
|
7
7
|
exports.parseStoryLedger = parseStoryLedger;
|
|
8
8
|
exports.partialDaysOf = partialDaysOf;
|
|
9
9
|
exports.onDaysOf = onDaysOf;
|
|
10
10
|
exports.successDaysOf = successDaysOf;
|
|
11
11
|
exports.nightResultOf = nightResultOf;
|
|
12
12
|
exports.splitFrontmatter = splitFrontmatter;
|
|
13
|
+
exports.dayRelationTargets = dayRelationTargets;
|
|
14
|
+
exports.storyNameLinks = storyNameLinks;
|
|
15
|
+
exports.splitByNameLinks = splitByNameLinks;
|
|
13
16
|
exports.storyDayMarkdown = storyDayMarkdown;
|
|
14
17
|
exports.isEngineQuietDay = isEngineQuietDay;
|
|
15
18
|
exports.todayJst = todayJst;
|
|
@@ -162,6 +165,88 @@ function splitFrontmatter(content) {
|
|
|
162
165
|
return { frontmatter: null, body: text };
|
|
163
166
|
return { frontmatter: m[1] ?? "", body: text.slice(m[0].length) };
|
|
164
167
|
}
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// 名前のリンク(`story.screen-name-links`)。日のページの `relations` で当てる(本文からは名前を拾わない)
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
/** 名前のリンクにする記録の種類(id の頭)。同じ名前が複数の種類にあるときはこの順で勝つ(関係者 → 用語 → ルール・設計 A4) */
|
|
172
|
+
exports.STORY_LINK_KINDS = ["member", "term", "rule"];
|
|
173
|
+
/** 名前の最小の長さ(1 文字の名前は本文のあちこちに当たるので当てない) */
|
|
174
|
+
exports.STORY_LINK_MIN_NAME = 2;
|
|
175
|
+
/**
|
|
176
|
+
* 日のページの frontmatter から `relations` の `target` を取る(純関数・YAML としては読まない)。
|
|
177
|
+
* `- { rel: relates_to, target: "term:設計" }` も、`- rel:` と `target:` を行で分けた形も読む。壊れた行は飛ばす
|
|
178
|
+
*/
|
|
179
|
+
function dayRelationTargets(frontmatter) {
|
|
180
|
+
if (!frontmatter)
|
|
181
|
+
return [];
|
|
182
|
+
const out = [];
|
|
183
|
+
for (const line of frontmatter.split("\n")) {
|
|
184
|
+
const m = line.match(/\btarget\s*:\s*(?:"([^"]*)"|'([^']*)'|([^\s,}"']+))/);
|
|
185
|
+
const t = (m ? (m[1] ?? m[2] ?? m[3] ?? "") : "").trim();
|
|
186
|
+
if (t && !out.includes(t))
|
|
187
|
+
out.push(t);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* その日の `relations` の target から、名前のリンクの表を作る(純関数)。
|
|
193
|
+
*
|
|
194
|
+
* - 材料(`sources`)に **無い id は捨てる**(廃止・削除された記録、実在しない id、記録ではない id=決定など)。飛び先の無い線は出さない
|
|
195
|
+
* - 同じ名前が複数の記録にあれば、関係者 → 用語 → ルールの順で勝つ。同じ種類なら先に出た target
|
|
196
|
+
* - 2 文字未満の名前は当てない。返りは名前の長い順(同じ長さは文字の順)。1 件も無ければ空
|
|
197
|
+
*/
|
|
198
|
+
function storyNameLinks(targets, sources) {
|
|
199
|
+
const kindOf = (id) => exports.STORY_LINK_KINDS.findIndex((k) => id.startsWith(`${k}:`));
|
|
200
|
+
const ids = [...new Set(targets)].filter((t) => kindOf(t) >= 0 && sources.has(t));
|
|
201
|
+
ids.sort((a, b) => kindOf(a) - kindOf(b));
|
|
202
|
+
const byName = new Map();
|
|
203
|
+
for (const id of ids) {
|
|
204
|
+
for (const raw of sources.get(id).names) {
|
|
205
|
+
const name = String(raw ?? "").trim();
|
|
206
|
+
if (name.length >= exports.STORY_LINK_MIN_NAME && !byName.has(name))
|
|
207
|
+
byName.set(name, id);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return [...byName.entries()]
|
|
211
|
+
.map(([name, id]) => ({ name, id }))
|
|
212
|
+
.sort((a, b) => b.name.length - a.name.length || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* 画面側の純関数: 1 つの文字列を、名前に当たる所とそれ以外に分ける。**画面の JS にそのまま埋め込む**ので、このファイルの外の物を使わない。
|
|
216
|
+
* 長い名前を先に当てる(`links` は長い順で渡す)。当たらなければ 1 件(元の文字)を返す
|
|
217
|
+
*/
|
|
218
|
+
function splitByNameLinks(text, links) {
|
|
219
|
+
if (!text || !links || !links.length)
|
|
220
|
+
return [{ text: text }];
|
|
221
|
+
const byName = {};
|
|
222
|
+
const names = [];
|
|
223
|
+
links.forEach((l) => {
|
|
224
|
+
if (l && l.name && l.id && !Object.prototype.hasOwnProperty.call(byName, l.name)) {
|
|
225
|
+
byName[l.name] = l.id;
|
|
226
|
+
names.push(l.name);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
names.sort((a, b) => b.length - a.length);
|
|
230
|
+
// 英数字で始まる・終わる名前は、前後が英数字のときは当てない(`AI` が `MAIN` の途中に当たらない)。日本語の名前は途中でも当てる
|
|
231
|
+
const word = (n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
232
|
+
const re = new RegExp(names.map((n) => (/^[A-Za-z0-9_]/.test(n) ? "(?<![A-Za-z0-9_])" : "") + word(n) + (/[A-Za-z0-9_]$/.test(n) ? "(?![A-Za-z0-9_])" : "")).join("|"), "g");
|
|
233
|
+
const out = [];
|
|
234
|
+
let last = 0;
|
|
235
|
+
let m;
|
|
236
|
+
while ((m = re.exec(text)) !== null) {
|
|
237
|
+
if (!m[0]) {
|
|
238
|
+
re.lastIndex++;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (m.index > last)
|
|
242
|
+
out.push({ text: text.slice(last, m.index) });
|
|
243
|
+
out.push({ text: m[0], id: byName[m[0]] });
|
|
244
|
+
last = m.index + m[0].length;
|
|
245
|
+
}
|
|
246
|
+
if (last < text.length)
|
|
247
|
+
out.push({ text: text.slice(last) });
|
|
248
|
+
return out.length ? out : [{ text: text }];
|
|
249
|
+
}
|
|
165
250
|
/** 曜日つきの見出し(`# 2026-09-24(木)`)の文字。無ければ日付だけ */
|
|
166
251
|
function headingOf(lines, day) {
|
|
167
252
|
for (const l of lines) {
|
|
@@ -372,7 +457,9 @@ editUrlOf = () => null,
|
|
|
372
457
|
// 日本時間の今日(テストは時計を注入する)
|
|
373
458
|
today = todayJst(),
|
|
374
459
|
// 帳簿の置き場(既定は `<repoRoot>/.cortex`)
|
|
375
|
-
ledgerDir
|
|
460
|
+
ledgerDir,
|
|
461
|
+
// 日の `relations` の target から名前のリンクの表を作る口(build が記録の表を握って渡す。無ければリンクは付けない)
|
|
462
|
+
linksOf = () => []) {
|
|
376
463
|
const ledger = await loadStoryLedger(repoRoot, true, ledgerDir);
|
|
377
464
|
if (!ledger)
|
|
378
465
|
return null;
|
|
@@ -432,7 +519,12 @@ ledgerDir) {
|
|
|
432
519
|
continue;
|
|
433
520
|
const { heading, markdown } = storyDayMarkdown(raw, f.day);
|
|
434
521
|
const editUrl = editUrlOf(f.rel);
|
|
435
|
-
|
|
522
|
+
const links = linksOf(dayRelationTargets(splitFrontmatter(raw).frontmatter));
|
|
523
|
+
out.push({
|
|
524
|
+
date: f.day, heading: heading, bodyHtml: await renderMarkdown(markdown),
|
|
525
|
+
...(editUrl ? { editUrl: editUrl } : {}),
|
|
526
|
+
...(links.length ? { links: links } : {}),
|
|
527
|
+
});
|
|
436
528
|
}
|
|
437
529
|
// 一番古い「動きのある日」から今日まで、日付の抜けを動きのない日の行で埋める。動きのある日が 1 つも無ければ出さない
|
|
438
530
|
// 抜けの日は、帳簿の「成功した日」(行があり・on で・途中まででない日)なら動きなし、そうでなければ更新の失敗として出す
|
package/package.json
CHANGED