@takagaki/cortex-decisions-viewer 0.12.113 → 0.12.114
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 +41 -0
- package/dist/ranking.js +89 -0
- package/dist/render.js +176 -12
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -50,6 +50,7 @@ exports.collectMeetings = collectMeetings;
|
|
|
50
50
|
exports.findBacklogDir = findBacklogDir;
|
|
51
51
|
exports.figmaUrlFromRef = figmaUrlFromRef;
|
|
52
52
|
exports.identifyEvidence = identifyEvidence;
|
|
53
|
+
exports.attachGlow = attachGlow;
|
|
53
54
|
exports.bodyFileSlug = bodyFileSlug;
|
|
54
55
|
exports.bodyStampId = bodyStampId;
|
|
55
56
|
exports.bodyStampComment = bodyStampComment;
|
|
@@ -66,6 +67,7 @@ const evidence_names_1 = require("./evidence-names");
|
|
|
66
67
|
const repo_1 = require("./repo");
|
|
67
68
|
const engagement_1 = require("./engagement");
|
|
68
69
|
const story_1 = require("./story");
|
|
70
|
+
const ranking_1 = require("./ranking");
|
|
69
71
|
const rederivation_1 = require("./rederivation");
|
|
70
72
|
// 直近のGold昇格で追加されたレコードのパス集合。レコード生成の各所から参照するのでモジュールスコープに置く
|
|
71
73
|
// (引数で引き回すと呼び出し側の改修が広範囲になる)。build() の冒頭で1回だけ解決する。
|
|
@@ -1854,6 +1856,30 @@ function identifyEvidence(value, ctx, humanLabel) {
|
|
|
1854
1856
|
kind: canon.kind,
|
|
1855
1857
|
};
|
|
1856
1858
|
}
|
|
1859
|
+
/**
|
|
1860
|
+
* 順位のファイルが持つ 4 区画の置き場(`Cortex/<区画>/records/` の下か)。分類(frontmatter の `type`)では決めない——
|
|
1861
|
+
* engine は置き場で順位を付けるので、`type` が無い・違う記録だけが一律の大きさで目立つ、を防ぐ
|
|
1862
|
+
*/
|
|
1863
|
+
const RANKED_DIR_RE = /(?:^|\/)(?:Decisions|Rules|Glossary|Members)\/(?:records\/)?[^/]+$/;
|
|
1864
|
+
/**
|
|
1865
|
+
* 記録の点に、目次と同じ順位から決めた明るさ(`glow`・0〜1)を付ける(engine #551・オーナー決定 10/1)。
|
|
1866
|
+
*
|
|
1867
|
+
* 順位は engine の書いたファイル(`ranking.ts`)をそのまま読み、ここでは並べ替えない。
|
|
1868
|
+
* - 4 区画(決定・ルール・用語・関係者)の記録で順位に載っていないもの(目次に出さない割り振りの決定・順位を書いた夜より
|
|
1869
|
+
* 後に足された記録)は**一番暗い 0**(目次に載らない=一番下。一律の大きさのままだと中位より目立ってしまう)
|
|
1870
|
+
* - 根拠の点・Home・4 区画の外の記録には付けない(今までどおりの見た目)
|
|
1871
|
+
*/
|
|
1872
|
+
function attachGlow(nodes, glow, recordRelPaths) {
|
|
1873
|
+
for (const n of nodes) {
|
|
1874
|
+
if (!n.recordId)
|
|
1875
|
+
continue;
|
|
1876
|
+
const rel = recordRelPaths.get(n.recordId);
|
|
1877
|
+
if (rel === undefined || !RANKED_DIR_RE.test(rel))
|
|
1878
|
+
continue;
|
|
1879
|
+
const g = glow.get(rel);
|
|
1880
|
+
n.glow = g === undefined ? 0 : g;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1857
1883
|
/**
|
|
1858
1884
|
* 全レコードのfrontmatter(relations / source / references)と本文リンクから関係グラフを構築する(engine #551・分類A 10/1)。
|
|
1859
1885
|
*
|
|
@@ -2395,12 +2421,21 @@ async function build(opts) {
|
|
|
2395
2421
|
// 関与の帳簿(便 14b・#709)。関係者の一覧を目次と同じ「関わりの多い順」に並べる材料。
|
|
2396
2422
|
// **読めない・使えないなら `null`** で、関係者の並びは今までと 1 バイトも同じ
|
|
2397
2423
|
const engagement = await (0, engagement_1.readEngagementLedger)(repoRoot);
|
|
2424
|
+
// 目次と同じ順位(engine #551)。**読めないなら `null`** で、つながり画面の点は今までどおり一律
|
|
2425
|
+
const memoryRanking = await (0, ranking_1.readMemoryRanking)(repoRoot);
|
|
2426
|
+
// 記録の id → リポジトリルート相対のパス(順位のファイルの鍵と突き合わせる。`repoRoot` 起点・cwd 起点にしない)
|
|
2427
|
+
const recordRelPaths = new Map();
|
|
2428
|
+
const relOf = (dir, fileName) => {
|
|
2429
|
+
const rel = path.relative(repoRoot, dir).split(path.sep).join("/");
|
|
2430
|
+
return rel ? `${rel}/${fileName}` : fileName;
|
|
2431
|
+
};
|
|
2398
2432
|
const decisions = [];
|
|
2399
2433
|
const rawDates = new Map();
|
|
2400
2434
|
for (const fileName of mdFiles) {
|
|
2401
2435
|
const raw = await node_fs_1.promises.readFile(path.join(dirAbs, fileName), "utf8");
|
|
2402
2436
|
const rec = await parseDecision(raw, fileName, repoRelDir, repo.baseUrl, repo.branch, refs);
|
|
2403
2437
|
decisions.push(rec);
|
|
2438
|
+
recordRelPaths.set(rec.id, relOf(dirAbs, fileName));
|
|
2404
2439
|
// 置き換えの逆引きの決定日(書かれたままの文字列・SiteData には載せない)
|
|
2405
2440
|
const rd = rawFrontmatterDate(raw);
|
|
2406
2441
|
if (rd !== undefined)
|
|
@@ -2475,6 +2510,9 @@ async function build(opts) {
|
|
|
2475
2510
|
// **点と線は同じなのに地図の絵が毎晩組み変わる**(cortex-context で 1414/1414 の
|
|
2476
2511
|
// 座標が動くのを実測)。並びが効くのは一覧だけ、という約束をここで固定する
|
|
2477
2512
|
graphSections.push({ name: e.name, records: records });
|
|
2513
|
+
for (const r of records)
|
|
2514
|
+
if (!recordRelPaths.has(r.id))
|
|
2515
|
+
recordRelPaths.set(r.id, relOf(recordsDir, r.fileName));
|
|
2478
2516
|
}
|
|
2479
2517
|
}
|
|
2480
2518
|
sections.sort((a, b) => a.name.localeCompare(b.name, "ja"));
|
|
@@ -2514,6 +2552,9 @@ async function build(opts) {
|
|
|
2514
2552
|
}
|
|
2515
2553
|
const graph = buildGraph(decisions, home, graphSections, targetUrls, repo.baseUrl, evidenceBook);
|
|
2516
2554
|
computeLayout(graph); // 各ノードに x/y を書き込む(クライアントはこれを初期位置に使いウォームアップ省略)
|
|
2555
|
+
// 点の明るさと大きさ(engine #551)。順位のファイルが読めない案件では 1 点も付かない(今までと同じ画面)
|
|
2556
|
+
if (memoryRanking)
|
|
2557
|
+
attachGlow(graph.nodes, (0, ranking_1.glowByPath)(memoryRanking), recordRelPaths);
|
|
2517
2558
|
// データソースと自動化(fleet-status.json 由来・上で 1 回だけ読んだもの)。無ければ undefined でセクション非表示。
|
|
2518
2559
|
// すでにある会議(投入モーダルの「会議名」の選択肢)。intake の有無に関わらず載せる——
|
|
2519
2560
|
// 出し分けは表示側の仕事で、ここで絞ると「投入の口を後から付けたら選択肢が空」になる
|
package/dist/ranking.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MEMORY_RANKING_SECTIONS = exports.MEMORY_RANKING_VERSION = exports.MEMORY_RANKING_PATH = void 0;
|
|
7
|
+
exports.parseMemoryRanking = parseMemoryRanking;
|
|
8
|
+
exports.readMemoryRanking = readMemoryRanking;
|
|
9
|
+
exports.glowByPath = glowByPath;
|
|
10
|
+
/**
|
|
11
|
+
* つながり画面の点の明るさと大きさ(cortex-engine #551・オーナー決定 2026-10-01)。
|
|
12
|
+
*
|
|
13
|
+
* ## 🔴 順位の決め方を画面に持たない
|
|
14
|
+
*
|
|
15
|
+
* 「光の強さ(大きさ)は目次(`Cortex/Index.md`)と同じ順位で変える。順位は全件ぶんを目次と同じ 1 つの決め方で出し、
|
|
16
|
+
* 画面はそれを読む(決め方を画面側に写さない)」。だからここは engine が書いた `.cortex/memory-ranking.json`
|
|
17
|
+
* (engine `scripts/memory-ranking.mjs`・目次の書き手が目次と同じコミットで書く)を**読むだけ**で、並べ替えない。
|
|
18
|
+
* 決めるのは「区画の中で何番目か」を明るさ(0〜1)にする写し方だけ(一番上=1・一番下=0・間は等間隔)。
|
|
19
|
+
*
|
|
20
|
+
* ## 🔴 ファイルが無い・壊れている・版が違う案件は「今までと 1 バイトも同じ画面」
|
|
21
|
+
*
|
|
22
|
+
* 読めなければ `null` を返し、点に `glow` を付けない(画面は今までどおり一律の大きさ・光り)。
|
|
23
|
+
* 読めた案件で 4 区画の記録なのに載っていないもの(目次に出さない割り振りの決定・順位を書いた夜より後に足された記録)は
|
|
24
|
+
* 一番暗い 0 にする(`build.ts` の `attachGlow`)。Home・区画の外の記録・根拠の点には付けない。
|
|
25
|
+
*/
|
|
26
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
27
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
+
/** 置き場(engine `scripts/memory-ranking.mjs` の `RANKING_PATH`) */
|
|
29
|
+
exports.MEMORY_RANKING_PATH = ".cortex/memory-ranking.json";
|
|
30
|
+
/** 形の版(engine の `RANKING_VERSION`。違ったら読まない) */
|
|
31
|
+
exports.MEMORY_RANKING_VERSION = 1;
|
|
32
|
+
/** 載る区画(engine の `RANKING_SECTIONS`) */
|
|
33
|
+
exports.MEMORY_RANKING_SECTIONS = ["Decisions", "Rules", "Glossary", "Members"];
|
|
34
|
+
/** 中身を読む(投げない)。形が違う・版が違う → `null` */
|
|
35
|
+
function parseMemoryRanking(text) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(text);
|
|
38
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
39
|
+
return null;
|
|
40
|
+
const o = parsed;
|
|
41
|
+
if (o.version !== exports.MEMORY_RANKING_VERSION)
|
|
42
|
+
return null;
|
|
43
|
+
const r = o.ranking;
|
|
44
|
+
if (!r || typeof r !== "object" || Array.isArray(r))
|
|
45
|
+
return null;
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const key of exports.MEMORY_RANKING_SECTIONS) {
|
|
48
|
+
const list = r[key];
|
|
49
|
+
if (!Array.isArray(list))
|
|
50
|
+
return null;
|
|
51
|
+
out[key] = list.filter((p) => typeof p === "string" && p !== "");
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** 案件リポから読む。無い → `null`(黙る)・壊れている → `null`(build のログに 1 行) */
|
|
60
|
+
async function readMemoryRanking(repoRoot) {
|
|
61
|
+
let text;
|
|
62
|
+
try {
|
|
63
|
+
text = await promises_1.default.readFile(node_path_1.default.join(repoRoot, exports.MEMORY_RANKING_PATH), "utf8");
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const parsed = parseMemoryRanking(text);
|
|
69
|
+
if (!parsed)
|
|
70
|
+
console.warn(`${node_path_1.default.basename(exports.MEMORY_RANKING_PATH)}: 読めないので、つながり画面の点は一律の明るさで出します`);
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 記録のパス → 明るさ(0〜1)。区画の中の位置だけで決める(一番上=1・一番下=0・1 件だけの区画は 1)。
|
|
75
|
+
* 小数は 3 桁に丸める(画面のデータを太らせない)。
|
|
76
|
+
*/
|
|
77
|
+
function glowByPath(ranking) {
|
|
78
|
+
const out = new Map();
|
|
79
|
+
for (const key of exports.MEMORY_RANKING_SECTIONS) {
|
|
80
|
+
const list = ranking[key];
|
|
81
|
+
const last = list.length - 1;
|
|
82
|
+
list.forEach((p, i) => {
|
|
83
|
+
if (out.has(p))
|
|
84
|
+
return;
|
|
85
|
+
out.set(p, last <= 0 ? 1 : Math.round((1 - i / last) * 1000) / 1000);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
package/dist/render.js
CHANGED
|
@@ -4789,6 +4789,12 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
|
|
|
4789
4789
|
background: rgba(13,20,45,0.75); color: #cdd8f2; font-size: 12.5px; font-weight: 600; cursor: pointer;
|
|
4790
4790
|
}
|
|
4791
4791
|
.graph-reset:hover { background: rgba(30,40,80,0.9); border-color: rgba(160,180,230,0.7); }
|
|
4792
|
+
/* 期間で光らせる(engine #551・オーナー決定 10/1)。キーワードの検索の下の 1 行 */
|
|
4793
|
+
.graph-period { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 10px; font-size: 13px; color: var(--muted); }
|
|
4794
|
+
.graph-period select, .graph-period input[type="date"] { padding: 5px 8px; border: 1px solid var(--border); border-radius: 8px; font: inherit; color: var(--text); background: var(--surface); }
|
|
4795
|
+
.graph-period button { padding: 4px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: inherit; cursor: pointer; font: inherit; }
|
|
4796
|
+
.graph-period button:disabled, .graph-period input:disabled { opacity: 0.45; cursor: default; }
|
|
4797
|
+
.graph-period-text { color: var(--text); font-weight: 600; }
|
|
4792
4798
|
|
|
4793
4799
|
/* ---- knowledge view switch(ストーリーの日次/週次/月次タブも同じ見た目・分類B 2026-09-30) ---- */
|
|
4794
4800
|
.view-switch, .story-sub-tabs {
|
|
@@ -4984,6 +4990,7 @@ a.pager-btn:hover { text-decoration: none; }
|
|
|
4984
4990
|
.story-card {
|
|
4985
4991
|
border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 18px; background: var(--surface);
|
|
4986
4992
|
}
|
|
4993
|
+
.story-card-focus { border-color: #2563eb; box-shadow: 0 0 0 2px rgba(37,99,235,0.25); }
|
|
4987
4994
|
.story-quiet { margin: -6px 0; padding: 0 18px; font-size: 12.5px; color: var(--muted); }
|
|
4988
4995
|
.story-quiet-date { font-variant-numeric: tabular-nums; }
|
|
4989
4996
|
.story-card-heading { margin: 0 0 8px; font-size: 15px; }
|
|
@@ -8536,6 +8543,7 @@ ${calendarAddUrl.toString()}
|
|
|
8536
8543
|
// 文中のリンクは「押したら進む=戻れる」に見えるので、そちらだけ積む
|
|
8537
8544
|
function switchTab(tabKey, push) {
|
|
8538
8545
|
listState.tab = tabKey;
|
|
8546
|
+
storyFocus = null;
|
|
8539
8547
|
listState.page = 1;
|
|
8540
8548
|
if (push) history.pushState({}, "", tabUrl(tabKey));
|
|
8541
8549
|
else history.replaceState(null, "", tabUrl(tabKey));
|
|
@@ -8817,6 +8825,8 @@ ${calendarAddUrl.toString()}
|
|
|
8817
8825
|
var STORY_SUB_DEFAULT = "daily";
|
|
8818
8826
|
var STORY_SUBS = [["daily", "日次"], ["weekly", "週次"], ["monthly", "月次"]];
|
|
8819
8827
|
var storySub = STORY_SUB_DEFAULT;
|
|
8828
|
+
// 記録の詳細の「この日のストーリー」から着いたときに目立たせる日(日次)・週の月曜(週次)。URL の ?day= / ?week=(engine #551)
|
|
8829
|
+
var storyFocus = null;
|
|
8820
8830
|
|
|
8821
8831
|
function storySubUrl(sub) {
|
|
8822
8832
|
return sub === STORY_SUB_DEFAULT ? "?tab=story" : "?tab=story&sub=" + encodeURIComponent(sub);
|
|
@@ -8824,6 +8834,7 @@ ${calendarAddUrl.toString()}
|
|
|
8824
8834
|
function switchStorySub(sub) {
|
|
8825
8835
|
if (storySub === sub) return;
|
|
8826
8836
|
storySub = sub;
|
|
8837
|
+
storyFocus = null;
|
|
8827
8838
|
history.replaceState(null, "", storySubUrl(sub));
|
|
8828
8839
|
renderList();
|
|
8829
8840
|
}
|
|
@@ -8846,7 +8857,7 @@ ${calendarAddUrl.toString()}
|
|
|
8846
8857
|
]));
|
|
8847
8858
|
return;
|
|
8848
8859
|
}
|
|
8849
|
-
list.appendChild(el("section", { class: "story-card" }, [
|
|
8860
|
+
list.appendChild(el("section", { class: "story-card", "data-story-key": d.date }, [
|
|
8850
8861
|
el("h3", { class: "story-card-heading", text: d.heading || d.date }),
|
|
8851
8862
|
d.bodyHtml ? el("div", { class: "body", html: d.bodyHtml }) : null,
|
|
8852
8863
|
// その日のファイルを GitHub で開く(表示の文字にファイル名を出さない)。URL が無い案件は出さない
|
|
@@ -8862,7 +8873,7 @@ ${calendarAddUrl.toString()}
|
|
|
8862
8873
|
el("h4", { text: "人の追記" }),
|
|
8863
8874
|
el("div", { class: "body", html: entry.humanHtml }),
|
|
8864
8875
|
]) : null;
|
|
8865
|
-
return el("section", { class: "story-card" }, [
|
|
8876
|
+
return el("section", { class: "story-card", "data-story-key": entry.key }, [
|
|
8866
8877
|
el("h3", { class: "story-card-heading", text: entry.heading }),
|
|
8867
8878
|
entry.phase ? el("p", { class: "story-phase" }, [
|
|
8868
8879
|
el("span", { class: "story-phase-label", text: phaseLabel }),
|
|
@@ -8964,6 +8975,53 @@ ${calendarAddUrl.toString()}
|
|
|
8964
8975
|
if (storySub === "weekly") renderStoryWeekly();
|
|
8965
8976
|
else if (storySub === "monthly") renderStoryMonthly();
|
|
8966
8977
|
else renderStoryDaily();
|
|
8978
|
+
// 記録の詳細から着いた日(週)のカードを目立たせて、そこまで送る
|
|
8979
|
+
if (storyFocus) {
|
|
8980
|
+
var focused = null;
|
|
8981
|
+
app.querySelectorAll(".story-card[data-story-key]").forEach(function (c) {
|
|
8982
|
+
if (!focused && c.getAttribute("data-story-key") === storyFocus) focused = c;
|
|
8983
|
+
});
|
|
8984
|
+
if (focused) {
|
|
8985
|
+
focused.classList.add("story-card-focus");
|
|
8986
|
+
if (typeof focused.scrollIntoView === "function") focused.scrollIntoView({ block: "start" });
|
|
8987
|
+
}
|
|
8988
|
+
}
|
|
8989
|
+
}
|
|
8990
|
+
|
|
8991
|
+
/**
|
|
8992
|
+
* 記録の日付から「その日のストーリー」の行き先を決める(engine #551・オーナー決定 10/1)。
|
|
8993
|
+
* その日の日次のカード(動きのあった日)があれば日次へ、無ければその日を含む週のあらすじへ。どちらも無ければ null(リンクを出さない)
|
|
8994
|
+
*/
|
|
8995
|
+
function storyTargetOf(date) {
|
|
8996
|
+
if (!date || !/^\\d{4}-\\d{2}-\\d{2}$/.test(date) || !hasTab("story")) return null;
|
|
8997
|
+
var days = SITE.story || [];
|
|
8998
|
+
for (var i = 0; i < days.length; i++) {
|
|
8999
|
+
if (!days[i].quiet && days[i].date === date) return { sub: "daily", key: date, href: "?tab=story&day=" + encodeURIComponent(date) };
|
|
9000
|
+
}
|
|
9001
|
+
var weeks = (SITE.storyDigest && SITE.storyDigest.weeks) || [];
|
|
9002
|
+
var monday = storyMondayOf(date);
|
|
9003
|
+
for (var j = 0; j < weeks.length; j++) {
|
|
9004
|
+
if (weeks[j].key === monday) return { sub: "weekly", key: monday, href: "?tab=story&sub=weekly&week=" + encodeURIComponent(monday) };
|
|
9005
|
+
}
|
|
9006
|
+
return null;
|
|
9007
|
+
}
|
|
9008
|
+
/** 記録の詳細の「この日のストーリー」(行き先が無ければ null) */
|
|
9009
|
+
function storyLinkFor(rec) {
|
|
9010
|
+
var target = storyTargetOf(rec && rec.date);
|
|
9011
|
+
if (!target) return null;
|
|
9012
|
+
var a = el("a", { class: "trace-btn story-link-btn", href: target.href, text: target.sub === "daily" ? "📖 この日のストーリー" : "📖 この週のストーリー" });
|
|
9013
|
+
a.addEventListener("click", function (ev) {
|
|
9014
|
+
// 新しいタブで開く操作(修飾キー・中ボタン)はブラウザに任せる(href は本物の URL)
|
|
9015
|
+
if (ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey || (typeof ev.button === "number" && ev.button !== 0)) return;
|
|
9016
|
+
ev.preventDefault();
|
|
9017
|
+
listState.tab = "story";
|
|
9018
|
+
storySub = target.sub;
|
|
9019
|
+
storyFocus = target.key;
|
|
9020
|
+
lastWasDetail = false;
|
|
9021
|
+
history.pushState({}, "", target.href);
|
|
9022
|
+
renderList();
|
|
9023
|
+
});
|
|
9024
|
+
return a;
|
|
8967
9025
|
}
|
|
8968
9026
|
|
|
8969
9027
|
// 導入経緯(overview.adoption)の表示情報。Home見出し横のチップに出すラベルだけを返す。
|
|
@@ -20275,7 +20333,7 @@ ${calendarAddUrl.toString()}
|
|
|
20275
20333
|
// 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
|
|
20276
20334
|
var delMsgR = attachDeleteToEditRow(r, editRowR);
|
|
20277
20335
|
head.appendChild(el("div", { class: "detail-actions" }, [
|
|
20278
|
-
el("div", { class: "detail-actions-row" }, [ traceBtn2 ]),
|
|
20336
|
+
el("div", { class: "detail-actions-row" }, [ traceBtn2, storyLinkFor(r) ]),
|
|
20279
20337
|
editRowR.children.length ? editRowR : null,
|
|
20280
20338
|
delMsgR,
|
|
20281
20339
|
]));
|
|
@@ -20353,7 +20411,32 @@ ${calendarAddUrl.toString()}
|
|
|
20353
20411
|
REL_COLOR[r] = REL_META[r].color;
|
|
20354
20412
|
if (REL_META[r].kind === "evidence" || REL_META[r].kind === "topic") WEAK_RELS[r] = true;
|
|
20355
20413
|
});
|
|
20356
|
-
|
|
20414
|
+
// period: 期間で光らせる(engine #551)。unit は all(光らせない)/day/week、day は基準の日(YYYY-MM-DD)
|
|
20415
|
+
var graphState = { showWeak: true, q: "", period: { unit: "all", day: null } };
|
|
20416
|
+
|
|
20417
|
+
/** 期間の範囲(両端を含む YYYY-MM-DD)。「すべて」・日付が無いときは null。週は月曜〜日曜(ストーリーの週と同じ区切り) */
|
|
20418
|
+
function graphPeriodRange(period) {
|
|
20419
|
+
if (!period || !period.day || !/^\\d{4}-\\d{2}-\\d{2}$/.test(period.day)) return null;
|
|
20420
|
+
if (period.unit === "day") return { from: period.day, to: period.day };
|
|
20421
|
+
if (period.unit === "week") {
|
|
20422
|
+
var monday = storyMondayOf(period.day);
|
|
20423
|
+
return { from: monday, to: storyDayOfMs(storyUtcMs(monday) + 6 * 86400000) };
|
|
20424
|
+
}
|
|
20425
|
+
return null;
|
|
20426
|
+
}
|
|
20427
|
+
/** 期間の見出し(「10/1」「9/28〜10/4 の週」) */
|
|
20428
|
+
function graphPeriodLabel(period) {
|
|
20429
|
+
var range = graphPeriodRange(period);
|
|
20430
|
+
if (!range) return "";
|
|
20431
|
+
if (period.unit === "week") return storyWeekLabel(range.from);
|
|
20432
|
+
var p = range.from.split("-");
|
|
20433
|
+
return parseInt(p[1], 10) + "/" + parseInt(p[2], 10) + " の日";
|
|
20434
|
+
}
|
|
20435
|
+
/** 記録の日付(生まれた・変わった日)がその期間に入るか。日付の無い記録(関係者など)は入らない */
|
|
20436
|
+
function recordInPeriod(rec, range) {
|
|
20437
|
+
var d = rec && rec.date;
|
|
20438
|
+
return !!(d && /^\\d{4}-\\d{2}-\\d{2}$/.test(d) && d >= range.from && d <= range.to);
|
|
20439
|
+
}
|
|
20357
20440
|
|
|
20358
20441
|
function renderGraph() {
|
|
20359
20442
|
// 削除済みを除いたグラフを1本作り、件数・凡例・描画の全部をこれで賄う
|
|
@@ -20365,6 +20448,8 @@ ${calendarAddUrl.toString()}
|
|
|
20365
20448
|
search.value = graphState.q;
|
|
20366
20449
|
filters.appendChild(search);
|
|
20367
20450
|
app.appendChild(filters);
|
|
20451
|
+
var periodCtl = buildGraphPeriod(graph);
|
|
20452
|
+
app.appendChild(periodCtl.row);
|
|
20368
20453
|
|
|
20369
20454
|
var wrap = el("div", { class: "graph-wrap" });
|
|
20370
20455
|
var canvas = el("canvas", { class: "graph-canvas" });
|
|
@@ -20405,10 +20490,67 @@ ${calendarAddUrl.toString()}
|
|
|
20405
20490
|
legend.appendChild(toggle);
|
|
20406
20491
|
app.appendChild(legend);
|
|
20407
20492
|
|
|
20408
|
-
startGraph(canvas, search, graph);
|
|
20493
|
+
startGraph(canvas, search, graph, periodCtl);
|
|
20494
|
+
}
|
|
20495
|
+
|
|
20496
|
+
/**
|
|
20497
|
+
* 期間で光らせるつまみ(engine #551・オーナー決定 10/1)。「すべて/日/週」と基準の日・前後の送り。
|
|
20498
|
+
* 選ぶと、記録の日付(生まれた・変わった日)がその期間に入る記録と、そのつながりだけが光る。基準の日の既定は、点の中で一番新しい記録の日
|
|
20499
|
+
*/
|
|
20500
|
+
function buildGraphPeriod(graph) {
|
|
20501
|
+
var latest = "";
|
|
20502
|
+
graph.nodes.forEach(function (n) {
|
|
20503
|
+
var rec = n.recordId ? byId[n.recordId] : null;
|
|
20504
|
+
if (rec && rec.date && /^\\d{4}-\\d{2}-\\d{2}$/.test(rec.date) && rec.date > latest) latest = rec.date;
|
|
20505
|
+
});
|
|
20506
|
+
// 日付を持つ記録が 1 件も無い案件は、ビルドの日(日本時間)を基準にする(「日」「週」を選んでも何も起きない、を防ぐ)
|
|
20507
|
+
if (!graphState.period.day) graphState.period.day = latest || SITE.storyToday || (SITE.generatedAt ? String(SITE.generatedAt).slice(0, 10) : null);
|
|
20508
|
+
var row = el("div", { class: "graph-period" });
|
|
20509
|
+
var unit = el("select", { class: "graph-period-unit", "aria-label": "期間の区切り" });
|
|
20510
|
+
[["all", "すべて"], ["day", "日"], ["week", "週"]].forEach(function (o) {
|
|
20511
|
+
var opt = el("option", { value: o[0], text: o[1] });
|
|
20512
|
+
if (graphState.period.unit === o[0]) opt.selected = true;
|
|
20513
|
+
unit.appendChild(opt);
|
|
20514
|
+
});
|
|
20515
|
+
var day = el("input", { type: "date", class: "graph-period-day", "aria-label": "基準の日" });
|
|
20516
|
+
if (graphState.period.day) day.value = graphState.period.day;
|
|
20517
|
+
var prev = el("button", { type: "button", class: "graph-period-prev", text: "◀ 前", title: "前の期間" });
|
|
20518
|
+
var next = el("button", { type: "button", class: "graph-period-next", text: "次 ▶", title: "次の期間" });
|
|
20519
|
+
var label = el("span", { class: "graph-period-text" });
|
|
20520
|
+
row.appendChild(el("span", { text: "期間で光らせる:" }));
|
|
20521
|
+
[unit, day, prev, next, label].forEach(function (c) { row.appendChild(c); });
|
|
20522
|
+
var listeners = [];
|
|
20523
|
+
var shown = null; // 今の期間で光っている記録の数(つながり画面が数えて渡す)
|
|
20524
|
+
function paint() {
|
|
20525
|
+
var off = graphState.period.unit === "all";
|
|
20526
|
+
day.disabled = off; prev.disabled = off; next.disabled = off;
|
|
20527
|
+
label.textContent = off ? "" : graphPeriodLabel(graphState.period) + (shown === null ? "" : "に生まれた・変わった記録 " + shown + " 件");
|
|
20528
|
+
}
|
|
20529
|
+
function sync() {
|
|
20530
|
+
shown = null;
|
|
20531
|
+
paint();
|
|
20532
|
+
listeners.forEach(function (f) { f(); });
|
|
20533
|
+
}
|
|
20534
|
+
function step(sign) {
|
|
20535
|
+
if (!graphState.period.day) return;
|
|
20536
|
+
var days = graphState.period.unit === "week" ? 7 : 1;
|
|
20537
|
+
graphState.period.day = storyDayOfMs(storyUtcMs(graphState.period.day) + sign * days * 86400000);
|
|
20538
|
+
day.value = graphState.period.day;
|
|
20539
|
+
sync();
|
|
20540
|
+
}
|
|
20541
|
+
unit.addEventListener("change", function () { graphState.period.unit = unit.value; sync(); });
|
|
20542
|
+
day.addEventListener("change", function () { if (/^\\d{4}-\\d{2}-\\d{2}$/.test(day.value)) { graphState.period.day = day.value; sync(); } });
|
|
20543
|
+
prev.addEventListener("click", function () { step(-1); });
|
|
20544
|
+
next.addEventListener("click", function () { step(1); });
|
|
20545
|
+
sync();
|
|
20546
|
+
return {
|
|
20547
|
+
row: row,
|
|
20548
|
+
onChange: function (f) { listeners.push(f); },
|
|
20549
|
+
showCount: function (n) { shown = n; paint(); },
|
|
20550
|
+
};
|
|
20409
20551
|
}
|
|
20410
20552
|
|
|
20411
|
-
function startGraph(canvas, searchInput, graph) {
|
|
20553
|
+
function startGraph(canvas, searchInput, graph, periodCtl) {
|
|
20412
20554
|
var allEdges = graph.edges.filter(function (e) {
|
|
20413
20555
|
return graphState.showWeak || !WEAK_RELS[e.rel];
|
|
20414
20556
|
});
|
|
@@ -20421,6 +20563,7 @@ ${calendarAddUrl.toString()}
|
|
|
20421
20563
|
.filter(function (n) { return n.recordId || used[n.id]; })
|
|
20422
20564
|
.map(function (n, i) {
|
|
20423
20565
|
return { id: n.id, label: n.label, group: n.group, recordId: n.recordId, url: n.url,
|
|
20566
|
+
glow: typeof n.glow === "number" ? n.glow : null,
|
|
20424
20567
|
x: typeof n.x === "number" ? n.x : 0, y: typeof n.y === "number" ? n.y : 0,
|
|
20425
20568
|
vx: 0, vy: 0, phase: i * 2.399 };
|
|
20426
20569
|
});
|
|
@@ -20462,9 +20605,11 @@ ${calendarAddUrl.toString()}
|
|
|
20462
20605
|
|
|
20463
20606
|
// 検索: 一致ノード(matched)が光り、その隣接(lit)は残し、他は減光する
|
|
20464
20607
|
var matched = {}, lit = {}, searching = false;
|
|
20608
|
+
// 期間(engine #551)を選んでいるときは、その期間に生まれた・変わった記録だけが一致になる(キーワードと両方なら両方を満たす記録)
|
|
20465
20609
|
function applySearch() {
|
|
20466
20610
|
var q = graphState.q.trim().toLowerCase();
|
|
20467
|
-
|
|
20611
|
+
var range = graphPeriodRange(graphState.period.unit === "all" ? null : graphState.period);
|
|
20612
|
+
searching = q.length > 0 || !!range;
|
|
20468
20613
|
matched = {}; lit = {};
|
|
20469
20614
|
if (!searching) {
|
|
20470
20615
|
countEl.textContent = "点 " + nodes.length + " / つながり " + edges.length;
|
|
@@ -20472,12 +20617,16 @@ ${calendarAddUrl.toString()}
|
|
|
20472
20617
|
}
|
|
20473
20618
|
var count = 0;
|
|
20474
20619
|
nodes.forEach(function (n) {
|
|
20475
|
-
if (n.search.indexOf(q)
|
|
20620
|
+
if (q && n.search.indexOf(q) === -1) return;
|
|
20621
|
+
if (range && !recordInPeriod(n.recordId ? byId[n.recordId] : null, range)) return;
|
|
20622
|
+
matched[n.id] = true; lit[n.id] = true; count++;
|
|
20476
20623
|
});
|
|
20477
20624
|
edges.forEach(function (e) {
|
|
20478
20625
|
if (matched[e.a.id] || matched[e.b.id]) { lit[e.a.id] = true; lit[e.b.id] = true; }
|
|
20479
20626
|
});
|
|
20480
20627
|
countEl.textContent = count + " 件ヒット(つながりも一緒に光っています)";
|
|
20628
|
+
// 期間を選んでいるときは、つまみの行に「◯◯に生まれた・変わった記録 N 件」(キーワードと両方なら両方を満たす数)
|
|
20629
|
+
if (periodCtl && range) periodCtl.showCount(count);
|
|
20481
20630
|
}
|
|
20482
20631
|
applySearch();
|
|
20483
20632
|
if (searchInput) {
|
|
@@ -20486,6 +20635,7 @@ ${calendarAddUrl.toString()}
|
|
|
20486
20635
|
applySearch();
|
|
20487
20636
|
});
|
|
20488
20637
|
}
|
|
20638
|
+
if (periodCtl) periodCtl.onChange(applySearch);
|
|
20489
20639
|
|
|
20490
20640
|
var dpr = window.devicePixelRatio || 1;
|
|
20491
20641
|
var ctx = canvas.getContext("2d");
|
|
@@ -20668,8 +20818,20 @@ ${calendarAddUrl.toString()}
|
|
|
20668
20818
|
if (alpha > 0.003) alpha *= 0.995;
|
|
20669
20819
|
}
|
|
20670
20820
|
|
|
20821
|
+
// 記録の点の大きさ: 目次と同じ順位から決めた明るさ(glow・0〜1・engine #551)があれば 6〜12、無ければ今までどおり一律 9
|
|
20671
20822
|
function nodeRadius(n) {
|
|
20672
|
-
|
|
20823
|
+
if (!n.recordId) return 6;
|
|
20824
|
+
if (n.glow === null) return 9;
|
|
20825
|
+
var r = 6 + 6 * n.glow;
|
|
20826
|
+
// 一致した点は少なくとも今までの一律の大きさ(9)にする(順位の低い点でも見つけられるように)
|
|
20827
|
+
return matched[n.id] ? Math.max(r, 9) : r;
|
|
20828
|
+
}
|
|
20829
|
+
// 記録の点の明るさの係数(glow が無ければ 1=今までどおり)。目次の上位ほど明るく、下位は沈む
|
|
20830
|
+
// 🔴 検索・期間で一致した点とホバー中の点は順位で沈めない(その日に足されたばかりで順位に載っていない記録が、
|
|
20831
|
+
// 「この期間の記録」を選んだのに一番暗く小さく出る、を防ぐ)
|
|
20832
|
+
function glowAlpha(n) {
|
|
20833
|
+
if (matched[n.id] || n === hovered) return 1;
|
|
20834
|
+
return n.recordId && n.glow !== null ? 0.4 + 0.6 * n.glow : 1;
|
|
20673
20835
|
}
|
|
20674
20836
|
|
|
20675
20837
|
function draw() {
|
|
@@ -20817,7 +20979,7 @@ ${calendarAddUrl.toString()}
|
|
|
20817
20979
|
var isGold = !!n.recordId;
|
|
20818
20980
|
// Gold以外の星はゆっくり瞬く
|
|
20819
20981
|
var tw = isGold ? 1 : 0.72 + 0.28 * (0.5 + 0.5 * Math.sin(now * 0.0017 + n.phase));
|
|
20820
|
-
ctx.globalAlpha = alphaN * tw;
|
|
20982
|
+
ctx.globalAlpha = alphaN * tw * glowAlpha(n);
|
|
20821
20983
|
var r0 = nodeRadius(n);
|
|
20822
20984
|
var fill = isGold ? style.color : "#c9d6f2";
|
|
20823
20985
|
// 検索一致ノードとホバー中のノードは金色のリングで発光させる
|
|
@@ -20842,7 +21004,7 @@ ${calendarAddUrl.toString()}
|
|
|
20842
21004
|
ctx.shadowColor =
|
|
20843
21005
|
introFlash > 0.01 ? "#bcd0ff" : isGold ? style.color : "#9fb4e8";
|
|
20844
21006
|
ctx.shadowBlur =
|
|
20845
|
-
(isGold ? 16 : 7) * (dim ? 0.3 : 1) * (inChain ? 1.7 : 1) * (1 + introFlash * 1.8);
|
|
21007
|
+
(isGold ? (n.glow === null || matched[n.id] ? Math.max(16, n.glow === null ? 16 : 6 + 22 * n.glow) : 6 + 22 * n.glow) : 7) * (dim ? 0.3 : 1) * (inChain ? 1.7 : 1) * (1 + introFlash * 1.8);
|
|
20846
21008
|
ctx.beginPath();
|
|
20847
21009
|
ctx.arc(n.x, n.y, r0, 0, Math.PI * 2);
|
|
20848
21010
|
ctx.fillStyle = fill;
|
|
@@ -20979,7 +21141,7 @@ ${calendarAddUrl.toString()}
|
|
|
20979
21141
|
// 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
|
|
20980
21142
|
var delMsgD = attachDeleteToEditRow(d, editRowD);
|
|
20981
21143
|
head.appendChild(el("div", { class: "detail-actions" }, [
|
|
20982
|
-
el("div", { class: "detail-actions-row" }, [ traceBtn ]),
|
|
21144
|
+
el("div", { class: "detail-actions-row" }, [ traceBtn, storyLinkFor(d) ]),
|
|
20983
21145
|
editRowD.children.length ? editRowD : null,
|
|
20984
21146
|
delMsgD,
|
|
20985
21147
|
]));
|
|
@@ -21493,6 +21655,8 @@ ${calendarAddUrl.toString()}
|
|
|
21493
21655
|
var validSub = false;
|
|
21494
21656
|
STORY_SUBS.forEach(function (s) { if (s[0] === sub) validSub = true; });
|
|
21495
21657
|
storySub = validSub ? sub : STORY_SUB_DEFAULT;
|
|
21658
|
+
var focusKey = storySub === "weekly" ? getParam("week") : storySub === "daily" ? getParam("day") : null;
|
|
21659
|
+
storyFocus = focusKey && /^\\d{4}-\\d{2}-\\d{2}$/.test(focusKey) ? focusKey : null;
|
|
21496
21660
|
}
|
|
21497
21661
|
renderList();
|
|
21498
21662
|
}
|
package/package.json
CHANGED