diff --git a/src/serializers/html/html.test.ts b/src/serializers/html/html.test.ts index 935f761b..f6fa5c26 100644 --- a/src/serializers/html/html.test.ts +++ b/src/serializers/html/html.test.ts @@ -622,6 +622,12 @@ Answer: [Doist Frontend](channel://190200)`), ) }) + test('angle-bracket URLs are preserved as text', () => { + expect(htmlSerializer.serialize('')).toBe( + '

<https://duckduckgo.com>

', + ) + }) + test('styled links HTML output is correct', () => { expect(htmlSerializer.serialize(MARKDOWN_INPUT_STYLED_LINKS)).toBe( '

I love supporting the EFF.
I love supporting the https://eff.org.
This is the Markdown Guide.
This is the https://www.markdownguide.org.
See the section on code.

', @@ -809,6 +815,12 @@ My favorite search engine is [Duck Duck Go](https://duckduckgo.com "The best sea My favorite search engine is https://duckduckgo.com.

`) }) + test('angle-bracket URLs are preserved as text without the link extension', () => { + expect(htmlSerializer.serialize('')).toBe( + '

<https://duckduckgo.com>

', + ) + }) + test('styled links HTML output is preserved', () => { expect(htmlSerializer.serialize(MARKDOWN_INPUT_STYLED_LINKS)) .toBe(`

I love supporting the **[EFF](https://eff.org)**. diff --git a/src/serializers/html/html.ts b/src/serializers/html/html.ts index e6791b9d..9fd0b3e4 100644 --- a/src/serializers/html/html.ts +++ b/src/serializers/html/html.ts @@ -19,6 +19,7 @@ import { rehypeTable } from './plugins/rehype-table' import { rehypeTaskList } from './plugins/rehype-task-list' import { remarkAutolinkLiteral } from './plugins/remark-autolink-literal' import { remarkDisableConstructs } from './plugins/remark-disable-constructs' +import { remarkIgnoreAngleBracketAutolinks } from './plugins/remark-ignore-angle-bracket-autolinks' import { remarkStrikethrough } from './plugins/remark-strikethrough' import { remarkTable } from './plugins/remark-table' @@ -101,6 +102,9 @@ function createHTMLSerializer(schema: Schema): HTMLSerializerReturnType { // supported extensions that are enabled in the editor schema unifiedProcessor.use(remarkDisableConstructs, schema) + // Ignore angle-bracket autolinks because they are not a supported rich-text Markdown shortcut + unifiedProcessor.use(remarkIgnoreAngleBracketAutolinks) + // Configure the unified processor to use a third-party plugin to turn soft line endings into // hard breaks (i.e. `
`), which will display user content closer to how it was authored // (although not CommonMark compliant, this resembles the behaviour we always supported) diff --git a/src/serializers/html/plugins/remark-ignore-angle-bracket-autolinks.ts b/src/serializers/html/plugins/remark-ignore-angle-bracket-autolinks.ts new file mode 100644 index 00000000..bc57a38b --- /dev/null +++ b/src/serializers/html/plugins/remark-ignore-angle-bracket-autolinks.ts @@ -0,0 +1,35 @@ +import { visit } from 'unist-util-visit' + +import type { Root } from 'mdast' +import type { Transformer } from 'unified' + +/** + * Treats angle-bracket HTTP(S) autolinks as plain text because they are not a supported rich-text + * Markdown shortcut. + */ +function remarkIgnoreAngleBracketAutolinks(): Transformer { + return (tree, file) => { + visit(tree, 'link', (node, index, parent) => { + const startOffset = node.position?.start.offset + const endOffset = node.position?.end.offset + + if ( + node.type !== 'link' || + typeof startOffset !== 'number' || + typeof endOffset !== 'number' || + typeof index !== 'number' || + !parent + ) { + return + } + + const source = String(file.value).slice(startOffset, endOffset) + + if (/^$/i.test(source)) { + parent.children[index] = { type: 'text', value: source } + } + }) + } +} + +export { remarkIgnoreAngleBracketAutolinks }