diff --git a/packages/comark/SPEC/COMARK/link-with-span.md b/packages/comark/SPEC/COMARK/link-with-span.md new file mode 100644 index 00000000..9d41e107 --- /dev/null +++ b/packages/comark/SPEC/COMARK/link-with-span.md @@ -0,0 +1,76 @@ +## Input + +```md +[[1]{} Document](#) + +[[link-name]{.cls} more](https://example.com) +``` + +## AST + +```json + +{ + "frontmatter":{ + + }, + "meta":{ + + }, + "nodes":[ + [ + "p", + { + + }, + [ + "a", + { + "href":"#" + }, + [ + "span", + {}, + "1" + ], + " Document" + ] + ], + [ + "p", + { + + }, + [ + "a", + { + "href":"https://example.com" + }, + [ + "span", + { + "class": "cls" + }, + "link-name" + ], + " more" + ] + ] + ] +} +``` + +## HTML + +```html +
+ +``` + +## Markdown + +```md +[[1]{} Document](#) + +[[link-name]{.cls} more](https://example.com) +``` diff --git a/packages/comark/SPEC/common-mark/links.md b/packages/comark/SPEC/common-mark/links.md new file mode 100644 index 00000000..96125775 --- /dev/null +++ b/packages/comark/SPEC/common-mark/links.md @@ -0,0 +1,82 @@ +## Input + +```md +[Document](#) + +[[1] Document](#) + +[[link-name] more](https://example.com) +``` + +## AST + +```json + +{ + "frontmatter":{ + + }, + "meta":{ + + }, + "nodes":[ + [ + "p", + { + + }, + [ + "a", + { + "href":"#" + }, + "Document" + ] + ], + [ + "p", + { + + }, + [ + "a", + { + "href":"#" + }, + "[1] Document" + ] + ], + [ + "p", + { + + }, + [ + "a", + { + "href":"https://example.com" + }, + "[link-name] more" + ] + ] + ] +} +``` + +## HTML + +```html + + + +``` + +## Markdown + +```md +[Document](#) + +[\[1\] Document](#) + +[\[link-name\] more](https://example.com) +``` diff --git a/packages/comark/src/internal/stringify/handlers/mdc.ts b/packages/comark/src/internal/stringify/handlers/mdc.ts index 70b6fb59..5f8e4ce2 100644 --- a/packages/comark/src/internal/stringify/handlers/mdc.ts +++ b/packages/comark/src/internal/stringify/handlers/mdc.ts @@ -36,9 +36,14 @@ export async function mdc(node: ComarkElement, state: State, parent?: ComarkElem } content = content.trimEnd() - const attrs = attributeEntries.length > 0 ? comarkAttributes(attributes) : '' + let attrs = attributeEntries.length > 0 ? comarkAttributes(attributes) : '' if (tag === 'span') { + if (!attrs && parent?.[0] == 'a') { + // Add empty attributes syntax to end of spans inside Anchor links + // This will force Comark to parse them as span and prevent conflict + attrs ||= '{}' + } return `[${content}]${attrs}` + (inline ? '' : state.context.blockSeparator) } diff --git a/packages/comark/src/plugins/syntax.ts b/packages/comark/src/plugins/syntax.ts index 742ff224..0d22a61e 100644 --- a/packages/comark/src/plugins/syntax.ts +++ b/packages/comark/src/plugins/syntax.ts @@ -449,7 +449,12 @@ const markdownItInlineSpan: PluginSimple = (md) => { const nextChar = state.src[index + 1] if (nextChar === '(' || nextChar === '[') return false - if (silent) return true + // Inside a link label, bare `[text]` stays literal (plain-markdown behavior, + // e.g. `[[1] Document](#)`); only an explicit `[text]{attrs}` span matches + if (state.linkLevel > 0 && nextChar !== '{') return false + + // Returning `false` lets `parseLinkLabel`'s own depth tracking consume nested brackets and the outer link parse + if (silent) return false state.push('mdc_inline_span', 'span', 1)