@takagaki/cortex-decisions-viewer 0.12.130 → 0.12.132
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 +671 -186
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -2471,6 +2471,10 @@ function sanitizeGithubOwnersNow(v) {
|
|
|
2471
2471
|
const row = { owner, appInstalled: r.appInstalled === true ? true : r.appInstalled === false ? false : null };
|
|
2472
2472
|
if (typeof r.repos === "number" && Number.isFinite(r.repos) && r.repos >= 0)
|
|
2473
2473
|
row.repos = r.repos;
|
|
2474
|
+
// その案件のテナントのアプリの slug(行の「入れる」リンクの宛先・cortex-engine #1328 T-6)。🔴 slug の形だけを残す
|
|
2475
|
+
// (画面がリンクに組み立てる)。⚠ この関数は toString で埋め込みJSへ入るので、正規表現は関数の中に逆斜線なしで書く
|
|
2476
|
+
if (typeof r.appSlug === "string" && /^[a-z0-9][a-z0-9-]{0,33}$/.test(r.appSlug))
|
|
2477
|
+
row.appSlug = r.appSlug;
|
|
2474
2478
|
out.push(row);
|
|
2475
2479
|
}
|
|
2476
2480
|
return out.length ? out : null;
|
|
@@ -5716,6 +5720,17 @@ a.ds-tile:hover { text-decoration: none; }
|
|
|
5716
5720
|
.ds-gate-ok { color: #1f7a45; }
|
|
5717
5721
|
.ds-gate-warn { color: #b45309; }
|
|
5718
5722
|
.ds-gate-muted { color: #6b7280; }
|
|
5723
|
+
/* 行の事実の札(c-07 の 2 本目): 固定 IP 経由・リアルタイム同期の稼働中/未受信。
|
|
5724
|
+
.ds-gate(届くか札は 1 つ)とは別の部品。状態の色(緑・橙)は使わず、事実の弱い札として読ませる */
|
|
5725
|
+
.ds-route-chip, .ds-rt-chip { display: inline-flex; align-items: baseline; gap: 6px; flex: 0 0 auto; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--border); font-size: 11.5px; color: #6b7280; background: #fff; }
|
|
5726
|
+
.ds-rt-chip.is-live { color: #1c7a43; border-color: #b7e0c6; background: #e8f6ed; }
|
|
5727
|
+
.ds-rt-at { font-size: 11px; color: var(--muted); font-weight: 400; }
|
|
5728
|
+
/* 一覧の下の 1 行の一手(リアルタイム同期の設定・[設定する]で手順が開く)と、ふだん見えない「その他」 */
|
|
5729
|
+
.ds-rt-help { margin: 8px 0 0; font-size: 12px; line-height: 1.7; color: #6b7280; }
|
|
5730
|
+
.ds-more { margin-top: 14px; }
|
|
5731
|
+
.ds-more > summary { cursor: pointer; color: var(--muted); font-size: 12px; }
|
|
5732
|
+
.ds-more[open] > summary { margin-bottom: 8px; }
|
|
5733
|
+
.ds-more-key { margin: 6px 0 0; font-size: 12.5px; color: var(--text); display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
5719
5734
|
/* 行の状態に添える「次に誰が何をするか」(2026-08-18)。**必ず自分の行を占める**
|
|
5720
5735
|
(flex: 1 1 100%)——状態バッジの横に押し込むと、いちばん読ませたい一文が
|
|
5721
5736
|
カード幅に負けて省略される。状態の語より弱く、しかし読める大きさで出す */
|
|
@@ -10088,6 +10103,18 @@ ${calendarAddUrl.toString()}
|
|
|
10088
10103
|
resting: function (row) { return slackRowHelpParts(row); },
|
|
10089
10104
|
},
|
|
10090
10105
|
},
|
|
10106
|
+
// 🔴 **Backlog の行の一手(c-07 の 2 本目)。** 行の中身(そのスペースのキーの有無・同期経路・
|
|
10107
|
+
// スペースの数)で決まるので関数で持つ。row は { domain, projectKey, gate }。
|
|
10108
|
+
// DS_GATE_CAP.issues は札(APIキーが未登録です)だけを持ち、help を持たせない(一手の置き場は 1 か所)。
|
|
10109
|
+
// 部品の型は 文字列 / { copy, bare } / { act: "backlog-key" | "backlog-route-on", label, space }
|
|
10110
|
+
backlog: {
|
|
10111
|
+
no_token: {
|
|
10112
|
+
resting: function (row) { return backlogRowHelpParts(row); },
|
|
10113
|
+
},
|
|
10114
|
+
unreachable: {
|
|
10115
|
+
resting: function (row) { return backlogRowHelpParts(row); },
|
|
10116
|
+
},
|
|
10117
|
+
},
|
|
10091
10118
|
github: {
|
|
10092
10119
|
// 🔴 **読めない行の中に、理由に合った一手だけを出す(#1233)。** 全体の帯は撤去した。
|
|
10093
10120
|
// 一手は組織の App の導入状況(githubRowHelpParts)で決まるので、関数で持つ
|
|
@@ -10140,10 +10167,18 @@ ${calendarAddUrl.toString()}
|
|
|
10140
10167
|
var owner = String(row.ref || "").split("/")[0];
|
|
10141
10168
|
var meta = owner ? githubOwnerMeta(owner) : null;
|
|
10142
10169
|
if (meta && meta.source === "unknown") return null;
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10170
|
+
// 🔴 リンクの宛先は、その組織について窓口が答えたアプリ(案件のテナントのアプリ・#1328 T-6)。
|
|
10171
|
+
// 正本(op=state)が答えている画面で slug が無い組織は「分からない」回なので、**宛先を出さず文だけ**にする
|
|
10172
|
+
// (当社のアプリへ落とすと、会社ごとのアプリの会社の画面で別のアプリを入れさせる)。
|
|
10173
|
+
// 当社のアプリの定数へ落とすのは、正本の答えがまだ無い画面(まとめ表だけ)のときだけ
|
|
10174
|
+
var slug = githubAppSlugOf(meta);
|
|
10175
|
+
var linkable = !!slug || !LIVE_GH_OWNERS;
|
|
10176
|
+
var pick = row.gate === "unreachable" && meta && meta.appInstalled === true;
|
|
10177
|
+
var say = pick ? "GitHub App の設定で、このリポジトリを選んでください。" : "この組織に Cortex の GitHub App を入れてください。";
|
|
10178
|
+
if (!linkable) return [say];
|
|
10179
|
+
// 部品には行の生の値を渡す(宛先の検めは githubAppLinkUrl が持つ。ここの slug はリンクを出すかの判断だけ)
|
|
10180
|
+
var raw = meta && meta.appSlug;
|
|
10181
|
+
return [say, pick ? { link: "インストール設定を開く →", kind: "settings", slug: raw } : { link: "GitHub App を入れる →", kind: "install", slug: raw }];
|
|
10147
10182
|
}
|
|
10148
10183
|
/**
|
|
10149
10184
|
* Slack の「理由不明」の行(unreachable・組織不明の仮置き)に出す一手(c-07)。
|
|
@@ -10185,9 +10220,10 @@ ${calendarAddUrl.toString()}
|
|
|
10185
10220
|
var p = el("p", { class: cls });
|
|
10186
10221
|
parts.forEach(function (part) {
|
|
10187
10222
|
if (typeof part === "string") p.appendChild(document.createTextNode(part));
|
|
10188
|
-
else if (part && part.link) p.appendChild(el("a", { href: githubAppLinkUrl(part.kind), target: "_blank", rel: "noopener", text: part.link }));
|
|
10189
|
-
|
|
10190
|
-
else if (part && part.
|
|
10223
|
+
else if (part && part.link) p.appendChild(el("a", { href: githubAppLinkUrl(part.kind, part.slug), target: "_blank", rel: "noopener", text: part.link }));
|
|
10224
|
+
// bare … 文面に値が含まれているので[コピー]だけを出す(固定 IP の依頼文)
|
|
10225
|
+
else if (part && part.copy) p.appendChild(copyInline(part.copy, !part.bare));
|
|
10226
|
+
else if (part && part.act) { var an = actNode(part.act, part.label, part.space); if (an) p.appendChild(an); }
|
|
10191
10227
|
});
|
|
10192
10228
|
return p;
|
|
10193
10229
|
}
|
|
@@ -10195,11 +10231,18 @@ ${calendarAddUrl.toString()}
|
|
|
10195
10231
|
* 行の一手に置くボタン(c-07)。**押せないボタンは作らない**(null)。
|
|
10196
10232
|
* - "connect" … [接続する]。Slack の承認画面へ送る(slackConnectInline)。投入の口が無ければ null
|
|
10197
10233
|
* - "figma-key" … [トークンを入れる]/[トークンを入れ直す]。下の「Figma のトークン」の欄の入力へ移る
|
|
10234
|
+
* - "backlog-key" … [入れる]/[入れ直す]。**そのスペースの**鍵カードを開いて入力へ移る(arg=スペース)。
|
|
10235
|
+
* 受け皿が無いスペースには作らない(別のスペースの欄へ落とさない)
|
|
10236
|
+
* - "backlog-route-on" … [切り替える]。同期経路を固定 IP 経由にする(setBacklogRoute)
|
|
10198
10237
|
* @param label 省略時は既定の語
|
|
10238
|
+
* @param arg 部品が要る材料(backlog-key のスペース)
|
|
10199
10239
|
*/
|
|
10200
|
-
function actNode(act, label) {
|
|
10240
|
+
function actNode(act, label, arg) {
|
|
10201
10241
|
if (!SITE.intake) return null;
|
|
10202
10242
|
if (act === "connect") return slackConnectInline(label || "接続する");
|
|
10243
|
+
// Backlog の行の一手(c-07 の 2 本目)。arg はスペース(ドメイン)。**受け皿が無ければ作らない**
|
|
10244
|
+
if (act === "backlog-key") return backlogKeyJump(arg, label || "入れる");
|
|
10245
|
+
if (act === "backlog-route-on") return backlogRouteOnButton(label || "切り替える");
|
|
10203
10246
|
// 鍵の欄(KEY_SLOTS.figma)が無い構成では、移る先が無いので出さない
|
|
10204
10247
|
if (act === "figma-key") return secretKindsShown().indexOf("figma") >= 0 ? figmaKeyJump(label || "トークンを入れる") : null;
|
|
10205
10248
|
return null;
|
|
@@ -11416,11 +11459,19 @@ ${calendarAddUrl.toString()}
|
|
|
11416
11459
|
* **GitHub 側の画面が変わったらここが最初に嘘になる。** 宛先を変えるときは
|
|
11417
11460
|
* 「githubConfigureUrl」(サーバ)と揃えること。
|
|
11418
11461
|
*/
|
|
11419
|
-
function githubAppLinkUrl(kind) {
|
|
11462
|
+
function githubAppLinkUrl(kind, slug) {
|
|
11420
11463
|
// 「kind」: "install"(組織に App を入れる)/"settings"(入っている App の対象リポジトリを選ぶ)。
|
|
11421
|
-
// いまは同じ宛先(App
|
|
11422
|
-
//
|
|
11423
|
-
|
|
11464
|
+
// いまは同じ宛先(App 個別ページ)。**行の案内のリンクは全部ここを通す**(URL を 1 か所から作る)。
|
|
11465
|
+
// 「slug」: その組織について窓口が答えた、案件のテナントのアプリ(会社ごとのアプリ・cortex-engine #1328 T-6)。
|
|
11466
|
+
// 🔴 ここでも形を検める(まとめ表の行は許可リストを通らない)。無ければ当社の読む App(定数)
|
|
11467
|
+
var s = githubAppSlugOf({ appSlug: slug });
|
|
11468
|
+
var url = s ? "https://github.com/apps/" + encodeURIComponent(s) + "/installations/new" : GITHUB_APP_INSTALL_URL;
|
|
11469
|
+
return ({ install: url, settings: url })[kind] || url;
|
|
11470
|
+
}
|
|
11471
|
+
/** 行の slug(形が合わなければ null)。⚠ 埋め込みJSなので、正規表現は逆斜線なしで書く */
|
|
11472
|
+
function githubAppSlugOf(meta) {
|
|
11473
|
+
var v = meta && meta.appSlug;
|
|
11474
|
+
return typeof v === "string" && /^[a-z0-9][a-z0-9-]{0,33}$/.test(v) ? v : null;
|
|
11424
11475
|
}
|
|
11425
11476
|
|
|
11426
11477
|
/**
|
|
@@ -12188,13 +12239,33 @@ ${calendarAddUrl.toString()}
|
|
|
12188
12239
|
for (var i = 0; i < KEY_CARDS.length; i++) {
|
|
12189
12240
|
// **対応表に無い種別は隠さない**(keyTargetCount の既定と同じく出す側に倒す)。
|
|
12190
12241
|
// 3つ目の鍵が足されて分岐を書き忘れた場合は key-card-visibility の [全種0件] が落ちる
|
|
12191
|
-
|
|
12242
|
+
var hide = shown[KEY_CARDS[i].kind] === false;
|
|
12243
|
+
// 🔴 **Backlog の行の中のカードは、0 件の判定(上)に加えて「見せる理由があるとき」だけ見せる**
|
|
12244
|
+
//(c-07 の 2 本目)。0 件が勝つ——0 件のあいだは理由があっても出さない(順序の逆な案内になる)
|
|
12245
|
+
if (!hide && KEY_CARDS[i].compact) hide = !backlogKeyCardWanted(KEY_CARDS[i]);
|
|
12246
|
+
KEY_CARDS[i].el.hidden = hide;
|
|
12247
|
+
if (!hide && KEY_CARDS[i].compact && KEY_CARDS[i].refresh) KEY_CARDS[i].refresh();
|
|
12192
12248
|
}
|
|
12193
12249
|
eachKeySlot(function (slot) {
|
|
12194
12250
|
slot.hidden = !slot.querySelector("[data-secret-kind]:not([hidden])");
|
|
12195
12251
|
});
|
|
12196
12252
|
}
|
|
12197
12253
|
|
|
12254
|
+
/**
|
|
12255
|
+
* Backlog の行の中の鍵カードを、いま見せるか(c-07 の 2 本目・スペースごとの判定)。次の 4 つのどれか:
|
|
12256
|
+
* ① キーが要る(backlogKeyNeededForSpace=登録状況が「未登録」・足した直後で答えがまだ無い等)
|
|
12257
|
+
* ② 一手の[入れる]/[入れ直す]・「その他」の[入れ直す]が押された(forced)
|
|
12258
|
+
* ③ 触り始めた(開いた・値がある・結果の文がある)。🔴 **保存直後の「✓ 保存しました」と入力中の値を畳まない**(#223)
|
|
12259
|
+
* 使えているスペースのカードは、ふだん隠れている(DOM には常に在る=入力中の値・結果は消えない)。
|
|
12260
|
+
*/
|
|
12261
|
+
function backlogKeyCardWanted(ent) {
|
|
12262
|
+
if (ent.forced) return true;
|
|
12263
|
+
if (ent.fold && ent.fold.open) return true;
|
|
12264
|
+
if (ent.input && ent.input.value !== "") return true;
|
|
12265
|
+
if (ent.msg && ent.msg.textContent !== "") return true;
|
|
12266
|
+
return backlogKeyNeededForSpace(ent.space);
|
|
12267
|
+
}
|
|
12268
|
+
|
|
12198
12269
|
/** いま在る鍵の受け皿(.ds-keyrow)を順に渡す。置き場の正本は KEY_SLOTS */
|
|
12199
12270
|
function eachKeySlot(fn) {
|
|
12200
12271
|
var slots = KEY_SLOTS.backlog || {};
|
|
@@ -12535,6 +12606,10 @@ ${calendarAddUrl.toString()}
|
|
|
12535
12606
|
// 🔴 **Google Drive 専用の説明文(c-07・新設)。** SharePoint 等の別のアダプタの窓には出さない
|
|
12536
12607
|
// (Markdown 化の約束は Drive の取り込みの話)。追加の欄の手順 3 が持っていた文
|
|
12537
12608
|
m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいるフォルダの中身を、毎晩 共有資料 にコピーして Markdown 化します。やめるには「このフォルダを外す」を押してください。" }));
|
|
12609
|
+
} else if (cap === "課題管理" && (!adapter || adapter === "backlog")) {
|
|
12610
|
+
// 🔴 **Backlog の説明文(c-07 の 2 本目・新設)。** 追加の欄の説明から外した 3 つを、ここに 1 回だけ置く:
|
|
12611
|
+
// 何が同期されるか・起票先は変わらないこと(2026-08-20 に追加の確認をやめたとき追加の欄へ移した文)・止め方
|
|
12612
|
+
m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいる Backlog プロジェクトの課題・ドキュメント・Wiki を毎晩同期します。接続先を足しても、ビューアからの起票先は変わりません。やめるには「この接続先を外す」を押してください。" }));
|
|
12538
12613
|
} else if (cap === "開発") {
|
|
12539
12614
|
// 🔴 **開発の開示文は「記録に使う・決定の抽出には使わない」(#660)。** 2026-09-18 の
|
|
12540
12615
|
// オーナー決定でソースの性質は種類で固定になり、GitHub は「作業の記録」に固定された。
|
|
@@ -12574,16 +12649,17 @@ ${calendarAddUrl.toString()}
|
|
|
12574
12649
|
? ""
|
|
12575
12650
|
: (CAP_STATE_TEXT[dNow] || "状態が判定できません") + "(" + detailNow + ")";
|
|
12576
12651
|
}
|
|
12577
|
-
// 🔴 **Figma・Drive の「未接続(…が登録されていません)」は作らない(c-07)。** 0 件のとき、
|
|
12652
|
+
// 🔴 **Figma・Drive・Backlog の「未接続(…が登録されていません)」は作らない(c-07)。** 0 件のとき、
|
|
12578
12653
|
// 同じ窓に一覧の ds-empty「…はまだ登録されていません。」が出ていて二重だった。しかも一覧は
|
|
12579
12654
|
// liveList で追加に追随するが、この一文は開いた時点の値なので、1 件足すと「未接続」が足した行の
|
|
12580
|
-
// 上に残る。**一覧の 1 行だけ**を残す。off・broken の一文は残す(
|
|
12655
|
+
// 上に残る。**一覧の 1 行だけ**を残す。off・broken の一文は残す(Slack・GitHub は触らない)。
|
|
12581
12656
|
// 🔴 **「登録されていません」と言うだけの一文だけを落とす。** 「雛形のプレースホルダです」のように、
|
|
12582
12657
|
// 一覧の 1 行が言わない手がかりを運ぶ一文は残す(なぜ未接続扱いなのかが読めなくなる)
|
|
12583
12658
|
var unsetDup = noteIsUnset
|
|
12584
12659
|
&& /登録されていません/.test(noteText)
|
|
12585
12660
|
&& ((cap === "デザイン" && (!adapter || adapter === "figma"))
|
|
12586
|
-
|| (cap === "共有資料" && (!adapter || adapter === "google-drive"))
|
|
12661
|
+
|| (cap === "共有資料" && (!adapter || adapter === "google-drive"))
|
|
12662
|
+
|| (cap === "課題管理" && (!adapter || adapter === "backlog")));
|
|
12587
12663
|
if (!unsetDup) {
|
|
12588
12664
|
var noteEl = el("p", { class: "modal-desc", text: noteText });
|
|
12589
12665
|
// **消さずに hidden で伏せる。** 節点を残しておかないと、この一文を探している
|
|
@@ -12693,6 +12769,8 @@ ${calendarAddUrl.toString()}
|
|
|
12693
12769
|
KEY_SLOTS = { backlog: {}, backlogNamed: false, figma: null };
|
|
12694
12770
|
// 鍵カードも受け皿と同じく作り直す(前回の切り離されたカードを掴んだままにしない)
|
|
12695
12771
|
KEY_CARDS = [];
|
|
12772
|
+
// Backlog の節の行の控え・結果の帯・描き直しの口も作り直す(切り離された古い節を掴んだままにしない)
|
|
12773
|
+
BACKLOG_UI = { rows: [], msg: null, repaintRows: null };
|
|
12696
12774
|
// 作り直しはスナップショットから描き直すこと。応答で採った件数はここで捨てる
|
|
12697
12775
|
ADOPTED_TARGET_COUNT = null;
|
|
12698
12776
|
var internals = fleetVal("internalSources") || [];
|
|
@@ -14407,7 +14485,7 @@ ${calendarAddUrl.toString()}
|
|
|
14407
14485
|
}
|
|
14408
14486
|
var ipLabel = ip ? "固定の送信元 IP(" + ip + ")" : "固定の送信元 IP";
|
|
14409
14487
|
if (keyState === "no") {
|
|
14410
|
-
return "同期は" + ipLabel + "から行います。お客様にこの IP
|
|
14488
|
+
return "同期は" + ipLabel + "から行います。お客様にこの IP の許可を依頼し、接続先の行の「API キーを入れてください」から登録すると同期が始まります。";
|
|
14411
14489
|
}
|
|
14412
14490
|
// 2 文に収める(実装レビューの指摘)。押した直後の帯(サーバの文面)も「許可を依頼」を言うが、
|
|
14413
14491
|
// 帯は数秒で読み終える結果で、この行は次に開いたときも残る状態——役目が違うので両方に置く
|
|
@@ -14477,16 +14555,9 @@ ${calendarAddUrl.toString()}
|
|
|
14477
14555
|
// 切り替えていた)はサーバが changed:false・route 無しで返すので、onDone だと
|
|
14478
14556
|
// スイッチが古い向きのまま「✓ 変更はありません」が出て、次の取り直しまで直らない。
|
|
14479
14557
|
// onSaved は res.ok なら changed を問わず流れる(冪等な op の受け口)
|
|
14480
|
-
|
|
14481
|
-
|
|
14482
|
-
|
|
14483
|
-
LIVE_BACKLOG_ROUTE = backlogRouteOf(j && j.route) || (next ? "fixed-ip" : "runner");
|
|
14484
|
-
if (j && j.fixedEgressIp) LIVE_FIXED_IP = String(j.fixedEgressIp);
|
|
14485
|
-
live.__rebuild();
|
|
14486
|
-
// changed:false では sendConfig が読み直しを起こさない。正本の値で画面を揃えるために自分で起こす
|
|
14487
|
-
if (j && j.changed === false) loadLiveFleet(true);
|
|
14488
|
-
},
|
|
14489
|
-
});
|
|
14558
|
+
// 🔴 **操作は setBacklogRoute に 1 本化している(タイルの[切り替える]と同じ関数)。**
|
|
14559
|
+
// 応答の値を正とする・changed:false では取り直す・確認ダイアログは出さない(A4)は、そちらが持つ
|
|
14560
|
+
await setBacklogRoute(next, msg, { after: function () { live.__rebuild(); } });
|
|
14490
14561
|
// 断られた・通信に失敗したときはこのボタンが残っている(成功時は描き直されて外れている)
|
|
14491
14562
|
btn.disabled = disabled;
|
|
14492
14563
|
btn.classList.remove("is-busy");
|
|
@@ -14496,9 +14567,332 @@ ${calendarAddUrl.toString()}
|
|
|
14496
14567
|
});
|
|
14497
14568
|
wrap.appendChild(live);
|
|
14498
14569
|
wrap.appendChild(msg);
|
|
14570
|
+
// 節が当て直すための口(「その他」の中のスイッチ・c-07 の 2 本目)
|
|
14571
|
+
wrap.__live = live;
|
|
14499
14572
|
return wrap;
|
|
14500
14573
|
}
|
|
14501
14574
|
|
|
14575
|
+
/* ---------------------------------------------------------------------------
|
|
14576
|
+
* Backlog の窓を、GitHub・Figma・Slack・Google Drive・Google Chat の窓にそろえる(c-07 の 2 本目)
|
|
14577
|
+
*
|
|
14578
|
+
* 考え方は 4 つ(GitHub の窓 #1233・4 窓 #1257 と同じ):
|
|
14579
|
+
* 1. 追加の欄は入力と[追加]だけ
|
|
14580
|
+
* 2. 前提が足りないと分かったタイルの中にだけ、理由に合った一手を 1 行で出す
|
|
14581
|
+
* 3. 窓全体を囲む箱・常設の欄を出さない(キーの欄・IP 制限のスイッチ・リアルタイムの大きな箱は引っ込めた)
|
|
14582
|
+
* 4. 説明は上部の説明文に 1 回だけ
|
|
14583
|
+
*
|
|
14584
|
+
* 部品の置き場:
|
|
14585
|
+
* - 一手 … DS_GATE_RESTING_HELP.backlog → backlogRowHelpParts(行の中身で決まる)
|
|
14586
|
+
* - キーの欄 … これまでと同じスペースごとの受け皿(KEY_SLOTS.backlog・#223)に同じカード。
|
|
14587
|
+
* 変えるのは見え方だけ(syncKeyCardVisibility の backlogKeyCardWanted)
|
|
14588
|
+
* - 同期経路の切り替え … setBacklogRoute に 1 本化。入口はタイルの一手・[接続を確認]の結果・「その他」のスイッチ
|
|
14589
|
+
* - 札(事実)… 経路札「固定 IP 経由」・リアルタイム札(appendBacklogChips)
|
|
14590
|
+
* ------------------------------------------------------------------------- */
|
|
14591
|
+
|
|
14592
|
+
/**
|
|
14593
|
+
* 節(backlogTargetsSection)がいま描いている行と、結果の帯・行の描き直しの口。
|
|
14594
|
+
* 一手の文・鍵カードの見え方・ボタンが、節の外から引くための置き場(節を作り直すたびに入れ替わる)。
|
|
14595
|
+
* 🔴 **鍵の置き場の正本は KEY_SLOTS。ここは行の控えだけ**(#223 の「節の中に受け皿の控えを持たない」は守る)。
|
|
14596
|
+
*/
|
|
14597
|
+
var BACKLOG_UI = { rows: [], msg: null, repaintRows: null };
|
|
14598
|
+
|
|
14599
|
+
/** いま見えている行のスペース(小文字・重複なし) */
|
|
14600
|
+
function backlogDomainsNow() {
|
|
14601
|
+
var out = [];
|
|
14602
|
+
var rows = BACKLOG_UI.rows || [];
|
|
14603
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14604
|
+
var d = String(rows[i].domain || "").toLowerCase();
|
|
14605
|
+
if (d && out.indexOf(d) < 0) out.push(d);
|
|
14606
|
+
}
|
|
14607
|
+
return out;
|
|
14608
|
+
}
|
|
14609
|
+
|
|
14610
|
+
/** そのスペースの行のどれかが、届くかを測られているか */
|
|
14611
|
+
function backlogSpaceMeasured(domain) {
|
|
14612
|
+
var rows = BACKLOG_UI.rows || [];
|
|
14613
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14614
|
+
if (String(rows[i].domain || "").toLowerCase() !== domain) continue;
|
|
14615
|
+
if (reachableStateOf("issues", issuesRefOf(rows[i].domain, rows[i].projectKey))) return true;
|
|
14616
|
+
}
|
|
14617
|
+
return false;
|
|
14618
|
+
}
|
|
14619
|
+
|
|
14620
|
+
/** 正本(secretsNow)に、そのスペースの行が来ているか(unknown の行も数える) */
|
|
14621
|
+
function backlogSecretsRowFor(domain) {
|
|
14622
|
+
var rows = LIVE_SECRETS;
|
|
14623
|
+
if (!Array.isArray(rows)) return false;
|
|
14624
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14625
|
+
if (rows[i] && rows[i].kind === "backlog" && rows[i].space === domain) return true;
|
|
14626
|
+
}
|
|
14627
|
+
return false;
|
|
14628
|
+
}
|
|
14629
|
+
|
|
14630
|
+
/**
|
|
14631
|
+
* そのスペースに「API キーを入れてください」を出すか。**カードの見え方(2.4)と行の一手が同じ 1 本を通る。**
|
|
14632
|
+
* - 登録状況が「未登録」→ 出す(正本が言うので、行の札が古くても従う)
|
|
14633
|
+
* - 登録状況が分からない(複数スペースで答えが無い)とき:
|
|
14634
|
+
* · その行の札が「APIキーが未登録です」→ 出す
|
|
14635
|
+
* · 🔴 **そのスペースの答えの行が無く、どの行も測られていない → 出す**(足した直後の新しいスペース。
|
|
14636
|
+
* ここを隠すと、足した行に鍵を入れる口がどこにも無い=#223・#138 の再発)。見せる側に倒す
|
|
14637
|
+
* - 登録済み → 出さない(前夜の古い札には従わない)
|
|
14638
|
+
* @param gate その行の届くかの状態(無ければ null)
|
|
14639
|
+
*/
|
|
14640
|
+
function backlogKeyNeeded(domain, gate) {
|
|
14641
|
+
var st = secretStateOf("backlog", domain, backlogDomainsNow().length);
|
|
14642
|
+
if (st.known === "no") return true;
|
|
14643
|
+
if (st.known === "unknown") {
|
|
14644
|
+
if (gate === "no_token") return true;
|
|
14645
|
+
if (!backlogSpaceMeasured(domain) && !backlogSecretsRowFor(domain)) return true;
|
|
14646
|
+
}
|
|
14647
|
+
return false;
|
|
14648
|
+
}
|
|
14649
|
+
|
|
14650
|
+
/** そのスペースの鍵カードを見せるか(行のどれかが要るか。行が無ければ要らない) */
|
|
14651
|
+
function backlogKeyNeededForSpace(space) {
|
|
14652
|
+
var domains = backlogDomainsNow();
|
|
14653
|
+
var dom = space || (domains.length === 1 ? domains[0] : "");
|
|
14654
|
+
if (!dom) return false;
|
|
14655
|
+
var rows = BACKLOG_UI.rows || [];
|
|
14656
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14657
|
+
if (String(rows[i].domain || "").toLowerCase() !== dom) continue;
|
|
14658
|
+
var r = reachableStateOf("issues", issuesRefOf(rows[i].domain, rows[i].projectKey));
|
|
14659
|
+
if (backlogKeyNeeded(dom, r ? r.status : null)) return true;
|
|
14660
|
+
}
|
|
14661
|
+
return false;
|
|
14662
|
+
}
|
|
14663
|
+
|
|
14664
|
+
/**
|
|
14665
|
+
* Backlog の行に出す一手のパーツ(無ければ null)。**スペースをまたがない**(ボタンはその行のスペースの受け皿だけを引く)。
|
|
14666
|
+
*
|
|
14667
|
+
* キーが要る → 「API キーを入れてください。[入れる]」(複数スペースならスペース名つき)
|
|
14668
|
+
* 読めない・通常経路 → 「読めません。API キーを入れ直してください。[入れ直す] Backlog に IP 制限があるなら、固定 IP 経由で読んでください。[切り替える]」
|
|
14669
|
+
* 読めない・固定 IP → 「お客様に固定の送信元 IP(…)の許可を依頼してください。[コピー] 許可済みなら、API キーを入れ直してください。[入れ直す]」
|
|
14670
|
+
* 読めない・経路不明 → 「読めません。API キーを入れ直してください。[入れ直す]」(切り替えが押せるか分からないので[切り替える]は出さない)
|
|
14671
|
+
*
|
|
14672
|
+
* 🔴 **理由の語(HTTP の番号等)には依存しない。** 401・403・404 は画面から見分けられないので、読めない行は広い方の 1 行。
|
|
14673
|
+
* IP 制限と確定できるのは[接続を確認]の応答の reason だけ(appendBacklogProbeSwitch)。
|
|
14674
|
+
* 🔴 **投入の口(SITE.intake)が無い案件では出さない**(押せない案内を置かない)。
|
|
14675
|
+
* @param row { domain, projectKey, gate }
|
|
14676
|
+
*/
|
|
14677
|
+
function backlogRowHelpParts(row) {
|
|
14678
|
+
if (!row || !SITE.intake) return null;
|
|
14679
|
+
var dom = String(row.domain || "").toLowerCase();
|
|
14680
|
+
var multi = backlogDomainsNow().length > 1;
|
|
14681
|
+
if (backlogKeyNeeded(dom, row.gate)) {
|
|
14682
|
+
return [(multi ? dom + " の " : "") + "API キーを入れてください。", { act: "backlog-key", space: dom, label: "入れる" }];
|
|
14683
|
+
}
|
|
14684
|
+
if (row.gate !== "unreachable") return null;
|
|
14685
|
+
var reenter = { act: "backlog-key", space: dom, label: "入れ直す" };
|
|
14686
|
+
var route = LIVE_BACKLOG_ROUTE;
|
|
14687
|
+
if (route === "fixed-ip") {
|
|
14688
|
+
// IP が取れていなければ IP も[コピー]も出さない(嘘の値を書かない・#516)
|
|
14689
|
+
var ip = LIVE_FIXED_IP;
|
|
14690
|
+
var head = ip
|
|
14691
|
+
? ["お客様に固定の送信元 IP(" + ip + ")の許可を依頼してください。", { copy: ip, bare: true }]
|
|
14692
|
+
: ["お客様に固定の送信元 IP の許可を依頼してください。"];
|
|
14693
|
+
return head.concat([" 許可済みなら、API キーを入れ直してください。", reenter]);
|
|
14694
|
+
}
|
|
14695
|
+
var base = ["読めません。API キーを入れ直してください。", reenter];
|
|
14696
|
+
if (route === "runner") {
|
|
14697
|
+
// 切り替えは案件全体の経路(スペースごとではない)。複数スペースでは強めに言う
|
|
14698
|
+
var scope = multi
|
|
14699
|
+
? "この案件の Backlog 全体(すべてのスペース)の読み方が変わります。"
|
|
14700
|
+
: "この案件の Backlog 全体の読み方が変わります。";
|
|
14701
|
+
return base.concat([" Backlog に IP 制限があるなら、固定 IP 経由で読んでください。" + scope, { act: "backlog-route-on", label: "切り替える" }]);
|
|
14702
|
+
}
|
|
14703
|
+
return base;
|
|
14704
|
+
}
|
|
14705
|
+
|
|
14706
|
+
/**
|
|
14707
|
+
* Backlog の行の最後に一手を 1 段だけ足す。**一手を出した回は engine の理由文を重ねない**(行の補足は最大 1 段)。
|
|
14708
|
+
* 一手が無い状態(読めている・測っていない・取り込みの失敗など)は、これまでどおり appendReachableReason に任せる。
|
|
14709
|
+
*/
|
|
14710
|
+
function appendBacklogRowHelp(row, t, ref) {
|
|
14711
|
+
var r = reachableStateOf("issues", ref);
|
|
14712
|
+
var gate = r ? r.status : null;
|
|
14713
|
+
var info = { domain: t.domain, projectKey: t.projectKey, gate: gate };
|
|
14714
|
+
// 読めない・鍵が無い札は表(DS_GATE_RESTING_HELP.backlog)から、それ以外(読めている・測っていない)は
|
|
14715
|
+
// 「鍵が要るか」だけを見る(登録状況の正本が言うので、行の札が古くても従う)
|
|
14716
|
+
var parts = gate === "no_token" || gate === "unreachable"
|
|
14717
|
+
? restingHelpPartsOf("backlog", gate, info)
|
|
14718
|
+
: backlogRowHelpParts(info);
|
|
14719
|
+
if (parts) {
|
|
14720
|
+
row.appendChild(restingHelpNode(parts, "ds-gate-help ds-gate-resting"));
|
|
14721
|
+
return row;
|
|
14722
|
+
}
|
|
14723
|
+
return appendReachableReason(row, "issues", ref);
|
|
14724
|
+
}
|
|
14725
|
+
|
|
14726
|
+
/** そのスペースの鍵の受け皿(無ければ null)。名無しの受け皿は、スペースが 1 つ以下のあいだだけ代わりを務める(ensureKeySlots の項) */
|
|
14727
|
+
function backlogKeySlotFor(space) {
|
|
14728
|
+
var slots = KEY_SLOTS.backlog || {};
|
|
14729
|
+
var d = String(space || "").toLowerCase();
|
|
14730
|
+
if (d && slots[d]) return slots[d];
|
|
14731
|
+
if (slots[""] && backlogDomainsNow().length <= 1) return slots[""];
|
|
14732
|
+
return null;
|
|
14733
|
+
}
|
|
14734
|
+
|
|
14735
|
+
/** そのスペースの鍵カードの控え(無ければ null)。**ほかのスペースのカードへ落とさない** */
|
|
14736
|
+
function backlogKeyEntryFor(space) {
|
|
14737
|
+
var d = String(space || "").toLowerCase();
|
|
14738
|
+
var list = [];
|
|
14739
|
+
for (var i = 0; i < KEY_CARDS.length; i++) {
|
|
14740
|
+
if (KEY_CARDS[i].kind === "backlog" && KEY_CARDS[i].compact) list.push(KEY_CARDS[i]);
|
|
14741
|
+
}
|
|
14742
|
+
for (var j = 0; j < list.length; j++) if (d && list[j].space === d) return list[j];
|
|
14743
|
+
if (backlogDomainsNow().length <= 1) {
|
|
14744
|
+
for (var k = 0; k < list.length; k++) if (list[k].space === "") return list[k];
|
|
14745
|
+
}
|
|
14746
|
+
return null;
|
|
14747
|
+
}
|
|
14748
|
+
|
|
14749
|
+
/** そのスペースの鍵カードを見せて、入力へ移る([入れる]・[入れ直す])。見つからなければ false */
|
|
14750
|
+
function openBacklogKeyCard(space) {
|
|
14751
|
+
var ent = backlogKeyEntryFor(space);
|
|
14752
|
+
if (!ent) return false;
|
|
14753
|
+
ent.forced = true;
|
|
14754
|
+
ent.fold.open = true;
|
|
14755
|
+
syncKeyCardVisibility();
|
|
14756
|
+
try { if (ent.input.scrollIntoView) ent.input.scrollIntoView({ block: "center" }); } catch (e) { /* 動かせなくても focus は試す */ }
|
|
14757
|
+
ent.input.focus();
|
|
14758
|
+
return true;
|
|
14759
|
+
}
|
|
14760
|
+
|
|
14761
|
+
/**
|
|
14762
|
+
* [入れる]/[入れ直す]。**そのスペースの受け皿が無ければ作らない**(別のスペースの欄へ移さない)。
|
|
14763
|
+
* 🔴 **作る条件は受け皿(KEY_SLOTS)だけ。カードの控えは押した時点で引く**——節は鍵カードより先に組み立てられる
|
|
14764
|
+
*(buildDataSources の順序)ので、行を描く時点ではカードがまだ無い。受け皿は節が先に作っている
|
|
14765
|
+
*/
|
|
14766
|
+
function backlogKeyJump(space, label) {
|
|
14767
|
+
if (secretKindsShown().indexOf("backlog") < 0) return null;
|
|
14768
|
+
if (!backlogKeySlotFor(space)) return null;
|
|
14769
|
+
var btn = el("button", { class: "ds-toggle-btn ds-act-inline", type: "button", text: label || "入れる" });
|
|
14770
|
+
btn.addEventListener("click", function () { openBacklogKeyCard(space); });
|
|
14771
|
+
return btn;
|
|
14772
|
+
}
|
|
14773
|
+
|
|
14774
|
+
/**
|
|
14775
|
+
* 同期経路の切り替え(固定 IP 経由 ⇄ 通常)。**スイッチ(「その他」)とタイルの[切り替える]が同じこの関数を呼ぶ**
|
|
14776
|
+
* ——操作を 2 本書かない(op・値・確認なし・応答の route を正とする・changed:false の取り直しは 1 つだけ持つ)。
|
|
14777
|
+
* 🔴 **確認ダイアログは出さない(#503 A4)。** 可逆(「その他」のスイッチで戻せる)・押した記録はサーバの監査に残る。
|
|
14778
|
+
* @param next true=固定 IP 経由にする
|
|
14779
|
+
* @param msgEl 結果の帯(一覧の外に置いたもの。描き直しで消えない)
|
|
14780
|
+
* @param opts { after: 成功後に呼ぶ(スイッチの描き直し), hint: 成功した帯に添える 1 文 }
|
|
14781
|
+
*/
|
|
14782
|
+
function setBacklogRoute(next, msgEl, opts) {
|
|
14783
|
+
return sendConfig({ op: "backlog-route-set", route: next ? "fixed-ip" : "runner" }, msgEl, null, null, {
|
|
14784
|
+
onSaved: function (j) {
|
|
14785
|
+
// **応答の値を正とする。** 無ければ送った向き(サーバは断らなかった=正本はその向き)
|
|
14786
|
+
LIVE_BACKLOG_ROUTE = backlogRouteOf(j && j.route) || (next ? "fixed-ip" : "runner");
|
|
14787
|
+
if (j && j.fixedEgressIp) LIVE_FIXED_IP = String(j.fixedEgressIp);
|
|
14788
|
+
if (opts && typeof opts.after === "function") opts.after();
|
|
14789
|
+
// 札・一手は行にあるので、行も描き直す(接続先の集合は触らない)
|
|
14790
|
+
if (typeof BACKLOG_UI.repaintRows === "function") BACKLOG_UI.repaintRows();
|
|
14791
|
+
if (opts && opts.hint && LIVE_BACKLOG_ROUTE === "fixed-ip" && !(j && j.changed === false)) {
|
|
14792
|
+
msgEl.appendChild(document.createTextNode(" " + opts.hint));
|
|
14793
|
+
}
|
|
14794
|
+
// changed:false では sendConfig が読み直しを起こさない。正本の値で画面を揃えるために自分で起こす
|
|
14795
|
+
if (j && j.changed === false) loadLiveFleet(true);
|
|
14796
|
+
},
|
|
14797
|
+
});
|
|
14798
|
+
}
|
|
14799
|
+
|
|
14800
|
+
/** 読めなかったタイルの[切り替える]。結果は一覧の外の帯(節の msg)に出す。成功したら行が描き直されてこのボタンは外れる */
|
|
14801
|
+
function backlogRouteOnButton(label) {
|
|
14802
|
+
var btn = el("button", { class: "ds-toggle-btn ds-act-inline", type: "button", text: label || "切り替える" });
|
|
14803
|
+
btn.addEventListener("click", async function () {
|
|
14804
|
+
if (btn.disabled) return;
|
|
14805
|
+
var msg = BACKLOG_UI.msg;
|
|
14806
|
+
if (!msg) return;
|
|
14807
|
+
btn.disabled = true;
|
|
14808
|
+
btn.classList.add("is-busy");
|
|
14809
|
+
await setBacklogRoute(true, msg, { hint: "元に戻すには、下の「その他」から同期経路を切り替えてください。" });
|
|
14810
|
+
// 断られた・通信に失敗したときはこのボタンが残っている
|
|
14811
|
+
btn.disabled = false;
|
|
14812
|
+
btn.classList.remove("is-busy");
|
|
14813
|
+
});
|
|
14814
|
+
return btn;
|
|
14815
|
+
}
|
|
14816
|
+
|
|
14817
|
+
/**
|
|
14818
|
+
* [接続を確認]の結果が IP 制限(応答の reason が ip-restricted)で、いまが通常の経路のときだけ、
|
|
14819
|
+
* 結果の文の横に[切り替える]を足す。**IP 制限と確定できるのはこの応答だけ**(行の札からは見分けられない)。
|
|
14820
|
+
*/
|
|
14821
|
+
function appendBacklogProbeSwitch(note, capId, j) {
|
|
14822
|
+
if (capId !== "issues" || !j || j.reason !== "ip-restricted") return;
|
|
14823
|
+
if (!SITE.intake || LIVE_BACKLOG_ROUTE !== "runner") return;
|
|
14824
|
+
note.appendChild(document.createTextNode(" "));
|
|
14825
|
+
note.appendChild(backlogRouteOnButton("切り替える"));
|
|
14826
|
+
}
|
|
14827
|
+
|
|
14828
|
+
/**
|
|
14829
|
+
* リアルタイム同期(Backlog Webhook)の受信の事実。専用 URL が無い案件は null(出さない)。
|
|
14830
|
+
* **ready でないうちは受信実績を読まない**(索引の行が無いのに lastEventAt だけ来ていても信じない)。
|
|
14831
|
+
* **時刻が読めない値は稼働中に数えない。**
|
|
14832
|
+
*/
|
|
14833
|
+
function backlogRealtimeFacts() {
|
|
14834
|
+
var wh = SITE.backlogWebhook;
|
|
14835
|
+
if (!wh || !wh.url || !wh.token) return null;
|
|
14836
|
+
var rt = LIVE_REALTIME;
|
|
14837
|
+
var ready = !!(rt && rt.ready);
|
|
14838
|
+
var receipts = (ready && rt.targets ? rt.targets : []).map(function (t) {
|
|
14839
|
+
return { key: t && t.projectKey ? String(t.projectKey).toLowerCase() : "", ago: t && t.lastEventAt ? rtAgo(t.lastEventAt) : "" };
|
|
14840
|
+
});
|
|
14841
|
+
return {
|
|
14842
|
+
receipts: receipts,
|
|
14843
|
+
anyLive: receipts.some(function (r) { return !!r.ago; }),
|
|
14844
|
+
viaFixedIp: !!(rt && rt.via === "fixed-ip"),
|
|
14845
|
+
};
|
|
14846
|
+
}
|
|
14847
|
+
|
|
14848
|
+
/** プロジェクトキーが同じ行が、いまの一覧にいくつあるか(受信実績はスペースを持たない) */
|
|
14849
|
+
function backlogKeyRowCount(projectKey) {
|
|
14850
|
+
var key = String(projectKey || "").toLowerCase();
|
|
14851
|
+
var n = 0;
|
|
14852
|
+
var rows = BACKLOG_UI.rows || [];
|
|
14853
|
+
for (var i = 0; i < rows.length; i++) if (String(rows[i].projectKey || "").toLowerCase() === key) n++;
|
|
14854
|
+
return n;
|
|
14855
|
+
}
|
|
14856
|
+
|
|
14857
|
+
/** その行のキーの受信時刻(読めるもの)。無ければ空文字 */
|
|
14858
|
+
function backlogRowReceiptAgo(facts, projectKey) {
|
|
14859
|
+
var key = String(projectKey || "").toLowerCase();
|
|
14860
|
+
for (var i = 0; i < facts.receipts.length; i++) {
|
|
14861
|
+
if (facts.receipts[i].key === key && facts.receipts[i].ago) return facts.receipts[i].ago;
|
|
14862
|
+
}
|
|
14863
|
+
return "";
|
|
14864
|
+
}
|
|
14865
|
+
|
|
14866
|
+
/**
|
|
14867
|
+
* 行に足す札(状態ではなく事実)。**ds-gate クラスは付けない**(「.ds-gate は届くか札 1 つ」の見張りを壊さない)。
|
|
14868
|
+
* - 経路札「固定 IP 経由」… 同期経路が固定 IP のとき(案件単位なので全行に出る)
|
|
14869
|
+
* - リアルタイム札「稼働中」… その行のキーの受信実績があるとき
|
|
14870
|
+
* - リアルタイム札「未受信」… **他の行に稼働があって**この行に受信が無いとき(増やした接続先が来ていないことに気づける・#123)
|
|
14871
|
+
* 🔴 **プロジェクトキーが複数の行で重なるときはリアルタイム札を出さない**(どのスペースの受信か決まらない=嘘を言わない)。
|
|
14872
|
+
* 投入の口が無い案件には出さない。
|
|
14873
|
+
*/
|
|
14874
|
+
function appendBacklogChips(row, t) {
|
|
14875
|
+
if (!SITE.intake) return row;
|
|
14876
|
+
if (LIVE_BACKLOG_ROUTE === "fixed-ip") {
|
|
14877
|
+
row.appendChild(el("span", { class: "ds-route-chip", title: "この案件の Backlog は、固定の送信元 IP から読んでいます", text: "固定 IP 経由" }));
|
|
14878
|
+
}
|
|
14879
|
+
var facts = backlogRealtimeFacts();
|
|
14880
|
+
if (!facts || backlogKeyRowCount(t.projectKey) > 1) return row;
|
|
14881
|
+
var ago = backlogRowReceiptAgo(facts, t.projectKey);
|
|
14882
|
+
var title = facts.viaFixedIp || LIVE_BACKLOG_ROUTE === "fixed-ip"
|
|
14883
|
+
? "反映は受信から数分後です"
|
|
14884
|
+
: "反映は受信から約1分後です(1分ごとにまとめて取り込みます)";
|
|
14885
|
+
if (ago) {
|
|
14886
|
+
row.appendChild(el("span", { class: "ds-rt-chip is-live", title: title }, [
|
|
14887
|
+
el("span", { text: "リアルタイム同期:稼働中" }),
|
|
14888
|
+
el("span", { class: "ds-rt-at", text: ago === "たった今" ? "たった今受信" : ago + "に受信" }),
|
|
14889
|
+
]));
|
|
14890
|
+
} else if (facts.anyLive) {
|
|
14891
|
+
row.appendChild(el("span", { class: "ds-rt-chip is-waiting", title: "この接続先の Webhook はまだ届いていません", text: "リアルタイム同期:未受信" }));
|
|
14892
|
+
}
|
|
14893
|
+
return row;
|
|
14894
|
+
}
|
|
14895
|
+
|
|
14502
14896
|
/**
|
|
14503
14897
|
* Backlog のAPIキーを、どのスペースの棚へ入れるか(#138)。
|
|
14504
14898
|
*
|
|
@@ -14869,8 +15263,9 @@ ${calendarAddUrl.toString()}
|
|
|
14869
15263
|
* 🔴 **控えた 「at」 で待つ(🔴2・壁時計を使わない)。** 押した時刻で見ると、
|
|
14870
15264
|
* 端末の時計がずれている人には永久に届かない(または届く前に届いたことになる)。
|
|
14871
15265
|
*/
|
|
14872
|
-
function probeEnterWait(note, key, capId, ref, before, j) {
|
|
15266
|
+
function probeEnterWait(note, key, capId, ref, before, j, reason) {
|
|
14873
15267
|
PROBE_WAITS[key] = {
|
|
15268
|
+
reason: reason || "",
|
|
14874
15269
|
cap: capId, ref: ref,
|
|
14875
15270
|
at: (before && before.at) || "",
|
|
14876
15271
|
startedAt: Date.now(),
|
|
@@ -14885,6 +15280,9 @@ ${calendarAddUrl.toString()}
|
|
|
14885
15280
|
/** 待ちの 1 文(#548)。「同期を開始しました」等、サーバの文言をそのまま出す */
|
|
14886
15281
|
function probeWaitNote(note, wait) {
|
|
14887
15282
|
note.textContent = (wait && wait.message) || "";
|
|
15283
|
+
// IP 制限の確定は[接続を確認]の応答だけ。待ちが持つ reason で、描き直しのたびに[切り替える]を足す
|
|
15284
|
+
//(結果は行を描く側に一本化。textContent で子を消すので二重にならない)
|
|
15285
|
+
if (wait && wait.reason === "ip-restricted") appendBacklogProbeSwitch(note, wait.cap, { reason: wait.reason });
|
|
14888
15286
|
}
|
|
14889
15287
|
|
|
14890
15288
|
/**
|
|
@@ -14937,7 +15335,8 @@ ${calendarAddUrl.toString()}
|
|
|
14937
15335
|
// 語が返らない回=この場では叩けなかった回。Backlog の IP 制限
|
|
14938
15336
|
//(「reason」 が 「ip-restricted」)で夜間と同じ経路へ倒したときと、
|
|
14939
15337
|
// 即答をまだ知らない古い Lambda が相手のとき。どちらも従来どおり待つ
|
|
14940
|
-
|
|
15338
|
+
// IP 制限と確定できるのはこの応答だけ(reason)。[切り替える]は待ちの文を描く側(probeWaitNote)が足す
|
|
15339
|
+
probeEnterWait(note, key, capId, ref, before, j, j.reason);
|
|
14941
15340
|
endBusy();
|
|
14942
15341
|
return;
|
|
14943
15342
|
}
|
|
@@ -15025,7 +15424,35 @@ ${calendarAddUrl.toString()}
|
|
|
15025
15424
|
*/
|
|
15026
15425
|
function backlogPaintSigOf(list) {
|
|
15027
15426
|
return ingestSigOf(list) + "|" + (list || []).map(function (t) { return t && t.pending === true ? "p" : "-"; }).join(",")
|
|
15028
|
-
+ "|" + reachSigOf("issues", (list || []).map(function (t) { return issuesRefOf(t.domain, t.projectKey); }))
|
|
15427
|
+
+ "|" + reachSigOf("issues", (list || []).map(function (t) { return issuesRefOf(t.domain, t.projectKey); }))
|
|
15428
|
+
+ "|" + backlogFactsSigOf();
|
|
15429
|
+
}
|
|
15430
|
+
/**
|
|
15431
|
+
* 行の札・一手が読む**一覧の外の事実**の署名(c-07 の 2 本目・設計レビュー🔴1)。
|
|
15432
|
+
* 同期経路・固定 IP・そのスペースのキーの登録状況・リアルタイムの受信は、接続先の集合や「届くか」と別に変わる。
|
|
15433
|
+
* 混ぜないと、**接続先を足した後(応答の一覧を採った後)や切り替えの後に、行の札・一手が描き直されない**。
|
|
15434
|
+
*/
|
|
15435
|
+
function backlogFactsSigOf() {
|
|
15436
|
+
var secrets = [];
|
|
15437
|
+
if (Array.isArray(LIVE_SECRETS)) {
|
|
15438
|
+
for (var i = 0; i < LIVE_SECRETS.length; i++) {
|
|
15439
|
+
var r = LIVE_SECRETS[i];
|
|
15440
|
+
if (r && r.kind === "backlog") secrets.push((r.space || "") + ":" + r.state);
|
|
15441
|
+
}
|
|
15442
|
+
}
|
|
15443
|
+
var rt = LIVE_REALTIME;
|
|
15444
|
+
var rtRows = rt && rt.ready && Array.isArray(rt.targets)
|
|
15445
|
+
? rt.targets.map(function (t) { return (t && t.projectKey) + "@" + (t && t.lastEventAt); })
|
|
15446
|
+
: [];
|
|
15447
|
+
return JSON.stringify([
|
|
15448
|
+
LIVE_BACKLOG_ROUTE === undefined ? "?" : LIVE_BACKLOG_ROUTE,
|
|
15449
|
+
LIVE_FIXED_IP,
|
|
15450
|
+
secrets,
|
|
15451
|
+
(fleetVal("secretSources") || {}).backlog || "",
|
|
15452
|
+
fleetVal("secretSpaces") || null,
|
|
15453
|
+
rtRows,
|
|
15454
|
+
rt && rt.via || "",
|
|
15455
|
+
]);
|
|
15029
15456
|
}
|
|
15030
15457
|
|
|
15031
15458
|
/**
|
|
@@ -15087,6 +15514,8 @@ ${calendarAddUrl.toString()}
|
|
|
15087
15514
|
var editable = backlogTargetsEditable();
|
|
15088
15515
|
/** 追加・削除の応答で一覧を受け取ったか(受け取っていたら、そちらが正本) */
|
|
15089
15516
|
var adopted = false;
|
|
15517
|
+
/** 「その他」(1 回だけ作る)。paint() は作る前には当て直さない */
|
|
15518
|
+
var moreEl = null;
|
|
15090
15519
|
|
|
15091
15520
|
// **Slack / GitHub / Figma / Drive と同じレコードの行にする(2026-08-21・オーナー指摘)。**
|
|
15092
15521
|
// それまでは「現在の接続先」というラベル付きの独自レイアウト(.ds-current /
|
|
@@ -15124,11 +15553,13 @@ ${calendarAddUrl.toString()}
|
|
|
15124
15553
|
// 理由の1段は**行の最後**(ボタンより後ろ)に置く(appendReachableReason の項)
|
|
15125
15554
|
var issuesRef = issuesRefOf(t.domain, t.projectKey);
|
|
15126
15555
|
appendReachableChip(row, "issues", issuesRef);
|
|
15556
|
+
// 札(事実): 固定 IP 経由・リアルタイム同期の稼働中/未受信(c-07 の 2 本目)。届くか札・確認時刻の後、最初のボタンの前
|
|
15557
|
+
appendBacklogChips(row, t);
|
|
15127
15558
|
// 「接続を確認」(#523 S3)。**外す権限が無くても押せる**——読むだけの操作で、
|
|
15128
15559
|
// 押せる人を絞らない(決定7)。ボタンの並びは Slack の行と同じ(外すボタンの後ろ)
|
|
15129
15560
|
if (!editable) {
|
|
15130
15561
|
appendDispatchProbe(row, "issues", issuesRef);
|
|
15131
|
-
return
|
|
15562
|
+
return appendBacklogRowHelp(row, t, issuesRef);
|
|
15132
15563
|
}
|
|
15133
15564
|
var btn = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: "この接続先を外す" });
|
|
15134
15565
|
btn.addEventListener("click", async function () {
|
|
@@ -15150,7 +15581,8 @@ ${calendarAddUrl.toString()}
|
|
|
15150
15581
|
});
|
|
15151
15582
|
row.appendChild(btn);
|
|
15152
15583
|
appendDispatchProbe(row, "issues", issuesRef);
|
|
15153
|
-
|
|
15584
|
+
// 🔴 **一手は行の最後(ボタンより後ろ)に 1 段だけ。** 読めている行・測っていない行には何も出さない
|
|
15585
|
+
return appendBacklogRowHelp(row, t, issuesRef);
|
|
15154
15586
|
}
|
|
15155
15587
|
|
|
15156
15588
|
/**
|
|
@@ -15176,6 +15608,8 @@ ${calendarAddUrl.toString()}
|
|
|
15176
15608
|
|
|
15177
15609
|
function paint() {
|
|
15178
15610
|
while (listEl.firstChild) listEl.removeChild(listEl.firstChild);
|
|
15611
|
+
// 一手・札・鍵カードの見え方が引く行の控え(行を描く前に入れ替える)
|
|
15612
|
+
BACKLOG_UI.rows = targets;
|
|
15179
15613
|
var shown = grouped(targets);
|
|
15180
15614
|
// そのスペースの**最後の行**を覚えておく(鍵の欄はそこへ入れる)
|
|
15181
15615
|
var lastRowOf = {};
|
|
@@ -15211,6 +15645,8 @@ ${calendarAddUrl.toString()}
|
|
|
15211
15645
|
// 足した瞬間に欄が現れ、最後の1件を外した瞬間に消えるのはここが効いているから。
|
|
15212
15646
|
// 節ごとの差し替え(refreshDataSources)はモーダルが開いている間は何もしない
|
|
15213
15647
|
syncKeyCardVisibility();
|
|
15648
|
+
// 一覧の下の一手・「その他」も、行と同じ材料で当て直す(まだ作っていない最初の 1 回は飛ばす)
|
|
15649
|
+
if (typeof refreshExtras === "function" && moreEl) refreshExtras();
|
|
15214
15650
|
}
|
|
15215
15651
|
|
|
15216
15652
|
/**
|
|
@@ -15357,8 +15793,16 @@ ${calendarAddUrl.toString()}
|
|
|
15357
15793
|
* 入力中の鍵と「保存中…」は保たれる。
|
|
15358
15794
|
*/
|
|
15359
15795
|
box.__rebuild = function () {
|
|
15360
|
-
// 🔴 応答に添えられた一覧(adoptTargets
|
|
15361
|
-
|
|
15796
|
+
// 🔴 応答に添えられた一覧(adoptTargets)のほうが新しい。**接続先の集合は上書きしない。**
|
|
15797
|
+
// ただし行の札・一手が読む外の事実(経路・固定 IP・キーの登録状況・リアルタイムの受信)は
|
|
15798
|
+
// 集合と別に変わるので、署名が変わったときは**行だけ**描き直す(c-07 の 2 本目・設計レビュー🔴1)
|
|
15799
|
+
if (adopted) {
|
|
15800
|
+
var sigAdopted = backlogPaintSigOf(targets);
|
|
15801
|
+
if (sigAdopted === lastIngestSig) return;
|
|
15802
|
+
lastIngestSig = sigAdopted;
|
|
15803
|
+
paint();
|
|
15804
|
+
return;
|
|
15805
|
+
}
|
|
15362
15806
|
var next = backlogTargetsNow(internalSourceOf(REGISTERED_CAP_NAMES.issues));
|
|
15363
15807
|
// 🔴 **変化が無いと言えるのは、接続先の集合と取り込みの状態が両方同じときだけ(段C)。**
|
|
15364
15808
|
// 世代だけで判断すると、**応答を待たずにモーダルを開いた人には取り込みが永久に出ない**
|
|
@@ -15371,6 +15815,16 @@ ${calendarAddUrl.toString()}
|
|
|
15371
15815
|
ensureKeySlots();
|
|
15372
15816
|
paint();
|
|
15373
15817
|
};
|
|
15818
|
+
/**
|
|
15819
|
+
* 行だけを描き直す口(切り替え・キー保存の後に呼ぶ)。**接続先の集合は触らない。**
|
|
15820
|
+
* 応答の一覧を採った後でも、行の札・一手は最新の事実で描かれる(lastIngestSig も揃える)
|
|
15821
|
+
*/
|
|
15822
|
+
box.__repaintRows = function () {
|
|
15823
|
+
lastIngestSig = backlogPaintSigOf(targets);
|
|
15824
|
+
paint();
|
|
15825
|
+
};
|
|
15826
|
+
BACKLOG_UI.repaintRows = box.__repaintRows;
|
|
15827
|
+
BACKLOG_UI.msg = msg;
|
|
15374
15828
|
// 接続先がまだ無い案件の受け皿は、0件の1行の直後に置く(行が無いので中へ入れられない)
|
|
15375
15829
|
if (KEY_SLOTS.backlog[""]) box.appendChild(KEY_SLOTS.backlog[""]);
|
|
15376
15830
|
if (editable) {
|
|
@@ -15380,27 +15834,11 @@ ${calendarAddUrl.toString()}
|
|
|
15380
15834
|
title: "+ 接続先を追加",
|
|
15381
15835
|
placeholder: "https://<スペース>.backlog.jp/projects/<プロジェクトキー>",
|
|
15382
15836
|
requiredNote: "BacklogのURLを入力してください(プロジェクトキーが入った形。数字だけのURLは受け付けません)。",
|
|
15383
|
-
//
|
|
15384
|
-
//
|
|
15385
|
-
//
|
|
15386
|
-
//
|
|
15387
|
-
|
|
15388
|
-
//
|
|
15389
|
-
// ## 4行 → 2行に圧縮した(2026-08-21・オーナー指摘「何も変わってない気がする」)
|
|
15390
|
-
//
|
|
15391
|
-
// **この欄は URL を貼るだけ**なので、4行の説明が読む負担に見合わない。
|
|
15392
|
-
// ただし**落としてよいのは1行目だけ**だった(欄を見れば分かる内容)。残り3つは
|
|
15393
|
-
// ここ以外に置き場が無いので、**消さずに2行へ畳んでいる**:
|
|
15394
|
-
// - 「起票先は変わりません」… 追加の確認をやめたとき(2026-08-20)に
|
|
15395
|
-
// ここへ移してきたもの。落とすと知る手段が無くなる
|
|
15396
|
-
// - 「スペースごとに別のキー」… 鍵の欄からは読めない。1スペースの案件は
|
|
15397
|
-
// 行が1本しか無いので、これから2つ目を足す人に「別の鍵が要る」が届かない
|
|
15398
|
-
// - 「案件専用URLの登録」… 救出先の realtimeSyncCard() は稼働中の案件では
|
|
15399
|
-
// 手順一式を <details> に畳むので、**いちばん要る人に対して閉じている**
|
|
15400
|
-
hint: [
|
|
15401
|
-
"URL を貼ると同期先が増えます。ビューアからの起票先は変わりません(スペースごとに別のAPIキーが要ります)。",
|
|
15402
|
-
"リアルタイム同期を使うなら、追加した Backlog プロジェクトにも、このプロジェクト専用URLの登録が要ります(同じURLを使います)。",
|
|
15403
|
-
],
|
|
15837
|
+
// 🔴 **追加の欄は URL の入力と[追加]だけ(c-07 の 2 本目)。** 手順の文は置かない(hint を渡さない)。
|
|
15838
|
+
// 外した情報の行き先:
|
|
15839
|
+
// - 「起票先は変わりません」「毎晩同期します」「外すには」… 上部の説明文(openCapabilityModal)
|
|
15840
|
+
// - 「スペースごとに別のAPIキー」… 足した行の一手「◯◯ の API キーを入れてください」と、「その他」のスペースごとの行
|
|
15841
|
+
// - 「専用 URL の登録が要ります」… 追加した行のリアルタイム札(未受信)と、一覧の下の一手[設定する]
|
|
15404
15842
|
build: function (url) {
|
|
15405
15843
|
return { op: "backlog-target-add", url: url, baseGeneration: targetsGenerationOf(targets) };
|
|
15406
15844
|
},
|
|
@@ -15409,7 +15847,86 @@ ${calendarAddUrl.toString()}
|
|
|
15409
15847
|
}));
|
|
15410
15848
|
}
|
|
15411
15849
|
box.appendChild(msg);
|
|
15850
|
+
|
|
15851
|
+
// 投入の口(intake)が無い案件には、一手も「その他」も出さない(押せない案内を置かない)
|
|
15852
|
+
if (editable) {
|
|
15853
|
+
// ---- 一覧の下の一手(リアルタイム同期)と、ふだん見えない「その他」(c-07 の 2 本目)----
|
|
15854
|
+
//
|
|
15855
|
+
// 🔴 **リアルタイム同期は大きな箱を常設しない。** 稼働中は行の札だけ。受信が無い・一部だけのときだけ、
|
|
15856
|
+
// 一覧の下に 1 行の一手([設定する]で手順と専用 URL が開く)。全部稼働中なら、URL の再確認は「その他」に置く。
|
|
15857
|
+
// 🔴 **「その他」は 1 回だけ作る**(結果の帯・開閉を描き直しで消さない)。中の一覧は liveList で追いつかせる。
|
|
15858
|
+
var rtOpen = false;
|
|
15859
|
+
var rtBox = liveList("backlog-realtime", function () {
|
|
15860
|
+
var st = backlogRealtimeState();
|
|
15861
|
+
return JSON.stringify([st ? [st.total, st.live] : null, backlogFactsSigOf()]);
|
|
15862
|
+
}, function (b) {
|
|
15863
|
+
var st = backlogRealtimeState();
|
|
15864
|
+
// 稼働中の行が無い/一部だけのときだけ。全部稼働中は何も出さない(再確認は「その他」)
|
|
15865
|
+
if (!st || st.live === st.total) return;
|
|
15866
|
+
var text = st.live === 0
|
|
15867
|
+
? "リアルタイム同期(任意)を使うと、Backlog の変更をすぐ取り込めます。"
|
|
15868
|
+
: "まだ受信していない接続先があります(Backlog プロジェクトごとに Webhook の登録が要ります)。";
|
|
15869
|
+
var setup = el("div", { class: "rt-card rt-setup" }, realtimeSetupNodes());
|
|
15870
|
+
setup.hidden = !rtOpen;
|
|
15871
|
+
var openBtn = el("button", { class: "ds-toggle-btn ds-act-inline", type: "button", text: "設定する", "aria-expanded": rtOpen ? "true" : "false" });
|
|
15872
|
+
openBtn.addEventListener("click", function () {
|
|
15873
|
+
rtOpen = !rtOpen;
|
|
15874
|
+
setup.hidden = !rtOpen;
|
|
15875
|
+
openBtn.setAttribute("aria-expanded", rtOpen ? "true" : "false");
|
|
15876
|
+
});
|
|
15877
|
+
b.appendChild(el("p", { class: "ds-rt-help" }, [document.createTextNode(text), openBtn]));
|
|
15878
|
+
b.appendChild(setup);
|
|
15879
|
+
});
|
|
15880
|
+
box.appendChild(rtBox);
|
|
15881
|
+
|
|
15882
|
+
var routeWrap = backlogRouteSwitch();
|
|
15883
|
+
var keyRows = liveList("backlog-key-rows", function () {
|
|
15884
|
+
var ds = backlogDomainsNow();
|
|
15885
|
+
return JSON.stringify(ds.map(function (d) { return [d, secretStateOf("backlog", d, ds.length).known]; }));
|
|
15886
|
+
}, function (b) {
|
|
15887
|
+
var ds = backlogDomainsNow();
|
|
15888
|
+
ds.forEach(function (d) {
|
|
15889
|
+
var st = secretStateOf("backlog", d, ds.length);
|
|
15890
|
+
var word = st.known === "yes" ? "登録済みです" : st.known === "no" ? "未登録です" : "確認中です";
|
|
15891
|
+
// ボタンはそのスペースの鍵カードを開く(受け皿が無ければ作らない)
|
|
15892
|
+
b.appendChild(el("p", { class: "ds-more-key" }, [
|
|
15893
|
+
el("span", { text: d + ":API キーは" + word }),
|
|
15894
|
+
backlogKeyJump(d, st.known === "no" ? "入れる" : "入れ直す"),
|
|
15895
|
+
]));
|
|
15896
|
+
});
|
|
15897
|
+
});
|
|
15898
|
+
var rtMore = liveList("backlog-realtime-more", function () {
|
|
15899
|
+
var st = backlogRealtimeState();
|
|
15900
|
+
return JSON.stringify([st ? [st.total, st.live] : null, backlogFactsSigOf()]);
|
|
15901
|
+
}, function (b) {
|
|
15902
|
+
var st = backlogRealtimeState();
|
|
15903
|
+
if (!st || st.live !== st.total) return;
|
|
15904
|
+
var fold = el("details", { class: "rt-card rt-steps-fold" }, [
|
|
15905
|
+
el("summary", { text: "リアルタイム同期(任意)の設定手順と専用 URL(再確認・張り替え用)" }),
|
|
15906
|
+
]);
|
|
15907
|
+
realtimeSetupNodes().forEach(function (n) { fold.appendChild(n); });
|
|
15908
|
+
b.appendChild(fold);
|
|
15909
|
+
});
|
|
15910
|
+
moreEl = el("details", { class: "ds-more" }, [
|
|
15911
|
+
el("summary", { text: "その他" }),
|
|
15912
|
+
routeWrap,
|
|
15913
|
+
keyRows,
|
|
15914
|
+
rtMore,
|
|
15915
|
+
]);
|
|
15916
|
+
box.appendChild(moreEl);
|
|
15917
|
+
refreshExtras();
|
|
15918
|
+
}
|
|
15412
15919
|
return box;
|
|
15920
|
+
|
|
15921
|
+
/** 「その他」の出し入れと、中の一覧の追いつかせ。**出すのは接続先が 1 件以上、または経路が固定 IP のとき**(ON は 0 件でも戻せる) */
|
|
15922
|
+
function refreshExtras() {
|
|
15923
|
+
if (!moreEl) return;
|
|
15924
|
+
moreEl.hidden = !(keyTargetCount("課題管理") > 0 || LIVE_BACKLOG_ROUTE === "fixed-ip");
|
|
15925
|
+
rtBox.__rebuild();
|
|
15926
|
+
if (routeWrap.__live) routeWrap.__live.__rebuild();
|
|
15927
|
+
keyRows.__rebuild();
|
|
15928
|
+
rtMore.__rebuild();
|
|
15929
|
+
}
|
|
15413
15930
|
}
|
|
15414
15931
|
|
|
15415
15932
|
/** 接続先を外すときの確認。**最後の1件は、止まるものを名指しで伝える** */
|
|
@@ -15524,20 +16041,13 @@ ${calendarAddUrl.toString()}
|
|
|
15524
16041
|
// 接続先の正本(issues-config.json)は雛形から作られるので、立ち上げ直後の案件にも
|
|
15525
16042
|
// 実在する。同期ミラーの有無で門前払いすると、**新規案件がこの画面から
|
|
15526
16043
|
// Backlog に繋げない**——設定UIを置いている意味が無くなる
|
|
16044
|
+
// 🔴 **同期経路のスイッチ・リアルタイム同期は、節の中(一覧の下の一手と「その他」)に移した**
|
|
16045
|
+
// (c-07 の 2 本目)。ここでは節だけを置く
|
|
15527
16046
|
box.appendChild(backlogTargetsSection(s));
|
|
15528
16047
|
// **「この画面からは接続先を変更できません」の枝は撤去した(#516)。** ゲートが
|
|
15529
16048
|
// 「intake があるか」になり、この関数は intake の無い案件では上で return しているので、
|
|
15530
16049
|
// 到達しない枝だった(到達不能な枝は変異検証の網から漏れる)。
|
|
15531
16050
|
//
|
|
15532
|
-
// **接続先の下・リアルタイム同期の上に、同期経路のスイッチを置く(#516・設計 v2 §6)。**
|
|
15533
|
-
// #497 の「固定 IP 経由で同期できます(切り替えは運用側)」の案内と画面の IP 定数は
|
|
15534
|
-
// これに置き換わった——切り替えは担当者が自分で押す。
|
|
15535
|
-
box.appendChild(backlogRouteSwitch());
|
|
15536
|
-
// **接続先の下に置く。** リアルタイム同期は「どのプロジェクトを見るか」が
|
|
15537
|
-
// 決まった後の話で、順序が逆だと、接続先を設定していない案件が先に
|
|
15538
|
-
// Webhook を貼りに行く(貼っても届かない)
|
|
15539
|
-
var rt = realtimeSyncCard();
|
|
15540
|
-
if (rt) box.appendChild(rt);
|
|
15541
16051
|
used = true;
|
|
15542
16052
|
}
|
|
15543
16053
|
|
|
@@ -17637,51 +18147,24 @@ ${calendarAddUrl.toString()}
|
|
|
17637
18147
|
}
|
|
17638
18148
|
|
|
17639
18149
|
/**
|
|
17640
|
-
* リアルタイム同期(Backlog Webhook
|
|
17641
|
-
*
|
|
17642
|
-
*
|
|
17643
|
-
* realtime に委ねる——画面が持っている静的データからは「ルーティング索引に自分の行が
|
|
17644
|
-
* あるか」が分からないし、2箇所で判定すると片方だけがずれる。
|
|
17645
|
-
* - via: "fixed-ip" … 固定 IP 経由で同期する案件(#516・#503 設計 v3 §6)。手順は同じ。
|
|
17646
|
-
* 反映が「約1分」ではなく「数分」になることだけを言う
|
|
17647
|
-
* - ready: false … 索引にまだ行が無い。設定しても届かないので手順を出さない
|
|
17648
|
-
* 「supported: false(固定 IP は未対応)」の枝は撤去した——固定 IP 案件でも Webhook を
|
|
17649
|
-
* 受ける(分類 A2)。その値を返す古い Lambda に当たっても、手順を出す側に倒す
|
|
18150
|
+
* リアルタイム同期(Backlog Webhook)の設定手順ひとそろい(#113)。**大きな箱では出さない**(c-07 の 2 本目):
|
|
18151
|
+
* 稼働中は行の札だけ。受信が無い・一部だけのときの一手[設定する]の中と、全部稼働中のときの「その他」の中に、
|
|
18152
|
+
* この同じ部品を置く。**URL が無い案件には呼ばない**(backlogRealtimeFacts が null)。
|
|
17650
18153
|
*
|
|
18154
|
+
* 🔴 **手順の部品は変えない**(貼り付け先・URL・コピー・チェックする 10 種・秘密の注意・反映の目安)。
|
|
18155
|
+
* チェック名は受信側が処理する 10 種を全部並べる(「等」で省くと、課題の 3 つだけにチェックして
|
|
18156
|
+
* 「設定した」と思い、Wiki とドキュメントの更新が夜間まで届かない)。増減するときは受信側
|
|
18157
|
+
*(backlog-webhook/logic.mjs の HANDLED_TYPES)と揃える——ずれると realtime-sync-card.test.mjs が赤くなる。
|
|
17651
18158
|
* **URLに載せるトークンは案件別**(SITE.backlogWebhook)。艦隊共通トークンを載せると、
|
|
17652
|
-
*
|
|
17653
|
-
*
|
|
17654
|
-
* **既に回っている案件には、手順より先に「動いている」ことを出す(#123)。** 設定手順は
|
|
17655
|
-
* まだ繋いでいない人のためのもので、繋ぎ終わった案件の担当者が知りたいのは
|
|
17656
|
-
* 「ちゃんと届いているか」。手順は畳んで残す(URLの再確認・張り替えに要る)。
|
|
18159
|
+
* この画面を見られる人が全案件ぶんの偽イベントを投げられる。
|
|
18160
|
+
* 固定 IP 経由か(#516)は **op=state の realtime.via**(受信を同期するのはサーバ側の索引が決める)で言う。
|
|
17657
18161
|
*/
|
|
17658
|
-
function
|
|
18162
|
+
function realtimeSetupNodes() {
|
|
17659
18163
|
var rt = LIVE_REALTIME;
|
|
17660
|
-
// 固定 IP 経由か(#516)。**経路の正本は op=state の realtime.via**——スイッチの状態
|
|
17661
|
-
//(LIVE_BACKLOG_ROUTE)から引かない。あちらは押した直後に先に変わるが、受信したイベントを
|
|
17662
|
-
// どちらが同期するかはサーバ側の索引が決めるので、同じ応答の値で言う
|
|
17663
18164
|
var viaFixedIp = !!(rt && rt.via === "fixed-ip");
|
|
17664
18165
|
var wh = SITE.backlogWebhook;
|
|
17665
|
-
|
|
17666
|
-
if (!wh || !wh.url || !wh.token) return null;
|
|
17667
|
-
// **索引に行があるか(ready)でカードごと隠さない(2026-08-24 オーナー決定)。**
|
|
17668
|
-
// それまでは「手順どおり設定すれば実際に効く案件だけに出す」としていたが、
|
|
17669
|
-
// ready の材料(ルーティング索引の行)は**設定を始める前には揃わない**ので、
|
|
17670
|
-
// これから設定する人ほど手順に辿り着けなかった。索引がまだ追いついていない案件も、
|
|
17671
|
-
// 取得前・取得失敗(rt が null)も、手順は出す——URLは案件専用で、先に貼っておいても
|
|
17672
|
-
// 困らない。ready のときだけ受信実績の行を足す
|
|
17673
|
-
var ready = !!(rt && rt.ready);
|
|
18166
|
+
if (!wh || !wh.url || !wh.token) return [];
|
|
17674
18167
|
var url = wh.url + (wh.url.indexOf("?") === -1 ? "?" : "&") + "t=" + encodeURIComponent(wh.token);
|
|
17675
|
-
// 接続先ごとの受信実績(#123)。**配列で受ける**——1案件=複数 Backlog プロジェクトは
|
|
17676
|
-
// 確定した将来要件(#107)で、今日は必ず1件。**時刻が読めない値は稼働中に数えない**
|
|
17677
|
-
// ——「最終受信: NaN分前」を出すくらいなら、未設定と同じ手順の画面のほうがまだ読める
|
|
17678
|
-
// **ready でないうちは受信実績を読まない。** ready の材料は索引の行(=自リポ宛だと
|
|
17679
|
-
// 確かめられた行)で、それが無いのに lastEventAt だけ来ている応答を信じると、
|
|
17680
|
-
// **届いていない案件に「✓ 稼働中」を出す**——一番出してはいけない嘘になる
|
|
17681
|
-
var targets = (ready && rt.targets ? rt.targets : []).map(function (t) {
|
|
17682
|
-
return { projectKey: t && t.projectKey ? String(t.projectKey) : "", ago: t && t.lastEventAt ? rtAgo(t.lastEventAt) : "" };
|
|
17683
|
-
});
|
|
17684
|
-
var live = targets.some(function (t) { return t.ago; });
|
|
17685
18168
|
|
|
17686
18169
|
// URLは**選択できる形**で出す(コピーボタンが使えない環境でも手で取れるように)
|
|
17687
18170
|
var field = el("input", { class: "sec-input rt-url", type: "text", readonly: "readonly", spellcheck: "false" });
|
|
@@ -17708,16 +18191,10 @@ ${calendarAddUrl.toString()}
|
|
|
17708
18191
|
} catch (e) { fallback(); }
|
|
17709
18192
|
});
|
|
17710
18193
|
|
|
17711
|
-
|
|
17712
|
-
var steps = [
|
|
18194
|
+
return [
|
|
17713
18195
|
el("ol", { class: "rt-steps" }, [
|
|
17714
18196
|
el("li", { text: "Backlog のプロジェクト設定 → インテグレーション → Webhook を開く" }),
|
|
17715
18197
|
el("li", { text: "「WebHook URL」に、下の(このプロジェクト専用の)URL を貼り付ける" }),
|
|
17716
|
-
// **チェック名は受信側が処理する10種を全部並べる。** 「等」で省くと、担当者は
|
|
17717
|
-
// 課題の3つだけにチェックして「設定した」と思い、Wikiとドキュメントの更新は
|
|
17718
|
-
// 夜間まで届かない(画面は稼働中と出るので、欠けていることに気づけない)。
|
|
17719
|
-
// ここを増減するときは受信側(backlog-webhook/logic.mjs の HANDLED_TYPES)と
|
|
17720
|
-
// 揃える——ずれると realtime-sync-card.test.mjs が赤くなる。
|
|
17721
18198
|
// **「他はチェック不要」とは言い切らない。** 未対応の type(ドキュメントの更新等)は
|
|
17722
18199
|
// 「無視した」ログから見つける設計なので、外させると発見の経路が塞がる
|
|
17723
18200
|
el("li", { text: "通知イベントで次の10個にチェックを入れて保存する(他のイベントはチェックしても取り込まれません)" }, [
|
|
@@ -17730,72 +18207,26 @@ ${calendarAddUrl.toString()}
|
|
|
17730
18207
|
]),
|
|
17731
18208
|
el("div", { class: "rt-url-row" }, [field, copyBtn]),
|
|
17732
18209
|
msg,
|
|
17733
|
-
// **この画面は顧客も見る。** URL
|
|
18210
|
+
// **この画面は顧客も見る。** URLに秘密が入っていることと、その扱いを書いておく(秘密の扱いの文は縮めない)
|
|
17734
18211
|
el("p", { class: "sec-note", text: "このURLはこのプロジェクト専用です(他のプロジェクトには届きません)。秘密のトークンを含むので、貼り付け先は、いま設定している Backlog プロジェクトの設定欄だけにしてください。" }),
|
|
17735
|
-
// 固定 IP 経由の案件は反映が「約1
|
|
17736
|
-
// **同じ行を差し替える**——2 行に分けると「約1分」と「数分」が並んで矛盾する
|
|
18212
|
+
// 固定 IP 経由の案件は反映が「約1分」にならない。**同じ行を差し替える**(2 行に分けると矛盾する)
|
|
17737
18213
|
el("p", { class: "sec-how", text: viaFixedIp
|
|
17738
18214
|
? "固定 IP 経由で同期します(反映は受信から数分後です)。初回は上の接続先の設定と、夜間同期が一度済んでいることが前提になります。"
|
|
17739
18215
|
: "反映は受信から約1分後です(1分ごとのミニバッチでまとめて取り込みます)。初回は上の接続先の設定と、夜間同期が一度済んでいることが前提になります。" }),
|
|
17740
18216
|
];
|
|
18217
|
+
}
|
|
17741
18218
|
|
|
17742
|
-
|
|
17743
|
-
|
|
17744
|
-
|
|
17745
|
-
|
|
17746
|
-
|
|
17747
|
-
|
|
17748
|
-
|
|
17749
|
-
|
|
17750
|
-
|
|
17751
|
-
|
|
17752
|
-
|
|
17753
|
-
var label = targets.length > 1 && t.projectKey ? t.projectKey + ": " : "";
|
|
17754
|
-
// 受信実績がまだ無い接続先も**黙って落とさない**。1つでも動いていれば
|
|
17755
|
-
// カードは稼働中の形になるので、落とすと「増やした接続先が来ていない」に気づけない
|
|
17756
|
-
return el("p", {
|
|
17757
|
-
class: t.ago ? "rt-live-line" : "rt-live-line is-waiting",
|
|
17758
|
-
text: t.ago ? label + "最終受信: " + t.ago : label + "まだ受信していません",
|
|
17759
|
-
});
|
|
17760
|
-
}));
|
|
17761
|
-
return el("div", { class: "ops-card rt-card is-live" }, [
|
|
17762
|
-
el("div", { class: "ops-card-head" }, [
|
|
17763
|
-
el("span", { class: "ops-card-icon", text: "⚡" }),
|
|
17764
|
-
// **「(任意)」を付ける(2026-08-21・オーナー指摘「リアルタイム同期は
|
|
17765
|
-
// オプションなので、一番下に配置してほしい」)。** 設定しなくても毎晩の同期で
|
|
17766
|
-
// 反映されるので、必須の手順と並べると「まだ終わっていない」と読まれる。
|
|
17767
|
-
// ⚠ **改名箇所は2つある**(稼働中の枝と未設定の枝)。片方だけ直すと、
|
|
17768
|
-
// **これから設定する人にだけ「(任意)」が付かない**=一番読ませたい人に届かない
|
|
17769
|
-
el("h3", { class: "ops-card-title", text: "リアルタイム同期(任意)" }),
|
|
17770
|
-
el("span", { class: "rt-live-badge", text: "✓ 稼働中" }),
|
|
17771
|
-
]),
|
|
17772
|
-
lines,
|
|
17773
|
-
// 固定 IP 経由の案件は「すぐ」ではなく「数分」(手順は畳んであるので、ここでも言う)
|
|
17774
|
-
el("p", { class: "ops-card-desc", text: viaFixedIp
|
|
17775
|
-
? "Backlog の変更を固定 IP 経由で取り込んでいます(反映まで数分・毎晩の同期を待ちません)。設定は済んでいるので、このままお使いください。"
|
|
17776
|
-
: "Backlog の変更をすぐ取り込んでいます(毎晩の同期を待ちません)。設定は済んでいるので、このままお使いください。" }),
|
|
17777
|
-
det,
|
|
17778
|
-
]);
|
|
17779
|
-
}
|
|
17780
|
-
|
|
17781
|
-
// まだ受け入れ体制が整っていない案件(接続先がこれから・索引が追いついていない)には、
|
|
17782
|
-
// **次の一手だけを1行足す。** 担当者への連絡は促さない——この画面で完結する話だから
|
|
17783
|
-
var head = [
|
|
17784
|
-
el("div", { class: "ops-card-head" }, [
|
|
17785
|
-
el("span", { class: "ops-card-icon", text: "⚡" }),
|
|
17786
|
-
// **稼働中の枝と同じ見出しにする**(上の ⚠ 参照。片方だけ直すと、
|
|
17787
|
-
// これから設定する人にだけ「(任意)」が付かない)
|
|
17788
|
-
el("h3", { class: "ops-card-title", text: "リアルタイム同期(任意)" }),
|
|
17789
|
-
]),
|
|
17790
|
-
el("p", { class: "ops-card-desc", text: "Backlog で課題やコメントが変わったときに、その内容をすぐ取り込みます(毎晩の同期を待たずに反映されます)。Backlog 側で一度だけ設定してください。" }),
|
|
17791
|
-
];
|
|
17792
|
-
if (!ready) {
|
|
17793
|
-
head.push(el("p", {
|
|
17794
|
-
class: "sec-how",
|
|
17795
|
-
text: "上の「接続先」を先に設定してから貼り付けてください。設定した直後は、この画面が追いつくまで少し時間がかかることがあります。",
|
|
17796
|
-
}));
|
|
17797
|
-
}
|
|
17798
|
-
return el("div", { class: "ops-card rt-card" }, head.concat(steps));
|
|
18219
|
+
/**
|
|
18220
|
+
* リアルタイム同期の「いまの様子」。専用 URL が無い案件・投入の口が無い案件・接続先が 0 件のあいだは null(何も出さない)。
|
|
18221
|
+
* total=行の数、live=そのキーの受信実績がある行の数(ready かつ時刻が読めるものだけ)。
|
|
18222
|
+
*/
|
|
18223
|
+
function backlogRealtimeState() {
|
|
18224
|
+
var facts = backlogRealtimeFacts();
|
|
18225
|
+
var rows = BACKLOG_UI.rows || [];
|
|
18226
|
+
if (!facts || !SITE.intake || !rows.length) return null;
|
|
18227
|
+
var live = 0;
|
|
18228
|
+
for (var i = 0; i < rows.length; i++) if (backlogRowReceiptAgo(facts, rows[i].projectKey)) live++;
|
|
18229
|
+
return { facts: facts, total: rows.length, live: live };
|
|
17799
18230
|
}
|
|
17800
18231
|
|
|
17801
18232
|
/**
|
|
@@ -17971,6 +18402,33 @@ ${calendarAddUrl.toString()}
|
|
|
17971
18402
|
return !!((fleetVal("secretSources") || {})[kind]);
|
|
17972
18403
|
}
|
|
17973
18404
|
|
|
18405
|
+
/**
|
|
18406
|
+
* 鍵カードの登録状況の 1 行を、答え(secretStateOf)に合わせる。**作るときと、見せる瞬間の当て直し
|
|
18407
|
+
* (Backlog の行の中のカード・c-07 の 2 本目)が同じこの 1 本を通る**——文言が 2 か所に割れない。
|
|
18408
|
+
* 🔴 **画面に出すのは「登録済み」の 1 語だけ(#841)。** 取得元の値は仕組みの名前
|
|
18409
|
+
*(保管庫のサービス名・旧名の区別)で、読む人が自分の画面で探せない。値に関わらず同じ 1 語にし、
|
|
18410
|
+
* **生の値は data-src に置く**(判定の is-managed は生の値のまま。前方一致で見る=旧名の移行期の取りこぼし防止)。
|
|
18411
|
+
* 🔴 **「登録済み」は「読めている」を意味しない。** 「接続OK」「同期OK」とは書かない。
|
|
18412
|
+
* 🔴 **断定しない(レビュー指摘)。** 黙って消すと、利用者は「入っているのか未登録なのか」も
|
|
18413
|
+
* 「なぜ表示が無いのか」も知る手段が無い。
|
|
18414
|
+
*/
|
|
18415
|
+
function applySecretState(node, st) {
|
|
18416
|
+
if (st.known === "yes") {
|
|
18417
|
+
var src = st.src || "";
|
|
18418
|
+
node.className = "sec-state" + (src.indexOf("Secrets Manager") === 0 ? " is-managed" : "");
|
|
18419
|
+
node.setAttribute("data-src", src);
|
|
18420
|
+
node.textContent = "登録済み";
|
|
18421
|
+
} else if (st.known === "no") {
|
|
18422
|
+
node.className = "sec-state is-none";
|
|
18423
|
+
node.removeAttribute("data-src");
|
|
18424
|
+
node.textContent = "未登録";
|
|
18425
|
+
} else {
|
|
18426
|
+
node.className = "sec-state is-unknown";
|
|
18427
|
+
node.removeAttribute("data-src");
|
|
18428
|
+
node.textContent = "登録状況を確認中です(スペースごとの状況は、次回の自動チェックのあとに表示されます)";
|
|
18429
|
+
}
|
|
18430
|
+
}
|
|
18431
|
+
|
|
17974
18432
|
/**
|
|
17975
18433
|
* @param opts { space, stateSpace, spaceCount, inRow }
|
|
17976
18434
|
* space … 保存時に名乗る Backlog スペース(名乗らないときは空)
|
|
@@ -17983,6 +18441,10 @@ ${calendarAddUrl.toString()}
|
|
|
17983
18441
|
var stateSpace = (opts && opts.stateSpace) || "";
|
|
17984
18442
|
var spaceCount = (opts && opts.spaceCount) || 0;
|
|
17985
18443
|
var inRow = !!(opts && opts.inRow);
|
|
18444
|
+
// 🔴 **Backlog の行の中のカードは新しい見え方(c-07 の 2 本目)。** 見出しを持たず(行の中なので「何の鍵か」は
|
|
18445
|
+
// 一手の文が言う)、ふだんは隠れていて、キーが無いと分かったとき・[入れる]/[入れ直す]を押したとき・
|
|
18446
|
+
// 触り始めた後だけ見える(syncKeyCardVisibility)。旧カード(行の外・intake 無しの案件)と Figma は従来どおり。
|
|
18447
|
+
var compact = kind === "backlog" && inRow;
|
|
17986
18448
|
/**
|
|
17987
18449
|
* 鍵がどこまで効くのか(#229)。**置き場所だけでは伝わらないので、言葉にする。**
|
|
17988
18450
|
*
|
|
@@ -17997,7 +18459,6 @@ ${calendarAddUrl.toString()}
|
|
|
17997
18459
|
// 画面で分かるようにする(接続状況は夜間バッチのスナップショットなので、
|
|
17998
18460
|
// 保存の直後は前日の値のまま。時刻は上の節に出している)。
|
|
17999
18461
|
var st = secretStateOf(kind, stateSpace, spaceCount);
|
|
18000
|
-
var src = st.known === "yes" ? st.src : "";
|
|
18001
18462
|
var cardTitle = meta.title;
|
|
18002
18463
|
// 🔴 **「登録済み」は「読めている」を意味しない。** 鍵が入っていても、そのアカウントから
|
|
18003
18464
|
// プロジェクトが見えなければ同期は失敗する。**「接続OK」「同期OK」とは書かないこと**
|
|
@@ -18011,16 +18472,8 @@ ${calendarAddUrl.toString()}
|
|
|
18011
18472
|
//(保管庫のサービス名・旧名の区別)で、読む人が自分の画面で探せない。値に関わらず
|
|
18012
18473
|
// 同じ 1 語にし、**生の値は data-src に置く**(判定の is-managed は生の値のまま)。
|
|
18013
18474
|
// 知らない値が来ても生で出さない——project-wording.test.mjs が画面の語を見張る
|
|
18014
|
-
var state =
|
|
18015
|
-
|
|
18016
|
-
"data-src": src,
|
|
18017
|
-
text: "登録済み" })
|
|
18018
|
-
: st.known === "no"
|
|
18019
|
-
? el("p", { class: "sec-state is-none", text: "未登録" })
|
|
18020
|
-
// **断定しない(レビュー指摘)。** 黙って消すと、利用者は
|
|
18021
|
-
// 「入っているのか未登録なのか」も「なぜ表示が無いのか」も知る手段が無い
|
|
18022
|
-
: el("p", { class: "sec-state is-unknown",
|
|
18023
|
-
text: "登録状況を確認中です(スペースごとの状況は、次回の自動チェックのあとに表示されます)" });
|
|
18475
|
+
var state = el("p", { class: "sec-state" });
|
|
18476
|
+
applySecretState(state, st);
|
|
18024
18477
|
|
|
18025
18478
|
var input = el("input", { class: "sec-input", type: "password", placeholder: "トークンを貼り付け", autocomplete: "off" });
|
|
18026
18479
|
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "保存" });
|
|
@@ -18064,6 +18517,12 @@ ${calendarAddUrl.toString()}
|
|
|
18064
18517
|
// 入れた値は画面に残さない(肩越しに見えるのを防ぐ)
|
|
18065
18518
|
input.value = "";
|
|
18066
18519
|
endBusy();
|
|
18520
|
+
// 🔴 **Backlog の行の中のカードは、保存したら行の一手を当て直す**(「API キーを入れてください」を
|
|
18521
|
+
// 登録状況の正本に合わせて消す・c-07 の 2 本目)。正本(secretsNow)は取り直しで届く
|
|
18522
|
+
if (compact) {
|
|
18523
|
+
if (typeof BACKLOG_UI.repaintRows === "function") BACKLOG_UI.repaintRows();
|
|
18524
|
+
loadLiveFleet(true);
|
|
18525
|
+
}
|
|
18067
18526
|
} catch (e) {
|
|
18068
18527
|
msg.className = "ds-toggle-msg is-error";
|
|
18069
18528
|
msg.textContent = "✗ 通信に失敗しました。";
|
|
@@ -18105,10 +18564,14 @@ ${calendarAddUrl.toString()}
|
|
|
18105
18564
|
// input のノードを直接触るし、テストも card.querySelector("input.sec-input") で掴む。
|
|
18106
18565
|
// ⚠ **<summary> は button ではない**ので、「保存」を掴む既存の取り方は変わらない。
|
|
18107
18566
|
var inputFold = el("details", { class: "sec-input-fold" }, [
|
|
18108
|
-
|
|
18567
|
+
// Backlog の行の中のカードは、一手の語([入れる]・[入れ直す])と揃える。Figma は「登録する」のまま
|
|
18568
|
+
el("summary", { class: "sec-input-summary", text: st.known === "yes" ? "入れ直す" : (compact ? "入れる" : "登録する") }),
|
|
18109
18569
|
]);
|
|
18110
18570
|
inputFold.appendChild(input);
|
|
18111
18571
|
inputFold.appendChild(el("div", { class: "ds-toggle" }, [btn, msg]));
|
|
18572
|
+
// 🔴 **値の扱いの 1 文は、開いた中に残す**(顧客に鍵の貼り付けを求める画面でいちばん落としてはいけない説明・#101)。
|
|
18573
|
+
// 短くした版(行の中のカード)。「他のプロジェクトには影響しません」は仕組みの事実で、利用者が選ぶものではないので外す
|
|
18574
|
+
if (compact) inputFold.appendChild(el("p", { class: "sec-note", text: "入れた値は保存だけでき、あとから表示されません。" }));
|
|
18112
18575
|
|
|
18113
18576
|
// ライブ取得後に状態の行だけを差し替えるための目印(カードごと作り直すと入力が消える)
|
|
18114
18577
|
//
|
|
@@ -18119,8 +18582,10 @@ ${calendarAddUrl.toString()}
|
|
|
18119
18582
|
// 押すまで見えないと成立しない
|
|
18120
18583
|
// - .sec-preferred … 「誰のアカウントで発行するのか」を**手順より先に**伝える行。
|
|
18121
18584
|
// 未登録のときだけ出る=これから貼る人にだけ出るので、畳むと存在理由が消える
|
|
18585
|
+
var preferred = meta.selfServe ? el("p", { class: "sec-preferred", text: meta.selfServe.text }) : null;
|
|
18122
18586
|
var card = el("div", { class: "ops-card" + (inRow ? " ds-keycard" : ""), "data-secret-kind": kind }, [
|
|
18123
|
-
|
|
18587
|
+
// 行の中の Backlog カードは見出しを持たない(何の鍵かは一手の文が言う)
|
|
18588
|
+
compact ? null : el("div", { class: "ops-card-head" }, [
|
|
18124
18589
|
el("span", { class: "ops-card-icon", text: meta.icon }),
|
|
18125
18590
|
el("h3", { class: "ops-card-title", text: cardTitle }),
|
|
18126
18591
|
]),
|
|
@@ -18134,19 +18599,39 @@ ${calendarAddUrl.toString()}
|
|
|
18134
18599
|
// **未登録のときだけ、先に試すべき手を出す。** 既に動いているカードに
|
|
18135
18600
|
// 「まずこれをやれ」と出すと、直す必要が無いものを触らせることになる。
|
|
18136
18601
|
// **「確認中」にも出さない**——入っているかもしれないものを触らせない
|
|
18137
|
-
st.known === "no"
|
|
18602
|
+
st.known === "no" ? preferred : null,
|
|
18138
18603
|
inputFold,
|
|
18139
18604
|
guidanceBox,
|
|
18140
18605
|
// **扱いの説明はカードに持たせる。** 節の見出しの下に置いていたときは、
|
|
18141
18606
|
// #101 でカードだけがモーダルへ移り、説明だけが画面から消えた。
|
|
18142
18607
|
// 顧客にトークンの貼り付けを求める画面で、貼った値がどう扱われるかの
|
|
18143
18608
|
// 説明が落ちるのは、いちばん落としてはいけない種類の欠落
|
|
18144
|
-
|
|
18609
|
+
// 行の中の Backlog カードは、同じ 1 文を開いた中(上の inputFold)に短くして持つ
|
|
18610
|
+
compact ? null : el("p", { class: "sec-note", text: "入れた値はここには表示されません(保存だけできます)。保存先はプロジェクトごとに分かれているので、他のプロジェクトには影響しません。" }),
|
|
18145
18611
|
]);
|
|
18146
18612
|
// **作った鍵カードは1枚残らずここに載せる。** 出し入れ(接続先が0件のときに畳む)は
|
|
18147
18613
|
// DOM 検索ではなくこの控えで回す——カードは能力モーダルの中身で、タイルを押すまで
|
|
18148
|
-
// 文書に入っていないため(syncKeyCardVisibility
|
|
18149
|
-
|
|
18614
|
+
// 文書に入っていないため(syncKeyCardVisibility の項)。
|
|
18615
|
+
// compact(Backlog の行の中)のカードは、見え方の規則(backlogKeyCardWanted)が要る材料を一緒に持つ:
|
|
18616
|
+
// space=状態を引くスペース(行のドメイン。名無しの受け皿は空文字)・forced=一手・「その他」から開かれた・
|
|
18617
|
+
// fold/input/msg=触り始めたかの判定と、入力への移動・refresh=見せる瞬間の状態の当て直し
|
|
18618
|
+
var entry = { kind: kind, el: card, compact: compact, space: stateSpace, forced: false, fold: inputFold, input: input, msg: msg };
|
|
18619
|
+
if (compact) {
|
|
18620
|
+
entry.refresh = function () {
|
|
18621
|
+
// 作ったときの答えは古いことがある(取り直しが後から届く)。見せる瞬間に状態の行と summary と
|
|
18622
|
+
// 「自分で繋げばよい」の 1 行を当て直す(入れた直後に前の答えのまま出さない・#227)
|
|
18623
|
+
var now = secretStateOf(kind, stateSpace, backlogDomainsNow().length);
|
|
18624
|
+
applySecretState(state, now);
|
|
18625
|
+
var sum = inputFold.querySelector(".sec-input-summary");
|
|
18626
|
+
if (sum) sum.textContent = now.known === "yes" ? "入れ直す" : "入れる";
|
|
18627
|
+
if (preferred) {
|
|
18628
|
+
var want = now.known === "no";
|
|
18629
|
+
if (want && !preferred.parentNode) card.insertBefore(preferred, inputFold);
|
|
18630
|
+
else if (!want && preferred.parentNode) preferred.parentNode.removeChild(preferred);
|
|
18631
|
+
}
|
|
18632
|
+
};
|
|
18633
|
+
}
|
|
18634
|
+
KEY_CARDS.push(entry);
|
|
18150
18635
|
return card;
|
|
18151
18636
|
}
|
|
18152
18637
|
|
package/package.json
CHANGED