@takagaki/cortex-decisions-viewer 0.4.48 → 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 +19 -0
- package/dist/render.js +473 -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)
|
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; }
|
|
@@ -770,6 +776,10 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
770
776
|
.sec-state { font-size: 12.5px; font-weight: 600; margin: 0 0 8px; color: var(--muted); }
|
|
771
777
|
.sec-state.is-managed { color: #1a7f37; }
|
|
772
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; }
|
|
773
783
|
.sec-how { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 0 0 10px; }
|
|
774
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); }
|
|
775
785
|
.sec-input:focus { outline: none; border-color: var(--accent); }
|
|
@@ -1426,6 +1436,9 @@ const CLIENT_JS = `
|
|
|
1426
1436
|
? el("span", { class: "ds-sync", text: "最終同期: " + dsFmtTime(s.lastSync) })
|
|
1427
1437
|
: null,
|
|
1428
1438
|
]);
|
|
1439
|
+
// 会議・共有資料・デザインの操作をカード内に置く(その情報源の文脈で押せるように)
|
|
1440
|
+
var ctl = internalControls(s, inactive);
|
|
1441
|
+
if (ctl) card.appendChild(ctl);
|
|
1429
1442
|
ilist.appendChild(card);
|
|
1430
1443
|
});
|
|
1431
1444
|
sec.appendChild(ilist);
|
|
@@ -1490,6 +1503,16 @@ const CLIENT_JS = `
|
|
|
1490
1503
|
if (devOff) wrap.appendChild(inactiveSourceRow("🐙", "GitHub", "ds-開発"));
|
|
1491
1504
|
sec.appendChild(wrap);
|
|
1492
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
|
+
}
|
|
1493
1516
|
|
|
1494
1517
|
// 注記は内部・外部の両グループの後ろに1つ(内部の直下だけに置くと外部ソースが
|
|
1495
1518
|
// Gold昇格の対象外に見えるため)。除外の方法もここで案内する。
|
|
@@ -1549,82 +1572,427 @@ const CLIENT_JS = `
|
|
|
1549
1572
|
* それを伝えないと「保存できていない」と受け取られるので、応答のメッセージをそのまま出す。
|
|
1550
1573
|
*/
|
|
1551
1574
|
function goldToggle(s) {
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
+
},
|
|
1558
1595
|
});
|
|
1559
|
-
|
|
1560
|
-
wrap.appendChild(btn);
|
|
1561
|
-
wrap.appendChild(msg);
|
|
1596
|
+
}
|
|
1562
1597
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
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;
|
|
1578
1641
|
}
|
|
1579
|
-
|
|
1580
|
-
if (editor === null) return;
|
|
1581
|
-
if (!editor.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ お名前を入力してください。"; return; }
|
|
1642
|
+
}
|
|
1582
1643
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
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
|
+
}));
|
|
1606
1688
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
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
|
+
// サーバの文言をそのまま出す(貼り方の案内・取り違えの警告を含んでいる)
|
|
1620
1852
|
msg.className = "ds-toggle-msg is-error";
|
|
1621
|
-
msg.textContent = "✗
|
|
1853
|
+
msg.textContent = "✗ " + (j.message || "保存できませんでした。");
|
|
1622
1854
|
btn.disabled = false;
|
|
1855
|
+
return;
|
|
1623
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
|
+
});
|
|
1624
1905
|
});
|
|
1906
|
+
wrap.appendChild(btn);
|
|
1907
|
+
wrap.appendChild(who);
|
|
1908
|
+
wrap.appendChild(msg);
|
|
1625
1909
|
return wrap;
|
|
1626
1910
|
}
|
|
1627
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;
|
|
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
|
+
});
|
|
1939
|
+
});
|
|
1940
|
+
|
|
1941
|
+
wrap.appendChild(btn);
|
|
1942
|
+
wrap.appendChild(who);
|
|
1943
|
+
wrap.appendChild(msg);
|
|
1944
|
+
return wrap;
|
|
1945
|
+
}
|
|
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
|
+
|
|
1628
1996
|
function extSourceCard(s, meta) {
|
|
1629
1997
|
var badges = el("div", { class: "ds-badges" });
|
|
1630
1998
|
// 「意図的に外した」と「宣言し忘れ」を分けて出す。同じ「除外」と書くと、
|
|
@@ -1648,6 +2016,47 @@ const CLIENT_JS = `
|
|
|
1648
2016
|
if (SITE.intake && s.type === "slack" && s.ref) {
|
|
1649
2017
|
card.appendChild(goldToggle(s));
|
|
1650
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
|
+
}
|
|
1651
2060
|
return card;
|
|
1652
2061
|
}
|
|
1653
2062
|
|
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
|
}
|