@takagaki/cortex-decisions-viewer 0.12.164 → 0.12.166
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/render.js +76 -35
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -2679,9 +2679,11 @@ function reflectedStateOf(rows, capId) {
|
|
|
2679
2679
|
/**
|
|
2680
2680
|
* ISO 時刻を「Gold の夜の日」の表示(`M/D`)にする(cortex-engine #968 設計 v1 §3)。
|
|
2681
2681
|
*
|
|
2682
|
-
* **engine の `
|
|
2683
|
-
*
|
|
2684
|
-
* JST の日付を読むと、
|
|
2682
|
+
* **engine の `NIGHT_CUTOFF_HOURS = 6`(`scripts/memory-v2/night-result.mjs`・日本時間 6 時)と
|
|
2683
|
+
* 同じ区切り。** Gold は JST 22:00 に始まり日をまたいで終わるので、素の日付だと同じ晩の記録が
|
|
2684
|
+
* 「9/28」と「9/29」に分かれる。6 時間引いてから JST の日付を読むと、JST 6:00 より前は前の日の
|
|
2685
|
+
* 晩、6:00 以降は当日の晩になる。🔴 区切りの数(6)はこの関数の `NIGHT_CUTOFF_HOURS` 1 か所だけに
|
|
2686
|
+
* 書く(engine と必ずそろえる。`.toString()` で渡すので関数の中に置く)。
|
|
2685
2687
|
*
|
|
2686
2688
|
* **クロージャを持たない純関数に保つこと**(`.toString()` でクライアントへ渡す)。
|
|
2687
2689
|
* @returns `M/D`(ゼロ埋めなし)。読めない値は null
|
|
@@ -2691,10 +2693,11 @@ function jstNightLabel(iso) {
|
|
|
2691
2693
|
if (!Number.isFinite(ms))
|
|
2692
2694
|
return null;
|
|
2693
2695
|
const JST_MS = 9 * 60 * 60 * 1000;
|
|
2694
|
-
const
|
|
2696
|
+
const NIGHT_CUTOFF_HOURS = 6;
|
|
2697
|
+
const d = new Date(ms - NIGHT_CUTOFF_HOURS * 60 * 60 * 1000 + JST_MS);
|
|
2695
2698
|
return (d.getUTCMonth() + 1) + "/" + d.getUTCDate();
|
|
2696
2699
|
}
|
|
2697
|
-
/** JST の暦日(`YYYY-MM-DD`)。`jstNightLabel`
|
|
2700
|
+
/** JST の暦日(`YYYY-MM-DD`)。`jstNightLabel` と違い夜の区切りで引かない(素の日付の比較用) */
|
|
2698
2701
|
function jstCalendarDay(ms) {
|
|
2699
2702
|
const JST_MS = 9 * 60 * 60 * 1000;
|
|
2700
2703
|
const d = new Date(ms + JST_MS);
|
|
@@ -5137,7 +5140,7 @@ a.pager-btn:hover { text-decoration: none; }
|
|
|
5137
5140
|
使っていない)ので、暗色側の指定は置かない。押したあとの見た目は「取り消し線+無効化」 */
|
|
5138
5141
|
.record-delete-btn {
|
|
5139
5142
|
flex: 0 0 auto; cursor: pointer; white-space: nowrap;
|
|
5140
|
-
padding: 8px
|
|
5143
|
+
padding: 8px 18px; border-radius: 999px;
|
|
5141
5144
|
border: 1px solid #fca5a5; background: #fff; color: #b91c1c;
|
|
5142
5145
|
font-size: 13.5px; font-weight: 600;
|
|
5143
5146
|
}
|
|
@@ -5150,7 +5153,8 @@ a.pager-btn:hover { text-decoration: none; }
|
|
|
5150
5153
|
.record-delete-msg.is-error { color: #b91c1c; }
|
|
5151
5154
|
/* 帯の中では1行を占める(ボタンの並びの下へ回り込ませる)。操作列では右寄せのまま畳む */
|
|
5152
5155
|
.draft-note > .record-delete-msg { flex: 1 0 100%; }
|
|
5153
|
-
|
|
5156
|
+
/* ページ最下部の削除の口(レコード情報の下)。結果の行はボタンの下 */
|
|
5157
|
+
.record-delete-foot { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 24px 0 8px; }
|
|
5154
5158
|
.deleted-note {
|
|
5155
5159
|
display: flex; gap: 8px; align-items: flex-start; margin: 4px 0 16px;
|
|
5156
5160
|
background: #fff; border: 1px solid var(--border); border-radius: 8px;
|
|
@@ -6759,7 +6763,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
6759
6763
|
// **削除もここに並べる(2026-08-28 のオーナー裁定)。** AIが起票した記録に対して
|
|
6760
6764
|
// 人がやることは「引き取る(確認済みにする)」か「要らないと言う(削除)」の二択で、
|
|
6761
6765
|
// 同じ場面の同じ判断なので、口を離さない。**この帯は draft にしか出ない**ので、
|
|
6762
|
-
// draft
|
|
6766
|
+
// draft でない記録の削除の口はページの最下部(appendDeleteFoot)に置く。
|
|
6763
6767
|
function draftNote(rec) {
|
|
6764
6768
|
if (!rec || !unconfirmed(rec)) return null;
|
|
6765
6769
|
var path = promotableRecordPath(rec);
|
|
@@ -7072,7 +7076,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
7072
7076
|
}
|
|
7073
7077
|
/** 筋の札(一覧の行)。札が無ければ null */
|
|
7074
7078
|
function strandBadge(rec) {
|
|
7075
|
-
return hasStrand(rec) ? el("span", { class: "badge strand", title: "
|
|
7079
|
+
return hasStrand(rec) ? el("span", { class: "badge strand", title: "論点", text: rec.strand }) : null;
|
|
7076
7080
|
}
|
|
7077
7081
|
var FIRMNESS_CLASS = { "確定": "fixed", "仮": "tentative", "検討中": "open" };
|
|
7078
7082
|
/** 固さの小札。語彙外は出さない(ビルド側でも捨てている・二重の網) */
|
|
@@ -18897,7 +18901,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
18897
18901
|
// 筋の札(#1094)。札を持つ決定が 1 件も無い案件では出さない
|
|
18898
18902
|
var strandOpts = strandOptions(visibleRecords(SITE.decisions));
|
|
18899
18903
|
if (strandOpts.length) {
|
|
18900
|
-
var strandSel = buildSelect("
|
|
18904
|
+
var strandSel = buildSelect("すべての論点", strandOpts, listState.strand, function (v) { listState.strand = v; listState.page = 1; drawCards(); });
|
|
18901
18905
|
strandSel.classList.add("strand-filter");
|
|
18902
18906
|
filters.appendChild(strandSel);
|
|
18903
18907
|
}
|
|
@@ -19215,6 +19219,9 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
19215
19219
|
//(acme.slack.com の acme の部分。fleet-status.json の slackWorkspaces[].domain)
|
|
19216
19220
|
var LI_SLACK_USER = /^[UW][A-Z0-9]{2,20}$/;
|
|
19217
19221
|
var LI_SLACK_DOMAIN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
19222
|
+
// エンタープライズの組織は classmethod.enterprise.slack.com のように「<組織>.enterprise」の形(台帳の domain には入らない)
|
|
19223
|
+
var LI_SLACK_ENT_DOMAIN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.enterprise$/i;
|
|
19224
|
+
var LI_SLACK_CHANNEL_HOST = /^https:\\/\\/([a-z0-9.-]{1,200})\\.slack\\.com\\//i;
|
|
19218
19225
|
var LI_SLACK_TEAM = /^[TE][A-Z0-9]{2,20}$/;
|
|
19219
19226
|
// Backlog のユーザー(engine は userId=ログイン名、無い相手は数値 id を入れる)と、
|
|
19220
19227
|
// スペースのドメイン(fleet の backlogTarget。正本は 課題管理/issues-config.json)。
|
|
@@ -19270,8 +19277,9 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
19270
19277
|
* 場が team_id の形なのにどれとも一致しなければ**リンク無し**
|
|
19271
19278
|
* 2. そうでなく、接続済みワークスペースが**ちょうど 1 つ**ならそれ
|
|
19272
19279
|
* 3. それ以外(0 個・2 個以上)は**リンク無し**
|
|
19273
|
-
* 🔴 **チャンネルの URL
|
|
19274
|
-
*
|
|
19280
|
+
* 🔴 **チャンネルの URL のホストは、台帳にドメインが無いときの最後の手だけ**(liSlackChannelHost。
|
|
19281
|
+
* 登録されたチャンネルの全部が同じホストのときだけ採る。共有チャンネルでは貼った人のホストに
|
|
19282
|
+
* なりうるので、食い違えばリンク無し)。**間違ったリンクは値だけより悪い**
|
|
19275
19283
|
*/
|
|
19276
19284
|
function liSlackDomain(scope) {
|
|
19277
19285
|
var list = slackWorkspaces() || [];
|
|
@@ -19287,7 +19295,33 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
19287
19295
|
}
|
|
19288
19296
|
if (!pick && list.length === 1) pick = list[0];
|
|
19289
19297
|
var d = pick && typeof pick.domain === "string" ? pick.domain.trim() : "";
|
|
19290
|
-
|
|
19298
|
+
if (LI_SLACK_DOMAIN.test(d)) return d;
|
|
19299
|
+
// 台帳にドメインが無い(auth.test の url が点を含む組織のアドレスのとき等)ときは、
|
|
19300
|
+
// 案件に登録されたチャンネルの URL のホストから補う(liSlackChannelHost)
|
|
19301
|
+
return liSlackChannelHost(pick ? pick.teamId : "", !pick && list.length > 0);
|
|
19302
|
+
}
|
|
19303
|
+
/**
|
|
19304
|
+
* 登録済みの Slack チャンネル(fleet の externalSources)の URL から、ワークスペースのドメインを引く。
|
|
19305
|
+
*
|
|
19306
|
+
* 🔴 **登録されたチャンネルの URL が全部同じホストを指すときだけ**採る(共有チャンネルの
|
|
19307
|
+
* 貼り付け元のホストが混ざる案件では決められない=リンク無し)。ワークスペースが決まっているなら、
|
|
19308
|
+
* そのワークスペースのチャンネル(workspace が一致するもの)に絞る。**ワークスペースが複数あるのに
|
|
19309
|
+
* 決まっていない行(skipAll)は引かない**。ホストは slack.com の配下の https だけ
|
|
19310
|
+
*/
|
|
19311
|
+
function liSlackChannelHost(teamId, skipAll) {
|
|
19312
|
+
if (skipAll) return "";
|
|
19313
|
+
var srcs = (fleetVal("externalSources") || []).filter(function (x) {
|
|
19314
|
+
return x && x.type === "slack" && typeof x.url === "string" && (!teamId || x.workspace === teamId);
|
|
19315
|
+
});
|
|
19316
|
+
var host = "";
|
|
19317
|
+
for (var i = 0; i < srcs.length; i++) {
|
|
19318
|
+
var m = LI_SLACK_CHANNEL_HOST.exec(srcs[i].url);
|
|
19319
|
+
var h = m ? m[1].toLowerCase() : "";
|
|
19320
|
+
if (!LI_SLACK_DOMAIN.test(h) && !LI_SLACK_ENT_DOMAIN.test(h)) return "";
|
|
19321
|
+
if (host && host !== h) return "";
|
|
19322
|
+
host = h;
|
|
19323
|
+
}
|
|
19324
|
+
return host;
|
|
19291
19325
|
}
|
|
19292
19326
|
/**
|
|
19293
19327
|
* Backlog のユーザーのページを組むスペースのドメイン(決められなければ空文字)。
|
|
@@ -19525,7 +19559,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
19525
19559
|
rec.deleted = true;
|
|
19526
19560
|
var title = app.querySelector(".detail-title");
|
|
19527
19561
|
if (title) title.classList.add("is-deleted");
|
|
19528
|
-
// **置き場は2つある**(draft
|
|
19562
|
+
// **置き場は2つある**(draft の警告帯の中/ページの最下部)。どちらに出ていても止める
|
|
19529
19563
|
var btns = app.querySelectorAll(".record-delete-btn");
|
|
19530
19564
|
for (var b = 0; b < btns.length; b++) { btns[b].disabled = true; btns[b].textContent = "削除しました"; }
|
|
19531
19565
|
// もう無い記録を直す口は押させない(AIで編集・GitHubで編集・確認済みにする)
|
|
@@ -19600,7 +19634,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
19600
19634
|
*
|
|
19601
19635
|
* 呼び出し側は2つ:
|
|
19602
19636
|
* - draftNote …… 未確認の記録。「確認済みにする」と並べて警告帯の中に置く
|
|
19603
|
-
* -
|
|
19637
|
+
* - 詳細ページの最下部(appendDeleteFoot) …… 警告帯が出ない記録
|
|
19604
19638
|
*/
|
|
19605
19639
|
function buildDeleteControls(rec) {
|
|
19606
19640
|
var path = deletableRecordPath(rec);
|
|
@@ -19620,19 +19654,18 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
19620
19654
|
return { btn: btn, msg: msg };
|
|
19621
19655
|
}
|
|
19622
19656
|
/**
|
|
19623
|
-
*
|
|
19657
|
+
* 警告帯が出ない記録の削除の口を、ページの一番下(「レコード情報(frontmatter)」の下・
|
|
19658
|
+
* 生成の行の上)へ置く。取り消せない操作を、高頻度の操作(AIで編集・GitHubで編集)の
|
|
19659
|
+
* 隣に置かない(2026-10-18 のオーナー指摘)。4 区画の詳細で同じ。
|
|
19624
19660
|
*
|
|
19625
19661
|
* **draft には出さない**——そちらは警告帯の中で「確認済みにする」と対になっている
|
|
19626
|
-
* (2つ出すと、同じ操作の口がページに2
|
|
19627
|
-
* 操作の隣に置くことにはなるが、**警告帯の無い記録には他に置き場が無い**。
|
|
19628
|
-
* 見た目は控えめな文字のボタンのままにして、隣の実体のあるボタンと重さを分ける。
|
|
19662
|
+
* (2つ出すと、同じ操作の口がページに2か所できる)。
|
|
19629
19663
|
*/
|
|
19630
|
-
function
|
|
19631
|
-
if (!rec || unconfirmed(rec)) return
|
|
19664
|
+
function appendDeleteFoot(rec) {
|
|
19665
|
+
if (!rec || unconfirmed(rec)) return;
|
|
19632
19666
|
var del = buildDeleteControls(rec);
|
|
19633
|
-
if (!del) return
|
|
19634
|
-
|
|
19635
|
-
return del.msg; // 結果を出す行は操作列の下(呼び出し側が最後に並べる)
|
|
19667
|
+
if (!del) return;
|
|
19668
|
+
app.appendChild(el("div", { class: "record-delete-foot" }, [del.btn, del.msg]));
|
|
19636
19669
|
}
|
|
19637
19670
|
/** 詳細ページの末尾(本文・参照の後)に、つながっている ID・AIの変更履歴・frontmatter を並べる */
|
|
19638
19671
|
function appendRecordFooter(rec) {
|
|
@@ -19643,6 +19676,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
19643
19676
|
if (history) app.appendChild(history);
|
|
19644
19677
|
var fmp = renderFmPanel(rec.fm);
|
|
19645
19678
|
if (fmp) app.appendChild(fmp);
|
|
19679
|
+
appendDeleteFoot(rec);
|
|
19646
19680
|
// 開き直しても削除済みのまま見せる(印は rec が持っている)
|
|
19647
19681
|
if (rec.deleted) markRecordDeleted(rec);
|
|
19648
19682
|
}
|
|
@@ -19879,12 +19913,9 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
19879
19913
|
if (r.editUrl) {
|
|
19880
19914
|
editRowR.appendChild(el("a", { class: "edit-btn", href: r.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
|
|
19881
19915
|
}
|
|
19882
|
-
// 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
|
|
19883
|
-
var delMsgR = attachDeleteToEditRow(r, editRowR);
|
|
19884
19916
|
head.appendChild(el("div", { class: "detail-actions" }, [
|
|
19885
19917
|
el("div", { class: "detail-actions-row" }, [ traceBtn2 ]),
|
|
19886
19918
|
editRowR.children.length ? editRowR : null,
|
|
19887
|
-
delMsgR,
|
|
19888
19919
|
]));
|
|
19889
19920
|
app.appendChild(head);
|
|
19890
19921
|
|
|
@@ -20771,12 +20802,9 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20771
20802
|
if (d.editUrl) {
|
|
20772
20803
|
editRowD.appendChild(el("a", { class: "edit-btn", href: d.editUrl, target: "_blank", rel: "noopener", text: "GitHubで編集" }));
|
|
20773
20804
|
}
|
|
20774
|
-
// 警告帯が出ない記録の削除の口はここ(編集の行の末尾)。draft には出さない
|
|
20775
|
-
var delMsgD = attachDeleteToEditRow(d, editRowD);
|
|
20776
20805
|
head.appendChild(el("div", { class: "detail-actions" }, [
|
|
20777
20806
|
el("div", { class: "detail-actions-row" }, [ traceBtn ]),
|
|
20778
20807
|
editRowD.children.length ? editRowD : null,
|
|
20779
|
-
delMsgD,
|
|
20780
20808
|
]));
|
|
20781
20809
|
app.appendChild(head);
|
|
20782
20810
|
|
|
@@ -20802,7 +20830,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
20802
20830
|
|
|
20803
20831
|
// 筋(#1094)。札の無い決定では何も作らない
|
|
20804
20832
|
if (hasStrand(d)) {
|
|
20805
|
-
app.appendChild(el("div", { class: "section-label", text: "
|
|
20833
|
+
app.appendChild(el("div", { class: "section-label", text: "論点" }));
|
|
20806
20834
|
app.appendChild(el("div", { class: "decision-strand" }, [el("span", { class: "chip", text: d.strand })]));
|
|
20807
20835
|
}
|
|
20808
20836
|
|
|
@@ -21153,6 +21181,12 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21153
21181
|
if (isHttp(target)) return el("a", { href: target, target: "_blank", rel: "noopener", text: target });
|
|
21154
21182
|
return el("span", { text: target });
|
|
21155
21183
|
}
|
|
21184
|
+
/** 値が http/https の URL 1 つだけ(前後の空白は落とす・空白を含むものは URL としない)なら、その URL。それ以外は空文字 */
|
|
21185
|
+
function fmHttpUrl(x) {
|
|
21186
|
+
if (typeof x !== "string") return "";
|
|
21187
|
+
var t = x.trim();
|
|
21188
|
+
return isHttp(t) && !/\\s/.test(t) ? t : "";
|
|
21189
|
+
}
|
|
21156
21190
|
var FM_EMPTY_TEXT = "(未記入)";
|
|
21157
21191
|
/** パネルの 1 値を文字にする。オブジェクト・配列は JSON([object Object] を出さない) */
|
|
21158
21192
|
function fmScalarText(x) {
|
|
@@ -21208,14 +21242,21 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
21208
21242
|
cell.appendChild(line);
|
|
21209
21243
|
});
|
|
21210
21244
|
} else if (Array.isArray(v)) {
|
|
21211
|
-
// 🔴 要素がオブジェクトなら JSON で出す(join に落とすと [object Object]
|
|
21212
|
-
|
|
21245
|
+
// 🔴 要素がオブジェクトなら JSON で出す(join に落とすと [object Object] になる)。
|
|
21246
|
+
// http/https の URL だけの要素はリンクにする(新しいタブ・それ以外の綴りは文字のまま)
|
|
21247
|
+
v.forEach(function (x, i) {
|
|
21248
|
+
if (i > 0) cell.appendChild(document.createTextNode(" / "));
|
|
21249
|
+
var u = fmHttpUrl(x);
|
|
21250
|
+
cell.appendChild(u
|
|
21251
|
+
? el("a", { href: u, target: "_blank", rel: "noopener", text: u })
|
|
21252
|
+
: el("span", { text: fmScalarText(x) }));
|
|
21253
|
+
});
|
|
21213
21254
|
} else if (v && typeof v === "object") {
|
|
21214
21255
|
cell.textContent = fmScalarText(v);
|
|
21215
21256
|
} else if (k === "source") {
|
|
21216
21257
|
cell.appendChild(fmTargetLink(String(v)));
|
|
21217
|
-
} else if (
|
|
21218
|
-
cell.appendChild(el("a", { href: v, target: "_blank", rel: "noopener", text: v }));
|
|
21258
|
+
} else if (fmHttpUrl(v)) {
|
|
21259
|
+
cell.appendChild(el("a", { href: fmHttpUrl(v), target: "_blank", rel: "noopener", text: fmHttpUrl(v) }));
|
|
21219
21260
|
} else {
|
|
21220
21261
|
cell.textContent = String(v);
|
|
21221
21262
|
}
|
package/package.json
CHANGED