@takagaki/cortex-decisions-viewer 0.12.200 → 0.12.214
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/README.md +4 -2
- package/dist/build.js +123 -85
- package/dist/canonical-evidence.js +11 -191
- package/dist/change-log.js +1 -1
- package/dist/config-flow.js +462 -0
- package/dist/config-mode.js +57 -0
- package/dist/confirmed.js +2 -2
- package/dist/engine/decision-v2-fields.js +188 -0
- package/dist/engine/ref-tokens.js +276 -0
- package/dist/engine/related-materials-field.js +148 -0
- package/dist/engine/supersedes-index.js +257 -0
- package/dist/google-confirm-page.js +14 -3
- package/dist/ledger-dir.js +1 -0
- package/dist/memory-dir.js +40 -0
- package/dist/memory-evidence.js +12 -6
- package/dist/rederivation.js +17 -8
- package/dist/render.js +909 -78
- package/dist/story.js +13 -9
- package/dist/supersedes.js +12 -110
- package/dist/view-dir.js +120 -3
- package/package.json +2 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// engine-src/decision-v2-fields.mjs
|
|
21
|
+
var decision_v2_fields_exports = {};
|
|
22
|
+
__export(decision_v2_fields_exports, {
|
|
23
|
+
DECIDED_BY_KEYS: () => DECIDED_BY_KEYS,
|
|
24
|
+
DECISION_DECIDED_BY_FIELD: () => DECISION_DECIDED_BY_FIELD,
|
|
25
|
+
DECISION_FIRMNESS_FIELD: () => DECISION_FIRMNESS_FIELD,
|
|
26
|
+
DECISION_FIRMNESS_READ_VALUES: () => DECISION_FIRMNESS_READ_VALUES,
|
|
27
|
+
DECISION_FIRMNESS_VALUES: () => DECISION_FIRMNESS_VALUES,
|
|
28
|
+
DECISION_SCENE_FIELD: () => DECISION_SCENE_FIELD,
|
|
29
|
+
DECISION_SCENE_KEYS: () => DECISION_SCENE_KEYS,
|
|
30
|
+
DECISION_STRAND_FIELD: () => DECISION_STRAND_FIELD,
|
|
31
|
+
DECISION_TOPIC_FIELD: () => DECISION_TOPIC_FIELD,
|
|
32
|
+
DECISION_V2_FIELDS: () => DECISION_V2_FIELDS,
|
|
33
|
+
decidedByEntryOf: () => decidedByEntryOf,
|
|
34
|
+
decidedByIssues: () => decidedByIssues,
|
|
35
|
+
decidersLabelOf: () => decidersLabelOf,
|
|
36
|
+
decidersOf: () => decidersOf,
|
|
37
|
+
decisionSceneIssues: () => decisionSceneIssues,
|
|
38
|
+
decisionStrandIssues: () => decisionStrandIssues,
|
|
39
|
+
decisionV2FieldsOf: () => decisionV2FieldsOf,
|
|
40
|
+
decisionV2Issues: () => decisionV2Issues,
|
|
41
|
+
memberNameOfId: () => memberNameOfId
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(decision_v2_fields_exports);
|
|
44
|
+
var DECISION_FIRMNESS_FIELD = "firmness";
|
|
45
|
+
var DECISION_TOPIC_FIELD = "topic";
|
|
46
|
+
var DECISION_DECIDED_BY_FIELD = "decided_by";
|
|
47
|
+
var DECISION_V2_FIELDS = Object.freeze([DECISION_FIRMNESS_FIELD, DECISION_TOPIC_FIELD, DECISION_DECIDED_BY_FIELD]);
|
|
48
|
+
var DECISION_STRAND_FIELD = "strand";
|
|
49
|
+
var DECISION_FIRMNESS_VALUES = Object.freeze(["\u78BA\u5B9A", "\u4EEE"]);
|
|
50
|
+
var DECISION_FIRMNESS_READ_VALUES = Object.freeze([...DECISION_FIRMNESS_VALUES, "\u691C\u8A0E\u4E2D"]);
|
|
51
|
+
var DECIDED_BY_KEYS = Object.freeze(["member", "name", "url"]);
|
|
52
|
+
var MEMBER_ID_RE = /^member:\S/;
|
|
53
|
+
var URL_RE = /^https?:\/\/\S+$/;
|
|
54
|
+
var isText = (v) => typeof v === "string" && v.trim() !== "";
|
|
55
|
+
var written = (fm, field) => Object.hasOwn(fm, field) && fm[field] != null && fm[field] !== "";
|
|
56
|
+
function decidedByEntryOf(entry) {
|
|
57
|
+
if (entry == null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
58
|
+
return { issue: "\u8981\u7D20\u306F member \u304B name \u3092\u6301\u3064\u30DE\u30C3\u30D7\u3067\u66F8\u304F" };
|
|
59
|
+
}
|
|
60
|
+
const extra = Object.keys(entry).filter((k) => !DECIDED_BY_KEYS.includes(k));
|
|
61
|
+
if (extra.length) return { issue: `\u8981\u7D20\u306B\u77E5\u3089\u306A\u3044\u9375\u304C\u3042\u308B: ${extra.join(", ")}\uFF08\u66F8\u3051\u308B\u306E\u306F ${DECIDED_BY_KEYS.join(" / ")}\uFF09` };
|
|
62
|
+
const hasMember = Object.hasOwn(entry, "member");
|
|
63
|
+
const hasName = Object.hasOwn(entry, "name");
|
|
64
|
+
if (hasMember && (hasName || Object.hasOwn(entry, "url"))) {
|
|
65
|
+
return { issue: "member \u3068 name\u30FBurl \u306F\u540C\u3058\u8981\u7D20\u306B\u66F8\u304B\u306A\u3044\uFF08\u7D50\u3079\u305F\u4EBA\u306F member \u3060\u3051\uFF09" };
|
|
66
|
+
}
|
|
67
|
+
if (hasMember) {
|
|
68
|
+
if (typeof entry.member !== "string" || !MEMBER_ID_RE.test(entry.member)) {
|
|
69
|
+
return { issue: "member \u306F\u95A2\u4FC2\u8005\u306E id\uFF08member:\u3014\u6C0F\u540D\u3015\uFF09\u3067\u66F8\u304F" };
|
|
70
|
+
}
|
|
71
|
+
return { entry: { member: entry.member } };
|
|
72
|
+
}
|
|
73
|
+
if (!hasName) return { issue: "\u8981\u7D20\u306B member \u3082 name \u3082\u7121\u3044" };
|
|
74
|
+
if (!isText(entry.name)) return { issue: "name \u306F\u8868\u8A18\u306E\u6587\u5B57\u5217\u3067\u66F8\u304F" };
|
|
75
|
+
if (entry.url == null) return { entry: { name: entry.name } };
|
|
76
|
+
if (typeof entry.url !== "string" || !URL_RE.test(entry.url)) {
|
|
77
|
+
return { issue: "url \u306F\u5143\u306E\u767A\u8A00\u306E http(s) \u306E URL \u3067\u66F8\u304F" };
|
|
78
|
+
}
|
|
79
|
+
return { entry: { name: entry.name, url: entry.url } };
|
|
80
|
+
}
|
|
81
|
+
function decisionV2Issues(fm) {
|
|
82
|
+
const out = [];
|
|
83
|
+
if (fm == null || typeof fm !== "object") return out;
|
|
84
|
+
if (written(fm, DECISION_FIRMNESS_FIELD) && !DECISION_FIRMNESS_READ_VALUES.includes(fm[DECISION_FIRMNESS_FIELD])) {
|
|
85
|
+
out.push({
|
|
86
|
+
code: "bad_vocab",
|
|
87
|
+
field: DECISION_FIRMNESS_FIELD,
|
|
88
|
+
message: `firmness\u306F${DECISION_FIRMNESS_READ_VALUES.join("|")}\uFF08\u5B9F\u969B: ${fm[DECISION_FIRMNESS_FIELD]}\uFF09`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (written(fm, DECISION_TOPIC_FIELD) && !isText(fm[DECISION_TOPIC_FIELD])) {
|
|
92
|
+
out.push({
|
|
93
|
+
code: "bad_format",
|
|
94
|
+
field: DECISION_TOPIC_FIELD,
|
|
95
|
+
message: "topic\u306F\u8AD6\u70B9\u306E\u672D\u306E\u540D\u524D\u30921\u3064\u306E\u6587\u5B57\u5217\u3067\u66F8\u304F"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
out.push(...decidedByIssues(fm));
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function decidedByIssues(fm) {
|
|
102
|
+
const out = [];
|
|
103
|
+
if (fm == null || typeof fm !== "object" || !written(fm, DECISION_DECIDED_BY_FIELD)) return out;
|
|
104
|
+
const list = fm[DECISION_DECIDED_BY_FIELD];
|
|
105
|
+
if (!Array.isArray(list)) {
|
|
106
|
+
out.push({
|
|
107
|
+
code: "bad_format",
|
|
108
|
+
field: DECISION_DECIDED_BY_FIELD,
|
|
109
|
+
message: "decided_by\u306F\u30EA\u30B9\u30C8\u3067\u66F8\u304F\uFF08\u7A7A\u3067\u3082\u3088\u3044\u3002\u8981\u7D20\u306F { member: member:\u3014\u6C0F\u540D\u3015 } \u304B { name, url }\uFF09"
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
list.forEach((entry, i) => {
|
|
113
|
+
const read = decidedByEntryOf(entry);
|
|
114
|
+
if (read.issue) out.push({ code: "bad_format", field: DECISION_DECIDED_BY_FIELD, message: `decided_by[${i}]: ${read.issue}` });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
function decisionStrandIssues(fm) {
|
|
120
|
+
if (fm == null || typeof fm !== "object" || !written(fm, DECISION_STRAND_FIELD) || isText(fm[DECISION_STRAND_FIELD])) return [];
|
|
121
|
+
return [{ code: "bad_format", field: DECISION_STRAND_FIELD, message: "strand\u306F\u8A71\u306E\u7B4B\u306E\u540D\u524D\u30921\u3064\u306E\u6587\u5B57\u5217\u3067\u66F8\u304F" }];
|
|
122
|
+
}
|
|
123
|
+
var DECISION_SCENE_FIELD = "scene";
|
|
124
|
+
var DECISION_SCENE_KEYS = Object.freeze(["venue", "date", "proposed_by", "agreed_by"]);
|
|
125
|
+
var SCENE_DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
126
|
+
var sceneDayOf = (v) => v instanceof Date && !Number.isNaN(v.getTime()) ? v.toISOString().slice(0, 10) : typeof v === "string" ? v.trim() : "";
|
|
127
|
+
function decisionSceneIssues(fm) {
|
|
128
|
+
if (fm == null || typeof fm !== "object" || !written(fm, DECISION_SCENE_FIELD)) return [];
|
|
129
|
+
const s = fm[DECISION_SCENE_FIELD];
|
|
130
|
+
const bad = (message) => [{ code: "bad_format", field: DECISION_SCENE_FIELD, message }];
|
|
131
|
+
if (typeof s !== "object" || Array.isArray(s)) return bad("scene\u306F venue\u30FBdate\u30FBproposed_by\u30FBagreed_by \u3092\u6301\u3064\u30DE\u30C3\u30D7\u3067\u66F8\u304F");
|
|
132
|
+
const extra = Object.keys(s).filter((k) => !DECISION_SCENE_KEYS.includes(k));
|
|
133
|
+
if (extra.length) return bad(`scene\u306B\u77E5\u3089\u306A\u3044\u9375\u304C\u3042\u308B: ${extra.join(", ")}\uFF08\u66F8\u3051\u308B\u306E\u306F ${DECISION_SCENE_KEYS.join(" / ")}\uFF09`);
|
|
134
|
+
if (!SCENE_DAY_RE.test(sceneDayOf(s.date))) return bad("scene.date\u306F\u6C7A\u3081\u305F\u65E5\u3092 YYYY-MM-DD \u3067\u66F8\u304F");
|
|
135
|
+
if (Object.hasOwn(s, "venue") && !isText(s.venue)) return bad("scene.venue\u306F\u5834\u306E\u540D\u524D\u3092\u6587\u5B57\u5217\u3067\u66F8\u304F");
|
|
136
|
+
for (const k of ["proposed_by", "agreed_by"]) {
|
|
137
|
+
if (!Object.hasOwn(s, k)) continue;
|
|
138
|
+
if (!Array.isArray(s[k]) || s[k].some((x) => typeof x !== "string" || !MEMBER_ID_RE.test(x))) return bad(`scene.${k}\u306F\u95A2\u4FC2\u8005\u306E id\uFF08member:\u2026\uFF09\u306E\u30EA\u30B9\u30C8\u3067\u66F8\u304F`);
|
|
139
|
+
}
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
function decisionV2FieldsOf(fm) {
|
|
143
|
+
const src = fm && typeof fm === "object" ? fm : {};
|
|
144
|
+
const firmness = DECISION_FIRMNESS_READ_VALUES.includes(src[DECISION_FIRMNESS_FIELD]) ? src[DECISION_FIRMNESS_FIELD] : null;
|
|
145
|
+
const topic = isText(src[DECISION_TOPIC_FIELD]) ? src[DECISION_TOPIC_FIELD].trim() : null;
|
|
146
|
+
const raw = src[DECISION_DECIDED_BY_FIELD];
|
|
147
|
+
const decidedBy = Array.isArray(raw) ? raw.map(decidedByEntryOf).filter((r) => r.entry).map((r) => r.entry) : null;
|
|
148
|
+
return { firmness, topic, decidedBy };
|
|
149
|
+
}
|
|
150
|
+
function decidersOf(fm, { deciders } = {}) {
|
|
151
|
+
const src = fm && typeof fm === "object" ? fm : {};
|
|
152
|
+
const base = Array.isArray(deciders) ? deciders : Array.isArray(src.deciders) ? src.deciders : [];
|
|
153
|
+
const names = base.map((v) => String(v ?? "").trim()).filter(Boolean);
|
|
154
|
+
const memberIds = [];
|
|
155
|
+
for (const entry of decisionV2FieldsOf(src).decidedBy || []) {
|
|
156
|
+
if (entry.member) memberIds.push(entry.member);
|
|
157
|
+
else if (entry.name && entry.name.trim()) names.push(entry.name.trim());
|
|
158
|
+
}
|
|
159
|
+
return { names, memberIds: [...new Set(memberIds)] };
|
|
160
|
+
}
|
|
161
|
+
var memberNameOfId = (id) => String(id ?? "").trim().replace(/^member:/, "");
|
|
162
|
+
function decidersLabelOf(decision) {
|
|
163
|
+
const names = Array.isArray(decision?.deciders) ? decision.deciders : [];
|
|
164
|
+
const ids = Array.isArray(decision?.deciderMemberIds) ? decision.deciderMemberIds.map(memberNameOfId).filter(Boolean) : [];
|
|
165
|
+
return [...names, ...ids.filter((n) => !names.includes(n))].join("\u30FB");
|
|
166
|
+
}
|
|
167
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
168
|
+
0 && (module.exports = {
|
|
169
|
+
DECIDED_BY_KEYS,
|
|
170
|
+
DECISION_DECIDED_BY_FIELD,
|
|
171
|
+
DECISION_FIRMNESS_FIELD,
|
|
172
|
+
DECISION_FIRMNESS_READ_VALUES,
|
|
173
|
+
DECISION_FIRMNESS_VALUES,
|
|
174
|
+
DECISION_SCENE_FIELD,
|
|
175
|
+
DECISION_SCENE_KEYS,
|
|
176
|
+
DECISION_STRAND_FIELD,
|
|
177
|
+
DECISION_TOPIC_FIELD,
|
|
178
|
+
DECISION_V2_FIELDS,
|
|
179
|
+
decidedByEntryOf,
|
|
180
|
+
decidedByIssues,
|
|
181
|
+
decidersLabelOf,
|
|
182
|
+
decidersOf,
|
|
183
|
+
decisionSceneIssues,
|
|
184
|
+
decisionStrandIssues,
|
|
185
|
+
decisionV2FieldsOf,
|
|
186
|
+
decisionV2Issues,
|
|
187
|
+
memberNameOfId
|
|
188
|
+
});
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// engine-src/ref-tokens.mjs
|
|
21
|
+
var ref_tokens_exports = {};
|
|
22
|
+
__export(ref_tokens_exports, {
|
|
23
|
+
canonicalEvidence: () => canonicalEvidence,
|
|
24
|
+
driveFileUrl: () => driveFileUrl,
|
|
25
|
+
gfileKeyOf: () => gfileKeyOf,
|
|
26
|
+
githubRefToken: () => githubRefToken,
|
|
27
|
+
isPathLikeTarget: () => isPathLikeTarget,
|
|
28
|
+
matchableRef: () => matchableRef,
|
|
29
|
+
refTokenSet: () => refTokenSet,
|
|
30
|
+
refTokens: () => refTokens,
|
|
31
|
+
refsOverlap: () => refsOverlap,
|
|
32
|
+
rowRefTokens: () => rowRefTokens,
|
|
33
|
+
syntaxNormalizedUrl: () => syntaxNormalizedUrl,
|
|
34
|
+
urlsInText: () => urlsInText
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(ref_tokens_exports);
|
|
37
|
+
var ISSUE_KEY_RE = /\b[A-Z][A-Z0-9_]{1,20}-\d+\b/g;
|
|
38
|
+
var URL_RE = /https?:\/\/[^\s<>()"'|、。」]+/g;
|
|
39
|
+
var GH_SHORT_RE = /\b([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)#(\d+)\b/g;
|
|
40
|
+
var GH_URL_RE = /^https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/]+)\/(?:issues|pull)\/(\d+)/i;
|
|
41
|
+
function normalizeUrl(raw) {
|
|
42
|
+
return String(raw).replace(/[).,;:、。]+$/, "").replace(/\/+$/, "").toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
var githubRefToken = (owner, repo, number) => `gh:${String(owner).toLowerCase()}/${String(repo).toLowerCase()}#${number}`;
|
|
45
|
+
function legacyUrlTokens(url) {
|
|
46
|
+
const normalized = normalizeUrl(url);
|
|
47
|
+
const gh = normalized.match(GH_URL_RE);
|
|
48
|
+
return gh ? [githubRefToken(gh[1], gh[2], gh[3]), normalized] : [normalized];
|
|
49
|
+
}
|
|
50
|
+
var driveFileUrl = (fileId) => `https://drive.google.com/file/d/${encodeURIComponent(fileId)}/view`;
|
|
51
|
+
function extractRefTokens(value, keyer = null) {
|
|
52
|
+
const raw = String(value ?? "").trim();
|
|
53
|
+
if (!raw) return { tokens: [], matchable: false };
|
|
54
|
+
if (keyer) return extractKeyedTokens(raw, keyer);
|
|
55
|
+
const out = /* @__PURE__ */ new Set();
|
|
56
|
+
for (const url of raw.match(URL_RE) || []) for (const t of legacyUrlTokens(url)) out.add(t);
|
|
57
|
+
for (const key of raw.match(ISSUE_KEY_RE) || []) out.add(key.toLowerCase());
|
|
58
|
+
for (const m of raw.matchAll(GH_SHORT_RE)) out.add(githubRefToken(m[1], m[2], m[3]));
|
|
59
|
+
if (out.size) return { tokens: [...out], matchable: true };
|
|
60
|
+
return wholeValueToken(raw);
|
|
61
|
+
}
|
|
62
|
+
var wholeValueToken = (raw) => ({ tokens: [raw.replace(/\/+$/, "").toLowerCase()], matchable: false });
|
|
63
|
+
function extractKeyedTokens(raw, keyer) {
|
|
64
|
+
const urls = raw.match(URL_RE) || [];
|
|
65
|
+
const whole = typeof keyer.canonical === "function" ? keyer.canonical(raw) : null;
|
|
66
|
+
const wholeKeys = keyer.keysOf(raw);
|
|
67
|
+
if (wholeKeys && wholeKeys.length && !(whole && whole.kind === "url" && urls.length)) {
|
|
68
|
+
return { tokens: [...new Set(wholeKeys)], matchable: true };
|
|
69
|
+
}
|
|
70
|
+
const out = /* @__PURE__ */ new Set();
|
|
71
|
+
let residue = raw;
|
|
72
|
+
for (const url of urls) {
|
|
73
|
+
const keys = keyer.keysOf(url);
|
|
74
|
+
const c = typeof keyer.canonical === "function" ? keyer.canonical(url) : null;
|
|
75
|
+
if (keys && keys.length) for (const k of keys) out.add(k);
|
|
76
|
+
else for (const t of legacyUrlTokens(url)) out.add(t);
|
|
77
|
+
if (c && c.kind === "url") {
|
|
78
|
+
const gh = normalizeUrl(url).match(GH_URL_RE);
|
|
79
|
+
if (gh) out.add(githubRefToken(gh[1], gh[2], gh[3]));
|
|
80
|
+
}
|
|
81
|
+
if (c && c.kind !== "url") residue = residue.split(url).join(" ");
|
|
82
|
+
}
|
|
83
|
+
for (const key of residue.match(ISSUE_KEY_RE) || []) out.add(key.toLowerCase());
|
|
84
|
+
for (const m of residue.matchAll(GH_SHORT_RE)) out.add(githubRefToken(m[1], m[2], m[3]));
|
|
85
|
+
if (out.size) return { tokens: [...out], matchable: true };
|
|
86
|
+
return wholeValueToken(raw);
|
|
87
|
+
}
|
|
88
|
+
function refTokens(value, keyer = null) {
|
|
89
|
+
return extractRefTokens(value, keyer).tokens;
|
|
90
|
+
}
|
|
91
|
+
var matchableRef = (value, keyer = null) => extractRefTokens(value, keyer).matchable;
|
|
92
|
+
var refTokenSet = (values = [], keyer = null) => new Set((values || []).flatMap((v) => refTokens(v, keyer)));
|
|
93
|
+
function refsOverlap(a, b) {
|
|
94
|
+
const [small, large] = a.size <= b.size ? [a, b] : [b, a];
|
|
95
|
+
for (const token of small) if (large.has(token)) return true;
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
function rowRefTokens(row, keyer = null) {
|
|
99
|
+
const refs = row && row.refs || {};
|
|
100
|
+
const out = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const key of refs.issue_keys || []) for (const t of refTokens(key, keyer)) out.add(t);
|
|
102
|
+
for (const url of refs.urls || []) for (const t of refTokens(url, keyer)) out.add(t);
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
var TEXT_URL_RE = /https?:\/\/[^\s<>()\[\]"'`|、。」)】』>\u3000-\u303f\u3040-\u30ff\u3400-\u9fff\uff00-\uffef]+/g;
|
|
106
|
+
var JA_LETTER_RE = /[\u3040-\u30ff\u3400-\u9fff]/;
|
|
107
|
+
function urlsInText(text, stats = null) {
|
|
108
|
+
const out = [];
|
|
109
|
+
const seen = /* @__PURE__ */ new Set();
|
|
110
|
+
const src = String(text ?? "").replace(/&/g, "&");
|
|
111
|
+
for (const m of src.matchAll(TEXT_URL_RE)) {
|
|
112
|
+
if (m[0].endsWith("/") && JA_LETTER_RE.test(src[m.index + m[0].length] || "")) {
|
|
113
|
+
if (stats) stats.cut = (stats.cut || 0) + 1;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const url = m[0].replace(/[.,;:!?*~]+$/, "");
|
|
117
|
+
if (!/^https?:\/\/[^/?#]+/i.test(url) || seen.has(url)) continue;
|
|
118
|
+
seen.add(url);
|
|
119
|
+
out.push(url);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
var WHOLE_URL_RE = /^(?:https?:\/\/\S+|\[[^\]\n]*\]\(https?:\/\/[^)\s]+\))$/i;
|
|
124
|
+
function isPathLikeTarget(target) {
|
|
125
|
+
const t = String(target ?? "").trim();
|
|
126
|
+
return t.includes("/") && t.includes(".md") && !WHOLE_URL_RE.test(t);
|
|
127
|
+
}
|
|
128
|
+
var WHOLE_ISSUE_KEY_RE = /^([A-Z][A-Z0-9_]{1,20})-(\d+)$/;
|
|
129
|
+
var WHOLE_GH_SHORT_RE = /^([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)#(\d+)$/;
|
|
130
|
+
var URL_PARTS_RE = /^(https?):\/\/([^/?#\s]+)([^\s]*)$/i;
|
|
131
|
+
function syntaxNormalizedUrl(raw) {
|
|
132
|
+
let t = String(raw ?? "").trim();
|
|
133
|
+
for (let prev = ""; prev !== t; ) {
|
|
134
|
+
prev = t;
|
|
135
|
+
t = t.replace(/[).,;:、。]+$/, "").replace(/\/+$/, "");
|
|
136
|
+
}
|
|
137
|
+
const m = t.match(URL_PARTS_RE);
|
|
138
|
+
if (!m) return null;
|
|
139
|
+
const scheme = m[1].toLowerCase();
|
|
140
|
+
let host = m[2].toLowerCase();
|
|
141
|
+
if (scheme === "http" && host.endsWith(":80") || scheme === "https" && host.endsWith(":443")) host = host.replace(/:\d+$/, "");
|
|
142
|
+
if (!host) return null;
|
|
143
|
+
const rest = m[3].replace(/%[0-9a-f]{2}/gi, (x) => x.toUpperCase()).replace(/\/+(?=$|[?#])/, "");
|
|
144
|
+
return `${scheme}://${host}${rest}`;
|
|
145
|
+
}
|
|
146
|
+
function wholeUrlOf(value) {
|
|
147
|
+
const v = String(value ?? "").trim();
|
|
148
|
+
const m = v.match(/^(https?:\/\/\S+)$/i) || v.match(/^\[[^\]\n]*\]\((https?:\/\/[^)\s]+)\)$/i) || v.match(/^<(https?:\/\/[^|>\s]+)(?:\|[^>]*)?>$/i);
|
|
149
|
+
return m ? m[1].replace(/\\_/g, "_") : null;
|
|
150
|
+
}
|
|
151
|
+
var queryOf = (rest) => {
|
|
152
|
+
const q = rest.indexOf("?");
|
|
153
|
+
if (q < 0) return new URLSearchParams();
|
|
154
|
+
const h = rest.indexOf("#", q);
|
|
155
|
+
return new URLSearchParams(rest.slice(q + 1, h < 0 ? void 0 : h));
|
|
156
|
+
};
|
|
157
|
+
var pathOf = (rest) => rest.replace(/[?#].*$/, "");
|
|
158
|
+
var fragmentOf = (rest) => {
|
|
159
|
+
const h = rest.indexOf("#");
|
|
160
|
+
return h < 0 ? "" : rest.slice(h + 1);
|
|
161
|
+
};
|
|
162
|
+
var gfileKeyOf = (fileId) => `gfile:${fileId}`;
|
|
163
|
+
var GDOC_KINDS = /* @__PURE__ */ new Set(["document", "spreadsheets", "presentation", "forms"]);
|
|
164
|
+
var BACKLOG_HOST_RE = /\.(?:backlog\.jp|backlog\.com|backlogtool\.com)$/;
|
|
165
|
+
function canonicalOfUrl(url, ctx) {
|
|
166
|
+
const m = url.match(URL_PARTS_RE);
|
|
167
|
+
if (!m) return null;
|
|
168
|
+
const host = m[2];
|
|
169
|
+
const rest = m[3];
|
|
170
|
+
const p = pathOf(rest);
|
|
171
|
+
const q = queryOf(rest);
|
|
172
|
+
if (host === "docs.google.com") {
|
|
173
|
+
const g = p.match(/^\/(?:a\/[^/]+\/)?([a-z]+)\/(?:u\/\d+\/)?d\/([A-Za-z0-9_-]{10,})/);
|
|
174
|
+
if (g && GDOC_KINDS.has(g[1])) return { kind: "gdoc", key: gfileKeyOf(g[2]), url: `https://docs.google.com/${g[1]}/d/${g[2]}` };
|
|
175
|
+
const fd = p.match(/^\/(?:a\/[^/]+\/)?file\/d\/([A-Za-z0-9_-]{10,})/);
|
|
176
|
+
if (fd) return { kind: "drive", key: gfileKeyOf(fd[1]), url: driveFileUrl(fd[1]) };
|
|
177
|
+
}
|
|
178
|
+
if (host === "drive.google.com") {
|
|
179
|
+
const f = p.match(/^\/(?:a\/[^/]+\/)?file\/(?:u\/\d+\/)?d\/([A-Za-z0-9_-]{10,})/);
|
|
180
|
+
const id = f ? f[1] : /^\/(?:open|uc)$/.test(p) && /^[A-Za-z0-9_-]{10,}$/.test(q.get("id") || "") ? q.get("id") : "";
|
|
181
|
+
if (id) return { kind: "drive", key: gfileKeyOf(id), url: driveFileUrl(id) };
|
|
182
|
+
const d = p.match(/^\/drive\/(?:u\/\d+\/)?folders\/([A-Za-z0-9_-]{10,})/);
|
|
183
|
+
if (d) return { kind: "drive-folder", key: `gfolder:${d[1]}`, url: `https://drive.google.com/drive/folders/${d[1]}` };
|
|
184
|
+
}
|
|
185
|
+
if (host.endsWith(".slack.com")) {
|
|
186
|
+
const s = p.match(/^\/archives\/([A-Z0-9]+)(?:\/p(\d+))?$/);
|
|
187
|
+
if (s) {
|
|
188
|
+
const hosts = ctx.slackHosts instanceof Map ? ctx.slackHosts : new Map(Object.entries(ctx.slackHosts || {}));
|
|
189
|
+
const h = String(hosts.get(s[1]) || "").trim().toLowerCase() || host;
|
|
190
|
+
const thread = s[2] && /^\d+\.\d+$/.test(q.get("thread_ts") || "") ? q.get("thread_ts") : "";
|
|
191
|
+
const key = `slack:${s[1]}${s[2] ? `:p${s[2]}` : ""}`;
|
|
192
|
+
const tail = `${s[2] ? `/p${s[2]}` : ""}${thread ? `?thread_ts=${thread}` : ""}`;
|
|
193
|
+
return { kind: "slack", key, url: `https://${h}/archives/${s[1]}${tail}` };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (host === "chat.google.com" || host === "mail.google.com") {
|
|
197
|
+
const room = host === "chat.google.com" ? p.match(/^\/(?:u\/\d+\/)?room\/([A-Za-z0-9_-]+)/) : null;
|
|
198
|
+
const inMail = host === "mail.google.com" && /^\/chat(?:\/|$)/.test(p) ? fragmentOf(rest).match(/^chat\/space\/([A-Za-z0-9_-]+)/) : null;
|
|
199
|
+
const space = room && room[1] || inMail && inMail[1];
|
|
200
|
+
if (space) return { kind: "gchat", key: `gchat:${space}`, url: `https://chat.google.com/room/${space}` };
|
|
201
|
+
}
|
|
202
|
+
if (BACKLOG_HOST_RE.test(host)) {
|
|
203
|
+
const v = p.match(/^\/view\/([A-Z][A-Z0-9_]{1,20}-\d+)$/);
|
|
204
|
+
if (v) {
|
|
205
|
+
const c = fragmentOf(rest).match(/^comment-(\d+)$/);
|
|
206
|
+
const issue = v[1].toLowerCase();
|
|
207
|
+
return {
|
|
208
|
+
kind: "backlog",
|
|
209
|
+
key: c ? `${issue}#comment-${c[1]}` : issue,
|
|
210
|
+
url: `https://${host}/view/${v[1]}${c ? `#comment-${c[1]}` : ""}`,
|
|
211
|
+
...c ? { parentKey: issue } : {}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const w = p.match(/^\/alias\/wiki\/(\d+)$/);
|
|
215
|
+
if (w) return { kind: "backlog", key: `backlog:${host}:wiki:${w[1]}`, url: `https://${host}/alias/wiki/${w[1]}` };
|
|
216
|
+
const d = p.match(/^\/document\/([^/]+)\/([0-9a-f]{32})$/i);
|
|
217
|
+
if (d) return { kind: "backlog", key: `backlog:${host}:doc:${d[2].toLowerCase()}`, url: `https://${host}/document/${d[1]}/${d[2].toLowerCase()}` };
|
|
218
|
+
}
|
|
219
|
+
if (host === "figma.com" || host === "www.figma.com") {
|
|
220
|
+
const f = p.match(/^\/(design|file|proto|board)\/([A-Za-z0-9]{10,})(?:\/branch\/([A-Za-z0-9]{10,}))?(?:\/|$)/);
|
|
221
|
+
if (f) {
|
|
222
|
+
const fileKey = f[3] || f[2];
|
|
223
|
+
const node = String(q.get("node-id") || "").trim().replace(/%3A/gi, ":");
|
|
224
|
+
if (node && !/^\d+[-:]\d+$/.test(node)) return { kind: "url", key: `url:${url}`, url };
|
|
225
|
+
const n = node ? node.replace(":", "-") : "";
|
|
226
|
+
const seg = f[1] === "board" ? "board" : "design";
|
|
227
|
+
return { kind: "figma", key: `figma:${fileKey}${n ? `:${n.replace("-", ":")}` : ""}`, url: `https://www.figma.com/${seg}/${fileKey}${n ? `?node-id=${n}` : ""}` };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (host === "github.com" || host === "www.github.com") {
|
|
231
|
+
const g = p.match(/^\/([^/]+)\/([^/]+)\/(?:issues|pull|discussions)\/(\d+)$/);
|
|
232
|
+
if (g) {
|
|
233
|
+
const [o, r] = [g[1].toLowerCase(), g[2].toLowerCase()];
|
|
234
|
+
const issue = githubRefToken(o, r, g[3]);
|
|
235
|
+
const c = fragmentOf(rest).match(/^(issuecomment-\d+|discussioncomment-\d+|discussion_r\d+|pullrequestreview-\d+)$/);
|
|
236
|
+
return {
|
|
237
|
+
kind: "github",
|
|
238
|
+
key: c ? `${issue}#${c[1]}` : issue,
|
|
239
|
+
url: `https://github.com/${o}/${r}/issues/${g[3]}${c ? `#${c[1]}` : ""}`,
|
|
240
|
+
...c ? { parentKey: issue } : {}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return { kind: "url", key: `url:${url}`, url };
|
|
245
|
+
}
|
|
246
|
+
function canonicalEvidence(value, ctx = {}) {
|
|
247
|
+
const raw = String(value ?? "").trim();
|
|
248
|
+
if (!raw) return null;
|
|
249
|
+
const key = raw.match(WHOLE_ISSUE_KEY_RE);
|
|
250
|
+
if (key) {
|
|
251
|
+
const domains = ctx.backlogDomains instanceof Map ? ctx.backlogDomains : new Map(Object.entries(ctx.backlogDomains || {}));
|
|
252
|
+
const host = String(domains.get(key[1]) || "").trim().toLowerCase().replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
253
|
+
return host ? canonicalOfUrl(`https://${host}/view/${raw}`, ctx) : null;
|
|
254
|
+
}
|
|
255
|
+
const gh = raw.match(WHOLE_GH_SHORT_RE);
|
|
256
|
+
if (gh) return canonicalOfUrl(`https://github.com/${gh[1]}/${gh[2]}/issues/${gh[3]}`, ctx);
|
|
257
|
+
const url = wholeUrlOf(raw);
|
|
258
|
+
if (!url) return null;
|
|
259
|
+
const normalized = syntaxNormalizedUrl(url);
|
|
260
|
+
return normalized ? canonicalOfUrl(normalized, ctx) : null;
|
|
261
|
+
}
|
|
262
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
263
|
+
0 && (module.exports = {
|
|
264
|
+
canonicalEvidence,
|
|
265
|
+
driveFileUrl,
|
|
266
|
+
gfileKeyOf,
|
|
267
|
+
githubRefToken,
|
|
268
|
+
isPathLikeTarget,
|
|
269
|
+
matchableRef,
|
|
270
|
+
refTokenSet,
|
|
271
|
+
refTokens,
|
|
272
|
+
refsOverlap,
|
|
273
|
+
rowRefTokens,
|
|
274
|
+
syntaxNormalizedUrl,
|
|
275
|
+
urlsInText
|
|
276
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// engine-src/related-materials-field.mjs
|
|
21
|
+
var related_materials_field_exports = {};
|
|
22
|
+
__export(related_materials_field_exports, {
|
|
23
|
+
RELATED_MATERIALS_FIELD: () => RELATED_MATERIALS_FIELD,
|
|
24
|
+
RELATED_URL_MAX: () => RELATED_URL_MAX,
|
|
25
|
+
RESOURCE_JOINT: () => RESOURCE_JOINT,
|
|
26
|
+
RESOURCE_LABEL: () => RESOURCE_LABEL,
|
|
27
|
+
hasRelatedMaterialsField: () => hasRelatedMaterialsField,
|
|
28
|
+
isRelatedUrl: () => isRelatedUrl,
|
|
29
|
+
relatedMaterialsIssues: () => relatedMaterialsIssues,
|
|
30
|
+
relatedMaterialsLines: () => relatedMaterialsLines,
|
|
31
|
+
relatedMaterialsOf: () => relatedMaterialsOf,
|
|
32
|
+
relatedMaterialsRead: () => relatedMaterialsRead,
|
|
33
|
+
withRelatedMaterials: () => withRelatedMaterials
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(related_materials_field_exports);
|
|
36
|
+
|
|
37
|
+
// engine-src/frontmatter-bounds.mjs
|
|
38
|
+
var BOM = "\uFEFF";
|
|
39
|
+
var BLANK_LINE = /^[ \t\r]*$/;
|
|
40
|
+
var bareLine = (line) => String(line ?? "").replace(/\r$/, "");
|
|
41
|
+
var lineTailOf = (lines) => (lines || []).some((l) => String(l ?? "").endsWith("\r")) ? "\r" : "";
|
|
42
|
+
function frontmatterBounds(raw) {
|
|
43
|
+
const text = String(raw ?? "");
|
|
44
|
+
let at = text.startsWith(BOM) ? BOM.length : 0;
|
|
45
|
+
for (; ; ) {
|
|
46
|
+
const nl = text.indexOf("\n", at);
|
|
47
|
+
if (nl === -1) break;
|
|
48
|
+
if (!BLANK_LINE.test(text.slice(at, nl))) break;
|
|
49
|
+
at = nl + 1;
|
|
50
|
+
}
|
|
51
|
+
const openEnd = text.indexOf("\n", at);
|
|
52
|
+
if (openEnd === -1) return null;
|
|
53
|
+
const openLine = text.slice(at, openEnd);
|
|
54
|
+
if (openLine !== "---" && openLine !== "---\r") return null;
|
|
55
|
+
const innerStart = openEnd + 1;
|
|
56
|
+
const close = text.indexOf("\n---", innerStart);
|
|
57
|
+
if (close === -1) return null;
|
|
58
|
+
return {
|
|
59
|
+
head: text.slice(0, innerStart),
|
|
60
|
+
inner: text.slice(innerStart, close),
|
|
61
|
+
rest: text.slice(close),
|
|
62
|
+
body: text.slice(close + 4)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// engine-src/related-materials-field.mjs
|
|
67
|
+
var RELATED_MATERIALS_FIELD = "related_materials";
|
|
68
|
+
var RESOURCE_LABEL = "\u8CC7\u6599";
|
|
69
|
+
var RESOURCE_JOINT = "\u30FB";
|
|
70
|
+
var RELATED_URL_MAX = 2e3;
|
|
71
|
+
function isRelatedUrl(v) {
|
|
72
|
+
if (typeof v !== "string" || !v || v.length > RELATED_URL_MAX) return false;
|
|
73
|
+
if (!/^https?:\/\/[^\s<>"\[\]〔〕\u0000-\u001f\u007f]+$/i.test(v)) return false;
|
|
74
|
+
try {
|
|
75
|
+
const u = new URL(v);
|
|
76
|
+
return (u.protocol === "http:" || u.protocol === "https:") && u.hostname !== "";
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function relatedMaterialsOf(list) {
|
|
82
|
+
const items = Array.isArray(list) ? list : [];
|
|
83
|
+
const urls = [];
|
|
84
|
+
let dropped = 0;
|
|
85
|
+
for (const v of items) {
|
|
86
|
+
if (!isRelatedUrl(v) || urls.includes(v)) dropped += 1;
|
|
87
|
+
else urls.push(v);
|
|
88
|
+
}
|
|
89
|
+
return { urls, dropped };
|
|
90
|
+
}
|
|
91
|
+
function hasRelatedMaterialsField(text) {
|
|
92
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(String(text ?? ""));
|
|
93
|
+
return Boolean(m) && new RegExp(`^${RELATED_MATERIALS_FIELD}:`, "m").test(m[1]);
|
|
94
|
+
}
|
|
95
|
+
var written = (fm) => {
|
|
96
|
+
if (fm == null || typeof fm !== "object" || !Object.hasOwn(fm, RELATED_MATERIALS_FIELD)) return false;
|
|
97
|
+
const v = fm[RELATED_MATERIALS_FIELD];
|
|
98
|
+
return !(v == null || v === "" || Array.isArray(v) && v.length === 0);
|
|
99
|
+
};
|
|
100
|
+
function relatedMaterialsIssues(fm) {
|
|
101
|
+
if (!written(fm)) return [];
|
|
102
|
+
const v = fm[RELATED_MATERIALS_FIELD];
|
|
103
|
+
const field = RELATED_MATERIALS_FIELD;
|
|
104
|
+
if (!Array.isArray(v)) return [{ code: "bad_format", field, message: `${field}\u306F http(s) \u306E URL \u306E\u30EA\u30B9\u30C8\u3067\u66F8\u304F\uFF08\u7A7A\u306A\u3089\u6B04\u3054\u3068\u66F8\u304B\u306A\u3044\uFF09` }];
|
|
105
|
+
const out = [];
|
|
106
|
+
v.forEach((u, i) => {
|
|
107
|
+
if (!isRelatedUrl(u)) out.push({ code: "bad_format", field, message: `${field}[${i}] \u306F http(s) \u306E URL \u3067\u66F8\u304F` });
|
|
108
|
+
});
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
function relatedMaterialsRead(fm) {
|
|
112
|
+
const v = fm && typeof fm === "object" ? fm[RELATED_MATERIALS_FIELD] : null;
|
|
113
|
+
return Array.isArray(v) ? v.filter(isRelatedUrl) : [];
|
|
114
|
+
}
|
|
115
|
+
function relatedMaterialsLines(urls) {
|
|
116
|
+
const list = Array.isArray(urls) ? urls.filter(isRelatedUrl) : [];
|
|
117
|
+
return list.length ? [`${RELATED_MATERIALS_FIELD}:`, ...list.map((u) => ` - ${JSON.stringify(u)}`)] : [];
|
|
118
|
+
}
|
|
119
|
+
function withRelatedMaterials(raw, urls) {
|
|
120
|
+
const text = String(raw ?? "");
|
|
121
|
+
if (hasRelatedMaterialsField(text)) return null;
|
|
122
|
+
const at = frontmatterBounds(text);
|
|
123
|
+
if (!at) return null;
|
|
124
|
+
const add = relatedMaterialsLines(urls);
|
|
125
|
+
if (!add.length) return null;
|
|
126
|
+
const lines = at.inner.split("\n");
|
|
127
|
+
if (lines.some((l) => bareLine(l).startsWith(`${RELATED_MATERIALS_FIELD}:`))) return null;
|
|
128
|
+
const tail = lineTailOf(lines);
|
|
129
|
+
const own = add.map((l) => `${l}${tail}`);
|
|
130
|
+
const i = lines.findIndex((l) => /^(?:relations|references):(?:[ \t]|$)/.test(bareLine(l)));
|
|
131
|
+
if (i === -1) return `${at.head}${at.inner}
|
|
132
|
+
${own.join("\n")}${at.rest}`;
|
|
133
|
+
return `${at.head}${[...lines.slice(0, i), ...own, ...lines.slice(i)].join("\n")}${at.rest}`;
|
|
134
|
+
}
|
|
135
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
136
|
+
0 && (module.exports = {
|
|
137
|
+
RELATED_MATERIALS_FIELD,
|
|
138
|
+
RELATED_URL_MAX,
|
|
139
|
+
RESOURCE_JOINT,
|
|
140
|
+
RESOURCE_LABEL,
|
|
141
|
+
hasRelatedMaterialsField,
|
|
142
|
+
isRelatedUrl,
|
|
143
|
+
relatedMaterialsIssues,
|
|
144
|
+
relatedMaterialsLines,
|
|
145
|
+
relatedMaterialsOf,
|
|
146
|
+
relatedMaterialsRead,
|
|
147
|
+
withRelatedMaterials
|
|
148
|
+
});
|