@dogsbay/format-astro 0.2.0-beta.137 → 0.2.0-beta.139

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.
Files changed (2) hide show
  1. package/dist/serialize.js +39 -4
  2. package/package.json +7 -7
package/dist/serialize.js CHANGED
@@ -1069,7 +1069,11 @@ function genericContainerToAstro(node, ctx) {
1069
1069
  const title = typeof node.meta?.title === "string" ? `<p class="font-semibold">${escapeTemplate(node.meta.title)}</p>\n` : "";
1070
1070
  const classAttr = mergeClassAttr("", node.props?.class);
1071
1071
  const passthrough = renderPassthroughAttrs(node.props, ["class"]);
1072
- return `<div data-directive="${escapeAttr(name)}"${classAttr}${passthrough}>\n${title}${childrenToAstro(node, ctx)}\n</div>`;
1072
+ // Nested props (a `---` block on an unknown directive) reach the page as one JSON attribute,
1073
+ // so a site's own component or script can read them; scalar props pass through as before.
1074
+ const nested = Object.fromEntries(Object.entries(node.props ?? {}).filter(([, v]) => v !== null && typeof v === "object"));
1075
+ const dataProps = Object.keys(nested).length ? ` data-props="${escapeAttr(JSON.stringify(nested))}"` : "";
1076
+ return `<div data-directive="${escapeAttr(name)}"${classAttr}${passthrough}${dataProps}>\n${title}${childrenToAstro(node, ctx)}\n</div>`;
1073
1077
  }
1074
1078
  function detailsToAstro(node, ctx) {
1075
1079
  ctx.imports.add("details");
@@ -1109,7 +1113,8 @@ function tabsToAstro(node, ctx) {
1109
1113
  if (tabs.length === 0)
1110
1114
  return "";
1111
1115
  const tabItems = tabs.map((tab, i) => {
1112
- const title = tab.props?.title || tab.props?.label || tab.props?.value || `Tab ${i + 1}`;
1116
+ // The same order as render-html: `label` is ours, `title` what a Comark import wrote
1117
+ const title = tab.props?.label || tab.props?.title || tab.props?.value || `Tab ${i + 1}`;
1113
1118
  const value = tab.props?.value || title;
1114
1119
  const label = title;
1115
1120
  return { value, label, node: tab };
@@ -1191,12 +1196,42 @@ const TITLE_TAG = /^<\/?(?:code|kbd|b|strong|i|em|s|del|mark|sub|sup|br)\s*\/?>$
1191
1196
  * and a title with `<img onerror=…>` ran a script in the summary. render-html reads titles the same way.
1192
1197
  */
1193
1198
  function titleInline(title) {
1194
- return captionInline(title).map((n) => (n.type === "html-inline" && !TITLE_TAG.test(n.html.trim()) ? { type: "text", text: n.html } : n));
1199
+ const nodes = captionInline(title);
1200
+ const kept = balancedTitleTags(nodes);
1201
+ return nodes.map((n, i) => (n.type === "html-inline" && !kept.has(i) ? { type: "text", text: n.html } : n));
1202
+ }
1203
+ /**
1204
+ * The indexes of the formatting tags a title keeps: a bare `TITLE_TAG` that is one half of a
1205
+ * matched pair (or `<br>`). An unclosed `<em>` was kept, and the browser carried it past the
1206
+ * title's element into the rest of the page (render-html has the same rule).
1207
+ */
1208
+ function balancedTitleTags(nodes) {
1209
+ const kept = new Set();
1210
+ const open = [];
1211
+ nodes.forEach((n, i) => {
1212
+ if (n.type !== "html-inline")
1213
+ return;
1214
+ const html = n.html.trim();
1215
+ if (!TITLE_TAG.test(html))
1216
+ return;
1217
+ const name = /^<\/?\s*(\w+)/.exec(html)[1].toLowerCase();
1218
+ if (name === "br")
1219
+ kept.add(i);
1220
+ else if (!html.startsWith("</"))
1221
+ open.push({ name, at: i });
1222
+ else if (open.length && open[open.length - 1].name === name) {
1223
+ kept.add(open.pop().at);
1224
+ kept.add(i);
1225
+ }
1226
+ });
1227
+ return kept;
1195
1228
  }
1196
1229
  /** A plain caption's text (escapes undone by the parse). */
1197
1230
  const captionText = (nodes) => nodes.map((n) => (n.type === "text" ? n.text : "")).join("");
1198
1231
  function captionToAstro(caption, ctx) {
1199
- const nodes = captionInline(caption);
1232
+ // Raw HTML other than balanced formatting tags is text, as in a title (an imported repo's caption
1233
+ // is someone else's; render-html draws captions the same way).
1234
+ const nodes = titleInline(caption);
1200
1235
  return isPlainCaption(nodes)
1201
1236
  ? escapeTemplate(captionText(nodes))
1202
1237
  : renderInline(nodes, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/format-astro",
3
- "version": "0.2.0-beta.137",
3
+ "version": "0.2.0-beta.139",
4
4
  "description": "Astro format plugin for Dogsbay — export TreeNode[] to Astro component markup",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,12 +32,12 @@
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
34
  "markdown-it": "^14.3.0",
35
- "@dogsbay/render-html": "0.2.0-beta.137",
36
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.137",
37
- "@dogsbay/site-files": "0.2.0-beta.137",
38
- "@dogsbay/serialize-core": "0.2.0-beta.137",
39
- "@dogsbay/types": "0.2.0-beta.137",
40
- "@dogsbay/ui": "0.2.0-beta.137"
35
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.139",
36
+ "@dogsbay/render-html": "0.2.0-beta.139",
37
+ "@dogsbay/serialize-core": "0.2.0-beta.139",
38
+ "@dogsbay/site-files": "0.2.0-beta.139",
39
+ "@dogsbay/types": "0.2.0-beta.139",
40
+ "@dogsbay/ui": "0.2.0-beta.139"
41
41
  },
42
42
  "repository": {
43
43
  "type": "git",