@takagaki/cortex-decisions-viewer 0.12.178 → 0.12.181
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/assets/guides/.gitkeep +0 -0
- package/assets/guides/backlog.mp4 +0 -0
- package/dist/backlog-guide-images.js +31 -0
- package/dist/build.js +59 -1
- package/dist/render.js +292 -29
- package/package.json +5 -2
package/dist/build.js
CHANGED
|
@@ -36,8 +36,10 @@ 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.REFERENCES_SECTIONS = exports.RELATED_MATERIALS_SECTIONS = exports.RELATED_URL_MAX = exports.RELATED_MATERIALS_FIELD = exports.DECISION_FIRMNESS = exports.GRAPH_RELS = exports.GRAPH_RECORD_TYPES = exports.PROJECT_ICON_EXTS = exports.MIRROR_RELS = void 0;
|
|
39
|
+
exports.REFERENCES_SECTIONS = exports.RELATED_MATERIALS_SECTIONS = exports.RELATED_URL_MAX = exports.RELATED_MATERIALS_FIELD = exports.DECISION_FIRMNESS = exports.GRAPH_RELS = exports.GRAPH_RECORD_TYPES = exports.GUIDE_VIDEO_SOURCES = exports.PROJECT_ICON_EXTS = exports.MIRROR_RELS = void 0;
|
|
40
40
|
exports.sanitizeBody = sanitizeBody;
|
|
41
|
+
exports.copyHashedAsset = copyHashedAsset;
|
|
42
|
+
exports.buildGuideAssets = buildGuideAssets;
|
|
41
43
|
exports.findProjectIcon = findProjectIcon;
|
|
42
44
|
exports.readFleetSources = readFleetSources;
|
|
43
45
|
exports.orderRecordsByLedgers = orderRecordsByLedgers;
|
|
@@ -299,6 +301,59 @@ function resolveKnowledgeRoot(decisionsDirAbs) {
|
|
|
299
301
|
* `.jpeg` は入れない(前の intake は `jpg` へ正規化して書いていた・拾うと「どちらが正か」が2つになる)。
|
|
300
302
|
*/
|
|
301
303
|
exports.PROJECT_ICON_EXTS = ["png", "jpg", "webp"];
|
|
304
|
+
/**
|
|
305
|
+
* 手順動画の置き場(#1353)。**今はまだ1件も無い**——npm パッケージに
|
|
306
|
+
* `viewer/assets/guides/<連携の種別>.mp4` を同梱したら、ここに1行足すだけで配信が始まる
|
|
307
|
+
* (npm の next → カナリア確認 → latest 昇格 → 各案件の次回ビルド、という既存の画像と
|
|
308
|
+
* 同じ広がり方。段階的に全案件へ届く)。キーは SECRET_FORMS 等で使う連携の種別(kind)。
|
|
309
|
+
*
|
|
310
|
+
* **絶対パスは `__dirname`(`dist/build.js`)から2つ上=パッケージルートの `assets/guides/`
|
|
311
|
+
* を指す。** `package.json` の `files` にも `assets` を加えてある(dist と同じ階層で配布される)。
|
|
312
|
+
*/
|
|
313
|
+
exports.GUIDE_VIDEO_SOURCES = {
|
|
314
|
+
// ポスターは動画の宣言には持たない(render.ts の SECRET_FORMS の guide.video.poster が data URI で持つ)
|
|
315
|
+
backlog: { video: path.join(__dirname, "..", "assets", "guides", "backlog.mp4") },
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* ファイルを内容ハッシュ名で `outAbs/assets/` へコピーする。CSS/JS と同じ規約
|
|
319
|
+
* (sha256 先頭8桁・`--cache-control public, max-age=31536000, immutable` で配信される
|
|
320
|
+
* `assets/` 配下)に動画も乗せる。**固定名にしないのは、動画の内容を変えても配信先に
|
|
321
|
+
* 古いバイト列が残ったまま長期キャッシュされる事故を防ぐため**(#1353 設計1.3節)。
|
|
322
|
+
*
|
|
323
|
+
* 読めない(まだ無い等)ときは undefined。**ビルドは止めない**——その連携の guide 宣言は
|
|
324
|
+
* 「動画なし」の表示に倒れる(動画ファイルが無い今の全案件がそうなる)。
|
|
325
|
+
*/
|
|
326
|
+
async function copyHashedAsset(outAbs, srcAbsPath, namePrefix) {
|
|
327
|
+
let bytes;
|
|
328
|
+
try {
|
|
329
|
+
bytes = await node_fs_1.promises.readFile(srcAbsPath);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
const hash = (0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex").slice(0, 8);
|
|
335
|
+
const fileExt = path.extname(srcAbsPath).replace(/^\./, "") || "bin";
|
|
336
|
+
const fileName = `${namePrefix}.${hash}.${fileExt}`;
|
|
337
|
+
await node_fs_1.promises.mkdir(path.join(outAbs, "assets"), { recursive: true });
|
|
338
|
+
await node_fs_1.promises.writeFile(path.join(outAbs, "assets", fileName), bytes);
|
|
339
|
+
return `assets/${fileName}`;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 連携の種別ごとの手順動画を `outAbs/assets/` へ写し、サイト内相対パスの表を返す。
|
|
343
|
+
* 1件も無ければ undefined(SiteData の「空の器は通さない」規約・`projectIcon` 等と同じ扱い)。
|
|
344
|
+
*/
|
|
345
|
+
async function buildGuideAssets(outAbs, sources) {
|
|
346
|
+
const out = {};
|
|
347
|
+
for (const kind of Object.keys(sources)) {
|
|
348
|
+
const videoAbsPath = sources[kind].video;
|
|
349
|
+
if (!videoAbsPath)
|
|
350
|
+
continue;
|
|
351
|
+
const url = await copyHashedAsset(outAbs, videoAbsPath, `guide-${kind}`);
|
|
352
|
+
if (url)
|
|
353
|
+
out[kind] = { video: url };
|
|
354
|
+
}
|
|
355
|
+
return Object.keys(out).length ? out : undefined;
|
|
356
|
+
}
|
|
302
357
|
/**
|
|
303
358
|
* `Cortex/icon.<ext>` を探す。無ければ undefined(キーごと出さないため)。
|
|
304
359
|
*
|
|
@@ -2638,6 +2693,9 @@ async function buildSite(opts, views) {
|
|
|
2638
2693
|
}
|
|
2639
2694
|
if (projectIcon)
|
|
2640
2695
|
await node_fs_1.promises.copyFile(projectIcon.srcAbs, path.join(outAbs, projectIcon.file));
|
|
2696
|
+
// 手順動画(#1353)。GUIDE_VIDEO_SOURCES が空の今は、1件も書き出されず site.guideAssets も
|
|
2697
|
+
// undefined のまま(キーごと省略・空の器は通さない)——今のどの案件の表示も変わらない
|
|
2698
|
+
site.guideAssets = await buildGuideAssets(outAbs, exports.GUIDE_VIDEO_SOURCES);
|
|
2641
2699
|
let thumbIndex = 0;
|
|
2642
2700
|
for (const node of graph.nodes) {
|
|
2643
2701
|
if (!node.thumb)
|
package/dist/render.js
CHANGED
|
@@ -89,6 +89,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
89
89
|
const google_chat_guide_images_1 = require("./google-chat-guide-images");
|
|
90
90
|
const meet_guide_images_1 = require("./meet-guide-images");
|
|
91
91
|
const figma_guide_images_1 = require("./figma-guide-images");
|
|
92
|
+
const backlog_guide_images_1 = require("./backlog-guide-images");
|
|
92
93
|
const story_1 = require("./story");
|
|
93
94
|
// 「人が確認したか/機械が触ってよいか」の口(#699 F2b)。正本は engine の confirmed.mjs・判定は流し込むだけ
|
|
94
95
|
const confirmed_1 = require("./confirmed");
|
|
@@ -5851,6 +5852,25 @@ a.ds-tile:hover { text-decoration: none; }
|
|
|
5851
5852
|
Google Chat の 2 枚がモーダルの内側幅(約 996px)まで**引き伸ばされてぼける**
|
|
5852
5853
|
——「原寸の幅に戻す」と言いながら原寸より大きく出すことになる */
|
|
5853
5854
|
.mt-shot-full { max-width: 100%; width: auto; cursor: zoom-out; }
|
|
5855
|
+
/* 手順動画+画像のカルーセル(#1353)。buildGuideCarousel が作る唯一の口。
|
|
5856
|
+
🔴 **ここでも幅はこの1か所だけ**(.mt-shot と同じ640pxに揃える)。表示切替は
|
|
5857
|
+
hidden属性のトグルだけで行う(CSSOMのstyleプロパティへの代入はしない)。
|
|
5858
|
+
スライドの高さは揃えない——固定枠にして object-fit で収めると、動画(16:9)と
|
|
5859
|
+
縦長のスクリーンショットが混在したときの比率計算にstyle操作が要ることになり、
|
|
5860
|
+
CSPの検査が拾いにくい書き方を増やす(実装レビュー指摘)。各スライドは
|
|
5861
|
+
.mt-shot / .mt-carousel-video と同じ「width:100%; height:auto」に任せ、
|
|
5862
|
+
枚数を切り替えるたびに高さが変わる(ガタつきはあるが、CSP安全性を優先する) */
|
|
5863
|
+
.mt-carousel { margin: 6px 0 0; max-width: 640px; }
|
|
5864
|
+
.mt-carousel-slide[hidden] { display: none; }
|
|
5865
|
+
.mt-carousel-slide .mt-shot { margin: 0; }
|
|
5866
|
+
.mt-carousel-video { display: block; width: 100%; height: auto; margin: 0; border: 1px solid var(--border); border-radius: 6px; background: #000; }
|
|
5867
|
+
.mt-carousel-nav { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 6px; }
|
|
5868
|
+
.mt-carousel-arrow { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; font-size: 16px; line-height: 1; padding: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; color: var(--text); cursor: pointer; }
|
|
5869
|
+
.mt-carousel-arrow:hover { background: var(--border); }
|
|
5870
|
+
.mt-carousel-dots { display: flex; gap: 6px; }
|
|
5871
|
+
.mt-carousel-dot { width: 8px; height: 8px; padding: 0; border: none; border-radius: 50%; background: var(--border); cursor: pointer; }
|
|
5872
|
+
.mt-carousel-dot.is-active { background: var(--accent); }
|
|
5873
|
+
.mt-carousel-caption { margin: 6px 0 0; font-size: 12.5px; color: var(--muted); }
|
|
5854
5874
|
/* 0件を黙って見せない(追加フォームだけだと「未登録」か「読めていない」か区別できない) */
|
|
5855
5875
|
.ds-empty { color: var(--muted); font-size: 12.5px; margin: 4px 0 10px; }
|
|
5856
5876
|
.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); }
|
|
@@ -6645,6 +6665,13 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
6645
6665
|
var FIGMA_SHOT_STEP1 = "${figma_guide_images_1.FIGMA_GUIDE_STEP1}";
|
|
6646
6666
|
var FIGMA_SHOT_STEP2 = "${figma_guide_images_1.FIGMA_GUIDE_STEP2}";
|
|
6647
6667
|
var FIGMA_SHOT_STEP3 = "${figma_guide_images_1.FIGMA_GUIDE_STEP3}";
|
|
6668
|
+
// Backlog の手順動画のポスターと手順画像5枚(#1353)。理由は src/backlog-guide-images.ts
|
|
6669
|
+
var BACKLOG_SHOT_POSTER = "${backlog_guide_images_1.BACKLOG_GUIDE_POSTER}";
|
|
6670
|
+
var BACKLOG_SHOT_STEP1 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP1}";
|
|
6671
|
+
var BACKLOG_SHOT_STEP2 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP2}";
|
|
6672
|
+
var BACKLOG_SHOT_STEP3 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP3}";
|
|
6673
|
+
var BACKLOG_SHOT_STEP4 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP4}";
|
|
6674
|
+
var BACKLOG_SHOT_STEP5 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP5}";
|
|
6648
6675
|
|
|
6649
6676
|
/**
|
|
6650
6677
|
* コピーボタンの動き(copyRow と copyInline の共通部分)。clipboard が使えない環境では
|
|
@@ -6762,6 +6789,139 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
6762
6789
|
});
|
|
6763
6790
|
return img;
|
|
6764
6791
|
}
|
|
6792
|
+
// 手順動画+画像のカルーセル(#1353)を作る**唯一の口**。guide が無ければ null
|
|
6793
|
+
// (呼び出し側は null を見て何も差し込まない。空の枠を出さない)。
|
|
6794
|
+
//
|
|
6795
|
+
// guide = { video?: { alt, poster? }, shots: [{ src, width, height, alt, heading? }, ...] }
|
|
6796
|
+
// - video の実 URL は guide には置かない。**SITE.guideAssets[kind].video**(build.ts が
|
|
6797
|
+
// 動画ファイルを site/assets/ へ写したときだけ注入する)から取る。guide.video があっても
|
|
6798
|
+
// SITE.guideAssets[kind].video が無ければ動画スライドを出さない——動画ファイルが
|
|
6799
|
+
// まだ無い今のすべての案件で、現状の表示から何も変わらないことの土台(#1353 実装設計1節)
|
|
6800
|
+
// - guide.video.poster … 動画の再生前に出す静止画。画像(shots)と同じ data URI で埋め込む
|
|
6801
|
+
// (配るのは動画1本だけ。ポスターは宣言側が持つ)。あれば <video poster> に付ける
|
|
6802
|
+
// - shots は今の meta.shots と同じ形。画像は data URI 埋め込みのまま(変更なし)
|
|
6803
|
+
//
|
|
6804
|
+
// 🔴 **ここでも幅の数字を呼び出し側に書かせない**(CSS の .mt-carousel 1 か所だけ)。
|
|
6805
|
+
// ⚠ **表示切替は hidden 属性(DOM プロパティ代入)だけ**。インラインの style を書き込む
|
|
6806
|
+
// 手段(CSP の検査・csp-compatible.test.mjs が禁じる書き方)は使わない。
|
|
6807
|
+
// ⚠ ハンドラは全部 addEventListener(インラインの on* 属性は配信先の CSP が実行しない)。
|
|
6808
|
+
// ⚠ guideShot 側の注意(直前のコメント)と同じ理由で、受け皿の変数名に s / p / src / ext を
|
|
6809
|
+
// 使わない(fleet-passthrough の走査の誤検知を避ける)。属性を組み立てる el() への
|
|
6810
|
+
// プロパティキー("src": ...)は対象外——シグネチャ上避けられないHTML属性名そのもの。
|
|
6811
|
+
function buildGuideCarousel(kind, guide) {
|
|
6812
|
+
if (!guide) return null;
|
|
6813
|
+
var asset = (SITE.guideAssets && SITE.guideAssets[kind]) || null;
|
|
6814
|
+
var slides = [];
|
|
6815
|
+
if (guide.video && asset && asset.video) {
|
|
6816
|
+
var videoAttrs = {
|
|
6817
|
+
class: "mt-carousel-video",
|
|
6818
|
+
src: asset.video,
|
|
6819
|
+
preload: "none",
|
|
6820
|
+
controls: "",
|
|
6821
|
+
playsinline: "",
|
|
6822
|
+
"aria-label": guide.video.alt || "",
|
|
6823
|
+
};
|
|
6824
|
+
if (guide.video.poster) videoAttrs.poster = guide.video.poster;
|
|
6825
|
+
slides.push({
|
|
6826
|
+
node: el("video", videoAttrs),
|
|
6827
|
+
caption: guide.video.alt || "",
|
|
6828
|
+
});
|
|
6829
|
+
}
|
|
6830
|
+
(guide.shots || []).forEach(function (shot) {
|
|
6831
|
+
slides.push({
|
|
6832
|
+
node: guideShot({ src: shot.src, width: shot.width, height: shot.height, alt: shot.alt }),
|
|
6833
|
+
caption: shot.heading || shot.alt || "",
|
|
6834
|
+
});
|
|
6835
|
+
});
|
|
6836
|
+
if (!slides.length) return null;
|
|
6837
|
+
|
|
6838
|
+
var viewport = el("div", { class: "mt-carousel-viewport" });
|
|
6839
|
+
var slideEls = slides.map(function (slide) {
|
|
6840
|
+
// data-caption … 並び順の見出し/altをDOMから直接読めるようにする(画面には出さない)。
|
|
6841
|
+
// 画面表示は .mt-carousel-caption の textContent(現在のスライドだけ)に任せる
|
|
6842
|
+
var wrap = el("div", { class: "mt-carousel-slide", "data-caption": slide.caption }, [slide.node]);
|
|
6843
|
+
viewport.appendChild(wrap);
|
|
6844
|
+
return wrap;
|
|
6845
|
+
});
|
|
6846
|
+
var caption = el("div", { class: "mt-carousel-caption", "aria-live": "polite" });
|
|
6847
|
+
var root = el("div", {
|
|
6848
|
+
class: "mt-carousel",
|
|
6849
|
+
role: "group",
|
|
6850
|
+
"aria-roledescription": "carousel",
|
|
6851
|
+
tabindex: "0",
|
|
6852
|
+
}, [viewport]);
|
|
6853
|
+
|
|
6854
|
+
var current = 0;
|
|
6855
|
+
var dotEls = [];
|
|
6856
|
+
function show(i) {
|
|
6857
|
+
current = ((i % slideEls.length) + slideEls.length) % slideEls.length;
|
|
6858
|
+
slideEls.forEach(function (slideEl, idx) {
|
|
6859
|
+
var hide = idx !== current;
|
|
6860
|
+
// 隠した動画は display:none でも音が鳴り続けるので止める
|
|
6861
|
+
if (hide && !slideEl.hidden) {
|
|
6862
|
+
var playing = slideEl.querySelector("video");
|
|
6863
|
+
if (playing && typeof playing.pause === "function") playing.pause();
|
|
6864
|
+
}
|
|
6865
|
+
slideEl.hidden = hide;
|
|
6866
|
+
});
|
|
6867
|
+
// フォーカスが隠れたスライドの中にあったら外枠へ戻す(キーボード操作が途切れない)
|
|
6868
|
+
if (document.activeElement && document.activeElement !== root && viewport.contains(document.activeElement)
|
|
6869
|
+
&& slideEls[current] && !slideEls[current].contains(document.activeElement)) root.focus();
|
|
6870
|
+
caption.textContent = slides[current].caption;
|
|
6871
|
+
dotEls.forEach(function (dotEl, idx) {
|
|
6872
|
+
dotEl.classList.toggle("is-active", idx === current);
|
|
6873
|
+
if (idx === current) dotEl.setAttribute("aria-current", "true"); else dotEl.removeAttribute("aria-current");
|
|
6874
|
+
});
|
|
6875
|
+
}
|
|
6876
|
+
|
|
6877
|
+
// 1枚だけなら矢印・ドットなしの素の枠にする(枚数が増えない限り視覚的ノイズを足さない)
|
|
6878
|
+
if (slides.length > 1) {
|
|
6879
|
+
var prev = el("button", { type: "button", class: "mt-carousel-arrow", "aria-label": "前の手順", text: "‹" });
|
|
6880
|
+
var next = el("button", { type: "button", class: "mt-carousel-arrow", "aria-label": "次の手順", text: "›" });
|
|
6881
|
+
var dots = el("div", { class: "mt-carousel-dots" });
|
|
6882
|
+
slides.forEach(function (_slide, idx) {
|
|
6883
|
+
var dot = el("button", { type: "button", class: "mt-carousel-dot", "aria-label": (idx + 1) + "枚目を表示" });
|
|
6884
|
+
dot.addEventListener("click", function () { show(idx); });
|
|
6885
|
+
dotEls.push(dot);
|
|
6886
|
+
dots.appendChild(dot);
|
|
6887
|
+
});
|
|
6888
|
+
prev.addEventListener("click", function () { show(current - 1); });
|
|
6889
|
+
next.addEventListener("click", function () { show(current + 1); });
|
|
6890
|
+
root.appendChild(el("div", { class: "mt-carousel-nav" }, [prev, dots, next]));
|
|
6891
|
+
// キーボード送り(カルーセルにフォーカスがあるとき)
|
|
6892
|
+
root.addEventListener("keydown", function (e) {
|
|
6893
|
+
// 動画のコントロール上の ←→(ネイティブのシーク)と、ブラウザの戻る系ショートカットは奪わない
|
|
6894
|
+
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
6895
|
+
if (e.target && e.target.tagName === "VIDEO") return;
|
|
6896
|
+
if (e.key === "ArrowLeft") { e.preventDefault(); show(current - 1); }
|
|
6897
|
+
else if (e.key === "ArrowRight") { e.preventDefault(); show(current + 1); }
|
|
6898
|
+
});
|
|
6899
|
+
// スワイプ(タッチ)。横移動が小さいときは無視する(縦スクロールの誤爆を防ぐ)
|
|
6900
|
+
var touchStartX = null;
|
|
6901
|
+
var touchStartY = null;
|
|
6902
|
+
viewport.addEventListener("touchstart", function (e) {
|
|
6903
|
+
// 動画のシークバーの横ドラッグはスワイプにしない。複数指も対象外
|
|
6904
|
+
var single = e.touches && e.touches.length === 1 && !(e.target && e.target.tagName === "VIDEO");
|
|
6905
|
+
touchStartX = single ? e.touches[0].clientX : null;
|
|
6906
|
+
touchStartY = single ? e.touches[0].clientY : null;
|
|
6907
|
+
}, { passive: true });
|
|
6908
|
+
viewport.addEventListener("touchcancel", function () { touchStartX = null; touchStartY = null; }, { passive: true });
|
|
6909
|
+
viewport.addEventListener("touchend", function (e) {
|
|
6910
|
+
if (touchStartX === null) return;
|
|
6911
|
+
var endX = e.changedTouches && e.changedTouches.length ? e.changedTouches[0].clientX : touchStartX;
|
|
6912
|
+
var endY = e.changedTouches && e.changedTouches.length ? e.changedTouches[0].clientY : touchStartY;
|
|
6913
|
+
var dx = endX - touchStartX;
|
|
6914
|
+
var dy = endY - touchStartY;
|
|
6915
|
+
touchStartX = null;
|
|
6916
|
+
touchStartY = null;
|
|
6917
|
+
if (Math.abs(dx) < 30 || Math.abs(dy) >= Math.abs(dx)) return;
|
|
6918
|
+
if (dx < 0) show(current + 1); else show(current - 1);
|
|
6919
|
+
}, { passive: true });
|
|
6920
|
+
}
|
|
6921
|
+
root.appendChild(caption);
|
|
6922
|
+
show(0);
|
|
6923
|
+
return root;
|
|
6924
|
+
}
|
|
6765
6925
|
// 未確認(status: draft = AI生成・人間未確認)の印。Gold層の4型(決定事項・用語集・メンバー・ルール)共通で、active・status無しには何も出さない
|
|
6766
6926
|
function draftBadge(rec) {
|
|
6767
6927
|
return rec && unconfirmed(rec) ? el("span", { class: "badge draft", text: "AI生成・未確認" }) : null;
|
|
@@ -15498,6 +15658,22 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
15498
15658
|
build: function (url) {
|
|
15499
15659
|
return { op: "backlog-target-add", url: url, baseGeneration: targetsGenerationOf(targets) };
|
|
15500
15660
|
},
|
|
15661
|
+
// **ほかのプロジェクトでも使われているプロジェクトキーの確認(#1354)。** 画面から接続先を足すのはこのフォームだけ
|
|
15662
|
+
//(set-backlog-target を送るフォームは無い)。振り分け表はスペースを持たないので「同じプロジェクトキー」と言う。
|
|
15663
|
+
// 相手のプロジェクト名は出さない(この画面の操作は認証を通れる人なら誰でもできる)
|
|
15664
|
+
duplicateConfirmLines: function (value) {
|
|
15665
|
+
return [
|
|
15666
|
+
"同じプロジェクトキーが、ほかのプロジェクトでも使われています。",
|
|
15667
|
+
"",
|
|
15668
|
+
"このまま追加すると、同じ課題・ドキュメント・Wiki が、このプロジェクトのリポジトリにも取り込まれます。",
|
|
15669
|
+
"",
|
|
15670
|
+
"別のBacklogプロジェクトと取り違えていないか、貼ったURLを確かめてください:",
|
|
15671
|
+
String(value || ""),
|
|
15672
|
+
"",
|
|
15673
|
+
"追加しますか?",
|
|
15674
|
+
];
|
|
15675
|
+
},
|
|
15676
|
+
duplicateKind: "backlog",
|
|
15501
15677
|
// 応答の一覧で本物の行が増えるので、フォーム側の控え(「追加済み・反映中」)は出さない
|
|
15502
15678
|
hooks: targetsHooks,
|
|
15503
15679
|
}));
|
|
@@ -15995,6 +16171,20 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
15995
16171
|
// 手順を置いていたが、トークンの発行手順の正本は鍵の欄(SECRET_FORMS.figma の how・note・画像)、
|
|
15996
16172
|
// 読めないファイルの一手は行の中(DS_GATE_CAP.design)、「何が同期されるか」は上部の説明文が持つ。
|
|
15997
16173
|
build: function (url, name) { return { op: "add-figma-file", url: url, name: name }; },
|
|
16174
|
+
// 別のプロジェクトでも追加されているファイルの確認(#1354)
|
|
16175
|
+
duplicateConfirmLines: function (value) {
|
|
16176
|
+
return [
|
|
16177
|
+
"このFigmaファイルは、別のプロジェクトでも追加されています。",
|
|
16178
|
+
"",
|
|
16179
|
+
"このまま追加すると、同じファイルのデザインが、このプロジェクトのリポジトリにも取り込まれます。",
|
|
16180
|
+
"",
|
|
16181
|
+
"別のファイルと取り違えていないか、貼ったURLを確かめてください:",
|
|
16182
|
+
String(value || ""),
|
|
16183
|
+
"",
|
|
16184
|
+
"追加しますか?",
|
|
16185
|
+
];
|
|
16186
|
+
},
|
|
16187
|
+
duplicateKind: "figma",
|
|
15998
16188
|
}));
|
|
15999
16189
|
used = true;
|
|
16000
16190
|
}
|
|
@@ -16068,6 +16258,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
16068
16258
|
"追加しますか?",
|
|
16069
16259
|
];
|
|
16070
16260
|
},
|
|
16261
|
+
duplicateKind: "slack",
|
|
16071
16262
|
adder: "slack",
|
|
16072
16263
|
}));
|
|
16073
16264
|
|
|
@@ -16079,7 +16270,7 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
16079
16270
|
// **接続(OAuth)のボタンは無い。** 顧客の Workspace 管理者による Admin install が
|
|
16080
16271
|
// 承認そのもので、画面に押す先が無い(Slack の接続ボタンを写経しないこと——
|
|
16081
16272
|
// 押しても何も起きないボタンが顧客の画面に出る)。
|
|
16082
|
-
//
|
|
16273
|
+
// **追加のたびの確認モーダルは出さない**(#167 で廃止済み。別のプロジェクトと重なったときの確認だけ出る・#1354。
|
|
16083
16274
|
// 「追加=Gold 昇格の対象」という重さは、モーダル上部の説明文が 1 回だけ言う)。
|
|
16084
16275
|
// Google Chat アプリの表示名は GCHAT_APP_NAME(Marketplace 公開名・#387)。
|
|
16085
16276
|
box.appendChild(addForm({
|
|
@@ -16109,6 +16300,20 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
16109
16300
|
// 読めなければ足した行の中が理由に合った一手を出す(githubRowHelpParts)。
|
|
16110
16301
|
// 「毎晩の記録に入る・決定と用語の材料にはしない」の説明は、モーダル上部の説明文が 1 回だけ持つ。
|
|
16111
16302
|
build: function (repo, name) { return { op: "add-github-source", repo: repo, name: name }; },
|
|
16303
|
+
// 別のプロジェクトでも追加されているリポジトリの確認(#1354)
|
|
16304
|
+
duplicateConfirmLines: function (value) {
|
|
16305
|
+
return [
|
|
16306
|
+
"このリポジトリは、別のプロジェクトでも追加されています。",
|
|
16307
|
+
"",
|
|
16308
|
+
"このまま追加すると、同じリポジトリの内容が、このプロジェクトの毎晩の記録にも入ります。",
|
|
16309
|
+
"",
|
|
16310
|
+
"別のリポジトリと取り違えていないか、入れた値を確かめてください:",
|
|
16311
|
+
String(value || ""),
|
|
16312
|
+
"",
|
|
16313
|
+
"追加しますか?",
|
|
16314
|
+
];
|
|
16315
|
+
},
|
|
16316
|
+
duplicateKind: "github",
|
|
16112
16317
|
adder: "github",
|
|
16113
16318
|
}));
|
|
16114
16319
|
}
|
|
@@ -16239,6 +16444,12 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
16239
16444
|
* 失敗(エラー応答・通信失敗・何も変わらなかった)では仮置きを**巻き戻して**理由を出す。
|
|
16240
16445
|
* 仮置きを作れない操作(切り出せない入力・仮置きの対象でない op)は、従来どおり待ち表示を出して応答を待つ。
|
|
16241
16446
|
*/
|
|
16447
|
+
/*
|
|
16448
|
+
* 重なりを確認すれば覆せる種別(2026-08-20 Slack・#1354 で 4 種別。Drive・Google Chat は中央のアカウントで読むので含めない)。綴りと並びは intake の
|
|
16449
|
+
* CONFIRMABLE_DUPLICATE_KINDS(infra/lambda/intake/logic.mjs)と完全一致させる(infra/test/duplicate-verdict.test.mjs が写しの一致を見る)。
|
|
16450
|
+
* サーバの印がこの許可リストに無い種別なら、確認は出さない(truthy で見ない)
|
|
16451
|
+
*/
|
|
16452
|
+
var CONFIRMABLE_KINDS = ["slack", "figma", "backlog", "github"];
|
|
16242
16453
|
async function sendConfig(payload, msg, btn, onDone, hooks) {
|
|
16243
16454
|
var intake = SITE.intake;
|
|
16244
16455
|
if (!intake) return;
|
|
@@ -16294,11 +16505,16 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
16294
16505
|
// **サーバを切り戻したとき**と**デプロイ中の版ズレ**。どちらも確認済みの再送だけが
|
|
16295
16506
|
// 断られる状態で、そこで出し続けると「はい」を押し続けることになる。
|
|
16296
16507
|
// 配る順番はサーバ→画面(これは順序の話とは別に必要)。
|
|
16508
|
+
//
|
|
16509
|
+
// **種別は許可リストの完全一致(#1354)**、さらに**そのフォームの種別と一致**するときだけ出す
|
|
16510
|
+
// (Drive の追加フォームに Slack の印が返っても、Drive の確認文で覆さない)。
|
|
16297
16511
|
var canConfirm =
|
|
16298
16512
|
res.status === 409 &&
|
|
16299
16513
|
j.confirmable === "duplicate" &&
|
|
16300
|
-
j.confirmableKind === "
|
|
16514
|
+
typeof j.confirmableKind === "string" &&
|
|
16515
|
+
CONFIRMABLE_KINDS.indexOf(j.confirmableKind) >= 0 &&
|
|
16301
16516
|
hooks &&
|
|
16517
|
+
j.confirmableKind === hooks.duplicateKind &&
|
|
16302
16518
|
typeof hooks.onDuplicate === "function" &&
|
|
16303
16519
|
payload.confirmDuplicate !== true;
|
|
16304
16520
|
if (canConfirm) {
|
|
@@ -16747,9 +16963,9 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
16747
16963
|
);
|
|
16748
16964
|
}, (function () {
|
|
16749
16965
|
// **既存の hooks(接続先の一覧が持つ onTargets)と、重複の確認を1つにまとめる。**
|
|
16750
|
-
// Slack
|
|
16751
|
-
//
|
|
16752
|
-
//
|
|
16966
|
+
// Slack などの追加フォームは onTargets を持たないが、重複の確認は要る。
|
|
16967
|
+
// 接続先の一覧(Backlog)は onTargets も重複の確認も持つ(#1354)。確認は印(confirmable: "duplicate")の
|
|
16968
|
+
// 完全一致でしか出ないので、あちらの「他の人が先に変えていた」(世代のずれ)の判定とは混ざらない。
|
|
16753
16969
|
var h = opts.hooks ? opts.hooks() : null;
|
|
16754
16970
|
// **送信した瞬間に入力欄を空にし(楽観更新・#968 E)、断られたら元の値へ戻す**(打ち直させない)
|
|
16755
16971
|
var sentMain = "";
|
|
@@ -16783,6 +16999,8 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
16783
16999
|
returnTo: btn,
|
|
16784
17000
|
});
|
|
16785
17001
|
};
|
|
17002
|
+
// このフォームの種別。サーバの印の種別と一致したときだけ確認を出す(sendConfig の canConfirm)
|
|
17003
|
+
h.duplicateKind = opts.duplicateKind;
|
|
16786
17004
|
return h;
|
|
16787
17005
|
})());
|
|
16788
17006
|
});
|
|
@@ -18022,27 +18240,30 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
18022
18240
|
// 現場で切り分けに時間がかかる(実際に踏んだ)。入力欄の隣に書いておく。
|
|
18023
18241
|
note: "発行したアカウントを、対象の Figma ファイルに閲覧者として招待しておいてください(招待が無いとトークンが正しくても読めません)。"
|
|
18024
18242
|
+ "発行するのは、対象ファイルのあるチームで Dev または Full の席を持つ方にしてください。閲覧席(View/Collab)のトークンは取得回数が月 20 回までで、夜間同期が数日で止まります。",
|
|
18025
|
-
// **手順の画像3枚(#547)。**
|
|
18026
|
-
//
|
|
18027
|
-
//
|
|
18028
|
-
//
|
|
18029
|
-
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
|
|
18033
|
-
|
|
18034
|
-
|
|
18035
|
-
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
|
|
18040
|
-
|
|
18041
|
-
|
|
18042
|
-
|
|
18043
|
-
|
|
18044
|
-
|
|
18045
|
-
|
|
18243
|
+
// **手順の画像3枚(#547)。** buildGuideCarousel(#1353)を通す1枚ずつ送りの枠に乗せる。
|
|
18244
|
+
// 見出しは画像の並び順(1〜3枚目)、画像の中の丸数字(①〜⑧)は1枚の中の細かい手順
|
|
18245
|
+
// ——役割が別なので、見出し側に丸数字を混ぜない。
|
|
18246
|
+
// 動画は無し(guide.video を省略)。将来動画を足すときは、この guide にそのまま
|
|
18247
|
+
// video: { alt } を足すだけで済む(SITE.guideAssets.figma.video が注入された日から配信開始)
|
|
18248
|
+
guide: {
|
|
18249
|
+
shots: [
|
|
18250
|
+
{
|
|
18251
|
+
src: FIGMA_SHOT_STEP1, width: "1000", height: "1114",
|
|
18252
|
+
heading: "1. 左上の名前 → Settings",
|
|
18253
|
+
alt: "Figma左上の自分の名前を押すとメニューが開き、その中のSettingsが赤枠で示されている。",
|
|
18254
|
+
},
|
|
18255
|
+
{
|
|
18256
|
+
src: FIGMA_SHOT_STEP2, width: "1200", height: "1145",
|
|
18257
|
+
heading: "2. Security タブ → Generate new token を押す",
|
|
18258
|
+
alt: "Settings画面のSecurityタブが赤枠で示され、下のPersonal access tokensにあるGenerate new tokenのリンクも赤枠で示されている。",
|
|
18259
|
+
},
|
|
18260
|
+
{
|
|
18261
|
+
src: FIGMA_SHOT_STEP3, width: "1100", height: "1428",
|
|
18262
|
+
heading: "3. 名前・期限・スコープを決めて発行する",
|
|
18263
|
+
alt: "Generate new tokenダイアログ。Token name欄・Expiration(90日)・file_comments:readとfile_content:readのチェックボックス・Generate tokenボタンが、それぞれ赤枠で示されている。",
|
|
18264
|
+
},
|
|
18265
|
+
],
|
|
18266
|
+
},
|
|
18046
18267
|
},
|
|
18047
18268
|
"backlog": {
|
|
18048
18269
|
icon: "📋",
|
|
@@ -18065,6 +18286,42 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
18065
18286
|
"担当が変わったら、新しい担当者が同じ手順で入れ直してください" +
|
|
18066
18287
|
"(前の担当者のアカウントが無効になると同期が止まります)。",
|
|
18067
18288
|
},
|
|
18289
|
+
// **手順の動画+画像(#1353)。** 動画の実 URL は guide に置かない(SITE.guideAssets.backlog.video・
|
|
18290
|
+
// build.ts の GUIDE_VIDEO_SOURCES が注入する)。動画が無い配信先では画像5枚だけで成立する。
|
|
18291
|
+
// 画像の並びは動画の流れ(URL をコピー → キーを発行 → 貼る → つながる)に揃える。
|
|
18292
|
+
guide: {
|
|
18293
|
+
video: {
|
|
18294
|
+
alt: "Backlog を Cortex につなぐ手順の動画(約1分30秒)。Backlog の URL のコピー、API キーの発行、操作タブへの貼り付け、接続OK の確認までを画面で見せます。",
|
|
18295
|
+
poster: BACKLOG_SHOT_POSTER,
|
|
18296
|
+
},
|
|
18297
|
+
shots: [
|
|
18298
|
+
{
|
|
18299
|
+
src: BACKLOG_SHOT_STEP1, width: "1481", height: "993",
|
|
18300
|
+
heading: "1. Backlog のアドレスバーから URL をコピー(操作タブの「接続先を追加」に貼る)",
|
|
18301
|
+
alt: "Backlogのプロジェクトホーム。ブラウザのアドレスバーに cm1.backlog.jp/projects/PJ_CORTEX の形のURLが出ている。",
|
|
18302
|
+
},
|
|
18303
|
+
{
|
|
18304
|
+
src: BACKLOG_SHOT_STEP2, width: "1536", height: "766",
|
|
18305
|
+
heading: "2. 個人設定 → API のメモに Cortex と入れて登録",
|
|
18306
|
+
alt: "Backlogの個人設定のAPI画面。新しいAPIキーを発行する欄のメモに Cortex と入力されていて、登録ボタンを押す前の状態。",
|
|
18307
|
+
},
|
|
18308
|
+
{
|
|
18309
|
+
src: BACKLOG_SHOT_STEP3, width: "1536", height: "766",
|
|
18310
|
+
heading: "3. 発行された API キーをコピー",
|
|
18311
|
+
alt: "登録されたAPIキーの一覧に、メモが Cortex の行が増えている。キーは先頭と末尾の文字だけ見え、行のコピーボタンでコピーできる。",
|
|
18312
|
+
},
|
|
18313
|
+
{
|
|
18314
|
+
src: BACKLOG_SHOT_STEP4, width: "1456", height: "819",
|
|
18315
|
+
heading: "4. 操作タブの Backlog の入力欄に貼って保存",
|
|
18316
|
+
alt: "Cortexの操作タブのBacklogのモーダル。API キーを入れる欄(表示は「トークンを貼り付け」)にカーソルがあり、その下に保存ボタンがある。",
|
|
18317
|
+
},
|
|
18318
|
+
{
|
|
18319
|
+
src: BACKLOG_SHOT_STEP5, width: "1568", height: "773",
|
|
18320
|
+
heading: "5. 「接続OK」と出れば完了",
|
|
18321
|
+
alt: "Backlogのモーダルの接続先の行に、緑の「接続OK」の表示が出ている。登録済みと保存しましたの表示も出ている。",
|
|
18322
|
+
},
|
|
18323
|
+
],
|
|
18324
|
+
},
|
|
18068
18325
|
},
|
|
18069
18326
|
};
|
|
18070
18327
|
|
|
@@ -18283,9 +18540,15 @@ ${story_1.splitByNameLinks.toString()}
|
|
|
18283
18540
|
// 発行のしかたを探している人がここを開く手掛かりを持てない(rt カードと同じ流儀)
|
|
18284
18541
|
var guidanceBox = el("details", { class: "sec-fold" }, [el("summary", { text: "発行のしかた" })]);
|
|
18285
18542
|
for (var gi = 0; gi < guidance.length; gi++) guidanceBox.appendChild(guidance[gi]);
|
|
18286
|
-
//
|
|
18287
|
-
// shots
|
|
18288
|
-
|
|
18543
|
+
// **手順の動画+画像(#547・#1353)。** 文章(guidance)の下に足す。
|
|
18544
|
+
// meta.guide を持つ種別(Figma)はカルーセル1つに乗せる。meta.guide も meta.shots も
|
|
18545
|
+
// 持たない種別(Backlog)ではこの節は素通りする
|
|
18546
|
+
if (meta.guide) {
|
|
18547
|
+
var carousel = buildGuideCarousel(kind, meta.guide);
|
|
18548
|
+
if (carousel) guidanceBox.appendChild(carousel);
|
|
18549
|
+
} else if (meta.shots) {
|
|
18550
|
+
// 後方互換(旧形)。今のところ参照するエントリは無い——将来 Google Chat/Meet を
|
|
18551
|
+
// この口に載せ替えるまでの踏み板として残す
|
|
18289
18552
|
for (var si = 0; si < meta.shots.length; si++) {
|
|
18290
18553
|
var shot = meta.shots[si];
|
|
18291
18554
|
guidanceBox.appendChild(el("p", { class: "sec-how", text: shot.heading }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takagaki/cortex-decisions-viewer",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.181",
|
|
4
4
|
"description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"cortex-decisions-viewer": "dist/cli.js"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"assets",
|
|
17
|
+
"!assets/guides/backlog-poster.jpg",
|
|
18
|
+
"!assets/guides/backlog-shots"
|
|
16
19
|
],
|
|
17
20
|
"engines": {
|
|
18
21
|
"node": ">=18"
|