@ampless/runtime 1.0.0-alpha.56 → 1.0.0-alpha.58
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/index.d.ts +15 -0
- package/dist/index.js +94 -2
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -165,6 +165,21 @@ interface ContentFieldRegistry {
|
|
|
165
165
|
kind: 'markdown-url';
|
|
166
166
|
}>;
|
|
167
167
|
}>;
|
|
168
|
+
/**
|
|
169
|
+
* Plugins that declared `htmlPlaceholder` on their `kind: 'tiptap'`
|
|
170
|
+
* entry, keyed by the **lowercased** `flagAttr` (htmlparser2 lowercases
|
|
171
|
+
* HTML attribute names while parsing, so the public html walker looks
|
|
172
|
+
* up `attribs[flagAttr.toLowerCase()]` and compares against these keys
|
|
173
|
+
* case-insensitively). Used by `renderHtmlNode` to expand canonical
|
|
174
|
+
* placeholder divs in `format: 'html'` bodies into the plugin's
|
|
175
|
+
* existing tiptap renderer output.
|
|
176
|
+
*/
|
|
177
|
+
htmlPlaceholder: ReadonlyMap<string, {
|
|
178
|
+
plugin: AmplessPlugin;
|
|
179
|
+
renderer: Extract<ContentFieldRenderer, {
|
|
180
|
+
kind: 'tiptap';
|
|
181
|
+
}>;
|
|
182
|
+
}>;
|
|
168
183
|
}
|
|
169
184
|
/**
|
|
170
185
|
* Build a `ContentFieldRegistry` from a list of plugins. Eagerly errors
|
package/dist/index.js
CHANGED
|
@@ -254,6 +254,7 @@ import {
|
|
|
254
254
|
createElement
|
|
255
255
|
} from "react";
|
|
256
256
|
import { marked } from "marked";
|
|
257
|
+
import { parseDocument, ElementType } from "htmlparser2";
|
|
257
258
|
function escape(s) {
|
|
258
259
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
259
260
|
}
|
|
@@ -355,6 +356,7 @@ function renderMarkdownString(md) {
|
|
|
355
356
|
}
|
|
356
357
|
function buildContentFieldRegistry(plugins) {
|
|
357
358
|
const tiptap = /* @__PURE__ */ new Map();
|
|
359
|
+
const htmlPlaceholder = /* @__PURE__ */ new Map();
|
|
358
360
|
const seenMarkdownPatterns = /* @__PURE__ */ new Set();
|
|
359
361
|
const markdownUrl = [];
|
|
360
362
|
for (const plugin of plugins) {
|
|
@@ -368,6 +370,15 @@ function buildContentFieldRegistry(plugins) {
|
|
|
368
370
|
);
|
|
369
371
|
}
|
|
370
372
|
tiptap.set(field.nodeType, { plugin, renderer: field });
|
|
373
|
+
if (field.htmlPlaceholder) {
|
|
374
|
+
const flagAttr = field.htmlPlaceholder.flagAttr.toLowerCase();
|
|
375
|
+
if (htmlPlaceholder.has(flagAttr)) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`[ampless contentFields] duplicate htmlPlaceholder flagAttr "${flagAttr}" \u2014 already registered by another plugin. Each flagAttr may be claimed by at most one plugin.`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
htmlPlaceholder.set(flagAttr, { plugin, renderer: field });
|
|
381
|
+
}
|
|
371
382
|
} else if (field.kind === "markdown-url") {
|
|
372
383
|
const key = field.pattern.source;
|
|
373
384
|
if (seenMarkdownPatterns.has(key)) {
|
|
@@ -380,7 +391,7 @@ function buildContentFieldRegistry(plugins) {
|
|
|
380
391
|
}
|
|
381
392
|
}
|
|
382
393
|
}
|
|
383
|
-
return { tiptap, markdownUrl };
|
|
394
|
+
return { tiptap, markdownUrl, htmlPlaceholder };
|
|
384
395
|
}
|
|
385
396
|
function htmlPassthrough(html) {
|
|
386
397
|
return createElement("span", { dangerouslySetInnerHTML: { __html: html } });
|
|
@@ -580,9 +591,90 @@ function extractSingleUrl(para) {
|
|
|
580
591
|
}
|
|
581
592
|
return null;
|
|
582
593
|
}
|
|
594
|
+
function isParsedElement(node) {
|
|
595
|
+
return ElementType.isTag(node);
|
|
596
|
+
}
|
|
597
|
+
function renderHtmlNode(html, opts) {
|
|
598
|
+
const registry = opts.contentFields?.htmlPlaceholder;
|
|
599
|
+
if (!registry || registry.size === 0) {
|
|
600
|
+
return htmlPassthroughBlock(html);
|
|
601
|
+
}
|
|
602
|
+
const lower = html.toLowerCase();
|
|
603
|
+
let anyFlagPresent = false;
|
|
604
|
+
for (const flagAttr of registry.keys()) {
|
|
605
|
+
if (lower.includes(flagAttr)) {
|
|
606
|
+
anyFlagPresent = true;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (!anyFlagPresent) {
|
|
611
|
+
return htmlPassthroughBlock(html);
|
|
612
|
+
}
|
|
613
|
+
let doc;
|
|
614
|
+
try {
|
|
615
|
+
doc = parseDocument(html, {
|
|
616
|
+
withStartIndices: true,
|
|
617
|
+
withEndIndices: true
|
|
618
|
+
});
|
|
619
|
+
} catch (err) {
|
|
620
|
+
console.warn(
|
|
621
|
+
`[ampless renderBody] htmlparser2 failed to parse a format:'html' body; falling back to raw passthrough: ${err instanceof Error ? err.message : String(err)}`
|
|
622
|
+
);
|
|
623
|
+
return htmlPassthroughBlock(html);
|
|
624
|
+
}
|
|
625
|
+
const children = [];
|
|
626
|
+
let chunk = 0;
|
|
627
|
+
let cursor = 0;
|
|
628
|
+
const flushRawUntil = (end) => {
|
|
629
|
+
if (end <= cursor) return;
|
|
630
|
+
const slice = html.slice(cursor, end);
|
|
631
|
+
children.push(htmlPassthroughBlock(slice, `html-raw-${chunk++}`));
|
|
632
|
+
cursor = end;
|
|
633
|
+
};
|
|
634
|
+
for (const node of doc.children) {
|
|
635
|
+
if (!isParsedElement(node)) continue;
|
|
636
|
+
if (node.startIndex == null || node.endIndex == null) continue;
|
|
637
|
+
const reg = matchHtmlPlaceholder(node.attribs, registry);
|
|
638
|
+
if (!reg) continue;
|
|
639
|
+
const start = node.startIndex;
|
|
640
|
+
const endExclusive = node.endIndex + 1;
|
|
641
|
+
flushRawUntil(start);
|
|
642
|
+
const placeholderSlice = html.slice(start, endExclusive);
|
|
643
|
+
const ctx = opts.ctxForPlugin?.(reg.plugin);
|
|
644
|
+
if (!ctx) {
|
|
645
|
+
children.push(htmlPassthroughBlock(placeholderSlice, `html-raw-${chunk++}`));
|
|
646
|
+
cursor = endExclusive;
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
const renderNode = {
|
|
651
|
+
type: reg.renderer.nodeType,
|
|
652
|
+
attrs: reg.renderer.htmlPlaceholder.attrsFromElement(node.attribs)
|
|
653
|
+
};
|
|
654
|
+
const out = reg.renderer.render(renderNode, ctx);
|
|
655
|
+
children.push(createElement(Fragment, { key: `html-embed-${start}` }, out));
|
|
656
|
+
} catch (err) {
|
|
657
|
+
console.warn(
|
|
658
|
+
`[ampless renderBody] plugin "${reg.plugin.instanceId ?? reg.plugin.name}" threw inside contentFields tiptap renderer for nodeType "${reg.renderer.nodeType}": ${err instanceof Error ? err.message : String(err)}`
|
|
659
|
+
);
|
|
660
|
+
children.push(htmlPassthroughBlock(placeholderSlice, `html-raw-${chunk++}`));
|
|
661
|
+
}
|
|
662
|
+
cursor = endExclusive;
|
|
663
|
+
}
|
|
664
|
+
flushRawUntil(html.length);
|
|
665
|
+
return createElement(Fragment, null, ...children);
|
|
666
|
+
}
|
|
667
|
+
function matchHtmlPlaceholder(attribs, registry) {
|
|
668
|
+
for (const flagAttr of registry.keys()) {
|
|
669
|
+
if (Object.prototype.hasOwnProperty.call(attribs, flagAttr)) {
|
|
670
|
+
return registry.get(flagAttr) ?? null;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
583
675
|
function renderBody(post, opts = {}) {
|
|
584
676
|
if (post.format === "html") {
|
|
585
|
-
return
|
|
677
|
+
return renderHtmlNode(String(post.body), opts);
|
|
586
678
|
}
|
|
587
679
|
if (post.format === "markdown") {
|
|
588
680
|
return renderMarkdownNode(String(post.body), opts);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/runtime",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.58",
|
|
4
4
|
"description": "Public-side runtime for ampless: post fetching, theme dispatch, middleware, public route handlers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -46,12 +46,13 @@
|
|
|
46
46
|
"@radix-ui/react-slot": "^1.2.4",
|
|
47
47
|
"class-variance-authority": "^0.7.1",
|
|
48
48
|
"clsx": "^2.1.1",
|
|
49
|
+
"htmlparser2": "^10.1.0",
|
|
49
50
|
"lucide-react": "^1.16.0",
|
|
50
51
|
"marked": "^18.0.4",
|
|
51
52
|
"sanitize-html": "^2.17.4",
|
|
52
53
|
"tailwind-merge": "^3.6.0",
|
|
53
|
-
"@ampless/plugin-og-image": "0.2.0-alpha.
|
|
54
|
-
"ampless": "1.0.0-alpha.
|
|
54
|
+
"@ampless/plugin-og-image": "0.2.0-alpha.49",
|
|
55
|
+
"ampless": "1.0.0-alpha.49"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
57
58
|
"@aws-amplify/adapter-nextjs": "^1",
|