@caretcms/core 0.1.2 → 0.2.0
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 +28 -10
- package/dist/contracts.d.ts +1 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +1 -0
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +18 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +109 -21
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +17 -6
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +33 -14
- package/dist/loader.js.map +1 -1
- package/dist/markdown/block-context.d.ts +21 -0
- package/dist/markdown/block-context.d.ts.map +1 -0
- package/dist/markdown/block-context.js +27 -0
- package/dist/markdown/block-context.js.map +1 -0
- package/dist/markdown/canonical-body.d.ts +30 -0
- package/dist/markdown/canonical-body.d.ts.map +1 -0
- package/dist/markdown/canonical-body.js +54 -0
- package/dist/markdown/canonical-body.js.map +1 -0
- package/dist/markdown/contracts.d.ts +66 -0
- package/dist/markdown/contracts.d.ts.map +1 -0
- package/dist/markdown/contracts.js +102 -0
- package/dist/markdown/contracts.js.map +1 -0
- package/dist/markdown/html-to-snodes.d.ts +26 -0
- package/dist/markdown/html-to-snodes.d.ts.map +1 -0
- package/dist/markdown/html-to-snodes.js +118 -0
- package/dist/markdown/html-to-snodes.js.map +1 -0
- package/dist/markdown/remark.d.ts +35 -0
- package/dist/markdown/remark.d.ts.map +1 -0
- package/dist/markdown/remark.js +66 -0
- package/dist/markdown/remark.js.map +1 -0
- package/dist/markdown/satteri.d.ts +19 -0
- package/dist/markdown/satteri.d.ts.map +1 -0
- package/dist/markdown/satteri.js +120 -0
- package/dist/markdown/satteri.js.map +1 -0
- package/dist/markdown/serialize.d.ts +60 -0
- package/dist/markdown/serialize.d.ts.map +1 -0
- package/dist/markdown/serialize.js +264 -0
- package/dist/markdown/serialize.js.map +1 -0
- package/dist/markdown/splice.d.ts +32 -0
- package/dist/markdown/splice.d.ts.map +1 -0
- package/dist/markdown/splice.js +44 -0
- package/dist/markdown/splice.js.map +1 -0
- package/dist/markdown/stamp.d.ts +68 -0
- package/dist/markdown/stamp.d.ts.map +1 -0
- package/dist/markdown/stamp.js +131 -0
- package/dist/markdown/stamp.js.map +1 -0
- package/dist/runtime/auth/rate-limiter.d.ts.map +1 -1
- package/dist/runtime/auth/rate-limiter.js +5 -0
- package/dist/runtime/auth/rate-limiter.js.map +1 -1
- package/dist/runtime/auth/session.d.ts +2 -3
- package/dist/runtime/auth/session.d.ts.map +1 -1
- package/dist/runtime/auth/session.js +52 -21
- package/dist/runtime/auth/session.js.map +1 -1
- package/dist/runtime/content.d.ts.map +1 -1
- package/dist/runtime/content.js +4 -2
- package/dist/runtime/content.js.map +1 -1
- package/dist/runtime/middleware.d.ts.map +1 -1
- package/dist/runtime/middleware.js +21 -4
- package/dist/runtime/middleware.js.map +1 -1
- package/dist/runtime/mutations/contracts.d.ts +25 -1
- package/dist/runtime/mutations/contracts.d.ts.map +1 -1
- package/dist/runtime/mutations/contracts.js +67 -0
- package/dist/runtime/mutations/contracts.js.map +1 -1
- package/dist/runtime/mutations/engine.d.ts +6 -1
- package/dist/runtime/mutations/engine.d.ts.map +1 -1
- package/dist/runtime/mutations/engine.js +151 -42
- package/dist/runtime/mutations/engine.js.map +1 -1
- package/dist/runtime/publish.d.ts +13 -1
- package/dist/runtime/publish.d.ts.map +1 -1
- package/dist/runtime/publish.js +75 -4
- package/dist/runtime/publish.js.map +1 -1
- package/dist/runtime/request-context.d.ts +9 -1
- package/dist/runtime/request-context.d.ts.map +1 -1
- package/dist/runtime/request-context.js.map +1 -1
- package/dist/runtime/rewrite.d.ts.map +1 -1
- package/dist/runtime/rewrite.js +109 -3
- package/dist/runtime/rewrite.js.map +1 -1
- package/dist/runtime/rich-allowlist.d.ts.map +1 -1
- package/dist/runtime/rich-allowlist.js +5 -1
- package/dist/runtime/rich-allowlist.js.map +1 -1
- package/dist/runtime/routes/admin.d.ts.map +1 -1
- package/dist/runtime/routes/admin.js +6 -2
- package/dist/runtime/routes/admin.js.map +1 -1
- package/dist/runtime/routes/entries.d.ts.map +1 -1
- package/dist/runtime/routes/entries.js +3 -2
- package/dist/runtime/routes/entries.js.map +1 -1
- package/dist/runtime/routes/history.d.ts.map +1 -1
- package/dist/runtime/routes/history.js +46 -21
- package/dist/runtime/routes/history.js.map +1 -1
- package/dist/runtime/routes/mutate.d.ts.map +1 -1
- package/dist/runtime/routes/mutate.js +40 -6
- package/dist/runtime/routes/mutate.js.map +1 -1
- package/dist/runtime/routes/publish.d.ts.map +1 -1
- package/dist/runtime/routes/publish.js +5 -2
- package/dist/runtime/routes/publish.js.map +1 -1
- package/dist/runtime/routes/schema.d.ts.map +1 -1
- package/dist/runtime/routes/schema.js +4 -1
- package/dist/runtime/routes/schema.js.map +1 -1
- package/dist/runtime/routes/theme-stylesheet.d.ts.map +1 -1
- package/dist/runtime/routes/theme-stylesheet.js +11 -1
- package/dist/runtime/routes/theme-stylesheet.js.map +1 -1
- package/dist/runtime/storage/atomic-write.d.ts.map +1 -1
- package/dist/runtime/storage/atomic-write.js +8 -3
- package/dist/runtime/storage/atomic-write.js.map +1 -1
- package/dist/runtime/storage/markdown-adapter.d.ts +19 -0
- package/dist/runtime/storage/markdown-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/markdown-adapter.js +41 -0
- package/dist/runtime/storage/markdown-adapter.js.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.d.ts +8 -0
- package/dist/runtime/storage/session-overlay-adapter.d.ts.map +1 -1
- package/dist/runtime/storage/session-overlay-adapter.js +24 -2
- package/dist/runtime/storage/session-overlay-adapter.js.map +1 -1
- package/dist/runtime/utils.d.ts +10 -0
- package/dist/runtime/utils.d.ts.map +1 -1
- package/dist/runtime/utils.js +16 -0
- package/dist/runtime/utils.js.map +1 -1
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/cms/admin-entry.js +36 -2
- package/static/cms/editor/guards.js +1 -1
- package/static/cms/editor/image-edit.js +41 -11
- package/static/cms/editor/image.css +7 -1
- package/static/cms/editor/md-block-edit.js +176 -0
- package/static/cms/editor/md-serialize.js +301 -0
- package/static/cms/editor/panel.css +69 -25
- package/static/cms/editor/panel.js +39 -0
- package/static/cms/editor/rich-toolbar.js +4 -3
- package/static/cms/editor/sanitize.js +2 -1
- package/static/cms/editor/sync.js +1 -1
- package/static/cms/editor/text-edit.js +68 -17
- package/static/cms/editor/text-link-interactions.js +4 -4
- package/static/cms/editor/toast.css +114 -2
- package/static/cms/editor/toast.js +121 -22
- package/static/cms/editor/tokens-and-text.css +64 -3
- package/static/cms/editor/toolbar.css +19 -6
- package/static/cms/editor/toolbar.js +64 -23
- package/static/cms/editor.js +13 -2
package/dist/loader.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Astro LiveLoader backed by the configured StorageAdapter.
|
|
3
3
|
*
|
|
4
|
-
* Compatible with Astro 6 (stable live content collections)
|
|
5
|
-
*
|
|
4
|
+
* Compatible with Astro 6 and 7 (stable live content collections). On Astro 7
|
|
5
|
+
* the loader also attaches `cacheHint` tags for publish-time purging.
|
|
6
6
|
*
|
|
7
7
|
* Usage in src/caret.config.ts:
|
|
8
8
|
*
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { getRequestContext, requireRequestContext, } from "./runtime/request-context.js";
|
|
18
18
|
import { stegaCombine } from "./runtime/stega.js";
|
|
19
|
+
import { stripBodyOverlay } from "./runtime/utils.js";
|
|
19
20
|
export class CaretLoaderError extends Error {
|
|
20
21
|
constructor(message, cause) {
|
|
21
22
|
super(message);
|
|
@@ -70,9 +71,15 @@ function requireAdapter() {
|
|
|
70
71
|
"(the integration registers it with order: 'pre').", cause);
|
|
71
72
|
}
|
|
72
73
|
}
|
|
74
|
+
/** Cache tags for a collection / entry: publishing can purge by these (Astro 7). */
|
|
75
|
+
const collectionTag = (collection) => `caret:${collection}`;
|
|
76
|
+
const entryTags = (collection, id) => [
|
|
77
|
+
`caret:${collection}`,
|
|
78
|
+
`caret:${collection}::${id}`,
|
|
79
|
+
];
|
|
73
80
|
/**
|
|
74
81
|
* Create an Astro LiveLoader that reads from the configured StorageAdapter.
|
|
75
|
-
* Works on Astro 6
|
|
82
|
+
* Works on Astro 6 and 7.
|
|
76
83
|
*
|
|
77
84
|
* @param collection - The collection name (must be known to the adapter).
|
|
78
85
|
*/
|
|
@@ -86,10 +93,15 @@ export function caretLoader(collection) {
|
|
|
86
93
|
const entry = await adapter.getEntry(collection, id);
|
|
87
94
|
if (!entry)
|
|
88
95
|
return undefined;
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
const editor = isEditorRequest();
|
|
97
|
+
const clean = stripBodyOverlay(entry.data);
|
|
98
|
+
const data = editor ? encodeEntryData(collection, entry.id, clean) : clean;
|
|
99
|
+
const result = { id: entry.id, data };
|
|
100
|
+
// Published content is cache-taggable so a publish can purge it by tag.
|
|
101
|
+
// Editor/draft requests get NO hint — edits must show immediately.
|
|
102
|
+
if (!editor)
|
|
103
|
+
result.cacheHint = { tags: entryTags(collection, entry.id) };
|
|
104
|
+
return result;
|
|
93
105
|
}
|
|
94
106
|
catch (error) {
|
|
95
107
|
return {
|
|
@@ -102,14 +114,21 @@ export function caretLoader(collection) {
|
|
|
102
114
|
const adapter = requireAdapter();
|
|
103
115
|
const allEntries = await adapter.listEntries(collection);
|
|
104
116
|
const editor = isEditorRequest();
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
const entries = allEntries.map((entry) => {
|
|
118
|
+
const clean = stripBodyOverlay(entry.data);
|
|
119
|
+
const mapped = {
|
|
107
120
|
id: entry.id,
|
|
108
|
-
data: editor
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
data: editor ? encodeEntryData(collection, entry.id, clean) : clean,
|
|
122
|
+
};
|
|
123
|
+
if (!editor)
|
|
124
|
+
mapped.cacheHint = { tags: entryTags(collection, entry.id) };
|
|
125
|
+
return mapped;
|
|
126
|
+
});
|
|
127
|
+
const result = { entries };
|
|
128
|
+
// Collection-level tag (merged with per-entry tags by Astro); drafts unhinted.
|
|
129
|
+
if (!editor)
|
|
130
|
+
result.cacheHint = { tags: [collectionTag(collection)] };
|
|
131
|
+
return result;
|
|
113
132
|
}
|
|
114
133
|
catch (error) {
|
|
115
134
|
return {
|
package/dist/loader.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive a block's serialization context from its SOURCE, not from client
|
|
3
|
+
* claims. A forged context could change a block's type (paragraph -> heading)
|
|
4
|
+
* or smuggle a hard break into a container-nested block (corrupting `> `/list
|
|
5
|
+
* structure on publish), so both facts are read off the canonical body at the
|
|
6
|
+
* verified source range instead of being accepted from the request.
|
|
7
|
+
*/
|
|
8
|
+
import type { BlockContext } from "./serialize.js";
|
|
9
|
+
import type { MdSrc } from "./contracts.js";
|
|
10
|
+
/**
|
|
11
|
+
* Block context for the range `src` in `canonical` (the canonical body the
|
|
12
|
+
* range was hash-verified against):
|
|
13
|
+
*
|
|
14
|
+
* - Heading when the slice starts with ATX markers — the level comes from the
|
|
15
|
+
* marker count (heading ranges include their `#`s).
|
|
16
|
+
* - Otherwise a paragraph; `nested` is true when anything (a `> ` marker, list
|
|
17
|
+
* indent) precedes the range on its line — nested blocks are single-line by
|
|
18
|
+
* the stamping contract, and `nested` makes the serializer refuse `<br>`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function deriveBlockContext(canonical: string, src: MdSrc): BlockContext;
|
|
21
|
+
//# sourceMappingURL=block-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-context.d.ts","sourceRoot":"","sources":["../../src/markdown/block-context.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAI5C;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,YAAY,CAO9E"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive a block's serialization context from its SOURCE, not from client
|
|
3
|
+
* claims. A forged context could change a block's type (paragraph -> heading)
|
|
4
|
+
* or smuggle a hard break into a container-nested block (corrupting `> `/list
|
|
5
|
+
* structure on publish), so both facts are read off the canonical body at the
|
|
6
|
+
* verified source range instead of being accepted from the request.
|
|
7
|
+
*/
|
|
8
|
+
const HEADING_RE = /^(#{1,6})[ \t]/;
|
|
9
|
+
/**
|
|
10
|
+
* Block context for the range `src` in `canonical` (the canonical body the
|
|
11
|
+
* range was hash-verified against):
|
|
12
|
+
*
|
|
13
|
+
* - Heading when the slice starts with ATX markers — the level comes from the
|
|
14
|
+
* marker count (heading ranges include their `#`s).
|
|
15
|
+
* - Otherwise a paragraph; `nested` is true when anything (a `> ` marker, list
|
|
16
|
+
* indent) precedes the range on its line — nested blocks are single-line by
|
|
17
|
+
* the stamping contract, and `nested` makes the serializer refuse `<br>`.
|
|
18
|
+
*/
|
|
19
|
+
export function deriveBlockContext(canonical, src) {
|
|
20
|
+
const slice = canonical.slice(src.start, src.end);
|
|
21
|
+
const heading = HEADING_RE.exec(slice);
|
|
22
|
+
if (heading)
|
|
23
|
+
return { block: "heading", level: heading[1].length };
|
|
24
|
+
const lineStart = canonical.lastIndexOf("\n", src.start - 1) + 1;
|
|
25
|
+
return { block: "paragraph", nested: lineStart < src.start };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=block-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-context.js","sourceRoot":"","sources":["../../src/markdown/block-context.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,UAAU,GAAG,gBAAgB,CAAC;AAEpC;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,GAAU;IAC9D,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAEnE,MAAM,SAAS,GAAG,SAAS,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical body base for markdown block offsets.
|
|
3
|
+
*
|
|
4
|
+
* Astro feeds the markdown processor a DIFFERENT source string per render path
|
|
5
|
+
* (verified against Astro 7.0.6): direct `.md` imports blank the frontmatter
|
|
6
|
+
* with equal-length whitespace, while content collections strip the
|
|
7
|
+
* frontmatter and `.trim()` the body. Raw mdast offsets are therefore not
|
|
8
|
+
* portable. Every `data-caret-md-src` offset is instead defined against ONE
|
|
9
|
+
* canonical base — the frontmatter-stripped, trimmed body — so the stamping
|
|
10
|
+
* plugin (any pipeline) and the server splicer agree on coordinates.
|
|
11
|
+
*
|
|
12
|
+
* Plugin side: `canonicalOffset = nodeOffset - (source.length - source.trimStart().length)`.
|
|
13
|
+
* Server side: this module maps canonical offsets back to file offsets for the
|
|
14
|
+
* publish splice.
|
|
15
|
+
*/
|
|
16
|
+
export interface CanonicalBody {
|
|
17
|
+
/** The frontmatter-stripped, trimmed body — the offset base. */
|
|
18
|
+
body: string;
|
|
19
|
+
/** Map a canonical body offset to an absolute offset in the source file. */
|
|
20
|
+
fileOffsetOf: (canonicalOffset: number) => number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Derive the canonical body and its offset mapping from a full source file.
|
|
24
|
+
*
|
|
25
|
+
* `body === fileSource.slice(bodyStart).trim()`, and `fileOffsetOf(0)` is the
|
|
26
|
+
* first non-whitespace byte after the frontmatter fence. Trailing-whitespace
|
|
27
|
+
* trimming does not affect the mapping (it only shortens `body`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function canonicalBody(fileSource: string): CanonicalBody;
|
|
30
|
+
//# sourceMappingURL=canonical-body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-body.d.ts","sourceRoot":"","sources":["../../src/markdown/canonical-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,YAAY,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD;AAwBD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,CAS/D"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical body base for markdown block offsets.
|
|
3
|
+
*
|
|
4
|
+
* Astro feeds the markdown processor a DIFFERENT source string per render path
|
|
5
|
+
* (verified against Astro 7.0.6): direct `.md` imports blank the frontmatter
|
|
6
|
+
* with equal-length whitespace, while content collections strip the
|
|
7
|
+
* frontmatter and `.trim()` the body. Raw mdast offsets are therefore not
|
|
8
|
+
* portable. Every `data-caret-md-src` offset is instead defined against ONE
|
|
9
|
+
* canonical base — the frontmatter-stripped, trimmed body — so the stamping
|
|
10
|
+
* plugin (any pipeline) and the server splicer agree on coordinates.
|
|
11
|
+
*
|
|
12
|
+
* Plugin side: `canonicalOffset = nodeOffset - (source.length - source.trimStart().length)`.
|
|
13
|
+
* Server side: this module maps canonical offsets back to file offsets for the
|
|
14
|
+
* publish splice.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Frontmatter fence matcher, MIRRORING Astro's own
|
|
18
|
+
* (`@astrojs/internal-helpers/dist/frontmatter.js` `frontmatterRE`) byte for
|
|
19
|
+
* byte. This must agree with what Astro strips before rendering — a stricter
|
|
20
|
+
* hand-rolled detector diverged on three legal shapes (UTF-8 BOM before the
|
|
21
|
+
* fence, blank lines before the fence, a `----` close line whose first three
|
|
22
|
+
* chars satisfy Astro's close) and every body save on such files 409'd as
|
|
23
|
+
* "stale" because plugin-side and server-side offsets disagreed.
|
|
24
|
+
*/
|
|
25
|
+
const ASTRO_FRONTMATTER_RE = /(?:^\uFEFF?|^\s*\n)(?:---|\+\+\+)([\s\S]*?\n)(?:---|\+\+\+)/;
|
|
26
|
+
/**
|
|
27
|
+
* Byte offset where the markdown body begins (0 when there is no frontmatter
|
|
28
|
+
* fence) — everything Astro's stripper would remove, including any BOM or
|
|
29
|
+
* leading blank lines consumed by its match.
|
|
30
|
+
*/
|
|
31
|
+
function bodyOffset(source) {
|
|
32
|
+
const match = ASTRO_FRONTMATTER_RE.exec(source);
|
|
33
|
+
if (!match)
|
|
34
|
+
return 0;
|
|
35
|
+
return match.index + match[0].length;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Derive the canonical body and its offset mapping from a full source file.
|
|
39
|
+
*
|
|
40
|
+
* `body === fileSource.slice(bodyStart).trim()`, and `fileOffsetOf(0)` is the
|
|
41
|
+
* first non-whitespace byte after the frontmatter fence. Trailing-whitespace
|
|
42
|
+
* trimming does not affect the mapping (it only shortens `body`).
|
|
43
|
+
*/
|
|
44
|
+
export function canonicalBody(fileSource) {
|
|
45
|
+
const start = bodyOffset(fileSource);
|
|
46
|
+
const afterFm = fileSource.slice(start);
|
|
47
|
+
const leading = afterFm.length - afterFm.trimStart().length;
|
|
48
|
+
const base = start + leading;
|
|
49
|
+
return {
|
|
50
|
+
body: afterFm.trim(),
|
|
51
|
+
fileOffsetOf: (canonicalOffset) => base + canonicalOffset,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=canonical-body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-body.js","sourceRoot":"","sources":["../../src/markdown/canonical-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH;;;;;;;;GAQG;AACH,MAAM,oBAAoB,GAAG,6DAA6D,CAAC;AAE3F;;;;GAIG;AACH,SAAS,UAAU,CAAC,MAAc;IAChC,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC;IACrB,OAAO,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC;IAC5D,MAAM,IAAI,GAAG,KAAK,GAAG,OAAO,CAAC;IAC7B,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE;QACpB,YAAY,EAAE,CAAC,eAAuB,EAAE,EAAE,CAAC,IAAI,GAAG,eAAe;KAClE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract surface for markdown BODY inline editing (v0.3.0).
|
|
3
|
+
*
|
|
4
|
+
* These identifiers gate a security- and correctness-critical path: the
|
|
5
|
+
* `data-caret-md` attribute binds a rendered block back to a byte range in a
|
|
6
|
+
* source `.md` file, and a Publish splices edited markdown into that range.
|
|
7
|
+
* The grammar, the source-hint format, and the content hash are therefore a
|
|
8
|
+
* compatibility promise shared across the render-time stamping plugins, the
|
|
9
|
+
* mutation engine, the publish splicer, and the browser editor.
|
|
10
|
+
*
|
|
11
|
+
* Zero-dependency by policy (core ships no runtime deps). The browser editor
|
|
12
|
+
* ships as raw unbundled JS and hand-mirrors the pieces it needs
|
|
13
|
+
* (`static/cms/editor/md-serialize.js`); a parity test holds the mirror to
|
|
14
|
+
* this source of truth.
|
|
15
|
+
*/
|
|
16
|
+
/** Attribute carrying the block binding (`collection::id::body::blockPath`). */
|
|
17
|
+
export declare const CARET_MD_ATTR = "data-caret-md";
|
|
18
|
+
/** Attribute carrying the source hint (`start:end:hash`). */
|
|
19
|
+
export declare const CARET_MD_SRC_ATTR = "data-caret-md-src";
|
|
20
|
+
/**
|
|
21
|
+
* Reserved field name for the body-draft map inside an entry's draft overlay
|
|
22
|
+
* `data`. Read boundaries strip it so it never surfaces to schema/loader
|
|
23
|
+
* consumers.
|
|
24
|
+
*/
|
|
25
|
+
export declare const BODY_OVERLAY_KEY = "__body";
|
|
26
|
+
/** The synthetic field segment identifying a body binding (vs a `data-caret` field). */
|
|
27
|
+
export declare const BODY_FIELD = "body";
|
|
28
|
+
/**
|
|
29
|
+
* A block path: dot-joined child indexes from the mdast root to the block node
|
|
30
|
+
* (`"4"` = fifth top-level block; `"6.2"` = third child of the seventh block).
|
|
31
|
+
* Stable identity for the block within an entry across edits.
|
|
32
|
+
*/
|
|
33
|
+
export declare const BLOCK_PATH_RE: RegExp;
|
|
34
|
+
export interface MdBinding {
|
|
35
|
+
collection: string;
|
|
36
|
+
id: string;
|
|
37
|
+
blockPath: string;
|
|
38
|
+
}
|
|
39
|
+
/** Format a block path from root-to-node child indexes. */
|
|
40
|
+
export declare function formatBlockPath(indexes: readonly number[]): string;
|
|
41
|
+
/** Parse a block path into its child indexes, or `null` if malformed. */
|
|
42
|
+
export declare function parseBlockPath(raw: string): number[] | null;
|
|
43
|
+
/** Format a `data-caret-md` binding value. */
|
|
44
|
+
export declare function formatMdBinding(b: MdBinding): string;
|
|
45
|
+
/** Parse a `data-caret-md` binding value, or `null` if it fails the grammar. */
|
|
46
|
+
export declare function parseMdBinding(raw: string): MdBinding | null;
|
|
47
|
+
export interface MdSrc {
|
|
48
|
+
/** Start offset in the canonical body (see `canonical-body.ts`). */
|
|
49
|
+
start: number;
|
|
50
|
+
/** End offset (exclusive) in the canonical body. */
|
|
51
|
+
end: number;
|
|
52
|
+
/** `fnv1a32` of the canonical body slice `[start, end)`. */
|
|
53
|
+
hash: string;
|
|
54
|
+
}
|
|
55
|
+
/** Format a `data-caret-md-src` hint value. */
|
|
56
|
+
export declare function formatMdSrc(s: MdSrc): string;
|
|
57
|
+
/** Parse a `data-caret-md-src` hint, validating bounds and hash shape. */
|
|
58
|
+
export declare function parseMdSrc(raw: string): MdSrc | null;
|
|
59
|
+
/**
|
|
60
|
+
* FNV-1a (32-bit) hash as an 8-char lowercase hex string. Not cryptographic —
|
|
61
|
+
* a staleness fingerprint over a source slice, chosen for a dependency-free,
|
|
62
|
+
* byte-identical implementation on both the TS and browser-JS sides. Iterates
|
|
63
|
+
* UTF-16 code units, so the two sides must hash the same string form.
|
|
64
|
+
*/
|
|
65
|
+
export declare function fnv1a32(str: string): string;
|
|
66
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../../src/markdown/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,gFAAgF;AAChF,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAE7C,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,WAAW,CAAC;AAEzC,wFAAwF;AACxF,eAAO,MAAM,UAAU,SAAS,CAAC;AAEjC;;;;GAIG;AACH,eAAO,MAAM,aAAa,QAAoB,CAAC;AAE/C,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAElE;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAG3D;AAED,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAEpD;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAS5D;AAED,MAAM,WAAW,KAAK;IACpB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;CACd;AAID,+CAA+C;AAC/C,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,CAE5C;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAWpD;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO3C"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract surface for markdown BODY inline editing (v0.3.0).
|
|
3
|
+
*
|
|
4
|
+
* These identifiers gate a security- and correctness-critical path: the
|
|
5
|
+
* `data-caret-md` attribute binds a rendered block back to a byte range in a
|
|
6
|
+
* source `.md` file, and a Publish splices edited markdown into that range.
|
|
7
|
+
* The grammar, the source-hint format, and the content hash are therefore a
|
|
8
|
+
* compatibility promise shared across the render-time stamping plugins, the
|
|
9
|
+
* mutation engine, the publish splicer, and the browser editor.
|
|
10
|
+
*
|
|
11
|
+
* Zero-dependency by policy (core ships no runtime deps). The browser editor
|
|
12
|
+
* ships as raw unbundled JS and hand-mirrors the pieces it needs
|
|
13
|
+
* (`static/cms/editor/md-serialize.js`); a parity test holds the mirror to
|
|
14
|
+
* this source of truth.
|
|
15
|
+
*/
|
|
16
|
+
import { COLLECTION_NAME_RE, ENTRY_ID_RE } from "../runtime/storage/id-contracts.js";
|
|
17
|
+
/** Attribute carrying the block binding (`collection::id::body::blockPath`). */
|
|
18
|
+
export const CARET_MD_ATTR = "data-caret-md";
|
|
19
|
+
/** Attribute carrying the source hint (`start:end:hash`). */
|
|
20
|
+
export const CARET_MD_SRC_ATTR = "data-caret-md-src";
|
|
21
|
+
/**
|
|
22
|
+
* Reserved field name for the body-draft map inside an entry's draft overlay
|
|
23
|
+
* `data`. Read boundaries strip it so it never surfaces to schema/loader
|
|
24
|
+
* consumers.
|
|
25
|
+
*/
|
|
26
|
+
export const BODY_OVERLAY_KEY = "__body";
|
|
27
|
+
/** The synthetic field segment identifying a body binding (vs a `data-caret` field). */
|
|
28
|
+
export const BODY_FIELD = "body";
|
|
29
|
+
/**
|
|
30
|
+
* A block path: dot-joined child indexes from the mdast root to the block node
|
|
31
|
+
* (`"4"` = fifth top-level block; `"6.2"` = third child of the seventh block).
|
|
32
|
+
* Stable identity for the block within an entry across edits.
|
|
33
|
+
*/
|
|
34
|
+
export const BLOCK_PATH_RE = /^\d+(?:\.\d+)*$/;
|
|
35
|
+
/** Format a block path from root-to-node child indexes. */
|
|
36
|
+
export function formatBlockPath(indexes) {
|
|
37
|
+
return indexes.join(".");
|
|
38
|
+
}
|
|
39
|
+
/** Parse a block path into its child indexes, or `null` if malformed. */
|
|
40
|
+
export function parseBlockPath(raw) {
|
|
41
|
+
if (!BLOCK_PATH_RE.test(raw))
|
|
42
|
+
return null;
|
|
43
|
+
return raw.split(".").map(Number);
|
|
44
|
+
}
|
|
45
|
+
/** Format a `data-caret-md` binding value. */
|
|
46
|
+
export function formatMdBinding(b) {
|
|
47
|
+
return `${b.collection}::${b.id}::${BODY_FIELD}::${b.blockPath}`;
|
|
48
|
+
}
|
|
49
|
+
/** Parse a `data-caret-md` binding value, or `null` if it fails the grammar. */
|
|
50
|
+
export function parseMdBinding(raw) {
|
|
51
|
+
const parts = raw.split("::");
|
|
52
|
+
if (parts.length !== 4)
|
|
53
|
+
return null;
|
|
54
|
+
const [collection, id, field, blockPath] = parts;
|
|
55
|
+
if (field !== BODY_FIELD)
|
|
56
|
+
return null;
|
|
57
|
+
if (!COLLECTION_NAME_RE.test(collection))
|
|
58
|
+
return null;
|
|
59
|
+
if (!ENTRY_ID_RE.test(id))
|
|
60
|
+
return null;
|
|
61
|
+
if (!BLOCK_PATH_RE.test(blockPath))
|
|
62
|
+
return null;
|
|
63
|
+
return { collection, id, blockPath };
|
|
64
|
+
}
|
|
65
|
+
const HASH_RE = /^[0-9a-f]{8}$/;
|
|
66
|
+
/** Format a `data-caret-md-src` hint value. */
|
|
67
|
+
export function formatMdSrc(s) {
|
|
68
|
+
return `${s.start}:${s.end}:${s.hash}`;
|
|
69
|
+
}
|
|
70
|
+
/** Parse a `data-caret-md-src` hint, validating bounds and hash shape. */
|
|
71
|
+
export function parseMdSrc(raw) {
|
|
72
|
+
const parts = raw.split(":");
|
|
73
|
+
if (parts.length !== 3)
|
|
74
|
+
return null;
|
|
75
|
+
const [startStr, endStr, hash] = parts;
|
|
76
|
+
if (!/^\d+$/.test(startStr) || !/^\d+$/.test(endStr))
|
|
77
|
+
return null;
|
|
78
|
+
if (!HASH_RE.test(hash))
|
|
79
|
+
return null;
|
|
80
|
+
const start = Number(startStr);
|
|
81
|
+
const end = Number(endStr);
|
|
82
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end))
|
|
83
|
+
return null;
|
|
84
|
+
if (start >= end)
|
|
85
|
+
return null;
|
|
86
|
+
return { start, end, hash };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* FNV-1a (32-bit) hash as an 8-char lowercase hex string. Not cryptographic —
|
|
90
|
+
* a staleness fingerprint over a source slice, chosen for a dependency-free,
|
|
91
|
+
* byte-identical implementation on both the TS and browser-JS sides. Iterates
|
|
92
|
+
* UTF-16 code units, so the two sides must hash the same string form.
|
|
93
|
+
*/
|
|
94
|
+
export function fnv1a32(str) {
|
|
95
|
+
let h = 0x811c9dc5;
|
|
96
|
+
for (let i = 0; i < str.length; i++) {
|
|
97
|
+
h ^= str.charCodeAt(i);
|
|
98
|
+
h = Math.imul(h, 0x01000193);
|
|
99
|
+
}
|
|
100
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=contracts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../src/markdown/contracts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side inline-HTML → SNode parser for the markdown body write path.
|
|
3
|
+
*
|
|
4
|
+
* Exists so the server NEVER trusts client-supplied markdown: an `md_block`
|
|
5
|
+
* mutation carries only HTML, which is re-sanitized against the rich allowlist
|
|
6
|
+
* and then converted here into the same minimal tree `serializeBlock` consumes.
|
|
7
|
+
* The markdown that eventually gets spliced into a source file is therefore
|
|
8
|
+
* always derived server-side, and anything outside the closed set hard-fails
|
|
9
|
+
* in the serializer.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately tiny and strict — this only needs to parse the SANITIZER'S
|
|
12
|
+
* output (balanced inline tags, double-quoted attributes, standard entities),
|
|
13
|
+
* not arbitrary HTML. Malformed input throws; the mutation returns 400.
|
|
14
|
+
*/
|
|
15
|
+
import type { SNode } from "./serialize.js";
|
|
16
|
+
export declare class HtmlParseError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse a sanitized inline-HTML fragment into SNodes. Throws
|
|
21
|
+
* {@link HtmlParseError} on anything structurally unexpected (unbalanced tags,
|
|
22
|
+
* malformed tag syntax, comments/doctypes/CDATA) — the sanitizer never emits
|
|
23
|
+
* those, so their presence means the input didn't come through it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function htmlToSNodes(html: string): SNode[];
|
|
26
|
+
//# sourceMappingURL=html-to-snodes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-to-snodes.d.ts","sourceRoot":"","sources":["../../src/markdown/html-to-snodes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAiCD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,EAAE,CA+DlD"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side inline-HTML → SNode parser for the markdown body write path.
|
|
3
|
+
*
|
|
4
|
+
* Exists so the server NEVER trusts client-supplied markdown: an `md_block`
|
|
5
|
+
* mutation carries only HTML, which is re-sanitized against the rich allowlist
|
|
6
|
+
* and then converted here into the same minimal tree `serializeBlock` consumes.
|
|
7
|
+
* The markdown that eventually gets spliced into a source file is therefore
|
|
8
|
+
* always derived server-side, and anything outside the closed set hard-fails
|
|
9
|
+
* in the serializer.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately tiny and strict — this only needs to parse the SANITIZER'S
|
|
12
|
+
* output (balanced inline tags, double-quoted attributes, standard entities),
|
|
13
|
+
* not arbitrary HTML. Malformed input throws; the mutation returns 400.
|
|
14
|
+
*/
|
|
15
|
+
export class HtmlParseError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "HtmlParseError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const NAMED_ENTITIES = {
|
|
22
|
+
amp: "&",
|
|
23
|
+
lt: "<",
|
|
24
|
+
gt: ">",
|
|
25
|
+
quot: '"',
|
|
26
|
+
apos: "'",
|
|
27
|
+
nbsp: " ",
|
|
28
|
+
};
|
|
29
|
+
function decodeEntities(text) {
|
|
30
|
+
return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body) => {
|
|
31
|
+
if (body.startsWith("#x") || body.startsWith("#X")) {
|
|
32
|
+
const cp = Number.parseInt(body.slice(2), 16);
|
|
33
|
+
return Number.isNaN(cp) ? whole : safeFromCodePoint(cp, whole);
|
|
34
|
+
}
|
|
35
|
+
if (body.startsWith("#")) {
|
|
36
|
+
const cp = Number.parseInt(body.slice(1), 10);
|
|
37
|
+
return Number.isNaN(cp) ? whole : safeFromCodePoint(cp, whole);
|
|
38
|
+
}
|
|
39
|
+
return NAMED_ENTITIES[body.toLowerCase()] ?? whole;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function safeFromCodePoint(cp, fallback) {
|
|
43
|
+
if (cp < 0 || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff))
|
|
44
|
+
return fallback;
|
|
45
|
+
return String.fromCodePoint(cp);
|
|
46
|
+
}
|
|
47
|
+
const TAG_NAME_RE = /^[a-zA-Z][a-zA-Z0-9]*$/;
|
|
48
|
+
const VOID_TAGS = new Set(["br"]);
|
|
49
|
+
/**
|
|
50
|
+
* Parse a sanitized inline-HTML fragment into SNodes. Throws
|
|
51
|
+
* {@link HtmlParseError} on anything structurally unexpected (unbalanced tags,
|
|
52
|
+
* malformed tag syntax, comments/doctypes/CDATA) — the sanitizer never emits
|
|
53
|
+
* those, so their presence means the input didn't come through it.
|
|
54
|
+
*/
|
|
55
|
+
export function htmlToSNodes(html) {
|
|
56
|
+
const roots = [];
|
|
57
|
+
const stack = [];
|
|
58
|
+
const push = (node) => {
|
|
59
|
+
if (stack.length > 0)
|
|
60
|
+
stack[stack.length - 1].children.push(node);
|
|
61
|
+
else
|
|
62
|
+
roots.push(node);
|
|
63
|
+
};
|
|
64
|
+
let i = 0;
|
|
65
|
+
while (i < html.length) {
|
|
66
|
+
if (html[i] !== "<") {
|
|
67
|
+
const next = html.indexOf("<", i);
|
|
68
|
+
const end = next === -1 ? html.length : next;
|
|
69
|
+
push({ type: "text", value: decodeEntities(html.slice(i, end)) });
|
|
70
|
+
i = end;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const gt = html.indexOf(">", i);
|
|
74
|
+
if (gt === -1)
|
|
75
|
+
throw new HtmlParseError("unterminated tag");
|
|
76
|
+
const raw = html.slice(i + 1, gt);
|
|
77
|
+
i = gt + 1;
|
|
78
|
+
if (raw.startsWith("/")) {
|
|
79
|
+
const name = raw.slice(1).trim().toLowerCase();
|
|
80
|
+
const top = stack.pop();
|
|
81
|
+
if (!top || top.tag !== name) {
|
|
82
|
+
throw new HtmlParseError(`unbalanced closing tag </${name}>`);
|
|
83
|
+
}
|
|
84
|
+
push({ type: "element", tag: top.tag, attrs: top.attrs, children: top.children });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (raw.startsWith("!") || raw.startsWith("?")) {
|
|
88
|
+
throw new HtmlParseError("comments and declarations are not allowed");
|
|
89
|
+
}
|
|
90
|
+
const selfClosing = raw.endsWith("/");
|
|
91
|
+
const tagBody = selfClosing ? raw.slice(0, -1) : raw;
|
|
92
|
+
const spaceIdx = tagBody.search(/[\s]/);
|
|
93
|
+
const name = (spaceIdx === -1 ? tagBody : tagBody.slice(0, spaceIdx)).toLowerCase();
|
|
94
|
+
if (!TAG_NAME_RE.test(name))
|
|
95
|
+
throw new HtmlParseError(`malformed tag <${raw}>`);
|
|
96
|
+
const attrs = {};
|
|
97
|
+
const attrSrc = spaceIdx === -1 ? "" : tagBody.slice(spaceIdx);
|
|
98
|
+
const attrRe = /([a-zA-Z-]+)(?:="([^"]*)")?/g;
|
|
99
|
+
for (let m; (m = attrRe.exec(attrSrc));) {
|
|
100
|
+
// Only carry href — the sole attribute the closed set represents. Other
|
|
101
|
+
// sanitizer-permitted attributes (target/rel/class) don't round-trip to
|
|
102
|
+
// markdown and are regenerated at render time.
|
|
103
|
+
if (m[1].toLowerCase() === "href")
|
|
104
|
+
attrs.href = decodeEntities(m[2] ?? "");
|
|
105
|
+
}
|
|
106
|
+
if (VOID_TAGS.has(name) || selfClosing) {
|
|
107
|
+
push({ type: "element", tag: name, attrs, children: [] });
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
stack.push({ tag: name, attrs, children: [] });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (stack.length > 0) {
|
|
114
|
+
throw new HtmlParseError(`unclosed tag <${stack[stack.length - 1].tag}>`);
|
|
115
|
+
}
|
|
116
|
+
return roots;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=html-to-snodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-to-snodes.js","sourceRoot":"","sources":["../../src/markdown/html-to-snodes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remark plugin that stamps `data-caret-md` bindings (Astro 6 / unified
|
|
3
|
+
* holdouts). Behaviour-identical to the Sätteri frontend — both call the shared
|
|
4
|
+
* `computeStamp`/`planParagraph`, and a parity test runs the two over the same
|
|
5
|
+
* trees. A tiny internal walker avoids a `unist-util-visit` dependency.
|
|
6
|
+
*/
|
|
7
|
+
interface CaretRemarkOptions {
|
|
8
|
+
contentRoot: string;
|
|
9
|
+
}
|
|
10
|
+
interface Pos {
|
|
11
|
+
start?: {
|
|
12
|
+
offset?: number;
|
|
13
|
+
};
|
|
14
|
+
end?: {
|
|
15
|
+
offset?: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
interface MdNode {
|
|
19
|
+
type: string;
|
|
20
|
+
position?: Pos;
|
|
21
|
+
children?: MdNode[];
|
|
22
|
+
data?: Record<string, unknown> & {
|
|
23
|
+
hProperties?: Record<string, unknown>;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
interface VFileLike {
|
|
27
|
+
value?: unknown;
|
|
28
|
+
path?: string;
|
|
29
|
+
toString(): string;
|
|
30
|
+
}
|
|
31
|
+
/** Remark transformer (returned by the attacher). Exposed for direct testing. */
|
|
32
|
+
export declare function transformCaretRemark(tree: MdNode, file: VFileLike, contentRoot: string): void;
|
|
33
|
+
export declare function caretRemarkPlugin(options: CaretRemarkOptions): () => (tree: MdNode, file: VFileLike) => void;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=remark.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remark.d.ts","sourceRoot":"","sources":["../../src/markdown/remark.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,UAAU,kBAAkB;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,GAAG;IACX,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5B,GAAG,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3B;AACD,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAC5E;AACD,UAAU,SAAS;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,IAAI,MAAM,CAAC;CACpB;AAOD,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAoD7F;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,UAG/B,MAAM,MAAM,EAAE,MAAM,SAAS,KAAG,IAAI,CAIjE"}
|