diff --git a/packages/text-markdown/src/__tests__/markdown.test.ts b/packages/text-markdown/src/__tests__/markdown.test.ts index 4ecdf49731..221bf004c1 100644 --- a/packages/text-markdown/src/__tests__/markdown.test.ts +++ b/packages/text-markdown/src/__tests__/markdown.test.ts @@ -13,6 +13,7 @@ // limitations under the License. // +import { MarkupNode } from '@hcengineering/text-core' import { markdownToMarkup, markupToMarkdown } from '..' import { isMarkdownsEquals } from '../compare' @@ -855,8 +856,65 @@ Lorem ipsum dolor sit amet. }) }) +describe('markupToMarkdown', () => { + const tests: Array<{ name: string, markdown: string, markup: object }> = [ + { + name: 'links', + markdown: `[Link](https://example.com) + +[Link with spaces]() + +[Link with spaces and braces](>)`, + markup: { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Link', + marks: [{ type: 'link', attrs: { href: 'https://example.com' } }] + } + ] + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Link with spaces', + marks: [{ type: 'link', attrs: { href: 'https://example.com/with spaces' } }] + } + ] + }, + { + type: 'paragraph', + content: [ + { + type: 'text', + text: 'Link with spaces and braces', + marks: [{ type: 'link', attrs: { href: 'https://example.com/' } }] + } + ] + } + ] + } + } + ] + + describe('to markdown', () => { + tests.forEach(({ name, markdown, markup }) => { + it(name, () => { + const result = markupToMarkdown(markup as MarkupNode, options) + expect(result).toEqual(markdown) + }) + }) + }) +}) + describe('markdownToMarkup -> markupToMarkdown', () => { - const tests: Array<{ name: string, markdown: string }> = [ + const tests: Array<{ name: string, markdown: string, alternate?: string }> = [ { name: 'Italic', markdown: '*Asteriscs* and _Underscores_' }, { name: 'Bold', markdown: '**Asteriscs** and __Underscores__' }, { name: 'Bullet list with asteriscs', markdown: 'Asterisks :\r\n* Firstly\r\n* Secondly' }, @@ -878,6 +936,16 @@ describe('markdownToMarkup -> markupToMarkdown', () => { name: 'Link', markdown: 'See [link](https://example.com)' }, + { + name: 'Link with spaces', + markdown: 'See [link]()', + alternate: 'See [link](https://example.com/with%20spaces)' + }, + { + name: 'Link with spaces and braces', + markdown: 'See [link](>)', + alternate: 'See [link](https://example.com/%3Cwith%20spaces%3E)' + }, { name: 'Codeblock', markdown: '```typescript\nconst x: number = 42;\n```' @@ -901,11 +969,11 @@ describe('markdownToMarkup -> markupToMarkdown', () => { // } ] - tests.forEach(({ name, markdown }) => { + tests.forEach(({ name, markdown, alternate }) => { it(name, () => { const json = markdownToMarkup(markdown, options) const serialized = markupToMarkdown(json, options) - expect(serialized).toEqualMarkdown(markdown) + expect(serialized).toEqualMarkdown(alternate ?? markdown) }) }) }) diff --git a/packages/text-markdown/src/serializer.ts b/packages/text-markdown/src/serializer.ts index d0a46022b2..53270e578d 100644 --- a/packages/text-markdown/src/serializer.ts +++ b/packages/text-markdown/src/serializer.ts @@ -351,11 +351,16 @@ export const storeMarks: Record = { } else { const { inAutolink } = state state.inAutolink = undefined + + const href = (mark.attrs?.href as string) ?? '' + // eslint-disable-next-line + const url = href.replace(/[\(\)"\\<>]/g, '\\$&') + const hasSpaces = url.includes(' ') + return inAutolink === true ? '>' : '](' + - // eslint-disable-next-line - (mark.attrs?.href as string).replace(/[\(\)"]/g, '\\$&') + + (hasSpaces ? `<${url}>` : url) + (mark.attrs?.title !== undefined ? ` "${(mark.attrs?.title as string).replace(/"/g, '\\"')}"` : '') + ')' }