@takagaki/cortex-decisions-viewer 0.12.160 → 0.12.163
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 +55 -39
- package/dist/external-id.js +1 -1
- package/dist/render.js +58 -502
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.RELATED_MATERIALS_SECTIONS = exports.RELATED_URL_MAX = exports.RELATED_MATERIALS_FIELD = exports.DECISION_FIRMNESS = exports.PROJECT_ICON_EXTS = exports.MIRROR_RELS = void 0;
|
|
39
|
+
exports.RELATED_MATERIALS_SECTIONS = exports.RELATED_URL_MAX = exports.RELATED_MATERIALS_FIELD = exports.DECISION_FIRMNESS = exports.GRAPH_RELS = exports.GRAPH_RECORD_TYPES = exports.PROJECT_ICON_EXTS = exports.MIRROR_RELS = void 0;
|
|
40
40
|
exports.sanitizeBody = sanitizeBody;
|
|
41
41
|
exports.findProjectIcon = findProjectIcon;
|
|
42
42
|
exports.readFleetSources = readFleetSources;
|
|
@@ -287,15 +287,11 @@ function resolveKnowledgeRoot(decisionsDirAbs) {
|
|
|
287
287
|
return null;
|
|
288
288
|
}
|
|
289
289
|
/**
|
|
290
|
-
* 案件アイコン(#348
|
|
290
|
+
* 案件アイコン(#348)で拾う拡張子。**探索の順でもある**(2枚あれば先頭を使う)。
|
|
291
291
|
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* 突き合わせは viewer/test/project-icon-parity.test.mjs が機械的に見張る。
|
|
296
|
-
*
|
|
297
|
-
* `.jpeg` は入れない——intake は `.jpeg` を `jpg` へ正規化して書くので、
|
|
298
|
-
* リポジトリにその名前は生まれない(拾うと「どちらが正か」が2つになる)。
|
|
292
|
+
* 置き場は案件リポの `Cortex/icon.<ext>`。画面と intake からアイコンを置く・外す口は外した(cortex-engine #1781・
|
|
293
|
+
* 仕様 common.screen-ops)ので、置くのは人が GitHub で直接。ポータルの取り込み(`v/icon.*`)も同じ 3 つを読む。
|
|
294
|
+
* `.jpeg` は入れない(前の intake は `jpg` へ正規化して書いていた・拾うと「どちらが正か」が2つになる)。
|
|
299
295
|
*/
|
|
300
296
|
exports.PROJECT_ICON_EXTS = ["png", "jpg", "webp"];
|
|
301
297
|
/**
|
|
@@ -1029,7 +1025,15 @@ function sanitizeFm(v) {
|
|
|
1029
1025
|
}
|
|
1030
1026
|
// グラフの凡例で独立した分類(=色)を持つ type。Gold 4区画(decision/term/member/rule)は
|
|
1031
1027
|
// それぞれ独立の分類にする(「その他レコード」に丸めない——2026-08-28 オーナー裁定)
|
|
1032
|
-
|
|
1028
|
+
/** つながり画面の点にする記録の種類(仕様 9 章 graph.records-and-evidence)。Home・レポート・その他は点にしない */
|
|
1029
|
+
exports.GRAPH_RECORD_TYPES = new Set(["decision", "term", "member", "rule"]);
|
|
1030
|
+
/** 区画(置き場)の名前 → 点の種類。`type` の無い記録の受け皿 */
|
|
1031
|
+
const GRAPH_SECTION_TYPES = { Decisions: "decision", Glossary: "term", Members: "member", Rules: "rule" };
|
|
1032
|
+
/**
|
|
1033
|
+
* つながり画面の線にする relations の種類(仕様 9 章・記録どうしの frontmatter のつながり)。
|
|
1034
|
+
* 根拠・生成元・関連・置換と、根拠への参照・出典だけ。話題(about)など、機械が名前の一致で付けた手がかりは線にしない
|
|
1035
|
+
*/
|
|
1036
|
+
exports.GRAPH_RELS = new Set(["based_on", "derived_from", "relates_to", "supersedes", "reference", "source"]);
|
|
1033
1037
|
/** ディレクトリ配下の .md を再帰的に列挙する(存在しなければ空) */
|
|
1034
1038
|
async function walkMd(dirAbs) {
|
|
1035
1039
|
const out = [];
|
|
@@ -1680,7 +1684,7 @@ function attachGlow(nodes, glow, recordRelPaths) {
|
|
|
1680
1684
|
* ほうがよい(`createRefResolver` と同じ考え方)。崩れた文字(宛先に見えない参照)も同様に、
|
|
1681
1685
|
* 点は作るが飛び先は付けない。
|
|
1682
1686
|
*/
|
|
1683
|
-
function buildGraph(decisions,
|
|
1687
|
+
function buildGraph(decisions, sections, targetUrls, repoBaseUrl, book = (0, evidence_names_1.emptyEvidenceBook)()) {
|
|
1684
1688
|
const nodes = new Map();
|
|
1685
1689
|
const edges = [];
|
|
1686
1690
|
const addNode = (node) => {
|
|
@@ -1692,11 +1696,14 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (
|
|
|
1692
1696
|
for (const d of decisions) {
|
|
1693
1697
|
addNode({ id: d.id, label: d.title, group: "decision", recordId: d.id });
|
|
1694
1698
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1699
|
+
// 🔴 点は決定・ルール・用語・関係者の記録と、その根拠だけ(仕様 graph.question / graph.records-and-evidence)。
|
|
1700
|
+
// Home・レポート・その他レコードは点にしない
|
|
1697
1701
|
for (const sec of sections) {
|
|
1698
1702
|
for (const r of sec.records) {
|
|
1699
|
-
|
|
1703
|
+
// 種類は type で決め、type の無い記録は置き場(区画)で決める。どちらでも 4 種類に当たらないものは点にしない
|
|
1704
|
+
const group = exports.GRAPH_RECORD_TYPES.has(r.rtype) ? r.rtype : GRAPH_SECTION_TYPES[sec.name];
|
|
1705
|
+
if (!group)
|
|
1706
|
+
continue;
|
|
1700
1707
|
addNode({ id: r.id, label: r.title, group, recordId: r.id });
|
|
1701
1708
|
}
|
|
1702
1709
|
}
|
|
@@ -1704,11 +1711,10 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (
|
|
|
1704
1711
|
const fileNameToId = new Map();
|
|
1705
1712
|
for (const d of decisions)
|
|
1706
1713
|
fileNameToId.set(d.fileName, d.id);
|
|
1707
|
-
if (home)
|
|
1708
|
-
fileNameToId.set(home.fileName, home.id);
|
|
1709
1714
|
for (const sec of sections)
|
|
1710
1715
|
for (const r of sec.records)
|
|
1711
|
-
|
|
1716
|
+
if (nodes.has(r.id))
|
|
1717
|
+
fileNameToId.set(r.fileName, r.id);
|
|
1712
1718
|
// 🔴 **GitHubの写し(blob)へ飛ぶ点は作らない**(2026-10-01 オーナー決定・分類B)。
|
|
1713
1719
|
// `targetUrls`(minute:/material:)や詳細画面と共用の解決器(RefResolver)は、元ツールの
|
|
1714
1720
|
// URLが無いとき自分のリポジトリの blob URL に倒すことがある(詳細画面ではそれでよい設計)。
|
|
@@ -1749,6 +1755,8 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (
|
|
|
1749
1755
|
return ev.id;
|
|
1750
1756
|
};
|
|
1751
1757
|
// 参照先IDから層を推定してノード化する
|
|
1758
|
+
// 🔴 引けない宛先(記録でも根拠でもないもの)は点を作らない。数だけ数えて、ビルドのログに出す(黙って捨てない)
|
|
1759
|
+
let unresolvedTargets = 0;
|
|
1752
1760
|
const classifyTarget = (target, humanLabel) => {
|
|
1753
1761
|
if (nodes.has(target) && !labelRank.has(target))
|
|
1754
1762
|
return target;
|
|
@@ -1759,30 +1767,44 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (
|
|
|
1759
1767
|
return resolved;
|
|
1760
1768
|
if (target.startsWith(NO_ORIGINAL_TOOL_PREFIX)) {
|
|
1761
1769
|
// 元ツールが無い根拠(engine #968 の将来の語彙)。リンクにせず、機械の語(urn:…)を出さない
|
|
1762
|
-
return addNode({ id: target, label: NO_ORIGINAL_TOOL_LABEL, group: "
|
|
1770
|
+
return addNode({ id: target, label: NO_ORIGINAL_TOOL_LABEL, group: "silver" });
|
|
1763
1771
|
}
|
|
1764
1772
|
const ev = identifyEvidence(target, identityCtx, humanLabel);
|
|
1765
1773
|
if (ev)
|
|
1766
1774
|
return addEvidence(ev);
|
|
1775
|
+
// 点にしない記録(Home・レポート・その他)へのパス参照は、根拠ではない。点を作らず数える
|
|
1776
|
+
if (/(^|\/)Cortex\/(Home\.md|[^/]+\/records\/)/.test(target)) {
|
|
1777
|
+
unresolvedTargets++;
|
|
1778
|
+
return null;
|
|
1779
|
+
}
|
|
1767
1780
|
if (target.includes("/") || target.toLowerCase().endsWith(".md")) {
|
|
1768
1781
|
// リポジトリ内ファイルへの生のパス参照で、帳(写しの課題・資料・議事録)に当たらないもの。
|
|
1769
1782
|
// 元ツールのURLがまだ無い記録は、点にするが飛び先を付けない(#968 の書き換えを待つ)。
|
|
1770
1783
|
// 名前は最後の段から拡張子を外したもの(`.md` のようなファイルの語を出さない)
|
|
1771
1784
|
return addNode({ id: target, label: (0, evidence_names_1.pathFallbackLabel)(target), group: "silver" });
|
|
1772
1785
|
}
|
|
1773
|
-
|
|
1786
|
+
unresolvedTargets++;
|
|
1787
|
+
return null;
|
|
1774
1788
|
};
|
|
1775
1789
|
const addRelationEdges = (fromId, relations) => {
|
|
1776
1790
|
for (const r of relations) {
|
|
1777
1791
|
// 🔴 `superseded_by` は描かない(#699 F3)。新しい側の `supersedes` の写しで、描くと同じ置き換えが 2 本になる
|
|
1778
1792
|
if (exports.MIRROR_RELS.has(r.rel))
|
|
1779
1793
|
continue;
|
|
1780
|
-
|
|
1794
|
+
// 🔴 線にするのは記録どうしの根拠・生成元・関連・置換だけ(話題 about などは引かない)
|
|
1795
|
+
if (!exports.GRAPH_RELS.has(r.rel))
|
|
1796
|
+
continue;
|
|
1797
|
+
const to = classifyTarget(r.target);
|
|
1798
|
+
if (to)
|
|
1799
|
+
edges.push({ from: fromId, to, rel: r.rel });
|
|
1781
1800
|
}
|
|
1782
1801
|
};
|
|
1783
1802
|
const addSourceEdge = (fromId, source) => {
|
|
1784
|
-
if (source && source.trim() !== "")
|
|
1785
|
-
|
|
1803
|
+
if (source && source.trim() !== "") {
|
|
1804
|
+
const to = classifyTarget(source.trim());
|
|
1805
|
+
if (to)
|
|
1806
|
+
edges.push({ from: fromId, to, rel: "source" });
|
|
1807
|
+
}
|
|
1786
1808
|
};
|
|
1787
1809
|
const addReferenceEdges = (fromId, references) => {
|
|
1788
1810
|
for (const ref of references) {
|
|
@@ -1791,21 +1813,16 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (
|
|
|
1791
1813
|
// (`[名前](URL)` の名前は `identifyEvidence` が書かれていた値から拾う。解決器が付けた名前は
|
|
1792
1814
|
// 種類の既定のこともあるので、人の名前としては渡さない)
|
|
1793
1815
|
const raw = REF_RAW.get(ref);
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
};
|
|
1800
|
-
const addLinkEdges = (fromId, links) => {
|
|
1801
|
-
for (const link of links) {
|
|
1802
|
-
edges.push({ from: fromId, to: classifyTarget(link), rel: "link" });
|
|
1816
|
+
const to = raw !== undefined
|
|
1817
|
+
? classifyTarget(raw)
|
|
1818
|
+
: classifyTarget(ref.url ?? ref.label, ref.url && ref.label !== ref.url ? ref.label : undefined);
|
|
1819
|
+
if (to)
|
|
1820
|
+
edges.push({ from: fromId, to, rel: "reference" });
|
|
1803
1821
|
}
|
|
1804
1822
|
};
|
|
1805
1823
|
for (const d of decisions) {
|
|
1806
1824
|
addRelationEdges(d.id, d.relations);
|
|
1807
1825
|
addReferenceEdges(d.id, d.references);
|
|
1808
|
-
addLinkEdges(d.id, d.bodyLinks);
|
|
1809
1826
|
}
|
|
1810
1827
|
// 決めた人の線(決定 → 関係者・#1094)。relations には書かれないので `decided_by[].member` から引く。
|
|
1811
1828
|
// 🔴 相手の関係者がグラフに無ければ引かない(宛先の無い点を作らない=classifyTarget を通さない)
|
|
@@ -1818,16 +1835,12 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (
|
|
|
1818
1835
|
edges.push({ from: d.id, to: who.member, rel: "decided_by" });
|
|
1819
1836
|
}
|
|
1820
1837
|
}
|
|
1821
|
-
if (home) {
|
|
1822
|
-
addRelationEdges(home.id, home.relations);
|
|
1823
|
-
addSourceEdge(home.id, home.source);
|
|
1824
|
-
addLinkEdges(home.id, home.bodyLinks);
|
|
1825
|
-
}
|
|
1826
1838
|
for (const sec of sections) {
|
|
1827
1839
|
for (const r of sec.records) {
|
|
1840
|
+
if (!nodes.has(r.id))
|
|
1841
|
+
continue; // 点にしない種類(Home・レポート・その他)の線は引かない
|
|
1828
1842
|
addRelationEdges(r.id, r.relations);
|
|
1829
1843
|
addSourceEdge(r.id, r.source);
|
|
1830
|
-
addLinkEdges(r.id, r.bodyLinks);
|
|
1831
1844
|
}
|
|
1832
1845
|
}
|
|
1833
1846
|
// 自己ループと重複辺を除去する(同じURLが複数回貼られていても辺は1本)
|
|
@@ -1841,6 +1854,9 @@ function buildGraph(decisions, home, sections, targetUrls, repoBaseUrl, book = (
|
|
|
1841
1854
|
seen.add(key);
|
|
1842
1855
|
return true;
|
|
1843
1856
|
});
|
|
1857
|
+
if (unresolvedTargets > 0) {
|
|
1858
|
+
console.log(` つながり: 引けない根拠 ${unresolvedTargets} 件は点にしませんでした(記録でも元のツールの根拠でもない宛先)`);
|
|
1859
|
+
}
|
|
1844
1860
|
return { nodes: [...nodes.values()], edges: deduped };
|
|
1845
1861
|
}
|
|
1846
1862
|
/** 反発(O(n²))。点が少ないうちはこれが最速。 */
|
|
@@ -2336,7 +2352,7 @@ async function buildSite(opts, views) {
|
|
|
2336
2352
|
const rankedDecisions = (0, ranking_1.orderByRanking)(decisions, (d) => recordRelPaths.get(d.id), memoryRanking?.Decisions ?? null);
|
|
2337
2353
|
// 期間フィルタ用の月(YYYY-MM)を日付から導出し、降順(新しい順)にする
|
|
2338
2354
|
const months = uniqueSorted(decisions.map((d) => d.date.slice(0, 7)).filter((m) => /^\d{4}-\d{2}$/.test(m))).reverse();
|
|
2339
|
-
const graph = buildGraph(decisions,
|
|
2355
|
+
const graph = buildGraph(decisions, graphSections, targetUrls, repo.baseUrl, evidenceBook);
|
|
2340
2356
|
computeLayout(graph); // 各ノードに x/y を書き込む(クライアントはこれを初期位置に使いウォームアップ省略)
|
|
2341
2357
|
// 点の明るさと大きさ(engine #551)。順位のファイルが読めない案件では 1 点も付かない(今までと同じ画面)
|
|
2342
2358
|
if (memoryRanking)
|
package/dist/external-id.js
CHANGED
|
@@ -31,7 +31,7 @@ exports.memberSearchTerms = memberSearchTerms;
|
|
|
31
31
|
/**
|
|
32
32
|
* このリポジトリの内側の呼び名。外の ID と見なさない。
|
|
33
33
|
*
|
|
34
|
-
* 前半は記録の id
|
|
34
|
+
* 前半は記録の id の種類(記録の種類の一覧と同じ)、後半は Silver の参照
|
|
35
35
|
* (`build.ts` の `classifyTarget` が正本の URL に解決する 3 つ)。
|
|
36
36
|
*/
|
|
37
37
|
const INSIDE_KINDS = new Set([
|
package/dist/render.js
CHANGED
|
@@ -4714,8 +4714,8 @@ a:hover { text-decoration: underline; }
|
|
|
4714
4714
|
別の切れ方をすると「設定したのと違う絵が出ている」と読まれる */
|
|
4715
4715
|
.site-icon-slot { position: relative; flex: 0 0 auto; display: flex; align-items: center; }
|
|
4716
4716
|
/* 大きさは案件名(18px・太字)の文字の高さに揃える(0.12.25 の 28px はオーナー実機で
|
|
4717
|
-
|
|
4718
|
-
|
|
4717
|
+
「小さくて見にくい」)。画像側(.site-icon-img)を丸く切る。
|
|
4718
|
+
🔴 丸は見せるだけで押せない(仕様 common.screen-ops)ので、ホバーの効果・カーソル・メニューは持たない */
|
|
4719
4719
|
.site-icon {
|
|
4720
4720
|
position: relative;
|
|
4721
4721
|
width: 36px; height: 36px; border-radius: 50%;
|
|
@@ -4729,59 +4729,7 @@ a:hover { text-decoration: underline; }
|
|
|
4729
4729
|
display: flex; align-items: center; justify-content: center;
|
|
4730
4730
|
width: 100%; height: 100%; border-radius: 50%;
|
|
4731
4731
|
}
|
|
4732
|
-
/* **「+」はホバー時だけ、右下に小さく重ねる**(常時は出さない)。
|
|
4733
|
-
受け口が無い案件(span.site-icon)には付かない(置けない口に誘いを出さない) */
|
|
4734
|
-
.site-icon-plus {
|
|
4735
|
-
position: absolute; right: -2px; bottom: -2px;
|
|
4736
|
-
width: 16px; height: 16px; border-radius: 50%;
|
|
4737
|
-
display: flex; align-items: center; justify-content: center;
|
|
4738
|
-
background: var(--accent); color: #fff; font-size: 12px; font-weight: 700; line-height: 1;
|
|
4739
|
-
box-shadow: 0 0 0 2px #fff;
|
|
4740
|
-
opacity: 0; transition: opacity 0.12s ease;
|
|
4741
|
-
}
|
|
4742
|
-
button.site-icon:hover .site-icon-plus,
|
|
4743
|
-
button.site-icon[aria-expanded="true"] .site-icon-plus { opacity: 1; }
|
|
4744
|
-
/* 設定済みの丸は画像だけ。**押せることはホバーで出す**——常時ラベルを重ねると
|
|
4745
|
-
選んだロゴが読めなくなる(アイコンを設定した意味が薄れる) */
|
|
4746
|
-
.site-icon-hover {
|
|
4747
|
-
position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 50%;
|
|
4748
|
-
display: flex; align-items: center; justify-content: center;
|
|
4749
|
-
background: rgba(0, 0, 0, 0.55); color: #fff;
|
|
4750
|
-
font-size: 11px; font-weight: 600; line-height: 1;
|
|
4751
|
-
opacity: 0; transition: opacity 0.12s ease;
|
|
4752
|
-
}
|
|
4753
|
-
button.site-icon:hover .site-icon-hover,
|
|
4754
|
-
button.site-icon[aria-expanded="true"] .site-icon-hover { opacity: 1; }
|
|
4755
|
-
/* **押せることはホバーで示す。** 受け口が無い案件の丸は span なので、この効果は付かない
|
|
4756
|
-
——押せない丸に指差しカーソルやリングを出すのが、画面の嘘のうちで一番たちが悪い */
|
|
4757
|
-
button.site-icon { cursor: pointer; }
|
|
4758
|
-
button.site-icon:hover, button.site-icon[aria-expanded="true"] { box-shadow: 0 0 0 2px var(--accent); }
|
|
4759
|
-
/* **保存中(#378)は丸の上に .busy-overlay(共通部品)が載る。** その間はホバーの「変更」「+」
|
|
4760
|
-
もリングも出さない(スピナーと重なって読めない・押せるように見せない)。
|
|
4761
|
-
ホバー規則と同じ強さなので、**この行はホバー規則より後に置く** */
|
|
4762
|
-
button.site-icon[aria-busy="true"] { cursor: wait; box-shadow: none; }
|
|
4763
|
-
button.site-icon[aria-busy="true"] .site-icon-hover,
|
|
4764
|
-
button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
|
|
4765
4732
|
.site-icon-img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
|
|
4766
|
-
/* 丸を押したときだけ出るメニュー。スロットを基準に浮かせる(ヘッダーは sticky なので
|
|
4767
|
-
ページを送っても位置がずれない) */
|
|
4768
|
-
.site-icon-menu {
|
|
4769
|
-
position: absolute; top: 44px; left: 0; z-index: 30; min-width: 220px;
|
|
4770
|
-
background: #fff; border: 1px solid var(--border); border-radius: 10px;
|
|
4771
|
-
box-shadow: 0 8px 24px rgba(0,0,0,.12); padding: 8px;
|
|
4772
|
-
display: flex; flex-direction: column; gap: 6px;
|
|
4773
|
-
}
|
|
4774
|
-
.site-icon-menu[hidden] { display: none; }
|
|
4775
|
-
.site-icon-menu button {
|
|
4776
|
-
text-align: left; padding: 8px 10px; border-radius: 8px;
|
|
4777
|
-
border: 1px solid var(--border); background: #fff; font-size: 13px; cursor: pointer;
|
|
4778
|
-
}
|
|
4779
|
-
.site-icon-menu button:hover { background: #f3f6fb; }
|
|
4780
|
-
.site-icon-menu button:disabled { opacity: .6; cursor: default; }
|
|
4781
|
-
/* 結果の行はメニューの中に置く(丸の横に出すとヘッダーの高さに押し込まれて読めない) */
|
|
4782
|
-
.site-icon-status { font-size: 12.5px; padding: 2px 4px; }
|
|
4783
|
-
.site-icon-status:empty { display: none; }
|
|
4784
|
-
.site-icon-status-error { color: #b91c1c; }
|
|
4785
4733
|
.record-count { color: var(--muted); font-size: 13px; }
|
|
4786
4734
|
.ask-ai-btn {
|
|
4787
4735
|
margin-left: auto; flex: 0 0 auto;
|
|
@@ -4842,14 +4790,6 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
|
|
|
4842
4790
|
box-shadow: var(--shadow); cursor: grab; display: block;
|
|
4843
4791
|
}
|
|
4844
4792
|
.graph-canvas.dragging { cursor: grabbing; }
|
|
4845
|
-
.graph-legend {
|
|
4846
|
-
display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
|
|
4847
|
-
margin: 10px 2px 0; font-size: 12.5px; color: var(--muted);
|
|
4848
|
-
}
|
|
4849
|
-
.graph-legend .dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; vertical-align: -1px; }
|
|
4850
|
-
.graph-legend .line { display: inline-block; width: 16px; height: 0; border-top: 3px solid; margin-right: 4px; vertical-align: 4px; }
|
|
4851
|
-
.graph-legend .sep { color: var(--muted); margin-left: 8px; padding-left: 8px; border-left: 1px solid var(--border); }
|
|
4852
|
-
.graph-legend label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; color: var(--text); }
|
|
4853
4793
|
|
|
4854
4794
|
.graph-reset {
|
|
4855
4795
|
position: absolute; top: 12px; right: 12px; z-index: 2;
|
|
@@ -8234,10 +8174,10 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
8234
8174
|
btnUrl: base ? base + "/actions/workflows/update-gold.yml" : null,
|
|
8235
8175
|
},
|
|
8236
8176
|
"graph": {
|
|
8237
|
-
text: "
|
|
8177
|
+
text: "決定・ルール・用語・関係者と、その根拠(元のツール)のつながりの地図です。点をクリックすると、そのページや元のツール(Backlog・GitHub等)に移動できます。",
|
|
8238
8178
|
},
|
|
8239
8179
|
"explorer": {
|
|
8240
|
-
text: "
|
|
8180
|
+
text: "決定・ルール・用語・関係者を起点に、つながり(根拠・出典)を1列ずつ辿る表示です。行をクリックすると右に次の列が開きます。",
|
|
8241
8181
|
},
|
|
8242
8182
|
"story": {
|
|
8243
8183
|
text: "このプロジェクトの日々の動きを、日次・週次・月次に分けてまとめた記録です。直したいことや足したいことは、各カードの「GitHubで編集」か「AIで編集」から書けます。",
|
|
@@ -8479,7 +8419,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
8479
8419
|
|
|
8480
8420
|
var sec = el("section", { class: "tut-section" });
|
|
8481
8421
|
sec.appendChild(el("h2", { class: "tut-section-head", text: "はじめに — このプロジェクトを掴む" + challenges.length + "ステップ" }));
|
|
8482
|
-
sec.appendChild(el("p", { class: "tut-intro", text: "
|
|
8422
|
+
sec.appendChild(el("p", { class: "tut-intro", text: "ビューアと Claude Code を使ってこのプロジェクトを掴むための" + challenges.length + "ステップです。各ボタンを押すと、質問が入力済みの状態で Claude Code Web が開きます(このリポジトリへのアクセス権が必要です)。" }));
|
|
8483
8423
|
|
|
8484
8424
|
// 進捗ヘッダ({完了数}/5・バー・全完了で祝福)
|
|
8485
8425
|
var doneCount = challenges.filter(function (c) { return tutIsDone(c.n); }).length;
|
|
@@ -8518,121 +8458,10 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
8518
8458
|
|
|
8519
8459
|
// ---------- 案件アイコン(#348) ----------
|
|
8520
8460
|
//
|
|
8521
|
-
// ポータルの案件一覧はカードにアイコンを出す。置き場はこの案件のリポジトリの
|
|
8522
|
-
//
|
|
8523
|
-
//
|
|
8524
|
-
//
|
|
8525
|
-
//
|
|
8526
|
-
// **出す場所はヘッダーの案件名の左**(0.12.24 の Home 本文から移した・#348)。
|
|
8527
|
-
// ヘッダーは全タブ共通なので、どのタブを開いていても同じ場所に居る。
|
|
8528
|
-
// 押せる口はメニューの中に畳む——常時見えるボタンは、たまにしか使わない操作には重い。
|
|
8529
|
-
//
|
|
8530
|
-
// 上限はサーバ側(logic.mjs の PROJECT_ICON_MAX_BYTES)と同じ値。画面の門は
|
|
8531
|
-
// 「送る前に断る」ためのもので、正本はサーバ側
|
|
8532
|
-
var PROJECT_ICON_MAX_BYTES = 1024 * 1024;
|
|
8533
|
-
|
|
8534
|
-
/* ---- 反映待ち(pending)----------------------------------------------------
|
|
8535
|
-
* 静的サイトなので、保存の commit が入っても**次のビルドまで SiteData は古いまま**。
|
|
8536
|
-
* 何もしないと「入れたのにまだ頭文字のまま」「外したのにまだ絵が出る」と見える。
|
|
8537
|
-
* 0.12.26 まではその待ち時間を「数分後に反映されます」の文で説明していたが、
|
|
8538
|
-
* オーナー実機で却下された(待たされている事実は説明されても消えない)。
|
|
8539
|
-
*
|
|
8540
|
-
* そこで**削除のトゥームストーンと同じ型**で、保存できたことをこのブラウザに覚える。
|
|
8541
|
-
* 覚えるのは押した本人のブラウザだけで、他の端末・他人には次のビルドで届く。
|
|
8542
|
-
*
|
|
8543
|
-
* ⚠ **キーは案件キー**(リポジトリのスラグではない)。同じオリジンに配られる
|
|
8544
|
-
* ポータルの一覧カードが、同じキーでこの仮画像を読んで先に出すため。
|
|
8545
|
-
*
|
|
8546
|
-
* ⚠ **いつまでも仮画像を出し続けない。** 本物が焼き直された後も残していると、
|
|
8547
|
-
* 別の画像に差し替えたつもりの人に、いつまでも古い仮画像が見え続ける
|
|
8548
|
-
* (破棄の条件は iconPendingStale=トゥームストーンと同じ判定)。
|
|
8549
|
-
*
|
|
8550
|
-
* ⚠ 反映待ちの入れ物に p という変数名を使わないこと。fleet-passthrough の検査は
|
|
8551
|
-
* その名前のドット参照を「艦隊データの欄」として走査するので、
|
|
8552
|
-
* ここで使うと build.ts の受け渡し漏れに化けて赤くなる
|
|
8553
|
-
*/
|
|
8554
|
-
function iconPendingKey() {
|
|
8555
|
-
return SITE.intake ? "cortex-icon-pending:" + SITE.intake.projectKey : "";
|
|
8556
|
-
}
|
|
8557
|
-
/** 覚えている反映待ち(無い・壊れている・形が違うなら null) */
|
|
8558
|
-
function readIconPending() {
|
|
8559
|
-
var key = iconPendingKey();
|
|
8560
|
-
if (!key) return null;
|
|
8561
|
-
try {
|
|
8562
|
-
var raw = localStorage.getItem(key);
|
|
8563
|
-
if (!raw) return null;
|
|
8564
|
-
var pend = JSON.parse(raw);
|
|
8565
|
-
if (!pend || typeof pend !== "object") return null;
|
|
8566
|
-
// 形の版と案件キーは**ポータルの読み側(usePendingProjectIcon)と同じ検査**にする。
|
|
8567
|
-
// 将来 v:2 を出したとき、古い画面が新しい形を読み違えないため
|
|
8568
|
-
if (pend.v !== 1 || pend.projectKey !== SITE.intake.projectKey) return null;
|
|
8569
|
-
if (pend.kind === "remove") return pend;
|
|
8570
|
-
// set は仮画像そのものが要る。**data:image/ 以外は載せない**(外部URLを持ち込まない・#110。
|
|
8571
|
-
// ポータル側と同じ検査)
|
|
8572
|
-
if (pend.kind === "set" && typeof pend.dataUrl === "string" && pend.dataUrl.indexOf("data:image/") === 0) return pend;
|
|
8573
|
-
return null;
|
|
8574
|
-
} catch (e) { return null; }
|
|
8575
|
-
}
|
|
8576
|
-
function writeIconPending(entry) {
|
|
8577
|
-
var key = iconPendingKey();
|
|
8578
|
-
if (!key) return;
|
|
8579
|
-
// 1MB の画像を data: にすると 1.4MB 前後。localStorage の枠を超えることがあるので、
|
|
8580
|
-
// 失敗しても操作は成功したまま進める(次のビルドで本物が出る)
|
|
8581
|
-
try { localStorage.setItem(key, JSON.stringify(entry)); } catch (e) {}
|
|
8582
|
-
}
|
|
8583
|
-
function clearIconPending() {
|
|
8584
|
-
var key = iconPendingKey();
|
|
8585
|
-
if (!key) return;
|
|
8586
|
-
try { localStorage.removeItem(key); } catch (e) {}
|
|
8587
|
-
}
|
|
8588
|
-
/**
|
|
8589
|
-
* この反映待ちはもう用済みか(=保存より後のビルドが来ている)。
|
|
8590
|
-
* **判定はトゥームストーン(tombstoneStale)と同じ**——同じ性質の待ちなので、
|
|
8591
|
-
* 破棄の条件を2つ持つと、片方だけ直したときに挙動が割れる。
|
|
8592
|
-
*/
|
|
8593
|
-
function iconPendingStale(pend, nowMs) {
|
|
8594
|
-
if (!pend) return false;
|
|
8595
|
-
return pendingStale(pend.at, pend.gen0, nowMs);
|
|
8596
|
-
}
|
|
8597
|
-
/** いま覚えている反映待ちを記録する(保存・解除に共通の形) */
|
|
8598
|
-
function rememberIconPending(kind, dataUrl) {
|
|
8599
|
-
var entry = { v: 1, projectKey: SITE.intake ? SITE.intake.projectKey : "", kind: kind, at: Date.now(), gen0: SITE.generatedAt || "" };
|
|
8600
|
-
if (dataUrl) entry.dataUrl = dataUrl;
|
|
8601
|
-
writeIconPending(entry);
|
|
8602
|
-
}
|
|
8603
|
-
/** 選んだ画像を data: にする。読めなければ空文字(仮画像は出さず、本物のビルドを待つ) */
|
|
8604
|
-
function fileToDataUrl(file) {
|
|
8605
|
-
return new Promise(function (resolve) {
|
|
8606
|
-
try {
|
|
8607
|
-
var r = new FileReader();
|
|
8608
|
-
r.onload = function () { resolve(typeof r.result === "string" ? r.result : ""); };
|
|
8609
|
-
r.onerror = function () { resolve(""); };
|
|
8610
|
-
r.readAsDataURL(file);
|
|
8611
|
-
} catch (e) { resolve(""); }
|
|
8612
|
-
});
|
|
8613
|
-
}
|
|
8614
|
-
|
|
8615
|
-
/** いま画面に出すべきアイコン(無ければ null)。**反映待ちがビルド時の値に優先する** */
|
|
8616
|
-
function projectIconCurrent() {
|
|
8617
|
-
var pend = readIconPending();
|
|
8618
|
-
if (pend) return pend.kind === "remove" ? null : { file: "", src: pend.dataUrl };
|
|
8619
|
-
var q = SITE.projectIcon;
|
|
8620
|
-
// ビルドが同梱した画像はサイト内の相対パス。**外部URLは載せない**
|
|
8621
|
-
return q ? { file: q.file, src: q.file } : null;
|
|
8622
|
-
}
|
|
8623
|
-
|
|
8624
|
-
/**
|
|
8625
|
-
* 名前から拡張子を引く(jpeg は jpg へ寄せる)。許可外・拡張子なしは null。
|
|
8626
|
-
* **サーバ側(logic.mjs の projectIconExtOf)と同じ判断**を画面でも先にやるのは、
|
|
8627
|
-
* 通らないと分かっているファイルを1MB 送ってから断らないため
|
|
8628
|
-
*/
|
|
8629
|
-
function projectIconExtOfName(name) {
|
|
8630
|
-
var m = /\\.([A-Za-z0-9]+)$/.exec(String(name || ""));
|
|
8631
|
-
if (!m) return null;
|
|
8632
|
-
var e = m[1].toLowerCase();
|
|
8633
|
-
if (e === "jpeg") e = "jpg";
|
|
8634
|
-
return e === "png" || e === "jpg" || e === "webp" ? e : null;
|
|
8635
|
-
}
|
|
8461
|
+
// ポータルの案件一覧はカードにアイコンを出す。置き場はこの案件のリポジトリの Cortex/icon.<ext> で、
|
|
8462
|
+
// ビルドが同梱した画像(SITE.projectIcon)を**ヘッダーの案件名の左に見せるだけ**にする。
|
|
8463
|
+
// 🔴 **画面からアイコンを置く・外す操作は持たない**(仕様 common.screen-ops「案件のアイコンの操作は持たない」・cortex-engine #1781)。
|
|
8464
|
+
// 変えたいときはリポジトリの Cortex/icon.<ext> を GitHub で直す
|
|
8636
8465
|
|
|
8637
8466
|
// いま出ているトーストを消すタイマ。**1本だけ持つ**——連続で出したときに
|
|
8638
8467
|
// 古いタイマが残っていると、2つ目が出た 0.2 秒後に消える
|
|
@@ -8676,35 +8505,6 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
8676
8505
|
});
|
|
8677
8506
|
}
|
|
8678
8507
|
|
|
8679
|
-
// ヘッダーの丸のメニューが開いているか。**DOM ではなくこの変数が正本。**
|
|
8680
|
-
// 保存・解除のあとに丸を描き直すので、開閉を DOM だけに持たせると描き直しで畳まれ、
|
|
8681
|
-
// 「✓ 保存しました」と保存先のリンクが一度も見えないまま消える
|
|
8682
|
-
var siteIconMenuOpen = false;
|
|
8683
|
-
// document に付ける開閉のリスナーは1組だけ。描き直しのたびに足すと Escape 1回で
|
|
8684
|
-
// 同じ処理が何度も走る(見た目は同じなので気づけない)
|
|
8685
|
-
var siteIconGlobalBound = false;
|
|
8686
|
-
// 保存中か(画像を選ぶ/変える/外す の3段が走っている間)。**DOM ではなくこの変数が正本**
|
|
8687
|
-
// (#378)。描き直しで丸が作り直されても、renderSiteIcon がこれを見てオーバーレイを付け直す。
|
|
8688
|
-
// setProjectIcon / removeProjectIcon の入口で見て、走っている間の2回目を捨てる(二重送信防止)
|
|
8689
|
-
var siteIconSaving = false;
|
|
8690
|
-
// いま丸に載っているオーバーレイの外し方(無ければ null)
|
|
8691
|
-
var siteIconBusyEnd = null;
|
|
8692
|
-
|
|
8693
|
-
/**
|
|
8694
|
-
* 丸の保存中の見た目を付ける/外す。丸は disabled にして押せなくする
|
|
8695
|
-
* (メニューの開閉もここで止まる。丸のクリックは siteIconSaving でも見る)
|
|
8696
|
-
*/
|
|
8697
|
-
function setSiteIconBusy(circle, on) {
|
|
8698
|
-
if (siteIconBusyEnd) { siteIconBusyEnd(); siteIconBusyEnd = null; }
|
|
8699
|
-
if (!circle) return;
|
|
8700
|
-
if (on) {
|
|
8701
|
-
siteIconBusyEnd = beginOverlaySpinner(circle, "保存中…");
|
|
8702
|
-
circle.disabled = true;
|
|
8703
|
-
} else {
|
|
8704
|
-
circle.disabled = false;
|
|
8705
|
-
}
|
|
8706
|
-
}
|
|
8707
|
-
|
|
8708
8508
|
/**
|
|
8709
8509
|
* 案件名の頭文字1字。**ポータルの ProjectIcon.tsx(initialOf)と同じ取り方**にする
|
|
8710
8510
|
* ——同じ案件がポータルとビューアで違う字になると「別の案件を見ている」と読まれる。
|
|
@@ -8715,236 +8515,36 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
8715
8515
|
return Array.from(String(SITE.title || "").trim())[0] || "?";
|
|
8716
8516
|
}
|
|
8717
8517
|
|
|
8718
|
-
/**
|
|
8518
|
+
/** 丸の中の頭文字 */
|
|
8719
8519
|
function siteIconInitialEl() {
|
|
8720
8520
|
return el("span", { class: "site-icon-initial", text: siteInitial() });
|
|
8721
8521
|
}
|
|
8722
8522
|
|
|
8723
|
-
/** メニューの開閉。描き直さずに hidden と aria-expanded だけ動かす */
|
|
8724
|
-
function setSiteIconMenu(open) {
|
|
8725
|
-
siteIconMenuOpen = !!open;
|
|
8726
|
-
var slot = document.getElementById("site-icon-slot");
|
|
8727
|
-
if (!slot) return;
|
|
8728
|
-
var menu = slot.querySelector(".site-icon-menu");
|
|
8729
|
-
if (menu) {
|
|
8730
|
-
if (siteIconMenuOpen) menu.removeAttribute("hidden");
|
|
8731
|
-
else menu.setAttribute("hidden", "");
|
|
8732
|
-
}
|
|
8733
|
-
var circle = slot.querySelector("button.site-icon");
|
|
8734
|
-
if (circle) circle.setAttribute("aria-expanded", siteIconMenuOpen ? "true" : "false");
|
|
8735
|
-
}
|
|
8736
|
-
|
|
8737
8523
|
/**
|
|
8738
8524
|
* ヘッダーの案件名の左の丸を描く(#site-icon-slot の中身を作り直す)。
|
|
8739
8525
|
*
|
|
8740
8526
|
* **丸は常に出す**(未設定なら頭文字)——ポータルの一覧と同じ顔をここでも見せる。
|
|
8741
|
-
*
|
|
8742
|
-
* メニューもファイル選択も付けない——押して何も起きない口は「壊れている」と読まれる。
|
|
8743
|
-
*
|
|
8527
|
+
* 🔴 **押せる形にしない**(受け口がある案件でも span・メニューもファイル選択も付けない)。仕様 common.screen-ops
|
|
8744
8528
|
*/
|
|
8745
8529
|
function renderSiteIcon() {
|
|
8746
8530
|
var slot = document.getElementById("site-icon-slot");
|
|
8747
8531
|
if (!slot) return;
|
|
8748
|
-
var
|
|
8749
|
-
// **本物が追いついたら仮画像を捨てる。** 残したままだと、次に差し替えた人にも
|
|
8750
|
-
// 古い仮画像が見え続ける(削除のトゥームストーンと同じ捨て方)
|
|
8751
|
-
var pend = readIconPending();
|
|
8752
|
-
if (pend && iconPendingStale(pend, pendingSiteTimeMs(SITE.generatedAt))) clearIconPending();
|
|
8753
|
-
var cur = projectIconCurrent();
|
|
8532
|
+
var q = SITE.projectIcon;
|
|
8754
8533
|
slot.textContent = "";
|
|
8755
|
-
|
|
8756
|
-
var circle = el(
|
|
8757
|
-
if (
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
// 未設定は「+」が見えているので、説明は「変更」ではなく置ける話にしない——
|
|
8762
|
-
// 読み上げと tooltip は、いまの状態でできることをそのまま言う
|
|
8763
|
-
var label = cur ? "プロジェクトアイコンを変更/外す" : "プロジェクトアイコンを設定";
|
|
8764
|
-
circle.setAttribute("title", label);
|
|
8765
|
-
circle.setAttribute("aria-label", label);
|
|
8766
|
-
} else {
|
|
8767
|
-
// すぐ隣に案件名が文字で出ているので、読み上げで名前を2回言わせない
|
|
8768
|
-
circle.setAttribute("aria-hidden", "true");
|
|
8769
|
-
}
|
|
8770
|
-
if (cur) {
|
|
8771
|
-
// **設定済みは画像だけ。** 頭文字も「+」も重ねない(選んだロゴが読めなくなる)
|
|
8772
|
-
var img = el("img", { class: "site-icon-img", src: cur.src, alt: "" });
|
|
8773
|
-
// **onerror 属性で書かない(CSP でインラインハンドラは動かない)。**
|
|
8774
|
-
// 仮画像の data: が配信先の img-src で拒まれることがあるので、そのときは頭文字に倒す
|
|
8534
|
+
// すぐ隣に案件名が文字で出ているので、読み上げで名前を2回言わせない
|
|
8535
|
+
var circle = el("span", { class: "site-icon", "aria-hidden": "true" });
|
|
8536
|
+
if (q && q.file) {
|
|
8537
|
+
// **設定済みは画像だけ**(頭文字を重ねない)。ビルドが同梱した画像はサイト内の相対パスで、外部URLは載せない
|
|
8538
|
+
var img = el("img", { class: "site-icon-img", src: q.file, alt: "" });
|
|
8539
|
+
// **onerror 属性で書かない(CSP でインラインハンドラは動かない)。** 読めなければ頭文字に倒す
|
|
8775
8540
|
img.addEventListener("error", function () {
|
|
8776
8541
|
if (img.parentNode) img.parentNode.replaceChild(siteIconInitialEl(), img);
|
|
8777
8542
|
});
|
|
8778
8543
|
circle.appendChild(img);
|
|
8779
|
-
// 押せる案件だけ、ホバーで「変更」と出す(見た目は CSS の opacity で切り替える)
|
|
8780
|
-
if (intake) circle.appendChild(el("span", { class: "site-icon-hover", "aria-hidden": "true", text: "変更" }));
|
|
8781
8544
|
} else {
|
|
8782
8545
|
circle.appendChild(siteIconInitialEl());
|
|
8783
|
-
// **未設定の押せる丸には、ホバー時だけ右下に小さく「+」を出す**(CSS の opacity)。
|
|
8784
|
-
// 頭文字ははっきり出す——0.12.25 の「薄い頭文字+大きな+」は「+だけの丸」に見えた
|
|
8785
|
-
if (intake) circle.appendChild(el("span", { class: "site-icon-plus", "aria-hidden": "true", text: "+" }));
|
|
8786
8546
|
}
|
|
8787
8547
|
slot.appendChild(circle);
|
|
8788
|
-
if (!intake) return;
|
|
8789
|
-
// 保存中に描き直されても、オーバーレイと押せない状態を引き継ぐ(正本は siteIconSaving)。
|
|
8790
|
-
// いまの呼び出し経路(起動時1回・ui.busy(false) の後の redraw)では保存中に来ないので
|
|
8791
|
-
// 防御。将来タブ切り替え等でヘッダーを描き直すようになっても、ぐるぐるが消えない
|
|
8792
|
-
if (siteIconSaving) setSiteIconBusy(circle, true);
|
|
8793
|
-
|
|
8794
|
-
circle.addEventListener("click", function () {
|
|
8795
|
-
// 保存中は丸が disabled なので click 自体が飛ばない(ブラウザの既定)。これは防御
|
|
8796
|
-
if (siteIconSaving) return;
|
|
8797
|
-
setSiteIconMenu(!siteIconMenuOpen);
|
|
8798
|
-
});
|
|
8799
|
-
|
|
8800
|
-
var menu = el("div", { class: "site-icon-menu", role: "menu" });
|
|
8801
|
-
if (!siteIconMenuOpen) menu.setAttribute("hidden", "");
|
|
8802
|
-
|
|
8803
|
-
var ui = {
|
|
8804
|
-
// 描き直すと押していたボタンが DOM から消えてフォーカスが body に落ちるので、
|
|
8805
|
-
// 丸へ戻す(キーボードの人が位置を失わない。Escape は document 側なので効く)
|
|
8806
|
-
redraw: function () {
|
|
8807
|
-
renderSiteIcon();
|
|
8808
|
-
ui.refocus();
|
|
8809
|
-
},
|
|
8810
|
-
// 丸へフォーカスを戻す。失敗のあとも呼ぶ——押していた項目は disabled にした瞬間に
|
|
8811
|
-
// フォーカスを失って body に落ちるので、戻さないとキーボードの人がやり直す位置を失う
|
|
8812
|
-
refocus: function () {
|
|
8813
|
-
var s = document.getElementById("site-icon-slot");
|
|
8814
|
-
var c = s && s.querySelector("button.site-icon");
|
|
8815
|
-
if (c && typeof c.focus === "function") c.focus();
|
|
8816
|
-
},
|
|
8817
|
-
input: null,
|
|
8818
|
-
buttons: [],
|
|
8819
|
-
};
|
|
8820
|
-
// 送信中は全部を止める(#378: 丸にオーバーレイ+スピナー・丸とメニューを押せなくする)。
|
|
8821
|
-
// **元から押せない項目は押せないまま戻す**——
|
|
8822
|
-
// 未設定の「画像を外す」が、失敗して戻ったときだけ押せるようになるのを防ぐ
|
|
8823
|
-
ui.busy = function (flag) {
|
|
8824
|
-
siteIconSaving = !!flag;
|
|
8825
|
-
ui.buttons.forEach(function (b) { b.disabled = flag ? true : !!b.lockedOff; });
|
|
8826
|
-
var s = document.getElementById("site-icon-slot");
|
|
8827
|
-
setSiteIconBusy(s && s.querySelector("button.site-icon"), siteIconSaving);
|
|
8828
|
-
};
|
|
8829
|
-
|
|
8830
|
-
var input = el("input", { type: "file", accept: "image/png,image/jpeg,image/webp", hidden: "" });
|
|
8831
|
-
ui.input = input;
|
|
8832
|
-
input.addEventListener("change", function () {
|
|
8833
|
-
var f = input.files && input.files[0];
|
|
8834
|
-
// **読んだらすぐ value を戻す**(成功時だけ戻す形だと、形式で断られた直後に
|
|
8835
|
-
// 同じファイルを選び直しても change が飛ばず、直す経路がここで死ぬ)
|
|
8836
|
-
try { input.value = ""; } catch (e) {}
|
|
8837
|
-
if (f) setProjectIcon(f, ui);
|
|
8838
|
-
});
|
|
8839
|
-
// **項目は常にこの2つ・この順。** 状態で数が変わると、押す場所が毎回動く
|
|
8840
|
-
// (未設定のときは「外す」を押せなくするだけで、消しはしない)
|
|
8841
|
-
var choose = el("button", { class: "site-icon-choose", type: "button", role: "menuitem", text: cur ? "画像を変える" : "画像を選ぶ" });
|
|
8842
|
-
choose.addEventListener("click", function () { input.click(); });
|
|
8843
|
-
ui.buttons.push(choose);
|
|
8844
|
-
menu.appendChild(choose);
|
|
8845
|
-
var remove = el("button", { class: "site-icon-remove", type: "button", role: "menuitem", text: "画像を外す" });
|
|
8846
|
-
if (!cur) {
|
|
8847
|
-
remove.disabled = true;
|
|
8848
|
-
remove.lockedOff = true;
|
|
8849
|
-
}
|
|
8850
|
-
remove.addEventListener("click", function () { removeProjectIcon(ui); });
|
|
8851
|
-
ui.buttons.push(remove);
|
|
8852
|
-
menu.appendChild(remove);
|
|
8853
|
-
menu.appendChild(input);
|
|
8854
|
-
slot.appendChild(menu);
|
|
8855
|
-
|
|
8856
|
-
if (!siteIconGlobalBound) {
|
|
8857
|
-
siteIconGlobalBound = true;
|
|
8858
|
-
// 外を押したら閉じる。**スロットの中では閉じない**——「画像を選ぶ」を押した
|
|
8859
|
-
// 瞬間に畳まれると、選び終えたときに結果を書く場所が無い
|
|
8860
|
-
document.addEventListener("click", function (ev) {
|
|
8861
|
-
if (!siteIconMenuOpen) return;
|
|
8862
|
-
var s = document.getElementById("site-icon-slot");
|
|
8863
|
-
if (s && s.contains(ev.target)) return;
|
|
8864
|
-
setSiteIconMenu(false);
|
|
8865
|
-
});
|
|
8866
|
-
document.addEventListener("keydown", function (ev) {
|
|
8867
|
-
if (siteIconMenuOpen && (ev.key === "Escape" || ev.key === "Esc")) setSiteIconMenu(false);
|
|
8868
|
-
});
|
|
8869
|
-
}
|
|
8870
|
-
}
|
|
8871
|
-
|
|
8872
|
-
/**
|
|
8873
|
-
* 画像を1枚選んで保存する。
|
|
8874
|
-
*
|
|
8875
|
-
* **画面側でも門を張る。** サーバ側(planProjectIconSet)が正本だが、ここで断れる
|
|
8876
|
-
* ものを送ると、1MB を上げきってから「大きすぎます」と言うことになる
|
|
8877
|
-
*/
|
|
8878
|
-
async function setProjectIcon(file, ui) {
|
|
8879
|
-
// 走っている間の2回目は捨てる(丸もメニューも止めてあるが、正本はこの変数)
|
|
8880
|
-
if (siteIconSaving) return;
|
|
8881
|
-
function fail(msg) { showToast("✗ " + msg, true); ui.busy(false); ui.refocus(); }
|
|
8882
|
-
// 画面側の門。**サーバ側と同じ文言**で、送る前に断る(fail で「✗ 」を揃える)
|
|
8883
|
-
if (!projectIconExtOfName(file.name)) return fail("PNG・JPEG・WebP の画像を選んでください");
|
|
8884
|
-
if (file.size > PROJECT_ICON_MAX_BYTES) return fail("1MB までの画像を選んでください");
|
|
8885
|
-
if (file.size === 0) return fail("画像が空です(0 バイト)。別の画像を選んでください");
|
|
8886
|
-
|
|
8887
|
-
var intake = SITE.intake;
|
|
8888
|
-
var ctype = file.type || "application/octet-stream";
|
|
8889
|
-
// 押した瞬間からぐるぐる(#378)。成功なら redraw で新しい丸に、失敗なら fail で元の丸に戻る
|
|
8890
|
-
ui.busy(true);
|
|
8891
|
-
|
|
8892
|
-
var pres;
|
|
8893
|
-
try {
|
|
8894
|
-
pres = await intakeJson(callIntake("presign", {
|
|
8895
|
-
projectKey: intake.projectKey, filename: file.name, contentType: ctype, purpose: "project-icon",
|
|
8896
|
-
}));
|
|
8897
|
-
} catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
|
|
8898
|
-
if (!pres.ok || !pres.j.url) return fail((pres.j && pres.j.message) || "アップロードURLの取得に失敗しました");
|
|
8899
|
-
|
|
8900
|
-
// 署名付きURLへの PUT だけは intake ではないので素の fetch(submitIntake と同じ)
|
|
8901
|
-
var put;
|
|
8902
|
-
try {
|
|
8903
|
-
put = await fetch(pres.j.url, { method: "PUT", headers: { "content-type": ctype }, body: file });
|
|
8904
|
-
} catch (net2) { return fail("アップロードに失敗しました(もう一度お試しください)"); }
|
|
8905
|
-
if (!put.ok) return fail("アップロードに失敗しました(" + put.status + ")");
|
|
8906
|
-
|
|
8907
|
-
var saved;
|
|
8908
|
-
try {
|
|
8909
|
-
saved = await intakeJson(callIntake("project-icon-set", {
|
|
8910
|
-
projectKey: intake.projectKey, key: pres.j.key, filename: file.name,
|
|
8911
|
-
}));
|
|
8912
|
-
} catch (net3) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
|
|
8913
|
-
if (!saved.ok) return fail((saved.j && saved.j.message) || "保存できませんでした");
|
|
8914
|
-
|
|
8915
|
-
// **選んだ画像そのものを反映待ちに置く。** blob: にしないのは、
|
|
8916
|
-
// ポータルの一覧カード(別のページ)が同じ画像を読むため——blob: は
|
|
8917
|
-
// 作ったページの中でしか開けない
|
|
8918
|
-
var dataUrl = await fileToDataUrl(file);
|
|
8919
|
-
// **読めなかったら反映待ちを書かない**(サーバ側の保存は済んでいるので、仮画像だけ
|
|
8920
|
-
// 諦めて次のビルドを待つ)。空の set を書くと、直前の有効な反映待ちを上書きして消す
|
|
8921
|
-
if (dataUrl) rememberIconPending("set", dataUrl);
|
|
8922
|
-
// **同じファイルをもう一度選べるようにする。** value を戻さないと、選び直しても
|
|
8923
|
-
// change が飛ばない(形式で断られた直後に選び直す経路がここで死ぬ)
|
|
8924
|
-
if (ui.input) ui.input.value = "";
|
|
8925
|
-
ui.busy(false);
|
|
8926
|
-
// 丸がその場で変わるので、メニューは畳んでよい(結果はトーストが伝える)
|
|
8927
|
-
setSiteIconMenu(false);
|
|
8928
|
-
ui.redraw();
|
|
8929
|
-
showToast("保存しました", false);
|
|
8930
|
-
}
|
|
8931
|
-
|
|
8932
|
-
/** 画像を外す。**確認は出さない**——押し間違えても、もう一度選び直せば戻る操作 */
|
|
8933
|
-
async function removeProjectIcon(ui) {
|
|
8934
|
-
// 保存中はメニューの項目が disabled なので普通は来ない(setProjectIcon と同じ防御)
|
|
8935
|
-
if (siteIconSaving) return;
|
|
8936
|
-
function fail(msg) { showToast("✗ " + msg, true); ui.busy(false); ui.refocus(); }
|
|
8937
|
-
ui.busy(true);
|
|
8938
|
-
var res;
|
|
8939
|
-
try {
|
|
8940
|
-
res = await intakeJson(callIntake("project-icon-remove", { projectKey: SITE.intake.projectKey }));
|
|
8941
|
-
} catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
|
|
8942
|
-
if (!res.ok) return fail((res.j && res.j.message) || "外せませんでした");
|
|
8943
|
-
rememberIconPending("remove", "");
|
|
8944
|
-
ui.busy(false);
|
|
8945
|
-
setSiteIconMenu(false);
|
|
8946
|
-
ui.redraw();
|
|
8947
|
-
showToast("外しました", false);
|
|
8948
8548
|
}
|
|
8949
8549
|
|
|
8950
8550
|
// ---------- ホーム(着地画面: Markdownをそのまま表示) ----------
|
|
@@ -20332,42 +19932,30 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20332
19932
|
term: { color: "#4ade80", label: "用語集" },
|
|
20333
19933
|
member: { color: "#f472b6", label: "関係者" },
|
|
20334
19934
|
rule: { color: "#fde047", label: "ルール" },
|
|
20335
|
-
report: { color: "#22d3ee", label: "レポート" },
|
|
20336
|
-
overview: { color: "#fb923c", label: "Home" },
|
|
20337
|
-
record: { color: "#0e7490", label: "その他レコード" },
|
|
20338
19935
|
silver: { color: "#9ca3af", label: "根拠(課題・議事録・資料・リンク等)" },
|
|
20339
19936
|
bronze: { color: "#9ca3af", label: "根拠(課題・議事録・資料・リンク等)" },
|
|
20340
19937
|
external: { color: "#9ca3af", label: "根拠(課題・議事録・資料・リンク等)" },
|
|
20341
|
-
phantom: { color: "#d4d9e0", label: "参照先が見つからないもの" },
|
|
20342
19938
|
};
|
|
19939
|
+
/* 点の色は区画を見分けるためだけに残す。凡例(分類の一覧)は出さない(仕様 graph.content-only) */
|
|
19940
|
+
var GROUP_FALLBACK = { color: "#9ca3af" };
|
|
20343
19941
|
/* 線(関係)の種類ごとの色・ラベルと、「Goldどうし」か「根拠」かの分類(engine #551・分類A 10/1)。
|
|
20344
19942
|
* **一覧(凡例)はこの表から組む**ので、将来 about(決定・ルール→用語)や is_a/part_of、
|
|
20345
19943
|
* 人→用語・人→人の線を足すときは、ここに1行足すだけでよい——凡例もトグルも自動で追従し、
|
|
20346
19944
|
* 既存の行・色は変わらない(語彙が増えても凡例が崩れない作り)。
|
|
20347
19945
|
* kind: "gold"=Goldのレコードどうしを結ぶ線/"evidence"=Goldがその根拠を指す線(弱いつながり)。 */
|
|
20348
19946
|
var REL_META = {
|
|
20349
|
-
based_on: { color: "#2563eb", label: "根拠"
|
|
20350
|
-
derived_from: { color: "#0d9488", label: "生成元"
|
|
20351
|
-
relates_to: { color: "#94a3b8", label: "関連"
|
|
20352
|
-
supersedes: { color: "#dc2626", label: "置換"
|
|
20353
|
-
reference: { color: "#d1d5db", label: "参照"
|
|
20354
|
-
source: { color: "#d1d5db", label: "出典"
|
|
20355
|
-
|
|
20356
|
-
// 話題の線(決定・ルール・関係者 → 用語・engine #551 の about)。名前の一致で機械が付けた手がかりなので、
|
|
20357
|
-
// 根拠の線と同じく細く薄い「ゆるいつながり」として描き、凡例は日本語の 1 行(栓 about_relations の on の条件)
|
|
20358
|
-
about: { color: "#ddd6fe", label: "話題", kind: "topic" },
|
|
20359
|
-
// 決めた人の線(決定 → 関係者・#1094)。relations ではなく decided_by から引く。
|
|
20360
|
-
// 🔴 凡例には**この線がグラフにあるときだけ**出す(欄の無い案件の凡例を変えない)
|
|
20361
|
-
decided_by: { color: "#f472b6", label: "決めた人", kind: "gold", onlyWhenPresent: true },
|
|
19947
|
+
based_on: { color: "#2563eb", label: "根拠" },
|
|
19948
|
+
derived_from: { color: "#0d9488", label: "生成元" },
|
|
19949
|
+
relates_to: { color: "#94a3b8", label: "関連" },
|
|
19950
|
+
supersedes: { color: "#dc2626", label: "置換" },
|
|
19951
|
+
reference: { color: "#d1d5db", label: "参照" },
|
|
19952
|
+
source: { color: "#d1d5db", label: "出典" },
|
|
19953
|
+
decided_by: { color: "#f472b6", label: "決めた人" },
|
|
20362
19954
|
};
|
|
20363
|
-
var WEAK_RELS = {};
|
|
20364
19955
|
var REL_COLOR = {};
|
|
20365
|
-
Object.keys(REL_META).forEach(function (r) {
|
|
20366
|
-
REL_COLOR[r] = REL_META[r].color;
|
|
20367
|
-
if (REL_META[r].kind === "evidence" || REL_META[r].kind === "topic") WEAK_RELS[r] = true;
|
|
20368
|
-
});
|
|
19956
|
+
Object.keys(REL_META).forEach(function (r) { REL_COLOR[r] = REL_META[r].color; });
|
|
20369
19957
|
// period: 期間で光らせる(engine #551)。unit は all(光らせない)/day/week、day は基準の日(YYYY-MM-DD)
|
|
20370
|
-
var graphState = {
|
|
19958
|
+
var graphState = { q: "", period: { unit: "all", day: null } };
|
|
20371
19959
|
|
|
20372
19960
|
/** 期間の範囲(両端を含む YYYY-MM-DD)。「すべて」・日付が無いときは null。週は月曜〜日曜(ストーリーの週と同じ区切り) */
|
|
20373
19961
|
function graphPeriodRange(period) {
|
|
@@ -20399,7 +19987,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20399
19987
|
countEl.textContent = "点 " + graph.nodes.length + " / つながり " + graph.edges.length;
|
|
20400
19988
|
|
|
20401
19989
|
var filters = el("div", { class: "filters" });
|
|
20402
|
-
var search = el("input", { type: "search", placeholder: "
|
|
19990
|
+
var search = el("input", { type: "search", placeholder: "キーワードで探す(決定・ルール・用語・関係者は本文の中まで探します。見つかった点とそのつながりが光ります)" });
|
|
20403
19991
|
search.value = graphState.q;
|
|
20404
19992
|
filters.appendChild(search);
|
|
20405
19993
|
app.appendChild(filters);
|
|
@@ -20411,43 +19999,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20411
19999
|
wrap.appendChild(canvas);
|
|
20412
20000
|
app.appendChild(wrap);
|
|
20413
20001
|
|
|
20414
|
-
//
|
|
20415
|
-
var legend = el("div", { class: "graph-legend" });
|
|
20416
|
-
var legendShown = {};
|
|
20417
|
-
Object.keys(GROUP_STYLE).forEach(function (g) {
|
|
20418
|
-
var anyNode = graph.nodes.some(function (n) { return n.group === g; });
|
|
20419
|
-
if (!anyNode || legendShown[GROUP_STYLE[g].label]) return;
|
|
20420
|
-
legendShown[GROUP_STYLE[g].label] = true;
|
|
20421
|
-
var item = el("span", {}, [el("span", { class: "dot" }), document.createTextNode(GROUP_STYLE[g].label)]);
|
|
20422
|
-
item.querySelector(".dot").style.background = GROUP_STYLE[g].color;
|
|
20423
|
-
legend.appendChild(item);
|
|
20424
|
-
});
|
|
20425
|
-
|
|
20426
|
-
// 線(関係)の色凡例。グラフ内の有無に関わらず全種別を、Goldどうし/根拠の2群で表示する
|
|
20427
|
-
[["gold", "線(Goldどうし):"], ["evidence", "線(根拠):"], ["topic", "線(話題):"]].forEach(function (pair) {
|
|
20428
|
-
var kind = pair[0], heading = pair[1];
|
|
20429
|
-
var keys = Object.keys(REL_META).filter(function (r) {
|
|
20430
|
-
if (REL_META[r].kind !== kind) return false;
|
|
20431
|
-
return !REL_META[r].onlyWhenPresent || graph.edges.some(function (e) { return e.rel === r; });
|
|
20432
|
-
});
|
|
20433
|
-
if (!keys.length) return;
|
|
20434
|
-
legend.appendChild(el("span", { class: "sep", text: heading }));
|
|
20435
|
-
keys.forEach(function (r) {
|
|
20436
|
-
var li = el("span", {}, [el("span", { class: "line" }), document.createTextNode(REL_META[r].label)]);
|
|
20437
|
-
li.querySelector(".line").style.borderTopColor = REL_META[r].color;
|
|
20438
|
-
legend.appendChild(li);
|
|
20439
|
-
});
|
|
20440
|
-
});
|
|
20441
|
-
|
|
20442
|
-
var toggle = el("label", {});
|
|
20443
|
-
var cb = el("input", { type: "checkbox" });
|
|
20444
|
-
cb.checked = graphState.showWeak;
|
|
20445
|
-
cb.addEventListener("change", function () { graphState.showWeak = cb.checked; renderList(); });
|
|
20446
|
-
toggle.appendChild(cb);
|
|
20447
|
-
toggle.appendChild(document.createTextNode("ゆるいつながり(参照・本文リンク・話題)も表示"));
|
|
20448
|
-
legend.appendChild(toggle);
|
|
20449
|
-
app.appendChild(legend);
|
|
20450
|
-
|
|
20002
|
+
// 🔴 凡例(分類・線の種類の一覧)と、根拠の点を隠す切り替えは持たない(仕様 graph.content-only / graph.evidence-always)
|
|
20451
20003
|
startGraph(canvas, search, graph, periodCtl);
|
|
20452
20004
|
}
|
|
20453
20005
|
|
|
@@ -20509,12 +20061,11 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20509
20061
|
}
|
|
20510
20062
|
|
|
20511
20063
|
function startGraph(canvas, searchInput, graph, periodCtl) {
|
|
20512
|
-
var allEdges = graph.edges
|
|
20513
|
-
|
|
20514
|
-
|
|
20515
|
-
// 弱い辺を消したとき、レコード以外の孤立ノードは隠す
|
|
20064
|
+
var allEdges = graph.edges;
|
|
20065
|
+
// レコード以外の点(根拠)は、線が 1 本でもあれば出す。🔴 根拠の点を隠す切り替えは持たない(仕様 graph.evidence-always)。
|
|
20066
|
+
// 消した記録だけが指していた点は線が無いので出ない
|
|
20516
20067
|
var used = {};
|
|
20517
|
-
|
|
20068
|
+
graph.edges.forEach(function (e) { used[e.from] = true; used[e.to] = true; });
|
|
20518
20069
|
// ビルド時に算出済みの座標(n.x/n.y)があればそれを使う=ウォームアップ不要で開いた瞬間に整列済み。
|
|
20519
20070
|
var preLaidOut = graph.nodes.some(function (n) { return typeof n.x === "number"; });
|
|
20520
20071
|
var nodes = graph.nodes
|
|
@@ -20525,6 +20076,8 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20525
20076
|
x: typeof n.x === "number" ? n.x : 0, y: typeof n.y === "number" ? n.y : 0,
|
|
20526
20077
|
vx: 0, vy: 0, phase: i * 2.399 };
|
|
20527
20078
|
});
|
|
20079
|
+
// 見えている点の数(開発者ツールとテストが見る。件数の表示はヘッダーから外した)
|
|
20080
|
+
canvas.setAttribute("data-points", String(nodes.length));
|
|
20528
20081
|
// Gold層を中心に据え、グレーのコンテキストはその外周に配置する(ハブ&スポーク)
|
|
20529
20082
|
var RING = Math.max(200, 26 * Math.sqrt(nodes.length));
|
|
20530
20083
|
var byNode = {};
|
|
@@ -20975,8 +20528,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20975
20528
|
var EDGE_DARK = {
|
|
20976
20529
|
based_on: "rgba(125,165,255,0.85)", derived_from: "rgba(94,234,212,0.75)",
|
|
20977
20530
|
relates_to: "rgba(148,163,184,0.6)", supersedes: "rgba(248,113,113,0.9)",
|
|
20978
|
-
reference: "rgba(180,196,230,0.
|
|
20979
|
-
about: "rgba(196,181,253,0.22)",
|
|
20531
|
+
reference: "rgba(180,196,230,0.6)", source: "rgba(180,196,230,0.6)",
|
|
20980
20532
|
decided_by: "rgba(244,114,182,0.75)",
|
|
20981
20533
|
};
|
|
20982
20534
|
// 画面に入っている範囲(世界座標)。両端が同じ側の外にある線は描かない
|
|
@@ -20996,9 +20548,9 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20996
20548
|
var span = Math.max(highO - lowO, 1) * SWEEP_STEP_MS;
|
|
20997
20549
|
var prog = Math.max(0, Math.min(1, (sweepT - lowO * SWEEP_STEP_MS) / span));
|
|
20998
20550
|
if (prog > 0) {
|
|
20999
|
-
ctx.globalAlpha =
|
|
20551
|
+
ctx.globalAlpha = 0.95;
|
|
21000
20552
|
ctx.strokeStyle = "#fcd34d";
|
|
21001
|
-
ctx.lineWidth = (e.rel === "supersedes" ? 2 :
|
|
20553
|
+
ctx.lineWidth = (e.rel === "supersedes" ? 2 : 1.6) / view.scale;
|
|
21002
20554
|
ctx.beginPath();
|
|
21003
20555
|
ctx.moveTo(lowN.x, lowN.y);
|
|
21004
20556
|
ctx.lineTo(lowN.x + (highN.x - lowN.x) * prog, lowN.y + (highN.y - lowN.y) * prog);
|
|
@@ -21007,18 +20559,18 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21007
20559
|
} else {
|
|
21008
20560
|
ctx.globalAlpha = 0.06; // リネージ外は沈める
|
|
21009
20561
|
ctx.strokeStyle = EDGE_DARK[e.rel] || "rgba(180,196,230,0.3)";
|
|
21010
|
-
ctx.lineWidth =
|
|
20562
|
+
ctx.lineWidth = 1.6 / view.scale;
|
|
21011
20563
|
ctx.beginPath(); ctx.moveTo(e.a.x, e.a.y); ctx.lineTo(e.b.x, e.b.y); ctx.stroke();
|
|
21012
20564
|
}
|
|
21013
20565
|
return;
|
|
21014
20566
|
}
|
|
21015
20567
|
// 通常 / 検索
|
|
21016
20568
|
var hot = searching && (matched[e.a.id] || matched[e.b.id]);
|
|
21017
|
-
var a = hot ? 0.95 :
|
|
20569
|
+
var a = hot ? 0.95 : 0.85;
|
|
21018
20570
|
if (searching && !hot) a = 0.06;
|
|
21019
20571
|
ctx.globalAlpha = a;
|
|
21020
20572
|
ctx.strokeStyle = hot ? "#fcd34d" : (EDGE_DARK[e.rel] || "rgba(180,196,230,0.3)");
|
|
21021
|
-
ctx.lineWidth = (hot ? 1.8 : e.rel === "supersedes" ? 2 :
|
|
20573
|
+
ctx.lineWidth = (hot ? 1.8 : e.rel === "supersedes" ? 2 : 1.6) / view.scale;
|
|
21022
20574
|
ctx.beginPath();
|
|
21023
20575
|
ctx.moveTo(e.a.x, e.a.y);
|
|
21024
20576
|
ctx.lineTo(e.b.x, e.b.y);
|
|
@@ -21030,7 +20582,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21030
20582
|
var cw = canvas.width, ch = canvas.height;
|
|
21031
20583
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
21032
20584
|
nodes.forEach(function (n) {
|
|
21033
|
-
var style = GROUP_STYLE[n.group] ||
|
|
20585
|
+
var style = GROUP_STYLE[n.group] || GROUP_FALLBACK;
|
|
21034
20586
|
var alphaN = 1;
|
|
21035
20587
|
if (searching && !lit[n.id]) alphaN = 0.12;
|
|
21036
20588
|
if (hovered) {
|
|
@@ -21311,7 +20863,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21311
20863
|
(adjDir[e.from] = adjDir[e.from] || []).push({ id: e.to, rel: e.rel, dir: "out" });
|
|
21312
20864
|
(adjDir[e.to] = adjDir[e.to] || []).push({ id: e.from, rel: e.rel, dir: "in" });
|
|
21313
20865
|
});
|
|
21314
|
-
var REL_ORDER = ["supersedes", "based_on", "derived_from", "relates_to", "decided_by", "source", "reference"
|
|
20866
|
+
var REL_ORDER = ["supersedes", "based_on", "derived_from", "relates_to", "decided_by", "source", "reference"];
|
|
21315
20867
|
|
|
21316
20868
|
function recordText(rec) {
|
|
21317
20869
|
if (!rec) return "";
|
|
@@ -21323,11 +20875,17 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21323
20875
|
|
|
21324
20876
|
// Gold層のフォルダ(1列目)。検索ヒット表示と使い分ける
|
|
21325
20877
|
var goldGroups = [];
|
|
21326
|
-
|
|
21327
|
-
//
|
|
21328
|
-
|
|
20878
|
+
// 削除済みは列にも件数にも出さない(一覧・グラフと同じ扱いに揃える)。
|
|
20879
|
+
// 🔴 点にならない記録(Home・レポート・その他)は出さない——グラフの点(nodeById)にある記録だけ
|
|
20880
|
+
var inGraph = {};
|
|
20881
|
+
(explorerGraph.nodes || []).forEach(function (n) { if (n.recordId) inGraph[n.recordId] = true; });
|
|
20882
|
+
function graphIds(list) {
|
|
20883
|
+
return visibleRecords(list).map(function (r) { return r.id; }).filter(function (id) { return inGraph[id]; });
|
|
20884
|
+
}
|
|
20885
|
+
goldGroups.push({ name: "Decisions", ids: graphIds(SITE.decisions) });
|
|
21329
20886
|
(SITE.sections || []).forEach(function (sec) {
|
|
21330
|
-
|
|
20887
|
+
var ids = graphIds(sec.records);
|
|
20888
|
+
if (ids.length) goldGroups.push({ name: sec.name, ids: ids });
|
|
21331
20889
|
});
|
|
21332
20890
|
|
|
21333
20891
|
function renderExplorer() {
|
|
@@ -21369,7 +20927,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21369
20927
|
app.appendChild(wrap);
|
|
21370
20928
|
|
|
21371
20929
|
function nodeDot(n) {
|
|
21372
|
-
var style = GROUP_STYLE[n.group] ||
|
|
20930
|
+
var style = GROUP_STYLE[n.group] || GROUP_FALLBACK;
|
|
21373
20931
|
var dot = el("span", { class: "mc-dot" });
|
|
21374
20932
|
dot.style.background = style.color;
|
|
21375
20933
|
return dot;
|
|
@@ -21533,10 +21091,8 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21533
21091
|
path.setAttribute("d", "M " + x1 + " " + y1 + " C " + mx + " " + y1 + ", " + mx + " " + y2 + ", " + x2 + " " + y2);
|
|
21534
21092
|
path.setAttribute("fill", "none");
|
|
21535
21093
|
path.setAttribute("stroke", REL_COLOR[rows[j].getAttribute("data-rel")] || "#cbd5e1");
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
path.setAttribute("stroke-width", weakLine ? "1" : "1.5");
|
|
21539
|
-
path.setAttribute("opacity", weakLine ? "0.5" : "0.85");
|
|
21094
|
+
path.setAttribute("stroke-width", "1.5");
|
|
21095
|
+
path.setAttribute("opacity", "0.85");
|
|
21540
21096
|
lines.appendChild(path);
|
|
21541
21097
|
}
|
|
21542
21098
|
}
|
package/package.json
CHANGED