Skip to content

Commit bc9c4bf

Browse files
merphxopencode
andcommitted
fix: preserve inline ADF nodes inside em/strong/del 🪤
- Handle adf_inline child tokens in em, strong, and del cases of inlineToAdf - Use flatMap with an adf_inline branch instead of map, so ADF nodes are emitted directly rather than passed through getSafeText (which drops them) - Add three failing tests covering inline ADF inside bold, italic, and mixed bold+inline content before fixing Co-Authored-By: opencode <noreply@opencode.ai>
1 parent 4dbd4f3 commit bc9c4bf

2 files changed

Lines changed: 130 additions & 15 deletions

File tree

lib/index.ts

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -552,25 +552,58 @@ function inlineToAdf(tokens?: RelaxedToken[]): AdfNode[] {
552552
];
553553

554554
case "em":
555-
return (token.tokens ?? []).map((t) => ({
556-
type: "text",
557-
text: getSafeText(t),
558-
marks: getMarks(t, { em: { type: "em" } }),
559-
}));
555+
return (token.tokens ?? []).flatMap((t) => {
556+
if (t.type === "adf_inline") {
557+
const node = parseAdfTag(
558+
`<adf>${(t as AdfInlineToken).adfJson}</adf>`,
559+
);
560+
if (!node) return [];
561+
return Array.isArray(node) ? node : [node];
562+
}
563+
return [
564+
{
565+
type: "text",
566+
text: getSafeText(t),
567+
marks: getMarks(t, { em: { type: "em" } }),
568+
},
569+
];
570+
});
560571

561572
case "strong":
562-
return (token.tokens ?? []).map((t) => ({
563-
type: "text",
564-
text: getSafeText(t),
565-
marks: getMarks(t, { strong: { type: "strong" } }),
566-
}));
573+
return (token.tokens ?? []).flatMap((t) => {
574+
if (t.type === "adf_inline") {
575+
const node = parseAdfTag(
576+
`<adf>${(t as AdfInlineToken).adfJson}</adf>`,
577+
);
578+
if (!node) return [];
579+
return Array.isArray(node) ? node : [node];
580+
}
581+
return [
582+
{
583+
type: "text",
584+
text: getSafeText(t),
585+
marks: getMarks(t, { strong: { type: "strong" } }),
586+
},
587+
];
588+
});
567589

568590
case "del":
569-
return (token.tokens ?? []).map((t) => ({
570-
type: "text",
571-
text: getSafeText(t),
572-
marks: getMarks(t, { strike: { type: "strike" } }),
573-
}));
591+
return (token.tokens ?? []).flatMap((t) => {
592+
if (t.type === "adf_inline") {
593+
const node = parseAdfTag(
594+
`<adf>${(t as AdfInlineToken).adfJson}</adf>`,
595+
);
596+
if (!node) return [];
597+
return Array.isArray(node) ? node : [node];
598+
}
599+
return [
600+
{
601+
type: "text",
602+
text: getSafeText(t),
603+
marks: getMarks(t, { strike: { type: "strike" } }),
604+
},
605+
];
606+
});
574607

575608
case "link":
576609
return [

lib/test/adf-passthrough.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,88 @@ test("throws when inline <adf> content is valid JSON but not an object or array"
278278
});
279279
});
280280

281+
// --- inline ADF inside emphasis/strong/del ---
282+
283+
test("passes through an inline ADF node inside bold text", (t) => {
284+
const mention = {
285+
type: "mention",
286+
attrs: { id: "abc-123", text: "@Alice", accessLevel: "APPLICATION" },
287+
};
288+
const result = markdownToAdf(
289+
`**Contact <adf>${JSON.stringify(mention)}</adf> for help.**`,
290+
);
291+
t.deepEqual(result, {
292+
version: 1,
293+
type: "doc",
294+
content: [
295+
{
296+
type: "paragraph",
297+
content: [
298+
{ type: "text", text: "Contact ", marks: [{ type: "strong" }] },
299+
mention,
300+
{ type: "text", text: " for help.", marks: [{ type: "strong" }] },
301+
],
302+
},
303+
],
304+
});
305+
});
306+
307+
test("passes through an inline ADF node inside italic text", (t) => {
308+
const date = { type: "date", attrs: { timestamp: "1777852800000" } };
309+
const result = markdownToAdf(`*Returning <adf>${JSON.stringify(date)}</adf>*`);
310+
t.deepEqual(result, {
311+
version: 1,
312+
type: "doc",
313+
content: [
314+
{
315+
type: "paragraph",
316+
content: [
317+
{ type: "text", text: "Returning ", marks: [{ type: "em" }] },
318+
date,
319+
],
320+
},
321+
],
322+
});
323+
});
324+
325+
test("passes through multiple inline ADF nodes inside bold text", (t) => {
326+
const mention1 = {
327+
type: "mention",
328+
attrs: { id: "id-1", text: "@Jamie", accessLevel: "APPLICATION" },
329+
};
330+
const date = { type: "date", attrs: { timestamp: "1777852800000" } };
331+
const mention2 = {
332+
type: "mention",
333+
attrs: { id: "id-2", text: "@Phoenix", accessLevel: "APPLICATION" },
334+
};
335+
const result = markdownToAdf(
336+
`**While <adf>${JSON.stringify(mention1)}</adf> is on leave (returning <adf>${JSON.stringify(date)}</adf>):** contact <adf>${JSON.stringify(mention2)}</adf>.`,
337+
);
338+
t.deepEqual(result, {
339+
version: 1,
340+
type: "doc",
341+
content: [
342+
{
343+
type: "paragraph",
344+
content: [
345+
{ type: "text", text: "While ", marks: [{ type: "strong" }] },
346+
mention1,
347+
{
348+
type: "text",
349+
text: " is on leave (returning ",
350+
marks: [{ type: "strong" }],
351+
},
352+
date,
353+
{ type: "text", text: "):", marks: [{ type: "strong" }] },
354+
{ type: "text", text: " contact " },
355+
mention2,
356+
{ type: "text", text: "." },
357+
],
358+
},
359+
],
360+
});
361+
});
362+
281363
// --- marked singleton isolation ---
282364

283365
test("importing marklassian does not affect marked.parse() HTML output", (t) => {

0 commit comments

Comments
 (0)