@takagaki/cortex-decisions-viewer 0.4.47 → 0.4.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.js +28 -1
- package/dist/cli.js +2 -0
- package/dist/render.js +586 -64
- package/package.json +4 -2
package/dist/build.js
CHANGED
|
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.readFleetSources = readFleetSources;
|
|
39
40
|
exports.build = build;
|
|
40
41
|
const node_fs_1 = require("node:fs");
|
|
41
42
|
const path = __importStar(require("node:path"));
|
|
@@ -140,6 +141,10 @@ async function readFleetSources(repoRoot) {
|
|
|
140
141
|
goldState: s.goldState != null ? String(s.goldState) : undefined,
|
|
141
142
|
notify: typeof s.notify === "boolean" ? s.notify : undefined,
|
|
142
143
|
gate: s.gate != null ? String(s.gate) : undefined,
|
|
144
|
+
// どこから来たか(derived / explicit / both)。**削除を出してよいかの判断に要る。**
|
|
145
|
+
// 自動導出されたものは登録ファイルから消しても翌日また現れるので、
|
|
146
|
+
// explicit のときだけ削除ボタンを出す。
|
|
147
|
+
origin: s.origin != null ? String(s.origin) : undefined,
|
|
143
148
|
}))
|
|
144
149
|
: undefined;
|
|
145
150
|
const internalSources = Array.isArray(obj.internalSources)
|
|
@@ -156,6 +161,20 @@ async function readFleetSources(repoRoot) {
|
|
|
156
161
|
: undefined,
|
|
157
162
|
driveSync: typeof s.driveSync === "boolean" ? s.driveSync : undefined,
|
|
158
163
|
enabled: typeof s.enabled === "boolean" ? s.enabled : undefined,
|
|
164
|
+
// 会議・資料の ON/OFF の現在値。**ここに列挙しないと画面へ届かない**
|
|
165
|
+
// (このマッピングは素通しではなく明示の許可リスト)。
|
|
166
|
+
// 生成が古い fleet-status.json には無いので undefined になりうる=「不明」として扱う。
|
|
167
|
+
ingestState: s.ingestState != null ? String(s.ingestState) : undefined,
|
|
168
|
+
driveState: s.driveState != null ? String(s.driveState) : undefined,
|
|
169
|
+
driveFolderCount: typeof s.driveFolderCount === "number" ? s.driveFolderCount : undefined,
|
|
170
|
+
// 登録済みFigmaファイルの一覧。**1件ずつ外せるようにするために要る**
|
|
171
|
+
// (先頭1件のURLしか無かった頃は、6ファイル持つ案件でどれも選べなかった)。
|
|
172
|
+
figmaFiles: Array.isArray(s.figmaFiles)
|
|
173
|
+
? s.figmaFiles
|
|
174
|
+
.filter((f) => f != null && typeof f === "object")
|
|
175
|
+
.map((f) => ({ key: String(f.key ?? ""), name: String(f.name ?? "") }))
|
|
176
|
+
.filter((f) => f.key)
|
|
177
|
+
: undefined,
|
|
159
178
|
}))
|
|
160
179
|
: undefined;
|
|
161
180
|
const pipelines = Array.isArray(obj.pipelines)
|
|
@@ -182,7 +201,10 @@ async function readFleetSources(repoRoot) {
|
|
|
182
201
|
tools = undefined;
|
|
183
202
|
}
|
|
184
203
|
const fleetGeneratedAt = typeof obj.generatedAt === "string" ? obj.generatedAt : undefined;
|
|
185
|
-
|
|
204
|
+
const secretSources = obj.secretSources && typeof obj.secretSources === "object"
|
|
205
|
+
? obj.secretSources
|
|
206
|
+
: undefined;
|
|
207
|
+
return { externalSources, internalSources, pipelines, tools, fleetGeneratedAt, secretSources };
|
|
186
208
|
}
|
|
187
209
|
/** パスがディレクトリとして存在するか */
|
|
188
210
|
async function isDirectory(dirAbs) {
|
|
@@ -982,8 +1004,13 @@ async function build(opts) {
|
|
|
982
1004
|
projectKey: opts.projectKey,
|
|
983
1005
|
}
|
|
984
1006
|
: undefined,
|
|
1007
|
+
secretKinds: (opts.secretKinds ?? "")
|
|
1008
|
+
.split(",")
|
|
1009
|
+
.map((s) => s.trim())
|
|
1010
|
+
.filter(Boolean),
|
|
985
1011
|
externalSources: fleet.externalSources,
|
|
986
1012
|
fleetGeneratedAt: fleet.fleetGeneratedAt,
|
|
1013
|
+
secretSources: fleet.secretSources,
|
|
987
1014
|
internalSources: fleet.internalSources,
|
|
988
1015
|
pipelines: fleet.pipelines,
|
|
989
1016
|
tools: fleet.tools,
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ program
|
|
|
19
19
|
.option("--intake-url <url>", "投入フォームのLambda URL(環境変数 INTAKE_URL でも可)")
|
|
20
20
|
.option("--intake-token <token>", "投入フォームの共有秘密トークン(環境変数 INTAKE_TOKEN でも可)")
|
|
21
21
|
.option("--project-key <key>", "案件キー(投入フォームのルーティングキー)")
|
|
22
|
+
.option("--secret-kinds <kinds>", "鍵の入力欄を出す連携(カンマ区切り。例: figma,external-sources,backlog)")
|
|
22
23
|
.action(async (dir, opts) => {
|
|
23
24
|
try {
|
|
24
25
|
const result = await (0, build_1.build)({
|
|
@@ -32,6 +33,7 @@ program
|
|
|
32
33
|
intakeUrl: opts.intakeUrl ?? process.env.INTAKE_URL,
|
|
33
34
|
intakeToken: opts.intakeToken ?? process.env.INTAKE_TOKEN,
|
|
34
35
|
projectKey: opts.projectKey,
|
|
36
|
+
secretKinds: opts.secretKinds,
|
|
35
37
|
});
|
|
36
38
|
console.log(`✓ ${result.count} 件のレコードを ${result.outDir} にビルドしました`);
|
|
37
39
|
}
|
package/dist/render.js
CHANGED
|
@@ -747,6 +747,12 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
747
747
|
.ds-toggle-btn { font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: #fff; color: var(--text); cursor: pointer; }
|
|
748
748
|
.ds-toggle-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
|
|
749
749
|
.ds-toggle-btn:disabled { opacity: .6; cursor: default; }
|
|
750
|
+
.ds-toggle-btn.ds-danger { color: #b42318; border-color: #f3c6c1; }
|
|
751
|
+
.ds-toggle-btn.ds-danger:hover:not(:disabled) { border-color: #b42318; color: #b42318; background: #fff5f4; }
|
|
752
|
+
.ds-subitems { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
|
|
753
|
+
.ds-subitem { border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; background: #fff; }
|
|
754
|
+
/* 削除できたことを行に残す。一覧そのものは翌朝まで更新されないので、消えたように見せない */
|
|
755
|
+
.ds-removed .ds-name { text-decoration: line-through; color: var(--muted); }
|
|
750
756
|
.ds-toggle-msg { font-size: 12px; color: var(--muted); }
|
|
751
757
|
.ds-toggle-msg.is-ok { color: #1a7f37; }
|
|
752
758
|
.ds-toggle-msg.is-error { color: #b42318; }
|
|
@@ -767,6 +773,16 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
767
773
|
/* ---- 操作タブ ---- */
|
|
768
774
|
.ops-intro { color: var(--muted); font-size: 14px; line-height: 1.8; margin: 4px 0 8px; }
|
|
769
775
|
.ops-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
|
|
776
|
+
.sec-state { font-size: 12.5px; font-weight: 600; margin: 0 0 8px; color: var(--muted); }
|
|
777
|
+
.sec-state.is-managed { color: #1a7f37; }
|
|
778
|
+
.sec-state.is-none { color: var(--muted); font-weight: 400; }
|
|
779
|
+
.ds-addform { border: 1px dashed var(--border); border-radius: 8px; padding: 12px 14px; margin-top: 10px; background: #fff; }
|
|
780
|
+
.ds-addform-title { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 6px; }
|
|
781
|
+
.ds-addform .sec-input { margin-bottom: 6px; }
|
|
782
|
+
.sec-input-inline { width: auto; max-width: 220px; margin-bottom: 0; }
|
|
783
|
+
.sec-how { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 0 0 10px; }
|
|
784
|
+
.sec-input { width: 100%; box-sizing: border-box; font-size: 13px; padding: 7px 10px; margin-bottom: 8px; border: 1px solid var(--border); border-radius: 6px; background: #fff; color: var(--text); }
|
|
785
|
+
.sec-input:focus { outline: none; border-color: var(--accent); }
|
|
770
786
|
.ops-card {
|
|
771
787
|
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
772
788
|
padding: 18px; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 10px;
|
|
@@ -1420,6 +1436,9 @@ const CLIENT_JS = `
|
|
|
1420
1436
|
? el("span", { class: "ds-sync", text: "最終同期: " + dsFmtTime(s.lastSync) })
|
|
1421
1437
|
: null,
|
|
1422
1438
|
]);
|
|
1439
|
+
// 会議・共有資料・デザインの操作をカード内に置く(その情報源の文脈で押せるように)
|
|
1440
|
+
var ctl = internalControls(s, inactive);
|
|
1441
|
+
if (ctl) card.appendChild(ctl);
|
|
1423
1442
|
ilist.appendChild(card);
|
|
1424
1443
|
});
|
|
1425
1444
|
sec.appendChild(ilist);
|
|
@@ -1484,6 +1503,16 @@ const CLIENT_JS = `
|
|
|
1484
1503
|
if (devOff) wrap.appendChild(inactiveSourceRow("🐙", "GitHub", "ds-開発"));
|
|
1485
1504
|
sec.appendChild(wrap);
|
|
1486
1505
|
}
|
|
1506
|
+
// **追加フォームは外部ソースの節の外に置く。** グループは0件だと描かれず、4件以上だと
|
|
1507
|
+
// 畳まれる(既定は閉)。さらに節そのものが「1件以上あるとき」しか描かれないので、
|
|
1508
|
+
// 中に入れると**新規案件(0件)=一番追加したい場面**で消える。
|
|
1509
|
+
var adders = renderExternalAdders();
|
|
1510
|
+
if (adders.childNodes.length) {
|
|
1511
|
+
if (!sources.length && !chatOff && !devOff) {
|
|
1512
|
+
sec.appendChild(el("div", { class: "ds-sub", text: "外部ソース(ライブ参照)" }));
|
|
1513
|
+
}
|
|
1514
|
+
sec.appendChild(adders);
|
|
1515
|
+
}
|
|
1487
1516
|
|
|
1488
1517
|
// 注記は内部・外部の両グループの後ろに1つ(内部の直下だけに置くと外部ソースが
|
|
1489
1518
|
// Gold昇格の対象外に見えるため)。除外の方法もここで案内する。
|
|
@@ -1543,82 +1572,427 @@ const CLIENT_JS = `
|
|
|
1543
1572
|
* それを伝えないと「保存できていない」と受け取られるので、応答のメッセージをそのまま出す。
|
|
1544
1573
|
*/
|
|
1545
1574
|
function goldToggle(s) {
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1575
|
+
// **共通部品(stateToggle)に載せる。** 以前はここだけ変更者名をダイアログで聞いており、
|
|
1576
|
+
// 同じカードの中で2つの流儀が並んでいた。送信・エラー表示・向きの反転も個別実装で、
|
|
1577
|
+
// 片方だけ直したときにずれる状態だった。
|
|
1578
|
+
return stateToggle({
|
|
1579
|
+
on: s.gold === true,
|
|
1580
|
+
labelOn: "Gold昇格を止める",
|
|
1581
|
+
labelOff: "Gold昇格を始める",
|
|
1582
|
+
confirmOn: [
|
|
1583
|
+
"「" + (s.name || s.ref) + "」の内容を、顧客も見る Decision・用語集に蒸留するようになります。",
|
|
1584
|
+
"",
|
|
1585
|
+
"社内限定の会話が含まれるチャンネルでは、顧客に見せられない内容が出てしまう可能性があります。",
|
|
1586
|
+
"顧客がこのチャンネルにいる(=すでに会話を見ている)場合にだけ有効にしてください。",
|
|
1587
|
+
"",
|
|
1588
|
+
"※ 今夜の蒸留(22時ごろ)までに止めれば取り込まれません。一度生成された記録は自動では消えません。",
|
|
1589
|
+
"",
|
|
1590
|
+
"有効にしますか?",
|
|
1591
|
+
],
|
|
1592
|
+
build: function (next) {
|
|
1593
|
+
return { op: "set-channel-gold", ref: s.ref, value: next };
|
|
1594
|
+
},
|
|
1552
1595
|
});
|
|
1553
|
-
|
|
1554
|
-
wrap.appendChild(btn);
|
|
1555
|
-
wrap.appendChild(msg);
|
|
1596
|
+
}
|
|
1556
1597
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1598
|
+
/**
|
|
1599
|
+
* リポジトリ内の情報源に足す操作(会議の取り込みON/OFF・Driveフォルダ追加とON/OFF・Figmaファイル追加)。
|
|
1600
|
+
*
|
|
1601
|
+
* **状態が「不明」のときは操作を出さない。** fleet-status.json の生成が古い案件では
|
|
1602
|
+
* ingestState / driveState が入っていないので、ONともOFFとも言えない。
|
|
1603
|
+
* そこにトグルを出すと、押した結果が現在値と食い違って見える。
|
|
1604
|
+
*
|
|
1605
|
+
* **設定ファイルが無い(unset)ときも出さない。** 雛形から作れるのは外部ソースの登録だけで、
|
|
1606
|
+
* 会議・資料の設定は押しても404になる。リポジトリ側の整備が要ることを文字で伝える。
|
|
1607
|
+
*/
|
|
1608
|
+
function internalControls(s, inactive) {
|
|
1609
|
+
// **inactive(未使用)でも出す。** 未使用=tools が none/未宣言 という状態で、
|
|
1610
|
+
// まさにここから設定を始めたい。以前は inactive で早期returnしており、
|
|
1611
|
+
// 「足したい案件でだけ足せない」という逆転になっていた。
|
|
1612
|
+
// 押しても必ず失敗するもの(別ツールが宣言済み)は usable() で弾く。
|
|
1613
|
+
if (!SITE.intake) return null;
|
|
1614
|
+
var tools = SITE.tools || {};
|
|
1615
|
+
var usable = function (cap, tool) { var v = tools[cap]; return !v || v === "none" || v === tool; };
|
|
1616
|
+
var box = el("div", {});
|
|
1617
|
+
var used = false;
|
|
1618
|
+
|
|
1619
|
+
if (s.kind === "会議" && usable("会議", "google-meet")) {
|
|
1620
|
+
if (s.ingestState === "unset") {
|
|
1621
|
+
box.appendChild(el("p", { class: "sec-how", text: "会議の取り込み設定がこのリポジトリにありません(ingest-config.json)。先に設置が必要です。" }));
|
|
1622
|
+
used = true;
|
|
1623
|
+
} else if (s.ingestState === "broken") {
|
|
1624
|
+
box.appendChild(el("p", { class: "sec-how", text: "会議の取り込み設定(ingest-config.json)が壊れているため、ここからは変更できません。手で直してください。" }));
|
|
1625
|
+
used = true;
|
|
1626
|
+
} else if (s.ingestState === "on" || s.ingestState === "off") {
|
|
1627
|
+
box.appendChild(stateToggle({
|
|
1628
|
+
on: s.ingestState === "on",
|
|
1629
|
+
labelOn: "取り込みを止める",
|
|
1630
|
+
labelOff: "取り込みを始める",
|
|
1631
|
+
confirmOn: [
|
|
1632
|
+
"この案件の会議の文字起こしを、自動で取り込むようにします。",
|
|
1633
|
+
"",
|
|
1634
|
+
"取り込まれた文字起こしから議事録が作られ、毎晩の Gold 昇格の材料になります。",
|
|
1635
|
+
"",
|
|
1636
|
+
"有効にしますか?",
|
|
1637
|
+
],
|
|
1638
|
+
build: function (next) { return { op: "set-meeting-enabled", value: next }; },
|
|
1639
|
+
}));
|
|
1640
|
+
used = true;
|
|
1572
1641
|
}
|
|
1573
|
-
|
|
1574
|
-
if (editor === null) return;
|
|
1575
|
-
if (!editor.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ お名前を入力してください。"; return; }
|
|
1642
|
+
}
|
|
1576
1643
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1644
|
+
if (s.kind === "共有資料" && usable("共有資料", "google-drive")) {
|
|
1645
|
+
if (s.driveState === "unset") {
|
|
1646
|
+
box.appendChild(el("p", { class: "sec-how", text: "資料の同期設定がこのリポジトリにありません(materials-config.json)。先に設置が必要です。" }));
|
|
1647
|
+
used = true;
|
|
1648
|
+
} else if (s.driveState === "broken") {
|
|
1649
|
+
// 壊れていると書き込みも失敗する。フォームを出して押させない
|
|
1650
|
+
box.appendChild(el("p", { class: "sec-how", text: "資料の同期設定(materials-config.json)が壊れているため、ここからは変更できません。手で直してください。" }));
|
|
1651
|
+
used = true;
|
|
1652
|
+
} else if (s.driveState) {
|
|
1653
|
+
// フォルダが1件も無いのに ON/OFF だけ出しても意味が無いので、追加フォームを先に出す
|
|
1654
|
+
box.appendChild(addForm({
|
|
1655
|
+
title: "+ Google Drive のフォルダを追加",
|
|
1656
|
+
placeholder: "https://drive.google.com/drive/folders/…",
|
|
1657
|
+
namePlaceholder: "メモ(任意)",
|
|
1658
|
+
requiredNote: "フォルダのURLを入力してください。",
|
|
1659
|
+
hint: "このフォルダの中身が 共有資料 にコピーされ、Markdown 化されます。取り込み用アカウントをそのフォルダの閲覧者に招待しておいてください(招待が無いと読めません)。",
|
|
1660
|
+
confirmLines: function (v) {
|
|
1661
|
+
return [
|
|
1662
|
+
"このフォルダの中身を、顧客も見るリポジトリにコピーします。",
|
|
1663
|
+
"",
|
|
1664
|
+
"読み取りは全案件共通のアカウントで行うため、他の案件のフォルダも指定できてしまいます。",
|
|
1665
|
+
"貼ったURLがこの案件のものか、もう一度確認してください。",
|
|
1666
|
+
"",
|
|
1667
|
+
v,
|
|
1668
|
+
"",
|
|
1669
|
+
"追加しますか?",
|
|
1670
|
+
];
|
|
1671
|
+
},
|
|
1672
|
+
build: function (url, name) { return { op: "add-drive-folder", url: url, name: name }; },
|
|
1673
|
+
}));
|
|
1674
|
+
if (s.driveState === "on" || s.driveState === "off") {
|
|
1675
|
+
box.appendChild(stateToggle({
|
|
1676
|
+
on: s.driveState === "on",
|
|
1677
|
+
labelOn: "自動取り込みを止める",
|
|
1678
|
+
labelOff: "自動取り込みを始める",
|
|
1679
|
+
confirmOn: [
|
|
1680
|
+
"登録済みのフォルダから、資料の自動取り込みを再開します。",
|
|
1681
|
+
"",
|
|
1682
|
+
"フォルダの中身が、顧客も見るリポジトリにコピーされます。",
|
|
1683
|
+
"",
|
|
1684
|
+
"有効にしますか?",
|
|
1685
|
+
],
|
|
1686
|
+
build: function (next) { return { op: "set-materials-enabled", value: next }; },
|
|
1687
|
+
}));
|
|
1600
1688
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1689
|
+
used = true;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
if (s.kind === "デザイン" && usable("デザイン", "figma")) {
|
|
1694
|
+
// 登録済みファイルを1件ずつ外せるようにする(6ファイル抱えている案件があり、
|
|
1695
|
+
// 古いスプリントのファイルを外したいのが実際に起きる操作)。
|
|
1696
|
+
var figs = s.figmaFiles || [];
|
|
1697
|
+
// **残り件数は「押した時点」で数える。** 一覧(figs)は夜間バッチのスナップショットで、
|
|
1698
|
+
// このページを開いている間はもちろん、その日のうちに再読み込みしても更新されない。
|
|
1699
|
+
// 描画時点の件数で判定すると、3件を1件ずつ外していったとき最後の1件でも警告が出ない。
|
|
1700
|
+
var figsLeft = figs.length;
|
|
1701
|
+
if (figs.length) {
|
|
1702
|
+
var flist = el("div", { class: "ds-subitems" });
|
|
1703
|
+
figs.forEach(function (f) {
|
|
1704
|
+
var row = el("div", { class: "ds-subitem" }, [
|
|
1705
|
+
dsNameEl(f.name || f.key, "https://www.figma.com/design/" + f.key),
|
|
1706
|
+
]);
|
|
1707
|
+
row.appendChild(removeButton({
|
|
1708
|
+
label: "このファイルを外す",
|
|
1709
|
+
confirmLines: function () {
|
|
1710
|
+
var lines = [
|
|
1711
|
+
"「" + (f.name || f.key) + "」をデザイン同期の対象から外します。",
|
|
1712
|
+
"",
|
|
1713
|
+
"次回の同期から、このファイルの画面は画面インベントリに現れなくなります。",
|
|
1714
|
+
];
|
|
1715
|
+
// **最後の1件は結果が違う。** 同期は空の設定だと何もせずに終わるので、
|
|
1716
|
+
// 取り込み済みの画面インベントリが消えずに残る(sync_designs.py は
|
|
1717
|
+
// 全再生成の手前で早期returnする)。ここで伝えないと放置される。
|
|
1718
|
+
if (figsLeft === 1) {
|
|
1719
|
+
lines.push(
|
|
1720
|
+
"",
|
|
1721
|
+
"これが最後の1件です。外すとデザイン同期は何もしなくなりますが、すでに取り込んだ画面インベントリはリポジトリに残ります(不要なら手で削除してください)。",
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
lines.push("", "外しますか?");
|
|
1725
|
+
return lines;
|
|
1726
|
+
},
|
|
1727
|
+
build: function () { return { op: "remove-figma-file", key: f.key }; },
|
|
1728
|
+
onDone: function () {
|
|
1729
|
+
row.className = "ds-subitem ds-removed";
|
|
1730
|
+
figsLeft -= 1;
|
|
1731
|
+
},
|
|
1732
|
+
}));
|
|
1733
|
+
flist.appendChild(row);
|
|
1734
|
+
});
|
|
1735
|
+
box.appendChild(flist);
|
|
1736
|
+
}
|
|
1737
|
+
box.appendChild(addForm({
|
|
1738
|
+
title: "+ Figmaファイルを追加",
|
|
1739
|
+
placeholder: "https://www.figma.com/design/…",
|
|
1740
|
+
namePlaceholder: "メモ(任意)",
|
|
1741
|
+
requiredNote: "FigmaのURLを入力してください。",
|
|
1742
|
+
hint: "このファイルの画面インベントリ(画面名・サムネイル)が毎晩同期され、顧客も見るリポジトリに入ります。",
|
|
1743
|
+
confirmLines: function (v) {
|
|
1744
|
+
return [
|
|
1745
|
+
"このFigmaファイルの画面名とサムネイルを、顧客も見るリポジトリに取り込みます。",
|
|
1746
|
+
"",
|
|
1747
|
+
"読み取りに使うトークンは担当者個人のもので、他の案件のファイルも読めることがあります。",
|
|
1748
|
+
"貼ったURLがこの案件のものか、もう一度確認してください。",
|
|
1749
|
+
"",
|
|
1750
|
+
v,
|
|
1751
|
+
"",
|
|
1752
|
+
"追加しますか?",
|
|
1753
|
+
];
|
|
1754
|
+
},
|
|
1755
|
+
build: function (url, name) { return { op: "add-figma-file", url: url, name: name }; },
|
|
1756
|
+
}));
|
|
1757
|
+
used = true;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
return used ? box : null;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* 外部ソースの追加フォーム(Slack・GitHub)。
|
|
1765
|
+
*
|
|
1766
|
+
* **tools の宣言でゲートする。** 別のツールが宣言されている案件(チャット: teams 等)では
|
|
1767
|
+
* サーバが必ず400で断るので、押せるのに必ず失敗するボタンを出さない。
|
|
1768
|
+
*/
|
|
1769
|
+
function renderExternalAdders() {
|
|
1770
|
+
var box = el("div", {});
|
|
1771
|
+
if (!SITE.intake) return box;
|
|
1772
|
+
var tools = SITE.tools || {};
|
|
1773
|
+
var usable = function (cap, tool) { var v = tools[cap]; return !v || v === "none" || v === tool; };
|
|
1774
|
+
|
|
1775
|
+
if (usable("チャット", "slack")) {
|
|
1776
|
+
box.appendChild(addForm({
|
|
1777
|
+
title: "+ Slackチャンネルを追加",
|
|
1778
|
+
placeholder: "https://…slack.com/archives/C0XXXXXXX",
|
|
1779
|
+
namePlaceholder: "表示名(例: #案件-共有)",
|
|
1780
|
+
needsName: true,
|
|
1781
|
+
requiredNote: "チャンネルのURLを入力してください。",
|
|
1782
|
+
hint: "チャンネル名を右クリック →「リンクをコピー」で出るURLを貼ってください。追加しただけでは Gold 昇格には載りません(載せるには各チャンネルの「Gold昇格を始める」を押します)。",
|
|
1783
|
+
// **「Goldに載る」と書かない。** 追加だけでは載らないので嘘になる。
|
|
1784
|
+
// 確認する理由は「中央Botで読みに行く」「他案件の取り違え」。
|
|
1785
|
+
confirmLines: function (v) {
|
|
1786
|
+
return [
|
|
1787
|
+
"このチャンネルを、Cortex が読みに行く対象に追加します。",
|
|
1788
|
+
"",
|
|
1789
|
+
"読み取りは全案件共通の Bot で行うため、他の案件のチャンネルも指定できてしまいます。",
|
|
1790
|
+
"貼ったURLがこの案件のものか、もう一度確認してください。",
|
|
1791
|
+
"",
|
|
1792
|
+
v,
|
|
1793
|
+
"",
|
|
1794
|
+
"追加しますか?",
|
|
1795
|
+
];
|
|
1796
|
+
},
|
|
1797
|
+
build: function (url, name) { return { op: "add-channel", url: url, name: name }; },
|
|
1798
|
+
}));
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
if (usable("開発", "github")) {
|
|
1802
|
+
box.appendChild(addForm({
|
|
1803
|
+
title: "+ GitHubリポジトリを追加",
|
|
1804
|
+
placeholder: "owner/repo または https://github.com/owner/repo",
|
|
1805
|
+
namePlaceholder: "メモ(任意)",
|
|
1806
|
+
requiredNote: "リポジトリを入力してください。",
|
|
1807
|
+
hint: "このリポジトリの Issues が、毎晩の Gold 昇格の材料になります。",
|
|
1808
|
+
confirmLines: function (v) {
|
|
1809
|
+
return [
|
|
1810
|
+
"このリポジトリの Issues を、Gold 昇格の材料に追加します。",
|
|
1811
|
+
"",
|
|
1812
|
+
"追加した時点で読み取りの対象になります。",
|
|
1813
|
+
"Issues の内容から、顧客も見る Decision・用語集が作られます。",
|
|
1814
|
+
"",
|
|
1815
|
+
v,
|
|
1816
|
+
"",
|
|
1817
|
+
"追加しますか?",
|
|
1818
|
+
];
|
|
1819
|
+
},
|
|
1820
|
+
build: function (repo, name) { return { op: "add-github-source", repo: repo, name: name }; },
|
|
1821
|
+
}));
|
|
1822
|
+
}
|
|
1823
|
+
return box;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// ---------- 設定の変更(?op=config)の共通部品 ----------
|
|
1827
|
+
//
|
|
1828
|
+
// 確認 → 送信 → 結果表示、までを1箇所に置く。3つの操作で書き写すと、
|
|
1829
|
+
// 片方だけ直したときにずれる(既存の goldToggle で使っている作法をそのまま一般化したもの)。
|
|
1830
|
+
|
|
1831
|
+
/**
|
|
1832
|
+
* サーバへ設定変更を送る。**成功しても一覧やバッジは書き換えない**
|
|
1833
|
+
* (接続状況は夜間バッチのスナップショットなので、「もう効いている」と誤解させないため)。
|
|
1834
|
+
* 呼び出し側は onDone で自分のボタンの向きだけを反転させる。
|
|
1835
|
+
*/
|
|
1836
|
+
async function sendConfig(payload, msg, btn, onDone) {
|
|
1837
|
+
var intake = SITE.intake;
|
|
1838
|
+
if (!intake) return;
|
|
1839
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
1840
|
+
btn.disabled = true;
|
|
1841
|
+
msg.className = "ds-toggle-msg";
|
|
1842
|
+
msg.textContent = "保存中…";
|
|
1843
|
+
try {
|
|
1844
|
+
var res = await fetch(pfx + "op=config&t=" + encodeURIComponent(intake.token), {
|
|
1845
|
+
method: "POST",
|
|
1846
|
+
headers: { "content-type": "application/json" },
|
|
1847
|
+
body: JSON.stringify(payload),
|
|
1848
|
+
});
|
|
1849
|
+
var j = await res.json().catch(function () { return {}; });
|
|
1850
|
+
if (!res.ok) {
|
|
1851
|
+
// サーバの文言をそのまま出す(貼り方の案内・取り違えの警告を含んでいる)
|
|
1614
1852
|
msg.className = "ds-toggle-msg is-error";
|
|
1615
|
-
msg.textContent = "✗
|
|
1853
|
+
msg.textContent = "✗ " + (j.message || "保存できませんでした。");
|
|
1616
1854
|
btn.disabled = false;
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
msg.className = "ds-toggle-msg is-ok";
|
|
1858
|
+
msg.textContent = "✓ " + (j.message || "保存しました。");
|
|
1859
|
+
btn.disabled = false;
|
|
1860
|
+
if (onDone) onDone();
|
|
1861
|
+
} catch (e) {
|
|
1862
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1863
|
+
msg.textContent = "✗ 通信に失敗しました。";
|
|
1864
|
+
btn.disabled = false;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
/** 危険な方向にだけ出す確認。**何が起きるかを具体に書く**(「よろしいですか」だけにしない) */
|
|
1869
|
+
function confirmDanger(lines) {
|
|
1870
|
+
return window.confirm(lines.join(String.fromCharCode(10)));
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* ON/OFF のトグル。**戻す操作を必ず残す**(誤って有効にしたとき翌朝まで止められないのは許容できない)。
|
|
1875
|
+
* @param opts { on, labelOn, labelOff, confirmOn, build(next) }
|
|
1876
|
+
*/
|
|
1877
|
+
function stateToggle(opts) {
|
|
1878
|
+
var wrap = el("div", { class: "ds-toggle" });
|
|
1879
|
+
var on = opts.on === true;
|
|
1880
|
+
var btn = el("button", { class: "ds-toggle-btn" + (on ? " is-on" : ""), type: "button" });
|
|
1881
|
+
var who = el("input", { class: "sec-input sec-input-inline", type: "text", placeholder: "変更者のお名前", autocomplete: "off" });
|
|
1882
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
1883
|
+
var paint = function () {
|
|
1884
|
+
btn.textContent = on ? opts.labelOn : opts.labelOff;
|
|
1885
|
+
btn.className = "ds-toggle-btn" + (on ? " is-on" : "");
|
|
1886
|
+
};
|
|
1887
|
+
paint();
|
|
1888
|
+
|
|
1889
|
+
btn.addEventListener("click", function () {
|
|
1890
|
+
var next = !on;
|
|
1891
|
+
// **入力の検証を先に。** 確認を先にすると、強い確認文を読んでOKした後に
|
|
1892
|
+
// 「お名前を入力してください」と言われ、同じ確認をもう一度読まされる。
|
|
1893
|
+
if (!who.value.trim()) {
|
|
1894
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1895
|
+
msg.textContent = "⚠ お名前を入力してください。";
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
// 読み始める方向(危険)だけ確認する。止める方向は素通し
|
|
1899
|
+
if (next && opts.confirmOn && !confirmDanger(opts.confirmOn)) { msg.textContent = ""; return; }
|
|
1900
|
+
sendConfig(Object.assign({ editor: who.value.trim() }, opts.build(next)), msg, btn, function () {
|
|
1901
|
+
// **押した内容をボタンに反映し、逆向きの操作を残す。** バッジや一覧は触らない
|
|
1902
|
+
on = next;
|
|
1903
|
+
paint();
|
|
1904
|
+
});
|
|
1905
|
+
});
|
|
1906
|
+
wrap.appendChild(btn);
|
|
1907
|
+
wrap.appendChild(who);
|
|
1908
|
+
wrap.appendChild(msg);
|
|
1909
|
+
return wrap;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/**
|
|
1913
|
+
* 削除ボタン。**必ず確認を出す**(stateToggle は「広げる方向」だけ確認するが、削除は
|
|
1914
|
+
* 向きに関わらず取り消せないので常に確認する)。
|
|
1915
|
+
*
|
|
1916
|
+
* 成功しても一覧からは消さない。接続状況は夜間バッチのスナップショットで、消したつもりの行が
|
|
1917
|
+
* 翌朝まで残るのが正しい状態のため——行に取り消し線を引き、ボタンだけを無効にする
|
|
1918
|
+
* (二度押しすると「登録されていません」で失敗するので、押せなくする)。
|
|
1919
|
+
* @param opts { label, confirmLines(), build(), onDone() }
|
|
1920
|
+
*/
|
|
1921
|
+
function removeButton(opts) {
|
|
1922
|
+
var wrap = el("div", { class: "ds-toggle" });
|
|
1923
|
+
var btn = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: opts.label || "削除" });
|
|
1924
|
+
var who = el("input", { class: "sec-input sec-input-inline", type: "text", placeholder: "変更者のお名前", autocomplete: "off" });
|
|
1925
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
1926
|
+
|
|
1927
|
+
btn.addEventListener("click", function () {
|
|
1928
|
+
// 入力の検証を先に(確認文を読ませた後で入力不足を告げると、同じ確認をもう一度読ませることになる)
|
|
1929
|
+
if (!who.value.trim()) {
|
|
1930
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1931
|
+
msg.textContent = "⚠ お名前を入力してください。";
|
|
1932
|
+
return;
|
|
1617
1933
|
}
|
|
1934
|
+
if (!confirmDanger(opts.confirmLines())) { msg.textContent = ""; return; }
|
|
1935
|
+
sendConfig(Object.assign({ editor: who.value.trim() }, opts.build()), msg, btn, function () {
|
|
1936
|
+
btn.disabled = true;
|
|
1937
|
+
if (opts.onDone) opts.onDone();
|
|
1938
|
+
});
|
|
1618
1939
|
});
|
|
1940
|
+
|
|
1941
|
+
wrap.appendChild(btn);
|
|
1942
|
+
wrap.appendChild(who);
|
|
1943
|
+
wrap.appendChild(msg);
|
|
1619
1944
|
return wrap;
|
|
1620
1945
|
}
|
|
1621
1946
|
|
|
1947
|
+
/**
|
|
1948
|
+
* 「+…を追加」のフォーム。URLをそのまま貼れるようにする(IDだけを要求すると転記ミスが増える)。
|
|
1949
|
+
* @param opts { title, placeholder, hint, needsName, confirmLines, build(values) }
|
|
1950
|
+
*/
|
|
1951
|
+
function addForm(opts) {
|
|
1952
|
+
var main = el("input", { class: "sec-input", type: "text", placeholder: opts.placeholder, autocomplete: "off" });
|
|
1953
|
+
var name = opts.needsName
|
|
1954
|
+
? el("input", { class: "sec-input", type: "text", placeholder: opts.namePlaceholder || "表示名", autocomplete: "off" })
|
|
1955
|
+
: null;
|
|
1956
|
+
var who = el("input", { class: "sec-input", type: "text", placeholder: "変更者のお名前(履歴に残ります)", autocomplete: "off" });
|
|
1957
|
+
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "追加" });
|
|
1958
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
1959
|
+
|
|
1960
|
+
btn.addEventListener("click", function () {
|
|
1961
|
+
if (!main.value.trim()) {
|
|
1962
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1963
|
+
msg.textContent = "⚠ " + (opts.requiredNote || "入力してください。");
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
if (name && !name.value.trim()) {
|
|
1967
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1968
|
+
msg.textContent = "⚠ 表示名を入力してください。";
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
if (!who.value.trim()) {
|
|
1972
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1973
|
+
msg.textContent = "⚠ お名前を入力してください。";
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
if (opts.confirmLines && !confirmDanger(opts.confirmLines(main.value.trim()))) { msg.textContent = ""; return; }
|
|
1977
|
+
var payload = Object.assign({ editor: who.value.trim() }, opts.build(main.value.trim(), name ? name.value.trim() : ""));
|
|
1978
|
+
sendConfig(payload, msg, btn, function () {
|
|
1979
|
+
// 追加できたら入力欄を空にする(同じものを二度押ししないように)
|
|
1980
|
+
main.value = "";
|
|
1981
|
+
if (name) name.value = "";
|
|
1982
|
+
});
|
|
1983
|
+
});
|
|
1984
|
+
|
|
1985
|
+
var box = el("div", { class: "ds-addform" }, [
|
|
1986
|
+
el("div", { class: "ds-addform-title", text: opts.title }),
|
|
1987
|
+
opts.hint ? el("p", { class: "sec-how", text: opts.hint }) : null,
|
|
1988
|
+
main,
|
|
1989
|
+
name,
|
|
1990
|
+
who,
|
|
1991
|
+
el("div", { class: "ds-toggle" }, [btn, msg]),
|
|
1992
|
+
]);
|
|
1993
|
+
return box;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1622
1996
|
function extSourceCard(s, meta) {
|
|
1623
1997
|
var badges = el("div", { class: "ds-badges" });
|
|
1624
1998
|
// 「意図的に外した」と「宣言し忘れ」を分けて出す。同じ「除外」と書くと、
|
|
@@ -1642,6 +2016,47 @@ const CLIENT_JS = `
|
|
|
1642
2016
|
if (SITE.intake && s.type === "slack" && s.ref) {
|
|
1643
2017
|
card.appendChild(goldToggle(s));
|
|
1644
2018
|
}
|
|
2019
|
+
// GitHubリポの読み取り ON/OFF。OFF は exclude で表現される(導出されたリポも確実に落ちる)。
|
|
2020
|
+
if (SITE.intake && s.type.indexOf("github") === 0 && s.ref) {
|
|
2021
|
+
card.appendChild(stateToggle({
|
|
2022
|
+
on: s.goldState !== "excluded",
|
|
2023
|
+
labelOn: "読み取りを止める",
|
|
2024
|
+
labelOff: "読み取りを始める",
|
|
2025
|
+
confirmOn: [
|
|
2026
|
+
"このリポジトリの Issues を、Gold 昇格の材料に戻します。",
|
|
2027
|
+
"",
|
|
2028
|
+
"Issues の内容から、顧客も見る Decision・用語集が作られます。",
|
|
2029
|
+
"",
|
|
2030
|
+
s.ref,
|
|
2031
|
+
"",
|
|
2032
|
+
"有効にしますか?",
|
|
2033
|
+
],
|
|
2034
|
+
build: function (next) {
|
|
2035
|
+
return { op: "set-github-source-enabled", repo: s.ref, value: next };
|
|
2036
|
+
},
|
|
2037
|
+
}));
|
|
2038
|
+
// **削除は「手で登録したもの」にだけ出す。** .gitmodules 等から自動で見つかったリポは
|
|
2039
|
+
// 登録ファイルから消しても翌日また現れる(=消えないのに消せたように見える)。
|
|
2040
|
+
// 止めたいだけなら上の「読み取りを止める」で足りる。
|
|
2041
|
+
if (s.origin === "explicit") {
|
|
2042
|
+
card.appendChild(removeButton({
|
|
2043
|
+
label: "登録から削除",
|
|
2044
|
+
confirmLines: function () {
|
|
2045
|
+
return [
|
|
2046
|
+
"「" + s.ref + "」を、読み取り対象の登録から削除します。",
|
|
2047
|
+
"",
|
|
2048
|
+
"読み取りを一時的に止めたいだけなら「読み取りを止める」で足ります(あとから戻せます)。",
|
|
2049
|
+
"",
|
|
2050
|
+
"すでに作られた Decision・用語集は消えません。",
|
|
2051
|
+
"",
|
|
2052
|
+
"削除しますか?",
|
|
2053
|
+
];
|
|
2054
|
+
},
|
|
2055
|
+
build: function () { return { op: "remove-github-source", repo: s.ref }; },
|
|
2056
|
+
onDone: function () { card.classList.add("ds-removed"); },
|
|
2057
|
+
}));
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
1645
2060
|
return card;
|
|
1646
2061
|
}
|
|
1647
2062
|
|
|
@@ -1656,9 +2071,116 @@ const CLIENT_JS = `
|
|
|
1656
2071
|
grid.appendChild(opsCard("🗒️", "会議の文字起こしを追加", "顧客主催の会議など、自動取り込みできない会議の文字起こしファイル(.txt / .docx 等)から議事録を生成します。", "transcript", "文字起こしを追加"));
|
|
1657
2072
|
grid.appendChild(opsCard("📎", "共有資料を追加", "提案書・仕様書・議事メモなどの資料を Markdown 化して 共有資料 に取り込みます。", "material", "資料を追加"));
|
|
1658
2073
|
app.appendChild(grid);
|
|
2074
|
+
renderSecrets();
|
|
1659
2075
|
}
|
|
1660
2076
|
}
|
|
1661
2077
|
|
|
2078
|
+
/** 鍵の入力欄に出す説明。kind は Lambda 側(resolveSecretTarget)と綴りを合わせること */
|
|
2079
|
+
var SECRET_FORMS = {
|
|
2080
|
+
"figma": {
|
|
2081
|
+
icon: "🎨",
|
|
2082
|
+
title: "Figma のトークン",
|
|
2083
|
+
desc: "デザインの画面インベントリを毎晩同期するために使います。",
|
|
2084
|
+
how: "Figma → Settings → Security → Personal access tokens で発行(file_content:read があれば足ります)",
|
|
2085
|
+
},
|
|
2086
|
+
"backlog": {
|
|
2087
|
+
icon: "📋",
|
|
2088
|
+
title: "Backlog のAPIキー",
|
|
2089
|
+
desc: "課題・Wiki・ドキュメントを毎晩同期するために使います。",
|
|
2090
|
+
how: "Backlog → 個人設定 → API で発行",
|
|
2091
|
+
},
|
|
2092
|
+
"external-sources": {
|
|
2093
|
+
icon: "🐙",
|
|
2094
|
+
title: "GitHub の読み取りトークン",
|
|
2095
|
+
desc: "開発リポジトリの Issues / Discussions を読み、Gold の原料にするために使います。",
|
|
2096
|
+
how: "GitHub → Settings → Developer settings → Personal access tokens で、対象リポジトリに Contents / Issues の Read-only を付けて発行",
|
|
2097
|
+
},
|
|
2098
|
+
};
|
|
2099
|
+
|
|
2100
|
+
/**
|
|
2101
|
+
* 連携の鍵(トークン)の投入。
|
|
2102
|
+
*
|
|
2103
|
+
* **入れた値は読み出せない。** 表示・確認の経路は作っていないので、間違えたら入れ直す。
|
|
2104
|
+
* 保存先は案件ごとに分かれており、他の案件には影響しない。
|
|
2105
|
+
* 反映は次回のワークフロー実行から(接続状況の表示が変わるのは翌朝の自己チェック後)。
|
|
2106
|
+
*/
|
|
2107
|
+
function renderSecrets() {
|
|
2108
|
+
var kinds = (SITE.secretKinds || []).filter(function (k) { return SECRET_FORMS[k]; });
|
|
2109
|
+
if (!kinds.length) return;
|
|
2110
|
+
|
|
2111
|
+
app.appendChild(el("div", { class: "section-label", text: "連携の鍵" }));
|
|
2112
|
+
app.appendChild(el("p", { class: "ops-intro", text: "各ツールを読むためのトークンです。入れた値はここには表示されません(保存だけできます)。保存先は案件ごとに分かれているので、他の案件には影響しません。" }));
|
|
2113
|
+
var grid = el("div", { class: "ops-grid" });
|
|
2114
|
+
kinds.forEach(function (k) { grid.appendChild(secretCard(k, SECRET_FORMS[k])); });
|
|
2115
|
+
app.appendChild(grid);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
function secretCard(kind, meta) {
|
|
2119
|
+
// **いま何で動いているか**を先に出す。入れ直したのに反映されていない状態が
|
|
2120
|
+
// 画面で分かるようにする(接続状況は夜間バッチのスナップショットなので、
|
|
2121
|
+
// 保存の直後は前日の値のまま。時刻は上の節に出している)。
|
|
2122
|
+
var src = (SITE.secretSources || {})[kind] || "";
|
|
2123
|
+
var state = src
|
|
2124
|
+
? el("p", { class: "sec-state" + (src === "Secrets Manager" ? " is-managed" : ""),
|
|
2125
|
+
text: "現在: " + src + " から取得中" })
|
|
2126
|
+
: el("p", { class: "sec-state is-none", text: "現在: 未設定" });
|
|
2127
|
+
|
|
2128
|
+
var input = el("input", { class: "sec-input", type: "password", placeholder: "トークンを貼り付け", autocomplete: "off" });
|
|
2129
|
+
var who = el("input", { class: "sec-input", type: "text", placeholder: "変更者のお名前(履歴に残ります)", autocomplete: "off" });
|
|
2130
|
+
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "保存" });
|
|
2131
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
2132
|
+
|
|
2133
|
+
btn.addEventListener("click", async function () {
|
|
2134
|
+
var value = input.value;
|
|
2135
|
+
var editor = who.value;
|
|
2136
|
+
if (!value.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ トークンを入力してください。"; return; }
|
|
2137
|
+
if (!editor.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ お名前を入力してください。"; return; }
|
|
2138
|
+
|
|
2139
|
+
btn.disabled = true;
|
|
2140
|
+
msg.className = "ds-toggle-msg";
|
|
2141
|
+
msg.textContent = "保存中…";
|
|
2142
|
+
var intake = SITE.intake;
|
|
2143
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
2144
|
+
try {
|
|
2145
|
+
var res = await fetch(pfx + "op=secret&t=" + encodeURIComponent(intake.token), {
|
|
2146
|
+
method: "POST",
|
|
2147
|
+
headers: { "content-type": "application/json" },
|
|
2148
|
+
// **シークレット名は送らない。** どの連携かだけを送り、書き込み先はサーバが案件から決める。
|
|
2149
|
+
body: JSON.stringify({ projectKey: intake.projectKey, kind: kind, value: value, editor: editor }),
|
|
2150
|
+
});
|
|
2151
|
+
var j = await res.json().catch(function () { return {}; });
|
|
2152
|
+
if (!res.ok) {
|
|
2153
|
+
msg.className = "ds-toggle-msg is-error";
|
|
2154
|
+
msg.textContent = "✗ " + (j.message || "保存できませんでした。");
|
|
2155
|
+
btn.disabled = false;
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
msg.className = "ds-toggle-msg is-ok";
|
|
2159
|
+
msg.textContent = "✓ " + (j.message || "保存しました。");
|
|
2160
|
+
// 入れた値は画面に残さない(肩越しに見えるのを防ぐ)。お名前は続けて直せるよう残す。
|
|
2161
|
+
input.value = "";
|
|
2162
|
+
btn.disabled = false;
|
|
2163
|
+
} catch (e) {
|
|
2164
|
+
msg.className = "ds-toggle-msg is-error";
|
|
2165
|
+
msg.textContent = "✗ 通信に失敗しました。";
|
|
2166
|
+
btn.disabled = false;
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2170
|
+
return el("div", { class: "ops-card" }, [
|
|
2171
|
+
el("div", { class: "ops-card-head" }, [
|
|
2172
|
+
el("span", { class: "ops-card-icon", text: meta.icon }),
|
|
2173
|
+
el("h3", { class: "ops-card-title", text: meta.title }),
|
|
2174
|
+
]),
|
|
2175
|
+
el("p", { class: "ops-card-desc", text: meta.desc }),
|
|
2176
|
+
state,
|
|
2177
|
+
el("p", { class: "sec-how", text: meta.how }),
|
|
2178
|
+
input,
|
|
2179
|
+
who,
|
|
2180
|
+
el("div", { class: "ds-toggle" }, [btn, msg]),
|
|
2181
|
+
]);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
1662
2184
|
// ---------- 接続マップ(ビルド時生成のSVGを<template>から取り出して表示) ----------
|
|
1663
2185
|
function renderConnMap() {
|
|
1664
2186
|
var tpl = document.getElementById("conn-map");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takagaki/cortex-decisions-viewer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.49",
|
|
4
4
|
"description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsc -p tsconfig.json",
|
|
22
22
|
"prepublishOnly": "npm run build",
|
|
23
|
-
"demo": "npm run build && node dist/cli.js build ../../aidd-project-cortex/Cortex/Decisions/records --out .demo-site"
|
|
23
|
+
"demo": "npm run build && node dist/cli.js build ../../aidd-project-cortex/Cortex/Decisions/records --out .demo-site",
|
|
24
|
+
"test": "npm run build && node --test test/*.test.mjs"
|
|
24
25
|
},
|
|
25
26
|
"dependencies": {
|
|
26
27
|
"commander": "^12.1.0",
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@types/node": "^22.10.0",
|
|
33
|
+
"jsdom": "^29.1.1",
|
|
32
34
|
"linkedom": "^0.18.13",
|
|
33
35
|
"typescript": "^5.7.0"
|
|
34
36
|
}
|