@takagaki/cortex-decisions-viewer 0.12.82 → 0.12.84
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 +6 -0
- package/dist/engagement.js +2 -0
- package/dist/rederivation.js +36 -1
- package/dist/render.js +296 -33
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -141,6 +141,10 @@ const SANITIZE_OPTIONS = {
|
|
|
141
141
|
// タスクリスト `- [ ]` / `- [x]` のチェック状態を残すのに要る。
|
|
142
142
|
// 落とすと**装飾ではなく情報(未完了/完了の別)が消える**
|
|
143
143
|
"input",
|
|
144
|
+
// 折りたたみ(#702)。engine が関係者の記録の末尾に「この人が関わった決定」を
|
|
145
|
+
// details で畳んで書く。落とすと一覧が常時展開のベタテキストになる。
|
|
146
|
+
// 属性は 1 つも許可しない(open も通さない=常に閉じた状態で出る)
|
|
147
|
+
"details", "summary",
|
|
144
148
|
],
|
|
145
149
|
allowedAttributes: {
|
|
146
150
|
a: ["href", "target", "rel"],
|
|
@@ -213,6 +217,8 @@ async function renderGroundingSection(markdown) {
|
|
|
213
217
|
*/
|
|
214
218
|
function toSearchText(md) {
|
|
215
219
|
return md
|
|
220
|
+
.replace(/<!--[\s\S]*?-->/g, " ") // HTML コメント(engine の機械ブロックの印など・画面にも出ない)
|
|
221
|
+
.replace(/<\/?(details|summary)>/g, " ") // 折りたたみのタグ(#702)。文字は検索に要らない
|
|
216
222
|
.replace(/!\[[^\]]*\]\([^)]*\)/g, " ") // 画像
|
|
217
223
|
.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1") // リンクはラベルだけ残す
|
|
218
224
|
.replace(/[#>*_~`|]+/g, " ") // markdown記号・表の罫線
|
package/dist/engagement.js
CHANGED
|
@@ -78,6 +78,8 @@ exports.WEIGHTS = Object.freeze({
|
|
|
78
78
|
"record.confirmed": 2.0,
|
|
79
79
|
"record.edited": 2.0,
|
|
80
80
|
"record.deleted": 0,
|
|
81
|
+
// 本文を取り込んだ(engine #695)。人の行為ではないので 0(話した人は `utterance.spoken` で数える)
|
|
82
|
+
"body.captured": 0,
|
|
81
83
|
});
|
|
82
84
|
const DAY_MS = 86400000;
|
|
83
85
|
const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
package/dist/rederivation.js
CHANGED
|
@@ -289,6 +289,41 @@ function normalizeForCompare(text) {
|
|
|
289
289
|
* 使われない 1 行(`side: cm`)が移行で消えたことだった。`identities` のような欄が
|
|
290
290
|
* 足された晩に、説明文が 1 文字も変わっていないのに印を立てない。
|
|
291
291
|
*/
|
|
292
|
+
/** engine が関係者の記録の末尾に毎晩書く「この人が関わった決定」の印(#702) */
|
|
293
|
+
const MEMBER_DECISIONS_START = "<!-- cortex-engine:member-decisions -->";
|
|
294
|
+
const MEMBER_DECISIONS_END = "<!-- /cortex-engine:member-decisions -->";
|
|
295
|
+
/**
|
|
296
|
+
* 本文から engine の機械ブロック(「この人が関わった決定」)を取り除く(#702)。
|
|
297
|
+
*
|
|
298
|
+
* 🔴 **engine の写し(`scripts/rederive/content-sha.mjs`)と同じ規則。** 変えたら両方を変える。
|
|
299
|
+
* 開始の印**だけ**の行(行末の `\r` は無視)から、終了の印**だけ**の行までを(両端を含めて)消す。終了の印が
|
|
300
|
+
* 無ければ本文の終わりまで消す。塊の前後の空行も除き、両側に本文が残れば空行 1 行でつなぐ。そのあと末尾の空行を落とす。ブロックの無い本文は触らない。
|
|
301
|
+
*
|
|
302
|
+
* 毎晩書き換わる機械の一覧を「人が確認した版から中身が変わっています」に数えないため。
|
|
303
|
+
* ブロックの外を人が 1 文字でも直せば、従来どおり指紋は変わる。
|
|
304
|
+
*/
|
|
305
|
+
function stripMemberDecisions(body) {
|
|
306
|
+
const lines = body.split("\n");
|
|
307
|
+
// 行末の `\r` は見ない(CRLF の記録でも印の行として当てる。当てないと毎晩印が立つ)
|
|
308
|
+
const is = (marker) => (line) => line.replace(/\r$/, "") === marker;
|
|
309
|
+
const start = lines.findIndex(is(MEMBER_DECISIONS_START));
|
|
310
|
+
if (start < 0)
|
|
311
|
+
return body;
|
|
312
|
+
const endRel = lines.slice(start + 1).findIndex(is(MEMBER_DECISIONS_END));
|
|
313
|
+
const end = endRel < 0 ? lines.length : start + 1 + endRel + 1;
|
|
314
|
+
const isBlank = (line) => line.trim() === "";
|
|
315
|
+
// 塊の前後の空行も一緒に除き、両側に本文が残れば空行 1 行でつなぐ(engine の書き手と同じ規則・#702 実装レビュー R1)
|
|
316
|
+
const before = lines.slice(0, start);
|
|
317
|
+
while (before.length && isBlank(before[before.length - 1]))
|
|
318
|
+
before.pop();
|
|
319
|
+
const after = lines.slice(end);
|
|
320
|
+
while (after.length && isBlank(after[0]))
|
|
321
|
+
after.shift();
|
|
322
|
+
const kept = [...before, ...(before.length && after.length ? [""] : []), ...after];
|
|
323
|
+
while (kept.length && isBlank(kept[kept.length - 1]))
|
|
324
|
+
kept.pop();
|
|
325
|
+
return kept.join("\n");
|
|
326
|
+
}
|
|
292
327
|
function comparableContent(raw) {
|
|
293
328
|
let data = {};
|
|
294
329
|
let content = String(raw ?? "");
|
|
@@ -303,7 +338,7 @@ function comparableContent(raw) {
|
|
|
303
338
|
}
|
|
304
339
|
return {
|
|
305
340
|
description: normalizeForCompare(String(data.description ?? "")),
|
|
306
|
-
body: normalizeForCompare(content),
|
|
341
|
+
body: normalizeForCompare(stripMemberDecisions(content)),
|
|
307
342
|
};
|
|
308
343
|
}
|
|
309
344
|
/** 2 つの版で「説明文と本文」が同じか(frontmatter の他の欄の差は見ない) */
|
package/dist/render.js
CHANGED
|
@@ -4952,7 +4952,13 @@ button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
|
|
|
4952
4952
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px;
|
|
4953
4953
|
color: var(--muted); margin-top: 8px; overflow-wrap: anywhere;
|
|
4954
4954
|
}
|
|
4955
|
+
.ip-reason { font-size: 13px; color: var(--muted); line-height: 1.7; margin: -4px 0 8px; }
|
|
4955
4956
|
.ip-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
4957
|
+
/* ボタンの下に、押すと何が起きるかを小さく添える(#712) */
|
|
4958
|
+
.ip-choice { display: flex; flex-direction: column; gap: 4px; flex: 1 1 180px; max-width: 280px; }
|
|
4959
|
+
.ip-choice > a { align-self: flex-start; }
|
|
4960
|
+
.ip-choice-note { font-size: 11.5px; color: var(--muted); line-height: 1.5; }
|
|
4961
|
+
.proposal-more { font-size: 12.5px; color: var(--muted); }
|
|
4956
4962
|
.ip-why { margin-top: 10px; }
|
|
4957
4963
|
.ip-why-summary { cursor: pointer; font-size: 12.5px; color: var(--muted); }
|
|
4958
4964
|
.ip-why-text { font-size: 13px; color: var(--muted); line-height: 1.7; margin-top: 6px; }
|
|
@@ -5735,6 +5741,10 @@ button.ops-card-btn:disabled { opacity: .6; cursor: default; }
|
|
|
5735
5741
|
.body pre code { background: none; padding: 0; color: inherit; }
|
|
5736
5742
|
.body table { border-collapse: collapse; width: 100%; }
|
|
5737
5743
|
.body th, .body td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; }
|
|
5744
|
+
/* 本文中の折りたたみ(#702・関係者の記録の「この人が関わった決定」)。見出しの 1 行を押して開く */
|
|
5745
|
+
.body details { margin: 12px 0; }
|
|
5746
|
+
.body details > summary { cursor: pointer; }
|
|
5747
|
+
.body details[open] > summary { margin-bottom: 8px; }
|
|
5738
5748
|
.body-loading { color: var(--muted); font-size: 13px; padding: 8px 2px; }
|
|
5739
5749
|
|
|
5740
5750
|
/* 一行の結果表示(トースト)。#348 の案件アイコンで使う。
|
|
@@ -7371,16 +7381,90 @@ ${calendarAddUrl.toString()}
|
|
|
7371
7381
|
}
|
|
7372
7382
|
return ids.length === 1 ? ids[0] : null;
|
|
7373
7383
|
}
|
|
7384
|
+
// 見出しの道具の言い方(#712)。🔴 **liKindLabel は使わない**——あちらは語彙外を綴りのまま
|
|
7385
|
+
// 返す(表の「道具」の列ではそれが正しい)が、見出しに backlog_user のような綴りを出さない。
|
|
7386
|
+
// 語彙外は「外のツール」に寄せる。email / git_author は ipAskText が別の文で組む
|
|
7387
|
+
var IP_ASK_TOOL = {
|
|
7388
|
+
backlog_user: "Backlog", slack_user: "Slack", github_login: "GitHub",
|
|
7389
|
+
google_chat_user: "Google Chat", figma_user: "Figma", portal_user: "ポータル",
|
|
7390
|
+
};
|
|
7391
|
+
var IP_NAME_MAX = 40;
|
|
7374
7392
|
/**
|
|
7375
|
-
*
|
|
7393
|
+
* 見出しに使ってよい表示名(使えなければ空文字)。
|
|
7376
7394
|
*
|
|
7377
7395
|
* 🔴 **名乗り(display_name)は engine が出したときだけ使う。推測で名前を作らない。**
|
|
7378
|
-
*
|
|
7396
|
+
* 🔴 **名前に見えないものは無い扱い**: 種類つき ID の形・urn: を含む・数字だけ・メールの形・
|
|
7397
|
+
* ID の値と同じ(名乗りの無い夜に engine が値を名前の欄へ入れることがある)
|
|
7398
|
+
*/
|
|
7399
|
+
function ipDisplayName(one, typed) {
|
|
7400
|
+
var n = ipText(one.display_name);
|
|
7401
|
+
if (!n) return "";
|
|
7402
|
+
if (/^[A-Za-z][A-Za-z0-9_]*:/.test(n)) return "";
|
|
7403
|
+
if (n.toLowerCase().indexOf("urn:") >= 0) return "";
|
|
7404
|
+
if (/^[0-9]+$/.test(n)) return "";
|
|
7405
|
+
if (LI_EMAIL.test(n)) return "";
|
|
7406
|
+
if (n === typed.value) return "";
|
|
7407
|
+
var chars = Array.from(n);
|
|
7408
|
+
return chars.length > IP_NAME_MAX ? chars.slice(0, IP_NAME_MAX).join("") + "…" : n;
|
|
7409
|
+
}
|
|
7410
|
+
/**
|
|
7411
|
+
* カードの見出しの問い。🔴 **常に画面で組む(#712 で #704 の「question を優先」を覆した)。**
|
|
7412
|
+
* 名乗りの無い夜の engine の question は「backlog_user:…」のような機械の綴りを含むため。
|
|
7413
|
+
*/
|
|
7414
|
+
function ipAskText(one, typed) {
|
|
7415
|
+
var name = ipDisplayName(one, typed);
|
|
7416
|
+
var k = typed.kind;
|
|
7417
|
+
if (k === "email") {
|
|
7418
|
+
return name ? "メールで「" + name + "」と名乗っている人は、この人ですか?" : "このメールアドレスの持ち主は、この人ですか?";
|
|
7419
|
+
}
|
|
7420
|
+
if (k === "git_author") {
|
|
7421
|
+
return name ? "git のコミットで「" + name + "」と名乗っている人は、この人ですか?" : "この git のコミットの作者は、この人ですか?";
|
|
7422
|
+
}
|
|
7423
|
+
var tool = Object.prototype.hasOwnProperty.call(IP_ASK_TOOL, k) ? IP_ASK_TOOL[k] : "";
|
|
7424
|
+
if (name) return (tool ? tool + " の" : "外のツールの") + "「" + name + "」は、この人ですか?";
|
|
7425
|
+
return tool ? tool + " のアカウントは、この人ですか?" : "外のツールのアカウントは、この人ですか?";
|
|
7426
|
+
}
|
|
7427
|
+
/** 見出しの直下の 1 行(機械の事情を人の言葉で 1 つに言い分ける。機械の綴りは出さない) */
|
|
7428
|
+
function ipReasonLine(why) {
|
|
7429
|
+
if (/[0-90-9二三四五六七八九十]+ *人/.test(why)) return "名簿に当てはまる人が何人かいて、どの人か決められません";
|
|
7430
|
+
if (why.indexOf("読み") >= 0) return "読みは同じですが、同じ人か確かめる材料が足りません";
|
|
7431
|
+
if (why.indexOf("呼び名") >= 0) return "呼び名は同じですが、同じ人か確かめる材料が足りません";
|
|
7432
|
+
if (why.indexOf("何人か") >= 0 || why.indexOf("複数") >= 0) return "名簿に当てはまる人が何人かいて、どの人か決められません";
|
|
7433
|
+
return "名前は同じですが、同じ人か確かめる材料が足りません";
|
|
7434
|
+
}
|
|
7435
|
+
/**
|
|
7436
|
+
* 種類つき ID の綴り(backlog_user: など)に当たる正規表現。語彙の中の種類に加えて、
|
|
7437
|
+
* **語彙外の snake_case の種類**(zoom_user: 等)も当てる。https: や 12:30 は当てない
|
|
7379
7438
|
*/
|
|
7380
|
-
function
|
|
7381
|
-
return
|
|
7382
|
-
|
|
7383
|
-
|
|
7439
|
+
function ipMachineRe() {
|
|
7440
|
+
return new RegExp("(?:(?:" + Object.keys(LI_KIND_LABEL).join("|") + ")|[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_]*) *[::]");
|
|
7441
|
+
}
|
|
7442
|
+
/** 画面に出してはいけない機械の綴りを含むか(URN・欄名・パス・種類つき ID) */
|
|
7443
|
+
function ipHasMachineWords(t) {
|
|
7444
|
+
if (/(?:^|[^A-Za-z])urn *:/i.test(t)) return true;
|
|
7445
|
+
for (var i = 0; i < IP_MACHINE_WORDS.length; i++) if (t.indexOf(IP_MACHINE_WORDS[i]) >= 0) return true;
|
|
7446
|
+
return ipMachineRe().test(t);
|
|
7447
|
+
}
|
|
7448
|
+
// 「くわしく」から落とす文の目印(記録のパス・欄名・URN。人が読んでも判断に使えない)
|
|
7449
|
+
var IP_MACHINE_WORDS = ["urn:", "not_aliases", "aliases", "identities", ".md", "Cortex/", "/records/"];
|
|
7450
|
+
/**
|
|
7451
|
+
* 「くわしく」に残す文。why||reason を文に割り、機械の事情の文・問いの文(見出しと重複)・
|
|
7452
|
+
* 1 行の理由と同じ文を落とす。🔴 **種類つき ID(backlog_user: 等)を含む文も落とす**
|
|
7453
|
+
*(位置で縛らない。文の途中に出ても落とす)
|
|
7454
|
+
*/
|
|
7455
|
+
function ipWhyDetail(why, line) {
|
|
7456
|
+
var kindRe = ipMachineRe();
|
|
7457
|
+
var parts = why.match(/[^。.??!!]*[。.??!!]+|[^。.??!!]+$/g) || [];
|
|
7458
|
+
var keep = [];
|
|
7459
|
+
parts.forEach(function (raw) {
|
|
7460
|
+
var t = raw.trim();
|
|
7461
|
+
if (!t || t === line) return;
|
|
7462
|
+
for (var i = 0; i < IP_MACHINE_WORDS.length; i++) if (t.indexOf(IP_MACHINE_WORDS[i]) >= 0) return;
|
|
7463
|
+
if (kindRe.test(t)) return;
|
|
7464
|
+
if (/[??]$/.test(t)) return;
|
|
7465
|
+
keep.push(t);
|
|
7466
|
+
});
|
|
7467
|
+
return keep.join("");
|
|
7384
7468
|
}
|
|
7385
7469
|
/** 名寄せのボタンが読ませる先(対象の記録 と 関係者の規約 README) */
|
|
7386
7470
|
function ipTarget(rec, sectionName) {
|
|
@@ -7393,13 +7477,6 @@ ${calendarAddUrl.toString()}
|
|
|
7393
7477
|
function ipText(v) {
|
|
7394
7478
|
return typeof v === "string" && v.trim() !== "" ? v.trim() : "";
|
|
7395
7479
|
}
|
|
7396
|
-
/**
|
|
7397
|
-
* カードの見出しの問い。🔴 **engine の question を優先する**(便 A・#534 で「人への問い」と
|
|
7398
|
-
* 「機械の事情」を別の欄にした)。無い提案(便 A より前の形)は画面で組み立てる
|
|
7399
|
-
*/
|
|
7400
|
-
function ipAskText(one, typed) {
|
|
7401
|
-
return ipText(one.question) || ipQuestion(liKindLabel(typed.kind), typed.value, ipText(one.display_name));
|
|
7402
|
-
}
|
|
7403
7480
|
/** 折りたたみに入れる機械の事情。why があればそれ、無ければ reason(前の形) */
|
|
7404
7481
|
function ipWhyText(one) {
|
|
7405
7482
|
return ipText(one.why) || ipText(one.reason);
|
|
@@ -7458,46 +7535,232 @@ ${calendarAddUrl.toString()}
|
|
|
7458
7535
|
].concat(aiFenced(ipDataLines(one, typed))).concat([""]).concat(ipTail(rec));
|
|
7459
7536
|
return aiLink("ai-diff-btn", lines.join("\\n") + AI_COMMON, "別人です");
|
|
7460
7537
|
}
|
|
7461
|
-
|
|
7538
|
+
// 根拠の行は最初の 3 行まで。残りは「ほか N 件」の 1 行に畳む(#712)
|
|
7539
|
+
var EV_MAX_ROWS = 3;
|
|
7540
|
+
var EV_URN_HEAD = "urn:cortex:obs:";
|
|
7541
|
+
var EV_ISSUE_KEY = /^[A-Z][A-Z0-9_]*-[0-9]+$/;
|
|
7542
|
+
var EV_PROJ = /^[A-Z][A-Z0-9_]*$/;
|
|
7543
|
+
var EV_REPO = /^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/;
|
|
7544
|
+
// Slack / Google Chat の名前が引けなかった形(チャンネル ID のまま)
|
|
7545
|
+
var EV_CHANNEL_ID = /^[CGD][A-Z0-9]{6,}$/;
|
|
7546
|
+
/**
|
|
7547
|
+
* 観測の URN(urn:cortex:obs:<cap>:<workspace>:<ref>:<item>:<rev>)を読む。読めなければ null。
|
|
7548
|
+
* 🔴 **ちょうど 8 段でなければ読めない**(段の中の : は %3A で来る)。% の崩れも読めない扱い
|
|
7549
|
+
*/
|
|
7550
|
+
function evParseUrn(raw) {
|
|
7551
|
+
var seg = raw.split(":");
|
|
7552
|
+
if (seg.length !== 8) return null;
|
|
7553
|
+
var out = [];
|
|
7554
|
+
for (var i = 0; i < seg.length; i++) {
|
|
7555
|
+
try { out.push(decodeURIComponent(seg[i])); } catch (err) { return null; }
|
|
7556
|
+
}
|
|
7557
|
+
return { cap: out[3], ws: out[4], ref: out[5], item: out[6] };
|
|
7558
|
+
}
|
|
7559
|
+
/** fleet の外部ソース(type ごと)から、チャンネル・スペースの読める名前を引く(引けなければ空文字) */
|
|
7560
|
+
function evSourceName(type, ws, ref) {
|
|
7561
|
+
var rows = fleetVal("externalSources") || [];
|
|
7562
|
+
var wsOk = ws && ws !== "-";
|
|
7563
|
+
for (var i = 0; i < rows.length; i++) {
|
|
7564
|
+
var row = rows[i];
|
|
7565
|
+
if (!row || row.type !== type || String(row.ref || "") !== ref) continue;
|
|
7566
|
+
if (wsOk && row.workspace && String(row.workspace) !== ws) continue;
|
|
7567
|
+
var nm = typeof row.name === "string" ? row.name.trim() : "";
|
|
7568
|
+
// 🔴 **名前に見えない値は引けなかった扱い**(ID のまま・コロンや / や山括弧を含む・spaces/… のまま)
|
|
7569
|
+
if (!nm || nm === ref || EV_CHANNEL_ID.test(nm) || /[:\/<>]/.test(nm)) return "";
|
|
7570
|
+
return nm;
|
|
7571
|
+
}
|
|
7572
|
+
return "";
|
|
7573
|
+
}
|
|
7574
|
+
/** 行の文(span.proposal-source)。parts は文字列か {text, href} の並び。href は呼び出し側で検査済み */
|
|
7575
|
+
function evSpan(parts) {
|
|
7576
|
+
var span = el("span", { class: "proposal-source" });
|
|
7577
|
+
parts.forEach(function (x) {
|
|
7578
|
+
if (typeof x === "string") span.appendChild(document.createTextNode(x));
|
|
7579
|
+
else if (x.href) span.appendChild(el("a", { href: x.href, target: "_blank", rel: "noopener", text: x.text }));
|
|
7580
|
+
else span.appendChild(document.createTextNode(x.text));
|
|
7581
|
+
});
|
|
7582
|
+
return span;
|
|
7583
|
+
}
|
|
7584
|
+
/** Backlog の束(ref = <domain>/<PROJ>)→ 文の並び */
|
|
7585
|
+
function evBacklogRows(g) {
|
|
7586
|
+
var cut = g.ref.indexOf("/");
|
|
7587
|
+
var domain = cut >= 0 ? g.ref.slice(0, cut) : "";
|
|
7588
|
+
var proj = cut >= 0 ? g.ref.slice(cut + 1) : g.ref;
|
|
7589
|
+
var projOk = EV_PROJ.test(proj);
|
|
7590
|
+
var projLabel = projOk ? proj : "Backlog";
|
|
7591
|
+
// 🔴 **課題のリンクは、接続先のスペースと同じドメインのときだけ**(別のスペースの課題を
|
|
7592
|
+
// 接続先のスペースの URL で組まない)。el() は href を無害化しないので、ここで決める
|
|
7593
|
+
var bd = domain ? liBacklogDomain(domain) : "";
|
|
7594
|
+
var linkDomain = bd && bd === domain.toLowerCase() ? bd : "";
|
|
7595
|
+
var keys = [], bad = 0, comments = 0, wiki = 0, other = 0;
|
|
7596
|
+
g.items.forEach(function (it) {
|
|
7597
|
+
if (it.indexOf("issue:") === 0) {
|
|
7598
|
+
var key = it.slice(6);
|
|
7599
|
+
if (projOk && EV_ISSUE_KEY.test(key) && key.slice(0, key.lastIndexOf("-")) === proj) keys.push(key);
|
|
7600
|
+
else bad++;
|
|
7601
|
+
} else if (it.indexOf("issue-comment:") === 0) comments++;
|
|
7602
|
+
else if (it.indexOf("wiki:") === 0) wiki++;
|
|
7603
|
+
else other++;
|
|
7604
|
+
});
|
|
7605
|
+
var out = [];
|
|
7606
|
+
if (keys.length) {
|
|
7607
|
+
var parts = [];
|
|
7608
|
+
keys.slice(0, 3).forEach(function (key, i) {
|
|
7609
|
+
if (i > 0) parts.push("・");
|
|
7610
|
+
var shown = i === 0 ? key : key.slice(key.lastIndexOf("-") + 1);
|
|
7611
|
+
parts.push({ text: shown, href: linkDomain ? "https://" + linkDomain + "/view/" + key : "" });
|
|
7612
|
+
});
|
|
7613
|
+
var rest = Math.max(0, keys.length - 3) + bad;
|
|
7614
|
+
parts.push(rest ? " ほか " + rest + " 件の課題を書いています" : " の課題を書いています");
|
|
7615
|
+
out.push(parts);
|
|
7616
|
+
} else if (bad) {
|
|
7617
|
+
out.push([projLabel + " の課題を " + bad + " 件書いています"]);
|
|
7618
|
+
}
|
|
7619
|
+
if (comments) out.push([projLabel + " の課題に " + comments + " 回コメントしています"]);
|
|
7620
|
+
if (wiki) out.push([projLabel + " の Wiki を " + wiki + " 回更新しています"]);
|
|
7621
|
+
if (other) out.push(["ほかの記録 " + other + " 件"]);
|
|
7622
|
+
return out;
|
|
7623
|
+
}
|
|
7624
|
+
/** GitHub の束(ref = owner/repo)→ 文の並び */
|
|
7625
|
+
function evDevRows(g) {
|
|
7626
|
+
var ok = EV_REPO.test(g.ref) && g.ref.split("/").every(function (x) { return x !== "." && x !== ".."; });
|
|
7627
|
+
var repo = ok ? { text: g.ref, href: "https://github.com/" + g.ref } : { text: "GitHub のリポジトリ" };
|
|
7628
|
+
// 🔴 **PR は開いた・マージした・閉じたで別の item(pr:<n>@opened 等)になる**ので、@ より前で 1 件に数える。
|
|
7629
|
+
// コミットは commit:<sha> か素の sha(記録の確定)。それ以外の語はコミットに数えない
|
|
7630
|
+
var talk = [], commits = 0, rest = 0;
|
|
7631
|
+
g.items.forEach(function (it) {
|
|
7632
|
+
if (/^(?:issue|discussion|issue-comment|discussion-comment|pr|review|pr-comment):/.test(it)) {
|
|
7633
|
+
var k = it.indexOf("pr:") === 0 ? it.split("@")[0] : it;
|
|
7634
|
+
if (talk.indexOf(k) < 0) talk.push(k);
|
|
7635
|
+
} else if (it.indexOf("commit:") === 0 || /^[0-9a-f]{7,64}$/i.test(it)) commits++;
|
|
7636
|
+
else rest++;
|
|
7637
|
+
});
|
|
7638
|
+
// 名前のときだけ空白で区切る(「GitHub のリポジトリに」と続けて読む)
|
|
7639
|
+
var sep = ok ? " " : "";
|
|
7640
|
+
var out = [];
|
|
7641
|
+
if (talk.length) out.push([repo, sep + "の Issue・PR で " + talk.length + " 件書いています"]);
|
|
7642
|
+
if (commits) out.push([repo, sep + "に " + commits + " 件コミットしています"]);
|
|
7643
|
+
if (rest) out.push(["ほかの記録 " + rest + " 件"]);
|
|
7644
|
+
return out;
|
|
7645
|
+
}
|
|
7646
|
+
/** URN の束 1 つ → 文の並び(1 束から 2 行出ることがある) */
|
|
7647
|
+
function evGroupRows(g) {
|
|
7648
|
+
var n = g.items.length;
|
|
7649
|
+
if (g.cap === "issues") return evBacklogRows(g);
|
|
7650
|
+
if (g.cap === "dev") return evDevRows(g);
|
|
7651
|
+
if (g.cap === "chat") {
|
|
7652
|
+
var ch = evSourceName("slack", g.ws, g.ref);
|
|
7653
|
+
return [[ch ? (ch.charAt(0) === "#" ? ch : "#" + ch) + " で " + n + " 回発言しています" : "Slack のチャンネルで " + n + " 回発言しています"]];
|
|
7654
|
+
}
|
|
7655
|
+
if (g.cap === "google-chat") {
|
|
7656
|
+
var sp = evSourceName("google-chat", g.ws, g.ref);
|
|
7657
|
+
return [[sp ? sp + " で " + n + " 回発言しています" : "Google Chat のスペースで " + n + " 回発言しています"]];
|
|
7658
|
+
}
|
|
7659
|
+
if (g.cap === "design") return [["Figma で " + n + " 件コメントしています"]];
|
|
7660
|
+
if (g.cap === "meetings") return [["会議の記録に " + n + " 回出ています"]];
|
|
7661
|
+
return [["ほかの記録 " + n + " 件"]];
|
|
7662
|
+
}
|
|
7663
|
+
/**
|
|
7664
|
+
* 根拠の並び(従来のカードと名寄せのカードで**同じ出し方**。引用が無い行は出典だけ)。
|
|
7665
|
+
*
|
|
7666
|
+
* 🔴 **観測の URN(urn:cortex:obs:…)は生のまま出さない**(#712)。場ごと(cap・workspace・ref)に
|
|
7667
|
+
* 束ね、item で重複を除き(rev 違いは同じ)、「DEMO-10・11 の課題を書いています」のような人の文にする。
|
|
7668
|
+
* 読めない URN も生で出さず「ほかの記録 N 件」に畳む。URN でない行(engine の「場・日付」の文)は今までどおり
|
|
7669
|
+
*/
|
|
7462
7670
|
function proposalEvidenceList(one) {
|
|
7463
7671
|
if (!one.evidence || !one.evidence.length) return null;
|
|
7464
7672
|
var ul = el("ul", { class: "proposal-evidence" });
|
|
7673
|
+
// 行の並び(最初に出てきた順)。{ parts, quote } か { group } か { other }
|
|
7674
|
+
var order = [];
|
|
7675
|
+
var groups = {};
|
|
7676
|
+
var other = null;
|
|
7465
7677
|
one.evidence.forEach(function (e) {
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7678
|
+
if (!e || typeof e.source !== "string" || e.source.trim() === "") return;
|
|
7679
|
+
var raw = e.source.trim();
|
|
7680
|
+
// 🔴 **引用にも機械の綴りを出さない**(ID やパスを含む引用は落とし、出典だけ出す)
|
|
7681
|
+
var quote = typeof e.quote === "string" && !ipHasMachineWords(e.quote) ? e.quote : "";
|
|
7682
|
+
e = { source: raw, quote: quote };
|
|
7683
|
+
if (raw.toLowerCase().indexOf(EV_URN_HEAD) !== 0) {
|
|
7684
|
+
// 🔴 **URN でなくても機械の綴り(パス・種類つき ID 等)を含む出典は生で出さない**(「ほかの記録」に畳む)
|
|
7685
|
+
if (ipHasMachineWords(raw)) {
|
|
7686
|
+
if (!other) { other = { keys: [], quote: "" }; order.push({ other: other }); }
|
|
7687
|
+
if (other.keys.indexOf(raw) < 0) other.keys.push(raw);
|
|
7688
|
+
return;
|
|
7689
|
+
}
|
|
7690
|
+
// **引用が無くても出典は出す。** 引用は公開範囲フィルタで落ちることがあり、
|
|
7691
|
+
// そこで根拠ごと畳むと「根拠の無い提案」に見える
|
|
7692
|
+
order.push({ parts: [raw], quote: quote });
|
|
7693
|
+
return;
|
|
7694
|
+
}
|
|
7695
|
+
var u = evParseUrn(raw);
|
|
7696
|
+
var known = u && { issues: 1, chat: 1, "google-chat": 1, dev: 1, design: 1, meetings: 1 }[u.cap];
|
|
7697
|
+
if (!known) {
|
|
7698
|
+
// 読めない URN と語彙外の場は 1 行に畳む(生の綴りは出さない)
|
|
7699
|
+
var okey = u ? JSON.stringify([u.cap, u.ws, u.ref, u.item]) : raw;
|
|
7700
|
+
if (!other) { other = { keys: [], quote: "" }; order.push({ other: other }); }
|
|
7701
|
+
if (other.keys.indexOf(okey) < 0) other.keys.push(okey);
|
|
7702
|
+
if (!other.quote && e.quote) other.quote = e.quote;
|
|
7703
|
+
return;
|
|
7704
|
+
}
|
|
7705
|
+
var gkey = JSON.stringify([u.cap, u.ws, u.ref]);
|
|
7706
|
+
var g = groups[gkey];
|
|
7707
|
+
if (!g) { g = groups[gkey] = { cap: u.cap, ws: u.ws, ref: u.ref, items: [], quote: "" }; order.push({ group: g }); }
|
|
7708
|
+
if (g.items.indexOf(u.item) < 0) g.items.push(u.item);
|
|
7709
|
+
if (!g.quote && e.quote) g.quote = e.quote;
|
|
7710
|
+
});
|
|
7711
|
+
var rows = [];
|
|
7712
|
+
order.forEach(function (o) {
|
|
7713
|
+
if (o.parts) { rows.push(o); return; }
|
|
7714
|
+
if (o.other) { rows.push({ parts: ["ほかの記録 " + o.other.keys.length + " 件"], quote: o.other.quote }); return; }
|
|
7715
|
+
evGroupRows(o.group).forEach(function (parts, i) {
|
|
7716
|
+
rows.push({ parts: parts, quote: i === 0 ? o.group.quote : "" });
|
|
7717
|
+
});
|
|
7718
|
+
});
|
|
7719
|
+
rows.slice(0, EV_MAX_ROWS).forEach(function (r) {
|
|
7720
|
+
var li = el("li", {}, [evSpan(r.parts)]);
|
|
7721
|
+
if (r.quote) li.appendChild(el("blockquote", { class: "proposal-quote", text: r.quote }));
|
|
7470
7722
|
ul.appendChild(li);
|
|
7471
7723
|
});
|
|
7724
|
+
if (rows.length > EV_MAX_ROWS) {
|
|
7725
|
+
ul.appendChild(el("li", { class: "proposal-more", text: "ほか " + (rows.length - EV_MAX_ROWS) + " 件" }));
|
|
7726
|
+
}
|
|
7472
7727
|
return ul;
|
|
7473
7728
|
}
|
|
7474
7729
|
/** 「同じ人ですか」のカード 1 枚。**ボタンはカードごと**(束ねる側からは外してある) */
|
|
7475
7730
|
function ipCard(rec, sectionName, one, typed) {
|
|
7731
|
+
// 見出し(常に画面で組む)→ 理由の 1 行 → 根拠 → 2 つの選択 → くわしく(残りの文と問い合わせ用の ID)
|
|
7732
|
+
var why = ipWhyText(one);
|
|
7733
|
+
var reasonLine = ipReasonLine(why);
|
|
7476
7734
|
var item = el("div", { class: "proposal proposal-identity" }, [
|
|
7477
7735
|
el("div", { class: "ip-ask", text: ipAskText(one, typed) }),
|
|
7736
|
+
el("div", { class: "ip-reason", text: reasonLine }),
|
|
7478
7737
|
]);
|
|
7479
7738
|
var ev = proposalEvidenceList(one);
|
|
7480
7739
|
if (ev) item.appendChild(ev);
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7740
|
+
item.appendChild(el("div", { class: "ip-actions" }, [
|
|
7741
|
+
el("div", { class: "ip-choice" }, [
|
|
7742
|
+
ipSameBtn(rec, sectionName, rec.title, one, typed),
|
|
7743
|
+
el("div", { class: "ip-choice-note", text: "承認すると、翌晩からこの人の活動としてまとめて数えます" }),
|
|
7744
|
+
]),
|
|
7745
|
+
el("div", { class: "ip-choice" }, [
|
|
7746
|
+
ipDiffBtn(rec, sectionName, rec.title, one, typed),
|
|
7747
|
+
el("div", { class: "ip-choice-note", text: "承認すると、翌晩からこの人に結び付けません" }),
|
|
7748
|
+
]),
|
|
7749
|
+
]));
|
|
7750
|
+
// 🔴 **ID の値は「くわしく」の中にだけ置く**(問い合わせ用。判断は見出しと根拠でする)。
|
|
7751
|
+
// リンクにしてよい形(許可リストで検査・Slack と Backlog は行き先のドメインが 1 つに
|
|
7752
|
+
// 決まるときだけ)は「つながっている ID」と同じ規則で決める
|
|
7753
|
+
var idCell = el("div", { class: "ip-id" }, [el("span", { text: "問い合わせ用の ID: " })]);
|
|
7484
7754
|
var whole = typed.kind + ":" + typed.value;
|
|
7485
7755
|
var href = liHref(typed.kind, typed.value);
|
|
7486
7756
|
idCell.appendChild(href
|
|
7487
7757
|
? el("a", { href: href, target: "_blank", rel: "noopener", text: whole })
|
|
7488
7758
|
: el("span", { text: whole }));
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
var why = ipWhyText(one);
|
|
7495
|
-
if (why) {
|
|
7496
|
-
item.appendChild(el("details", { class: "ip-why" }, [
|
|
7497
|
-
el("summary", { class: "ip-why-summary", text: "機械が決めきれなかった理由" }),
|
|
7498
|
-
el("div", { class: "ip-why-text", text: why }),
|
|
7499
|
-
]));
|
|
7500
|
-
}
|
|
7759
|
+
var det = el("details", { class: "ip-why" }, [el("summary", { class: "ip-why-summary", text: "くわしく" })]);
|
|
7760
|
+
var detail = why ? ipWhyDetail(why, reasonLine) : "";
|
|
7761
|
+
if (detail) det.appendChild(el("div", { class: "ip-why-text", text: detail }));
|
|
7762
|
+
det.appendChild(idCell);
|
|
7763
|
+
item.appendChild(det);
|
|
7501
7764
|
if (one.createdAt) {
|
|
7502
7765
|
item.appendChild(el("div", { class: "proposal-at", text: (rtAgo(one.createdAt) || one.createdAt) + "の提案" }));
|
|
7503
7766
|
}
|
package/package.json
CHANGED