@mulmoclaude/markdown-utils 1.3.2 → 1.3.3
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/dom/externalLink.js
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
// invoke the wrapper from their own `@click` handler and check the
|
|
9
9
|
// return value to decide whether to fall through to plugin-specific
|
|
10
10
|
// navigation.
|
|
11
|
+
// `nodeType`, not `instanceof Element`: this package is also consumed outside a
|
|
12
|
+
// browser (the Node + jsdom test harness installs `window`/`document` but not
|
|
13
|
+
// the DOM constructors), where the bare `Element` global is a ReferenceError.
|
|
14
|
+
// nodeType 1 is the DOM standard's own element discriminant, and it stops at
|
|
15
|
+
// Element rather than HTMLElement because an inline <svg> inside a link is a
|
|
16
|
+
// legitimate click target that must still resolve to its anchor.
|
|
17
|
+
const ELEMENT_NODE = 1;
|
|
18
|
+
const isElementNode = (target) => target !== null && "nodeType" in target && target.nodeType === ELEMENT_NODE;
|
|
11
19
|
// Pure predicate: is `href` an absolute http(s) URL pointing at an
|
|
12
20
|
// origin different from `currentOrigin`? Used by
|
|
13
21
|
// `handleExternalLinkClick` below, and directly by tests.
|
|
@@ -42,8 +50,8 @@ export function handleExternalLinkClick(event) {
|
|
|
42
50
|
return false;
|
|
43
51
|
if (event.ctrlKey || event.metaKey || event.shiftKey)
|
|
44
52
|
return false;
|
|
45
|
-
const target = event
|
|
46
|
-
if (!target)
|
|
53
|
+
const { target } = event;
|
|
54
|
+
if (!isElementNode(target))
|
|
47
55
|
return false;
|
|
48
56
|
const anchor = target.closest("a");
|
|
49
57
|
if (!anchor)
|
|
@@ -203,8 +203,17 @@ export function transformResolvableUrlsInHtml(html, transform) {
|
|
|
203
203
|
return tag.replace(ATTR_ITER_RE, (...captures) => replaceAttrIfResolvable(captures, resolvableAttrs ?? [], srcsetAttrs ?? [], transform));
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
|
+
// Named view of `ATTR_ITER_RE`'s capture list (group 4, the full quoted
|
|
207
|
+
// value, is captured for clarity and skipped here). Groups that never
|
|
208
|
+
// participate come back `undefined`; the three the regex always fills fall
|
|
209
|
+
// back to `""` so a non-string can only ever blank the attribute, never
|
|
210
|
+
// reach the rewriter as if it were text.
|
|
211
|
+
function readAttrCaptures(captures) {
|
|
212
|
+
const [full, leading, name, eqWithSpaces, , doubleQuoted, singleQuoted, bare] = captures.map((value) => (typeof value === "string" ? value : undefined));
|
|
213
|
+
return { full: full ?? "", leading: leading ?? "", name: name ?? "", eqWithSpaces, doubleQuoted, singleQuoted, bare };
|
|
214
|
+
}
|
|
206
215
|
function replaceAttrIfResolvable(captures, resolvableAttrs, srcsetAttrs, transform) {
|
|
207
|
-
const
|
|
216
|
+
const { full, leading, name, eqWithSpaces, doubleQuoted, singleQuoted, bare } = readAttrCaptures(captures);
|
|
208
217
|
if (!eqWithSpaces)
|
|
209
218
|
return full;
|
|
210
219
|
const lowerName = name.toLowerCase();
|
|
@@ -89,6 +89,9 @@ function extractBracketedAlt(raw) {
|
|
|
89
89
|
}
|
|
90
90
|
return null;
|
|
91
91
|
}
|
|
92
|
+
// Declares the four fields it reads rather than the whole `Tokens.Image`:
|
|
93
|
+
// `type === "image"` leaves marked's open `Tokens.Generic` member in the
|
|
94
|
+
// union, and every read below already tolerates a missing value.
|
|
92
95
|
function rewriteImageToken(token, basePath) {
|
|
93
96
|
const href = (token.href ?? "").trim();
|
|
94
97
|
if (href === "" || shouldSkip(href))
|
|
@@ -147,15 +150,24 @@ export function rewriteImgSrcAttrsInHtml(html, basePath) {
|
|
|
147
150
|
function isSkippable(token) {
|
|
148
151
|
return token.type === "code" || token.type === "codespan";
|
|
149
152
|
}
|
|
153
|
+
// `Tokens.Generic` — the open member of marked's `Token` union — requires
|
|
154
|
+
// only a string `type` and a string `raw`, so proving those two is enough
|
|
155
|
+
// to call a value a `Token`.
|
|
156
|
+
function isToken(value) {
|
|
157
|
+
if (typeof value !== "object" || value === null)
|
|
158
|
+
return false;
|
|
159
|
+
return "type" in value && typeof value.type === "string" && "raw" in value && typeof value.raw === "string";
|
|
160
|
+
}
|
|
161
|
+
function toTokenArray(value) {
|
|
162
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
163
|
+
return null;
|
|
164
|
+
const candidates = value;
|
|
165
|
+
return candidates.every(isToken) ? candidates : null;
|
|
166
|
+
}
|
|
150
167
|
function getContainerChildren(token) {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
if (Array.isArray(container.items) && container.items.length > 0) {
|
|
156
|
-
return container.items;
|
|
157
|
-
}
|
|
158
|
-
return null;
|
|
168
|
+
const tokens = "tokens" in token ? token.tokens : undefined;
|
|
169
|
+
const items = "items" in token ? token.items : undefined;
|
|
170
|
+
return toTokenArray(tokens) ?? toTokenArray(items);
|
|
159
171
|
}
|
|
160
172
|
// Render a container's children back into the output, preserving any
|
|
161
173
|
// structural glue the parent carries outside the children's combined
|
|
@@ -163,7 +175,7 @@ function getContainerChildren(token) {
|
|
|
163
175
|
// Returns true if the container was rendered via its children, false
|
|
164
176
|
// if the caller should fall back to emitting the parent's raw.
|
|
165
177
|
function renderContainerChildren(raw, children, basePath, out) {
|
|
166
|
-
const joined = children.map((token) => token.raw
|
|
178
|
+
const joined = children.map((token) => token.raw).join("");
|
|
167
179
|
if (joined === "")
|
|
168
180
|
return false;
|
|
169
181
|
const idx = raw.indexOf(joined);
|
|
@@ -201,13 +213,11 @@ function renderToken(token, basePath, out) {
|
|
|
201
213
|
// Block / inline HTML — rewrite raw <img> tags inside before
|
|
202
214
|
// emitting. Markdown image syntax () is handled by the
|
|
203
215
|
// image-token branch above; this branch covers the HTML-fallback
|
|
204
|
-
// path (#1011 Stage A).
|
|
205
|
-
|
|
206
|
-
const raw = token.raw ?? "";
|
|
207
|
-
out.push(rewriteImgSrcAttrsInHtml(raw, basePath));
|
|
216
|
+
// path (#1011 Stage A).
|
|
217
|
+
out.push(rewriteImgSrcAttrsInHtml(token.raw, basePath));
|
|
208
218
|
return;
|
|
209
219
|
}
|
|
210
|
-
const raw = token
|
|
220
|
+
const { raw } = token;
|
|
211
221
|
const children = getContainerChildren(token);
|
|
212
222
|
if (children && renderContainerChildren(raw, children, basePath, out)) {
|
|
213
223
|
return;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// lists, multi-line strings, escaping) instead of the regex
|
|
11
11
|
// approximation in the legacy `src/utils/format/frontmatter.ts`.
|
|
12
12
|
import { FAILSAFE_SCHEMA, dump as yamlDump, load as yamlLoad } from "js-yaml";
|
|
13
|
+
import { isRecord } from "@mulmoclaude/common";
|
|
13
14
|
const FRONTMATTER_OPEN = /^---\r?\n/;
|
|
14
15
|
// `(?:^|\r?\n)` lets the closing fence sit at the very start of
|
|
15
16
|
// `afterOpen` — needed for the empty-envelope case `---\n---\n`
|
|
@@ -116,7 +117,7 @@ function safeYamlLoad(text) {
|
|
|
116
117
|
// plain objects — anything else is a malformed header.
|
|
117
118
|
if (loaded === null || loaded === undefined)
|
|
118
119
|
return {};
|
|
119
|
-
if (
|
|
120
|
+
if (!isRecord(loaded))
|
|
120
121
|
return null;
|
|
121
122
|
return loaded;
|
|
122
123
|
}
|
package/package.json
CHANGED