@takagaki/cortex-decisions-viewer 0.12.64 → 0.12.65
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/README.md +1 -1
- package/dist/build.js +111 -10
- package/dist/render.js +117 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npx -y @takagaki/cortex-decisions-viewer build --out site --title "Decisions"
|
|
|
20
20
|
| `index.html` | 画面の骨組みと、埋め込みデータ(`<script type="application/json">`) |
|
|
21
21
|
| `404.html` | `index.html` と同じもの(サブパス直叩きのフォールバック) |
|
|
22
22
|
| `assets/viewer.<内容ハッシュ>.js` / `.css` | 画面のJSとCSS。内容が同じなら名前も同じ(版が同じなら全案件で同じ名前) |
|
|
23
|
-
| `bodies
|
|
23
|
+
| `bodies/decisions/<id>.html` / `bodies/home.html` / `bodies/<区画>/<id>.html` | レコードの本文(詳細を開いたときに取りに行く)。**名前は並び順ではなくレコードから決まる**(cortex-engine #382)。先頭に `<!-- cortex-body id="..." -->` の印が入り、画面は取ってきた本文がそのレコードのものか確かめる |
|
|
24
24
|
| `thumbs/*` | グラフのサムネイル画像 |
|
|
25
25
|
|
|
26
26
|
**配置は `site/` を丸ごと置く**(`index.html` だけを置いても画面は出ない)。
|
package/dist/build.js
CHANGED
|
@@ -44,8 +44,12 @@ exports.readGoldProposals = readGoldProposals;
|
|
|
44
44
|
exports.buildHomeGrounding = buildHomeGrounding;
|
|
45
45
|
exports.collectMeetings = collectMeetings;
|
|
46
46
|
exports.findBacklogDir = findBacklogDir;
|
|
47
|
+
exports.bodyFileSlug = bodyFileSlug;
|
|
48
|
+
exports.bodyStampId = bodyStampId;
|
|
49
|
+
exports.bodyStampComment = bodyStampComment;
|
|
47
50
|
exports.build = build;
|
|
48
51
|
exports.createRefResolver = createRefResolver;
|
|
52
|
+
const node_crypto_1 = require("node:crypto");
|
|
49
53
|
const node_fs_1 = require("node:fs");
|
|
50
54
|
const path = __importStar(require("node:path"));
|
|
51
55
|
const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
@@ -911,6 +915,7 @@ async function parseGeneric(raw, fileName, repoRelDir, repoBaseUrl, branch) {
|
|
|
911
915
|
summary,
|
|
912
916
|
bodyHtml,
|
|
913
917
|
bodyRef: "",
|
|
918
|
+
bodyId: "",
|
|
914
919
|
searchText: toSearchText(content),
|
|
915
920
|
fileName,
|
|
916
921
|
editUrl,
|
|
@@ -1700,25 +1705,120 @@ function computeLayout(graph) {
|
|
|
1700
1705
|
}
|
|
1701
1706
|
}
|
|
1702
1707
|
/**
|
|
1703
|
-
*
|
|
1708
|
+
* 本文ファイルの名前に使える形へ畳む。
|
|
1709
|
+
*
|
|
1710
|
+
* 🔴 **畳んだときの区別に連番を使わない。** 連番は「並び順で本文を引く」形に戻る——
|
|
1711
|
+
* それが cortex-engine #382 の不具合そのもの(レコードを1件消すと、それより古い全レコードの
|
|
1712
|
+
* 本文が1つずつずれる)。畳んだ跡があるときは**元の値から決まる印**を足して区別する。
|
|
1713
|
+
* 同じ入力なら常に同じ名前になり、他のレコードが増えても消えても変わらない。
|
|
1714
|
+
*
|
|
1715
|
+
* ASCII に倒すのは配布先(S3 + CloudFront)で確かめられていない形を持ち込まないため。
|
|
1716
|
+
* 案件の id と区画名は日本語を含みうる(レコードの id はファイル名から導出されることがある)。
|
|
1717
|
+
*/
|
|
1718
|
+
function bodyFileSlug(raw) {
|
|
1719
|
+
const s = String(raw ?? "").normalize("NFC");
|
|
1720
|
+
const folded = s
|
|
1721
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
1722
|
+
.replace(/-{2,}/g, "-")
|
|
1723
|
+
// 🔴 `..` を残さない。ポータルの門は URI に `..` を含むと無条件で 403 を返す
|
|
1724
|
+
// (classmethod-cortex-portal の viewer-gate.js)ので、本文が丸ごと読めなくなる
|
|
1725
|
+
.replace(/\.{2,}/g, ".")
|
|
1726
|
+
.replace(/^[-.]+|[-.]+$/g, "");
|
|
1727
|
+
if (s !== "" && folded === s)
|
|
1728
|
+
return s; // 畳んでいない=そのまま使える
|
|
1729
|
+
const h = shortDigest(s);
|
|
1730
|
+
return folded ? `${folded}-${h}` : h;
|
|
1731
|
+
}
|
|
1732
|
+
/** 元の値から決まる短い印(並び順に依らない区別に使う。assets のハッシュ名と同じ桁数) */
|
|
1733
|
+
function shortDigest(s) {
|
|
1734
|
+
return (0, node_crypto_1.createHash)("sha256").update(s, "utf8").digest("hex").slice(0, 8);
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* 本文がどのレコードのものかを表す印。`<kind>:<id>` の形で、
|
|
1738
|
+
* 本文ファイルの先頭コメントと inlineデータの `bodyId` の両方に同じ文字列が入る。
|
|
1739
|
+
*
|
|
1740
|
+
* `<` `>` `&` `"` を逃がすので、id に何が入っていてもコメントを閉じたり属性を割ったりしない。
|
|
1741
|
+
*
|
|
1742
|
+
* `extra`(同じ区画に id が重複したときのファイル名)を渡すと `<kind>:<id>:<extra>` になる。
|
|
1743
|
+
* 🔴 **重複しているときは印にも区別が要る。** 印が `<kind>:<id>` だけだと、重複した2件の
|
|
1744
|
+
* 一方が消えた日に、残った方の本文が**消えた方の印と一致してしまう**——古い画面に
|
|
1745
|
+
* 別レコードの本文が出る(#382 の症状そのもの)。
|
|
1746
|
+
*/
|
|
1747
|
+
function bodyStampId(kind, id, extra) {
|
|
1748
|
+
const esc = (v) => String(v ?? "")
|
|
1749
|
+
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1750
|
+
return `${esc(kind)}:${esc(id)}` + (extra ? `:${esc(extra)}` : "");
|
|
1751
|
+
}
|
|
1752
|
+
/** 本文ファイルの先頭に置く印の行(画面側の BODY_STAMP_RE と対) */
|
|
1753
|
+
function bodyStampComment(stampId) {
|
|
1754
|
+
return `<!-- cortex-body id="${stampId}" -->\n`;
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* 各レコードの bodyHtml を out/bodies/... へ書き出し、bodyRef にパスを設定して
|
|
1704
1758
|
* inlineデータ上の bodyHtml を空にする(初期ロードのpayloadから本文を外す)。
|
|
1759
|
+
*
|
|
1760
|
+
* 🔴 **ファイル名は並び順ではなくレコードから決まる**(cortex-engine #382)。
|
|
1761
|
+
* 前は `bodies/<並び順の番号>.html` だった。詳細を開いた画面は埋め込まれた bodyRef を
|
|
1762
|
+
* fetch するので、**削除で番号が詰まると、開きっぱなしの画面が別レコードの本文を出す**。
|
|
1763
|
+
* 見出しはそのレコードのまま本文だけが入れ替わるので、見比べない限り誰も気づけない。
|
|
1764
|
+
*
|
|
1765
|
+
* 置き場:
|
|
1766
|
+
* 決定 bodies/decisions/<id>.html
|
|
1767
|
+
* Home bodies/home.html
|
|
1768
|
+
* 区画レコード bodies/<区画のディレクトリ名>/<id>.html
|
|
1769
|
+
*
|
|
1770
|
+
* 併せて各ファイルの先頭に印(bodyStampComment)を埋める。画面はこれを突き合わせて、
|
|
1771
|
+
* 食い違ったら本文を出さない(配布の途中・移行期・id が重複した案件の保険)。
|
|
1705
1772
|
*/
|
|
1706
1773
|
async function externalizeBodies(site, outAbs) {
|
|
1707
1774
|
const all = [
|
|
1708
|
-
...site.decisions,
|
|
1709
|
-
...
|
|
1710
|
-
...site.sections.flatMap((s) => s.records),
|
|
1775
|
+
...site.decisions.map((rec) => ({ rec, kind: "decisions", dir: "bodies/decisions" })),
|
|
1776
|
+
...site.sections.flatMap((s) => s.records.map((rec) => ({ rec, kind: s.name, dir: `bodies/${bodyFileSlug(s.name)}` }))),
|
|
1711
1777
|
];
|
|
1712
|
-
|
|
1778
|
+
const home = site.home;
|
|
1779
|
+
// **先に丸ごと消す。** 出力ディレクトリは作り直されないので、消さないと前のビルドの
|
|
1780
|
+
// 本文(番号の名前を含む)がファイルとして残る。配布先は `aws s3 sync --delete` が
|
|
1781
|
+
// 消すが、手元のプレビューでは古い本文が URL 直打ちで生き続ける(アイコンと同じ手当て)。
|
|
1782
|
+
// 🔴 **0件の回にも消す。** 早期 return より前に置くこと——全部消した案件が
|
|
1783
|
+
// 「前のビルドの本文だけが残る」状態になる(消したはずのものが読める、が一番まずい)
|
|
1784
|
+
await node_fs_1.promises.rm(path.join(outAbs, "bodies"), { recursive: true, force: true });
|
|
1785
|
+
if (all.length === 0 && !home)
|
|
1713
1786
|
return;
|
|
1714
1787
|
await node_fs_1.promises.mkdir(path.join(outAbs, "bodies"), { recursive: true });
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
await node_fs_1.promises.writeFile(path.join(outAbs, ref), rec.bodyHtml ?? "", "utf8");
|
|
1788
|
+
const write = async (rec, ref, stampId) => {
|
|
1789
|
+
await node_fs_1.promises.mkdir(path.dirname(path.join(outAbs, ref)), { recursive: true });
|
|
1790
|
+
await node_fs_1.promises.writeFile(path.join(outAbs, ref), bodyStampComment(stampId) + (rec.bodyHtml ?? ""), "utf8");
|
|
1719
1791
|
rec.bodyRef = ref;
|
|
1792
|
+
rec.bodyId = stampId;
|
|
1720
1793
|
rec.bodyHtml = ""; // inlineデータからは本文を除外(payload削減)
|
|
1721
|
-
|
|
1794
|
+
};
|
|
1795
|
+
// Home は1件だけなので固定名。区画に "home" という名前があってもディレクトリなのでぶつからない
|
|
1796
|
+
if (home)
|
|
1797
|
+
await write(home, "bodies/home.html", bodyStampId("home", home.id));
|
|
1798
|
+
// 同じ区画に同じ id のレコードが2件あるときの避け方。
|
|
1799
|
+
//
|
|
1800
|
+
// 🔴 **`-1` `-2` の連番で避けない(設計レビューの指摘)。** 連番は並び順から決まるので、
|
|
1801
|
+
// 重複した2件の一方が消えた日に、残った方が空いた番号へ繰り上がる。古い画面の bodyRef と
|
|
1802
|
+
// 印がそのまま一致してしまい、**別レコードの本文が出る**——#382 の症状が形を変えて戻る。
|
|
1803
|
+
// だから**重複している側の全員**に、そのレコードの出どころから決まる印を付ける
|
|
1804
|
+
// (誰が消えても残った側の名前は変わらない)。印にも同じ区別を入れる。
|
|
1805
|
+
//
|
|
1806
|
+
// 区別の材料は**リポジトリの中での置き場**(editUrl にパスが入っている)。今は records/ を
|
|
1807
|
+
// 掘らないのでファイル名だけでも一意だが、掘るようになった日に黙って上書きし合う形にしない。
|
|
1808
|
+
//
|
|
1809
|
+
// 大文字小文字だけ違う id も同じ組として数える。macOS のローカルビルドでは
|
|
1810
|
+
// 別名にならず黙って上書きし合うため(S3 は区別するので手元だけ壊れる、が一番気づけない)
|
|
1811
|
+
const originOf = (rec) => `${rec.editUrl ?? ""}|${rec.fileName}`;
|
|
1812
|
+
const dupCount = new Map();
|
|
1813
|
+
for (const { rec, dir } of all) {
|
|
1814
|
+
const key = `${dir}/${bodyFileSlug(rec.id)}`.toLowerCase();
|
|
1815
|
+
dupCount.set(key, (dupCount.get(key) ?? 0) + 1);
|
|
1816
|
+
}
|
|
1817
|
+
for (const { rec, kind, dir } of all) {
|
|
1818
|
+
const base = `${dir}/${bodyFileSlug(rec.id)}`;
|
|
1819
|
+
const dup = (dupCount.get(base.toLowerCase()) ?? 0) > 1;
|
|
1820
|
+
const ref = dup ? `${base}-${shortDigest(originOf(rec))}.html` : `${base}.html`;
|
|
1821
|
+
await write(rec, ref, bodyStampId(kind, rec.id, dup ? originOf(rec) : undefined));
|
|
1722
1822
|
}
|
|
1723
1823
|
}
|
|
1724
1824
|
async function build(opts) {
|
|
@@ -1979,6 +2079,7 @@ async function parseDecision(raw, fileName, repoRelDir, repoBaseUrl, branch, ref
|
|
|
1979
2079
|
bodyLinks: extractBodyLinks(content),
|
|
1980
2080
|
bodyHtml,
|
|
1981
2081
|
bodyRef: "",
|
|
2082
|
+
bodyId: "",
|
|
1982
2083
|
searchText: toSearchText(content),
|
|
1983
2084
|
rstatus: data.status != null ? String(data.status) : "",
|
|
1984
2085
|
isLatest: isLatestPath(filePath),
|
package/dist/render.js
CHANGED
|
@@ -4183,6 +4183,21 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
|
|
|
4183
4183
|
padding: 10px 12px; font-size: 13px; line-height: 1.8; color: var(--muted);
|
|
4184
4184
|
}
|
|
4185
4185
|
.draft-note > .deleted-note { flex: 1 0 100%; margin-bottom: 0; }
|
|
4186
|
+
/* 「画面が古くなっています」の帯(#382)。本文の代わりに出るので、本文と同じ幅で
|
|
4187
|
+
目に入る濃さにする。帯は症状・行は一手——文は1つ、押すものは1つだけ */
|
|
4188
|
+
.stale-note {
|
|
4189
|
+
display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 4px 0 16px;
|
|
4190
|
+
background: #fffbeb; border: 1px solid #fcd34d; border-radius: 8px;
|
|
4191
|
+
padding: 10px 12px; font-size: 13.5px; line-height: 1.8; color: #92400e;
|
|
4192
|
+
}
|
|
4193
|
+
.draft-note > .stale-note { flex: 1 0 100%; margin-bottom: 0; }
|
|
4194
|
+
.stale-reload-btn {
|
|
4195
|
+
flex: 0 0 auto; cursor: pointer; white-space: nowrap;
|
|
4196
|
+
padding: 6px 14px; border-radius: 999px;
|
|
4197
|
+
border: 1px solid #b45309; background: #fff; color: #92400e;
|
|
4198
|
+
font-size: 13px; font-weight: 600;
|
|
4199
|
+
}
|
|
4200
|
+
.stale-reload-btn:hover { background: #fef3c7; }
|
|
4186
4201
|
/* 消したものは、消えたと分かる形で残す(一覧・詳細の両方) */
|
|
4187
4202
|
.is-deleted { text-decoration: line-through; color: var(--muted); }
|
|
4188
4203
|
/* 消したレコードでは、直す口(AIで編集・GitHubで編集)を押させない */
|
|
@@ -5291,7 +5306,7 @@ ${calendarAddUrl.toString()}
|
|
|
5291
5306
|
return '<a class="wikilink" data-go="' + attr + '">' + disp + "</a>";
|
|
5292
5307
|
});
|
|
5293
5308
|
}
|
|
5294
|
-
// 本文は bodies
|
|
5309
|
+
// 本文は bodies/ 以下へ外出しされ詳細表示時に fetch されるため、linkify は loadBody 側で適用する
|
|
5295
5310
|
// (ここで inline bodyHtml に適用すると検索テキストにHTMLが混ざるため適用しない)。
|
|
5296
5311
|
|
|
5297
5312
|
// Gold層ディレクトリ名(英語)→ タブ・フォルダの表示名(日本語)の辞書。
|
|
@@ -5988,16 +6003,88 @@ ${calendarAddUrl.toString()}
|
|
|
5988
6003
|
});
|
|
5989
6004
|
return box;
|
|
5990
6005
|
}
|
|
5991
|
-
|
|
6006
|
+
/**
|
|
6007
|
+
* この画面が古くなったか(開いたあとに並びが変わったか)。
|
|
6008
|
+
*
|
|
6009
|
+
* 立つのは2つ: 本文の印が食い違った・削除が成功した。立ったあとの詳細表示は
|
|
6010
|
+
* 本文を取りに行かず帯を出す(cortex-engine #382)。
|
|
6011
|
+
*/
|
|
6012
|
+
var pageStale = false;
|
|
6013
|
+
/**
|
|
6014
|
+
* 本文の先頭に埋めた印(build.ts の bodyStampComment と対)。
|
|
6015
|
+
* **両側を同じ形にしておくこと**——片方だけ変えると、印が常に食い違って
|
|
6016
|
+
* 全レコードの本文が出なくなる(帯だけの画面になる)。
|
|
6017
|
+
*/
|
|
6018
|
+
// ⚠ 埋め込みJSはテンプレート文字列の中なので、正規表現の逆スラッシュは2本で書くこと
|
|
6019
|
+
// (1本だと素の s になり、**印が常に食い違って全レコードの本文が消える**)
|
|
6020
|
+
var BODY_STAMP_RE = /^\\s*<!--\\s*cortex-body id="([^"]*)"\\s*-->/;
|
|
6021
|
+
/**
|
|
6022
|
+
* 「画面が古くなっています」の帯。**帯は症状・行は一手・繰り返さない。**
|
|
6023
|
+
*
|
|
6024
|
+
* 🔴 **自動で読み込み直さない。** 押した人の書きかけ・開いている場所を勝手に捨てない。
|
|
6025
|
+
* 押す口は1つだけ置いて、押すかどうかは読み手が決める。
|
|
6026
|
+
*/
|
|
6027
|
+
function staleReloadNote() {
|
|
6028
|
+
var box = el("div", { class: "stale-note" }, [
|
|
6029
|
+
el("span", { text: "画面が古くなっています。再読み込みしてください。" }),
|
|
6030
|
+
]);
|
|
6031
|
+
var btn = el("button", { class: "stale-reload-btn", type: "button", text: "再読み込み" });
|
|
6032
|
+
btn.addEventListener("click", function () { location.reload(); });
|
|
6033
|
+
box.appendChild(btn);
|
|
6034
|
+
return box;
|
|
6035
|
+
}
|
|
6036
|
+
/**
|
|
6037
|
+
* 本文の代わりに帯を出す(本文は出さない——別レコードの本文を読ませないため)。
|
|
6038
|
+
*
|
|
6039
|
+
* 🔴 **帯を出す経路の after(done)はここ1か所に集める。** Home は本文が決まるまで
|
|
6040
|
+
* 枠を隠している(renderHome)ので、呼び忘れた経路は**帯ごと消えて真っ白になる**。
|
|
6041
|
+
* 3経路(印の食い違い・404/403・削除後)で書き分けると、1つだけ落ちても気づけない。
|
|
6042
|
+
*/
|
|
6043
|
+
function showStaleBody(mount, after) {
|
|
6044
|
+
pageStale = true;
|
|
6045
|
+
mount.innerHTML = "";
|
|
6046
|
+
mount.appendChild(staleReloadNote());
|
|
6047
|
+
if (typeof after === "function") after(mount);
|
|
6048
|
+
}
|
|
6049
|
+
// 本文HTMLは bodies/<種別>/<id>.html に外出しされている。詳細表示時にfetchして埋める
|
|
6050
|
+
// (キャッシュ・失敗時フォールバック付き)
|
|
5992
6051
|
var bodyCache = {};
|
|
5993
6052
|
function loadBody(rec, mount, after) {
|
|
5994
6053
|
function done() { if (typeof after === "function") after(mount); }
|
|
6054
|
+
// 並びが変わったと分かっている画面では、本文を取りに行かない(取れても別レコードのもの)
|
|
6055
|
+
if (pageStale && rec && rec.bodyRef) { showStaleBody(mount, after); return; }
|
|
5995
6056
|
if (!rec || !rec.bodyRef) { mount.innerHTML = linkifyWiki((rec && rec.bodyHtml) || ""); done(); return; }
|
|
5996
6057
|
if (bodyCache[rec.bodyRef] != null) { mount.innerHTML = bodyCache[rec.bodyRef]; done(); return; }
|
|
5997
6058
|
mount.innerHTML = '<div class="body-loading">読み込み中…</div>';
|
|
5998
|
-
fetch(rec.bodyRef).then(function (res) {
|
|
5999
|
-
|
|
6000
|
-
|
|
6059
|
+
fetch(rec.bodyRef).then(function (res) {
|
|
6060
|
+
// 🔴 **本文が無いのは「配信されていない」ではなく「この画面が古い」。** 配布は
|
|
6061
|
+
// aws s3 sync --delete なので、開いたままの画面が指す本文は建て直しで消える。
|
|
6062
|
+
//
|
|
6063
|
+
// 🔴 **403 も同じ扱い(実装レビューの指摘)。** 本番の配信は CloudFront の OAC で、
|
|
6064
|
+
// 一覧の権限を渡していないので**無いオブジェクトは 404 ではなく 403 で返る**
|
|
6065
|
+
// (403 を握りつぶすカスタムエラー応答も置いていない)。404 だけを見ていると、
|
|
6066
|
+
// この帯は**本番でだけ出ない**。403 は通行証が切れたときにも返るが、
|
|
6067
|
+
// そのときも一手は同じ(再読み込みで入り口へ戻って取り直す)
|
|
6068
|
+
if (res.status === 404 || res.status === 403) { var e = new Error("stale"); e.stale = true; throw e; }
|
|
6069
|
+
if (!res.ok) throw new Error("http " + res.status);
|
|
6070
|
+
return res.text();
|
|
6071
|
+
})
|
|
6072
|
+
.then(function (html) {
|
|
6073
|
+
// 🔴 **印が一致しない本文は出さない。** 別のレコードの本文を、見出しはそのままに
|
|
6074
|
+
// 出してしまうのが cortex-engine #382 の症状——読み手は「その決定はそう決まった」と
|
|
6075
|
+
// 受け取るし、画面は正常に見えるので**見比べない限り気づけない**。
|
|
6076
|
+
// 読めないのは気づけるが、違うものが読めるのは気づけない。だから出さない側に倒す。
|
|
6077
|
+
var m = BODY_STAMP_RE.exec(html);
|
|
6078
|
+
if (rec.bodyId && (!m || m[1] !== rec.bodyId)) {
|
|
6079
|
+
// キャッシュにも入れない(正しい本文で開き直せるように)
|
|
6080
|
+
showStaleBody(mount, after);
|
|
6081
|
+
return;
|
|
6082
|
+
}
|
|
6083
|
+
var lk = linkifyWiki(m ? html.slice(m[0].length) : html);
|
|
6084
|
+
bodyCache[rec.bodyRef] = lk; mount.innerHTML = lk; done();
|
|
6085
|
+
})
|
|
6086
|
+
.catch(function (err) {
|
|
6087
|
+
if (err && err.stale) { showStaleBody(mount, after); return; }
|
|
6001
6088
|
// 🔴 文字列連結でHTMLを組み立てない(editUrl は frontmatter 由来の外部文字列)。
|
|
6002
6089
|
// el() は class/text/html 以外のキーをそのまま setAttribute に渡すので、
|
|
6003
6090
|
// href に javascript: を置けてしまう。**isHttp() が唯一の防御**(保険ではない)
|
|
@@ -16260,6 +16347,30 @@ ${calendarAddUrl.toString()}
|
|
|
16260
16347
|
var meta = app.querySelector(".detail-meta");
|
|
16261
16348
|
if (meta && meta.parentNode) meta.parentNode.insertBefore(note, meta.nextSibling);
|
|
16262
16349
|
}
|
|
16350
|
+
/**
|
|
16351
|
+
* **削除を押して成功したときだけ**呼ぶ。この画面の並びが変わったことが確定するので、
|
|
16352
|
+
* 以後の詳細表示で本文を取りに行かせない(cortex-engine #382)。
|
|
16353
|
+
*
|
|
16354
|
+
* 🔴 **markRecordDeleted の中に置いてはいけない(設計レビューの指摘)。** あちらは
|
|
16355
|
+
* トゥームストーンの復元(applyDeleteTombstones → appendRecordFooter)からも呼ばれる。
|
|
16356
|
+
* 前のセッションで消したレコードを**開き直しただけ**の画面まで古い扱いになり、
|
|
16357
|
+
* そのページの本文が軒並み出なくなる。
|
|
16358
|
+
*
|
|
16359
|
+
* 自動で読み込み直さない・モーダルも出さない——押した人の開いている場所を勝手に捨てない。
|
|
16360
|
+
*/
|
|
16361
|
+
function markPageStale() {
|
|
16362
|
+
pageStale = true;
|
|
16363
|
+
if (app.querySelector(".stale-note")) return; // 帯は1本だけ(繰り返さない)
|
|
16364
|
+
var stale = staleReloadNote();
|
|
16365
|
+
// ⚠ 未確認の警告帯(.draft-note)の**中**には入れない。同じ黄色の帯が入れ子になって、
|
|
16366
|
+
// どちらが何を言っているのか読めなくなる。すぐ下に、別の帯として並べる
|
|
16367
|
+
var banner = app.querySelector(".draft-note");
|
|
16368
|
+
if (banner && banner.parentNode) { banner.parentNode.insertBefore(stale, banner.nextSibling); return; }
|
|
16369
|
+
var note = app.querySelector(".deleted-note");
|
|
16370
|
+
if (note && note.parentNode) { note.parentNode.insertBefore(stale, note.nextSibling); return; }
|
|
16371
|
+
var meta = app.querySelector(".detail-meta");
|
|
16372
|
+
if (meta && meta.parentNode) meta.parentNode.insertBefore(stale, meta.nextSibling);
|
|
16373
|
+
}
|
|
16263
16374
|
/**
|
|
16264
16375
|
* 人の確認印が付いた記録か。**3値の意味は infra の HUMAN_CONFIRMED_STATUSES と同じ**
|
|
16265
16376
|
* (active = 人が確認済みにした印/superseded = active を経た記録の差し替え後/
|
|
@@ -16293,7 +16404,7 @@ ${calendarAddUrl.toString()}
|
|
|
16293
16404
|
btn.addEventListener("click", async function () {
|
|
16294
16405
|
// **確認を await する。** 落とすと Promise が truthy で確認なしに消える
|
|
16295
16406
|
if (!(await confirmDanger(deleteConfirmLines(rec, path), { okLabel: "削除する", returnTo: btn }))) return;
|
|
16296
|
-
await sendDeleteRecord(rec, path, msg, btn, function () { markRecordDeleted(rec); });
|
|
16407
|
+
await sendDeleteRecord(rec, path, msg, btn, function () { markRecordDeleted(rec); markPageStale(); });
|
|
16297
16408
|
});
|
|
16298
16409
|
return { btn: btn, msg: msg };
|
|
16299
16410
|
}
|
package/package.json
CHANGED