diff --git a/packages/core/src/BlockNoteEditor.ts b/packages/core/src/BlockNoteEditor.ts index 5a64ebb942..f1735e5a2c 100644 --- a/packages/core/src/BlockNoteEditor.ts +++ b/packages/core/src/BlockNoteEditor.ts @@ -1064,7 +1064,7 @@ export class BlockNoteEditor< * @returns The blocks, serialized as a Markdown string. */ public async blocksToMarkdownLossy( - blocks = this.topLevelBlocks + blocks: Block[] = this.topLevelBlocks ): Promise { return blocksToMarkdown(blocks, this._tiptapEditor.schema, this); } diff --git a/packages/core/src/api/exporters/html/__snapshots__/mention/basic/external.html b/packages/core/src/api/exporters/html/__snapshots__/mention/basic/external.html index dbec651469..e6ba4c7bfc 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/mention/basic/external.html +++ b/packages/core/src/api/exporters/html/__snapshots__/mention/basic/external.html @@ -1 +1 @@ -

I enjoy working with@Matthew

\ No newline at end of file +

I enjoy working with @Matthew

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/__snapshots__/mention/basic/internal.html b/packages/core/src/api/exporters/html/__snapshots__/mention/basic/internal.html index 3e00d69a9c..fa3e3e8414 100644 --- a/packages/core/src/api/exporters/html/__snapshots__/mention/basic/internal.html +++ b/packages/core/src/api/exporters/html/__snapshots__/mention/basic/internal.html @@ -1 +1 @@ -

I enjoy working with@Matthew

\ No newline at end of file +

I enjoy working with @Matthew

\ No newline at end of file diff --git a/packages/core/src/api/exporters/html/htmlConversion.test.ts b/packages/core/src/api/exporters/html/htmlConversion.test.ts index f6592f1bb7..cf018e6c10 100644 --- a/packages/core/src/api/exporters/html/htmlConversion.test.ts +++ b/packages/core/src/api/exporters/html/htmlConversion.test.ts @@ -2,299 +2,19 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { BlockNoteEditor } from "../../../BlockNoteEditor"; import { addIdsToBlocks, partialBlocksToBlocksForTesting } from "../../.."; -import { createBlockSpec } from "../../../extensions/Blocks/api/blocks/createSpec"; import { BlockSchema, - BlockSchemaFromSpecs, - BlockSpecs, PartialBlock, } from "../../../extensions/Blocks/api/blocks/types"; -import { - DefaultInlineContentSchema, - DefaultStyleSchema, - defaultBlockSpecs, -} from "../../../extensions/Blocks/api/defaultBlocks"; -import { defaultProps } from "../../../extensions/Blocks/api/defaultProps"; import { InlineContentSchema } from "../../../extensions/Blocks/api/inlineContent/types"; import { StyleSchema } from "../../../extensions/Blocks/api/styles/types"; -import { - imagePropSchema, - renderImage, -} from "../../../extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent"; -import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; -import { EditorTestCases } from "../../testCases"; +import { customBlocksTestCases } from "../../testCases/cases/customBlocks"; import { customInlineContentTestCases } from "../../testCases/cases/customInlineContent"; import { customStylesTestCases } from "../../testCases/cases/customStyles"; import { defaultSchemaTestCases } from "../../testCases/cases/defaultSchema"; import { createExternalHTMLExporter } from "./externalHTMLExporter"; import { createInternalHTMLSerializer } from "./internalHTMLSerializer"; -// This is a modified version of the default image block that does not implement -// a `serialize` function. It's used to test if the custom serializer by default -// serializes custom blocks using their `render` function. -const SimpleImage = createBlockSpec( - { - type: "simpleImage" as const, - propSchema: imagePropSchema, - content: "none", - }, - { render: renderImage as any } -); - -const CustomParagraph = createBlockSpec( - { - type: "customParagraph" as const, - propSchema: defaultProps, - content: "inline", - }, - { - render: () => { - const paragraph = document.createElement("p"); - paragraph.className = "custom-paragraph"; - - return { - dom: paragraph, - contentDOM: paragraph, - }; - }, - toExternalHTML: () => { - const paragraph = document.createElement("p"); - paragraph.className = "custom-paragraph"; - paragraph.innerHTML = "Hello World"; - - return { - dom: paragraph, - }; - }, - } -); - -const SimpleCustomParagraph = createBlockSpec( - { - type: "simpleCustomParagraph" as const, - propSchema: defaultProps, - content: "inline", - }, - { - render: () => { - const paragraph = document.createElement("p"); - paragraph.className = "simple-custom-paragraph"; - - return { - dom: paragraph, - contentDOM: paragraph, - }; - }, - } -); - -const customSpecs = { - ...defaultBlockSpecs, - simpleImage: SimpleImage, - customParagraph: CustomParagraph, - simpleCustomParagraph: SimpleCustomParagraph, -} satisfies BlockSpecs; - -const editorTestCases: EditorTestCases< - BlockSchemaFromSpecs, - DefaultInlineContentSchema, - DefaultStyleSchema -> = { - name: "custom schema", - createEditor: () => { - return BlockNoteEditor.create({ - blockSpecs: customSpecs, - uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, - }); - }, - documents: [ - { - name: "simpleImage/button", - blocks: [ - { - type: "simpleImage" as const, - }, - ], - }, - { - name: "simpleImage/basic", - blocks: [ - { - type: "simpleImage" as const, - props: { - url: "exampleURL", - caption: "Caption", - width: 256, - } as const, - }, - ], - }, - { - name: "simpleImage/nested", - blocks: [ - { - type: "simpleImage" as const, - props: { - url: "exampleURL", - caption: "Caption", - width: 256, - } as const, - children: [ - { - type: "simpleImage" as const, - props: { - url: "exampleURL", - caption: "Caption", - width: 256, - } as const, - }, - ], - }, - ], - }, - { - name: "customParagraph/basic", - blocks: [ - { - type: "customParagraph" as const, - content: "Custom Paragraph", - }, - ], - }, - { - name: "customParagraph/styled", - blocks: [ - { - type: "customParagraph" as const, - props: { - textAlignment: "center", - textColor: "orange", - backgroundColor: "pink", - } as const, - content: [ - { - type: "text", - styles: {}, - text: "Plain ", - }, - { - type: "text", - styles: { - textColor: "red", - }, - text: "Red Text ", - }, - { - type: "text", - styles: { - backgroundColor: "blue", - }, - text: "Blue Background ", - }, - { - type: "text", - styles: { - textColor: "red", - backgroundColor: "blue", - }, - text: "Mixed Colors", - }, - ], - }, - ], - }, - { - name: "customParagraph/nested", - blocks: [ - { - type: "customParagraph" as const, - content: "Custom Paragraph", - children: [ - { - type: "customParagraph" as const, - content: "Nested Custom Paragraph 1", - }, - { - type: "customParagraph" as const, - content: "Nested Custom Paragraph 2", - }, - ], - }, - ], - }, - { - name: "simpleCustomParagraph/basic", - blocks: [ - { - type: "simpleCustomParagraph" as const, - content: "Custom Paragraph", - }, - ], - }, - { - name: "simpleCustomParagraph/styled", - blocks: [ - { - type: "simpleCustomParagraph" as const, - props: { - textAlignment: "center", - textColor: "orange", - backgroundColor: "pink", - } as const, - content: [ - { - type: "text", - styles: {}, - text: "Plain ", - }, - { - type: "text", - styles: { - textColor: "red", - }, - text: "Red Text ", - }, - { - type: "text", - styles: { - backgroundColor: "blue", - }, - text: "Blue Background ", - }, - { - type: "text", - styles: { - textColor: "red", - backgroundColor: "blue", - }, - text: "Mixed Colors", - }, - ], - }, - ], - }, - { - name: "simpleCustomParagraph/nested", - blocks: [ - { - type: "simpleCustomParagraph" as const, - content: "Custom Paragraph", - children: [ - { - type: "simpleCustomParagraph" as const, - content: "Nested Custom Paragraph 1", - }, - { - type: "simpleCustomParagraph" as const, - content: "Nested Custom Paragraph 2", - }, - ], - }, - ], - }, - ], -}; - async function convertToHTMLAndCompareSnapshots< B extends BlockSchema, I extends InlineContentSchema, @@ -345,7 +65,7 @@ async function convertToHTMLAndCompareSnapshots< const testCases = [ defaultSchemaTestCases, - editorTestCases, + customBlocksTestCases, customStylesTestCases, customInlineContentTestCases, ]; diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/complex/misc/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/complex/misc/markdown.md new file mode 100644 index 0000000000..c0e8ed3d9b --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/complex/misc/markdown.md @@ -0,0 +1,5 @@ +## **Heading ***~~2~~* + +Paragraph + +* diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/basic/markdown.md new file mode 100644 index 0000000000..557db03de9 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/basic/markdown.md @@ -0,0 +1 @@ +Hello World diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/nested/markdown.md new file mode 100644 index 0000000000..f4f110c5fb --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/nested/markdown.md @@ -0,0 +1,5 @@ +Hello World + +Hello World + +Hello World diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/styled/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/styled/markdown.md new file mode 100644 index 0000000000..557db03de9 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/customParagraph/styled/markdown.md @@ -0,0 +1 @@ +Hello World diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/fontSize/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/fontSize/basic/markdown.md new file mode 100644 index 0000000000..a14913bf9b --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/fontSize/basic/markdown.md @@ -0,0 +1 @@ +This is text with a custom fontSize diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/formatConversions.test.ts.snap b/packages/core/src/api/exporters/markdown/__snapshots__/formatConversions.test.ts.snap deleted file mode 100644 index a3342cc7df..0000000000 --- a/packages/core/src/api/exporters/markdown/__snapshots__/formatConversions.test.ts.snap +++ /dev/null @@ -1,346 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Complex Block/HTML/Markdown Conversions > Convert complex blocks to HTML 1`] = `"

Heading 1

Heading 2

Heading 3

Paragraph

Paragraph

Paragraph

  • Bullet List Item

  • Bullet List Item

    • Bullet List Item

      • Bullet List Item

      Paragraph

      1. Numbered List Item

      2. Numbered List Item

      3. Numbered List Item

        1. Numbered List Item

      • Bullet List Item

    • Bullet List Item

  • Bullet List Item

"`; - -exports[`Complex Block/HTML/Markdown Conversions > Convert complex blocks to Markdown 1`] = ` -"# Heading 1 - -## Heading 2 - -### Heading 3 - -Paragraph - -P**ara***grap*h - -Para~~grap~~h - -* Bullet List Item - -* Bullet List Item - - * Bullet List Item - - * Bullet List Item - - Paragraph - - 1. Numbered List Item - - 2. Numbered List Item - - 3. Numbered List Item - - 1. Numbered List Item - - * Bullet List Item - - * Bullet List Item - -* Bullet List Item -" -`; - -exports[`Nested Block/HTML/Markdown Conversions > Convert nested blocks to HTML 1`] = `"

Heading

Paragraph

  • Bullet List Item

    1. Numbered List Item

"`; - -exports[`Nested Block/HTML/Markdown Conversions > Convert nested blocks to Markdown 1`] = ` -"# Heading - -Paragraph - -* Bullet List Item - - 1. Numbered List Item -" -`; - -exports[`Non-Nested Block/HTML/Markdown Conversions > Convert non-nested HTML to blocks 1`] = ` -[ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Heading", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Bullet List Item", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "bulletListItem", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Numbered List Item", - "type": "text", - }, - ], - "id": "4", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "numberedListItem", - }, -] -`; - -exports[`Non-Nested Block/HTML/Markdown Conversions > Convert non-nested Markdown to blocks 1`] = ` -[ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Heading", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "level": 1, - "textAlignment": "left", - "textColor": "default", - }, - "type": "heading", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph", - "type": "text", - }, - ], - "id": "2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Bullet List Item", - "type": "text", - }, - ], - "id": "3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "bulletListItem", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Numbered List Item", - "type": "text", - }, - ], - "id": "4", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "numberedListItem", - }, -] -`; - -exports[`Non-Nested Block/HTML/Markdown Conversions > Convert non-nested blocks to HTML 1`] = `"

Heading

Paragraph

  • Bullet List Item

  1. Numbered List Item

"`; - -exports[`Non-Nested Block/HTML/Markdown Conversions > Convert non-nested blocks to Markdown 1`] = ` -"# Heading - -Paragraph - -* Bullet List Item - -1. Numbered List Item -" -`; - -exports[`Styled Block/HTML/Markdown Conversions > Convert styled HTML to blocks 1`] = ` -[ - { - "children": [], - "content": [ - { - "styles": { - "bold": true, - }, - "text": "Bold", - "type": "text", - }, - { - "styles": { - "italic": true, - }, - "text": "Italic", - "type": "text", - }, - { - "styles": { - "underline": true, - }, - "text": "Underline", - "type": "text", - }, - { - "styles": { - "strike": true, - }, - "text": "Strikethrough", - "type": "text", - }, - { - "styles": { - "textColor": "red", - }, - "text": "TextColor", - "type": "text", - }, - { - "styles": { - "backgroundColor": "red", - }, - "text": "BackgroundColor", - "type": "text", - }, - { - "styles": { - "bold": true, - "italic": true, - }, - "text": "Multiple", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Styled Block/HTML/Markdown Conversions > Convert styled Markdown to blocks 1`] = ` -[ - { - "children": [], - "content": [ - { - "styles": { - "bold": true, - }, - "text": "Bold", - "type": "text", - }, - { - "styles": { - "italic": true, - }, - "text": "Italic", - "type": "text", - }, - { - "styles": {}, - "text": "Underline", - "type": "text", - }, - { - "styles": { - "strike": true, - }, - "text": "Strikethrough", - "type": "text", - }, - { - "styles": {}, - "text": "TextColorBackgroundColor", - "type": "text", - }, - { - "styles": { - "bold": true, - "italic": true, - }, - "text": "Multiple", - "type": "text", - }, - ], - "id": "1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, -] -`; - -exports[`Styled Block/HTML/Markdown Conversions > Convert styled blocks to HTML 1`] = `"

BoldItalicUnderlineStrikethroughTextColorBackgroundColorMultiple

"`; - -exports[`Styled Block/HTML/Markdown Conversions > Convert styled blocks to Markdown 1`] = ` -"**Bold***Italic*Underline~~Strikethrough~~TextColorBackgroundColor***Multiple*** -" -`; diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/basic/markdown.md new file mode 100644 index 0000000000..0fe906b288 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/basic/markdown.md @@ -0,0 +1,2 @@ +Text1\ +Text2 diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/between-links/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/between-links/markdown.md new file mode 100644 index 0000000000..3f74feb726 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/between-links/markdown.md @@ -0,0 +1,2 @@ +[Link1](https://www.website.com)\ +[Link2](https://www.website2.com) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/end/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/end/markdown.md new file mode 100644 index 0000000000..9d80a6ba66 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/end/markdown.md @@ -0,0 +1 @@ +Text1 diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/link/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/link/markdown.md new file mode 100644 index 0000000000..95a590abea --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/link/markdown.md @@ -0,0 +1,2 @@ +[Link1](https://www.website.com)\ +[Link1](https://www.website.com) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/multiple/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/multiple/markdown.md new file mode 100644 index 0000000000..f7e9c54f1f --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/multiple/markdown.md @@ -0,0 +1,3 @@ +Text1\ +Text2\ +Text3 diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/only/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/only/markdown.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/start/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/start/markdown.md new file mode 100644 index 0000000000..9d80a6ba66 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/start/markdown.md @@ -0,0 +1 @@ +Text1 diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/styles/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/styles/markdown.md new file mode 100644 index 0000000000..f92fc1d40e --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/hardbreak/styles/markdown.md @@ -0,0 +1,2 @@ +Text1\ +**Text2** diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md new file mode 100644 index 0000000000..dda13c76fa --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/basic/markdown.md @@ -0,0 +1,3 @@ +![](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/button/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/button/markdown.md new file mode 100644 index 0000000000..4f8610b831 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/button/markdown.md @@ -0,0 +1 @@ +Add Image diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md new file mode 100644 index 0000000000..d2d1ce4de4 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/image/nested/markdown.md @@ -0,0 +1,7 @@ +![](exampleURL) + +Caption + +![](exampleURL) + +Caption diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/link/adjacent/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/link/adjacent/markdown.md new file mode 100644 index 0000000000..4fe44186fa --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/link/adjacent/markdown.md @@ -0,0 +1 @@ +[Website](https://www.website.com)[Website2](https://www.website2.com) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/link/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/link/basic/markdown.md new file mode 100644 index 0000000000..bc9d83b3da --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/link/basic/markdown.md @@ -0,0 +1 @@ +[Website](https://www.website.com) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/link/styled/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/link/styled/markdown.md new file mode 100644 index 0000000000..ad7b143e27 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/link/styled/markdown.md @@ -0,0 +1 @@ +**[Web](https://www.website.com)**[site](https://www.website.com) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/mention/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/mention/basic/markdown.md new file mode 100644 index 0000000000..b6a2ae25b3 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/mention/basic/markdown.md @@ -0,0 +1 @@ +I enjoy working with @Matthew diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/basic/markdown.md new file mode 100644 index 0000000000..07e18e6d30 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/basic/markdown.md @@ -0,0 +1 @@ +Paragraph diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/empty/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/empty/markdown.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/nested/markdown.md new file mode 100644 index 0000000000..af7d1348ad --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/nested/markdown.md @@ -0,0 +1,5 @@ +Paragraph + +Nested Paragraph 1 + +Nested Paragraph 2 diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/styled/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/styled/markdown.md new file mode 100644 index 0000000000..4f45e63c5c --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/paragraph/styled/markdown.md @@ -0,0 +1 @@ +Plain Red Text Blue Background Mixed Colors diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/basic/markdown.md new file mode 100644 index 0000000000..fd50b044f8 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/basic/markdown.md @@ -0,0 +1 @@ +Custom Paragraph diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/nested/markdown.md new file mode 100644 index 0000000000..147effc747 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/nested/markdown.md @@ -0,0 +1,5 @@ +Custom Paragraph + +Nested Custom Paragraph 1 + +Nested Custom Paragraph 2 diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/styled/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/styled/markdown.md new file mode 100644 index 0000000000..4f45e63c5c --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleCustomParagraph/styled/markdown.md @@ -0,0 +1 @@ +Plain Red Text Blue Background Mixed Colors diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md new file mode 100644 index 0000000000..e90136ab90 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/basic/markdown.md @@ -0,0 +1 @@ +![placeholder](exampleURL) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md new file mode 100644 index 0000000000..d642ea87c6 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/button/markdown.md @@ -0,0 +1 @@ +![placeholder]() diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md new file mode 100644 index 0000000000..7d84311ed4 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/simpleImage/nested/markdown.md @@ -0,0 +1,3 @@ +![placeholder](exampleURL) + +![placeholder](exampleURL) diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/small/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/small/basic/markdown.md new file mode 100644 index 0000000000..02738ab95b --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/small/basic/markdown.md @@ -0,0 +1 @@ +This is a small text diff --git a/packages/core/src/api/exporters/markdown/__snapshots__/tag/basic/markdown.md b/packages/core/src/api/exporters/markdown/__snapshots__/tag/basic/markdown.md new file mode 100644 index 0000000000..8adc77839a --- /dev/null +++ b/packages/core/src/api/exporters/markdown/__snapshots__/tag/basic/markdown.md @@ -0,0 +1 @@ +I love #BlockNote diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.test.ts b/packages/core/src/api/exporters/markdown/markdownExporter.test.ts new file mode 100644 index 0000000000..63ca32c2fa --- /dev/null +++ b/packages/core/src/api/exporters/markdown/markdownExporter.test.ts @@ -0,0 +1,77 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { BlockNoteEditor } from "../../../BlockNoteEditor"; + +import { + BlockSchema, + PartialBlock, +} from "../../../extensions/Blocks/api/blocks/types"; +import { InlineContentSchema } from "../../../extensions/Blocks/api/inlineContent/types"; +import { StyleSchema } from "../../../extensions/Blocks/api/styles/types"; +import { partialBlocksToBlocksForTesting } from "../../nodeConversions/testUtil"; +import { customBlocksTestCases } from "../../testCases/cases/customBlocks"; +import { customInlineContentTestCases } from "../../testCases/cases/customInlineContent"; +import { customStylesTestCases } from "../../testCases/cases/customStyles"; +import { defaultSchemaTestCases } from "../../testCases/cases/defaultSchema"; + +async function convertToMarkdownAndCompareSnapshots< + B extends BlockSchema, + I extends InlineContentSchema, + S extends StyleSchema +>( + editor: BlockNoteEditor, + blocks: PartialBlock[], + snapshotDirectory: string, + snapshotName: string +) { + const fullBlocks = partialBlocksToBlocksForTesting( + editor.blockSchema, + blocks + ); + const md = await editor.blocksToMarkdownLossy(fullBlocks); + const snapshotPath = + "./__snapshots__/" + + snapshotDirectory + + "/" + + snapshotName + + "/markdown.md"; + expect(md).toMatchFileSnapshot(snapshotPath); +} + +const testCases = [ + defaultSchemaTestCases, + customBlocksTestCases, + customStylesTestCases, + customInlineContentTestCases, +]; + +describe("markdownExporter", () => { + for (const testCase of testCases) { + describe("Case: " + testCase.name, () => { + let editor: BlockNoteEditor; + + beforeEach(() => { + editor = testCase.createEditor(); + }); + + afterEach(() => { + editor._tiptapEditor.destroy(); + editor = undefined as any; + + delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS; + }); + + for (const document of testCase.documents) { + // eslint-disable-next-line no-loop-func + it("Convert " + document.name + " to HTML", async () => { + const nameSplit = document.name.split("/"); + await convertToMarkdownAndCompareSnapshots( + editor, + document.blocks, + nameSplit[0], + nameSplit[1] + ); + }); + } + }); + } +}); diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.ts b/packages/core/src/api/exporters/markdown/markdownExporter.ts index fbe1fdd15c..0c39d0d2e9 100644 --- a/packages/core/src/api/exporters/markdown/markdownExporter.ts +++ b/packages/core/src/api/exporters/markdown/markdownExporter.ts @@ -26,7 +26,6 @@ export function cleanHTMLToMarkdown(cleanHTMLString: string) { return markdownString.value as string; } -// TODO: add tests export function blocksToMarkdown< BSchema extends BlockSchema, I extends InlineContentSchema, diff --git a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap index 41b67fb5ca..c273c6e7e4 100644 --- a/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +++ b/packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap @@ -14,7 +14,7 @@ exports[`Test BlockNote-Prosemirror conversion > Case: custom inline content sch }, "content": [ { - "text": "I enjoy working with", + "text": "I enjoy working with ", "type": "text", }, { diff --git a/packages/core/src/api/parsers/markdown/__snapshots__/complex.json b/packages/core/src/api/parsers/markdown/__snapshots__/complex.json new file mode 100644 index 0000000000..37036e205d --- /dev/null +++ b/packages/core/src/api/parsers/markdown/__snapshots__/complex.json @@ -0,0 +1,353 @@ +[ + { + "id": "1", + "type": "heading", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left", + "level": 1 + }, + "content": [ + { + "type": "text", + "text": "Heading 1", + "styles": {} + } + ], + "children": [] + }, + { + "id": "2", + "type": "heading", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left", + "level": 2 + }, + "content": [ + { + "type": "text", + "text": "Heading 2", + "styles": {} + } + ], + "children": [] + }, + { + "id": "3", + "type": "heading", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left", + "level": 3 + }, + "content": [ + { + "type": "text", + "text": "Heading 3", + "styles": {} + } + ], + "children": [] + }, + { + "id": "4", + "type": "paragraph", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph", + "styles": {} + } + ], + "children": [] + }, + { + "id": "5", + "type": "paragraph", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "P", + "styles": {} + }, + { + "type": "text", + "text": "ara", + "styles": { + "bold": true + } + }, + { + "type": "text", + "text": "grap", + "styles": { + "italic": true + } + }, + { + "type": "text", + "text": "h", + "styles": {} + } + ], + "children": [] + }, + { + "id": "6", + "type": "paragraph", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "P", + "styles": {} + }, + { + "type": "text", + "text": "ara", + "styles": { + "italic": true + } + }, + { + "type": "text", + "text": "grap", + "styles": { + "strike": true + } + }, + { + "type": "text", + "text": "h", + "styles": {} + } + ], + "children": [] + }, + { + "id": "7", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [] + }, + { + "id": "8", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [ + { + "id": "9", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [ + { + "id": "10", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [] + } + ] + }, + { + "id": "11", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph", + "styles": {} + } + ], + "children": [ + { + "id": "12", + "type": "numberedListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Numbered List Item", + "styles": {} + } + ], + "children": [] + }, + { + "id": "13", + "type": "numberedListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Numbered List Item", + "styles": {} + } + ], + "children": [] + }, + { + "id": "14", + "type": "numberedListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Numbered List Item", + "styles": {} + } + ], + "children": [ + { + "id": "15", + "type": "numberedListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Numbered List Item", + "styles": {} + } + ], + "children": [] + } + ] + }, + { + "id": "16", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [] + } + ] + }, + { + "id": "17", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [] + } + ] + }, + { + "id": "18", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [] + } +] \ No newline at end of file diff --git a/packages/core/src/api/parsers/markdown/__snapshots__/issue-226-1.json b/packages/core/src/api/parsers/markdown/__snapshots__/issue-226-1.json new file mode 100644 index 0000000000..9c186d37b0 --- /dev/null +++ b/packages/core/src/api/parsers/markdown/__snapshots__/issue-226-1.json @@ -0,0 +1,71 @@ +[ + { + "id": "1", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "📝 item1", + "styles": {} + } + ], + "children": [] + }, + { + "id": "2", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "⚙️ item2", + "styles": {} + } + ], + "children": [] + }, + { + "id": "3", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "🔗 item3", + "styles": {} + } + ], + "children": [] + }, + { + "id": "4", + "type": "heading", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left", + "level": 1 + }, + "content": [ + { + "type": "text", + "text": "h1", + "styles": {} + } + ], + "children": [] + } +] \ No newline at end of file diff --git a/packages/core/src/api/parsers/markdown/__snapshots__/issue-226-2.json b/packages/core/src/api/parsers/markdown/__snapshots__/issue-226-2.json new file mode 100644 index 0000000000..fc1b9f3de9 --- /dev/null +++ b/packages/core/src/api/parsers/markdown/__snapshots__/issue-226-2.json @@ -0,0 +1,144 @@ +[ + { + "id": "1", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "a", + "styles": {} + } + ], + "children": [] + }, + { + "id": "2", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "b", + "styles": {} + } + ], + "children": [] + }, + { + "id": "3", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "c", + "styles": {} + } + ], + "children": [] + }, + { + "id": "4", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "d", + "styles": {} + } + ], + "children": [] + }, + { + "id": "5", + "type": "paragraph", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "anything", + "styles": {} + } + ], + "children": [] + }, + { + "id": "6", + "type": "paragraph", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "link", + "href": "http://example.com", + "content": [ + { + "type": "text", + "text": "a link", + "styles": {} + } + ] + } + ], + "children": [] + }, + { + "id": "7", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "another", + "styles": {} + } + ], + "children": [] + }, + { + "id": "8", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "list", + "styles": {} + } + ], + "children": [] + } +] \ No newline at end of file diff --git a/packages/core/src/api/parsers/markdown/__snapshots__/nested.json b/packages/core/src/api/parsers/markdown/__snapshots__/nested.json new file mode 100644 index 0000000000..627349f4f8 --- /dev/null +++ b/packages/core/src/api/parsers/markdown/__snapshots__/nested.json @@ -0,0 +1,72 @@ +[ + { + "id": "1", + "type": "heading", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left", + "level": 1 + }, + "content": [ + { + "type": "text", + "text": "Heading", + "styles": {} + } + ], + "children": [] + }, + { + "id": "2", + "type": "paragraph", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph", + "styles": {} + } + ], + "children": [] + }, + { + "id": "3", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [ + { + "id": "4", + "type": "numberedListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Numbered List Item", + "styles": {} + } + ], + "children": [] + } + ] + } +] \ No newline at end of file diff --git a/packages/core/src/api/parsers/markdown/__snapshots__/non-nested.json b/packages/core/src/api/parsers/markdown/__snapshots__/non-nested.json new file mode 100644 index 0000000000..05911c4983 --- /dev/null +++ b/packages/core/src/api/parsers/markdown/__snapshots__/non-nested.json @@ -0,0 +1,71 @@ +[ + { + "id": "1", + "type": "heading", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left", + "level": 1 + }, + "content": [ + { + "type": "text", + "text": "Heading", + "styles": {} + } + ], + "children": [] + }, + { + "id": "2", + "type": "paragraph", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Paragraph", + "styles": {} + } + ], + "children": [] + }, + { + "id": "3", + "type": "bulletListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bullet List Item", + "styles": {} + } + ], + "children": [] + }, + { + "id": "4", + "type": "numberedListItem", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Numbered List Item", + "styles": {} + } + ], + "children": [] + } +] \ No newline at end of file diff --git a/packages/core/src/api/parsers/markdown/__snapshots__/styled.json b/packages/core/src/api/parsers/markdown/__snapshots__/styled.json new file mode 100644 index 0000000000..43a1efe52d --- /dev/null +++ b/packages/core/src/api/parsers/markdown/__snapshots__/styled.json @@ -0,0 +1,58 @@ +[ + { + "id": "1", + "type": "paragraph", + "props": { + "textColor": "default", + "backgroundColor": "default", + "textAlignment": "left" + }, + "content": [ + { + "type": "text", + "text": "Bold", + "styles": { + "bold": true + } + }, + { + "type": "text", + "text": " ", + "styles": {} + }, + { + "type": "text", + "text": "Italic", + "styles": { + "italic": true + } + }, + { + "type": "text", + "text": " ", + "styles": {} + }, + { + "type": "text", + "text": "Strikethrough", + "styles": { + "strike": true + } + }, + { + "type": "text", + "text": " ", + "styles": {} + }, + { + "type": "text", + "text": "Multiple", + "styles": { + "bold": true, + "italic": true + } + } + ], + "children": [] + } +] \ No newline at end of file diff --git a/packages/core/src/api/parsers/markdown/parseMarkdown.test.ts b/packages/core/src/api/parsers/markdown/parseMarkdown.test.ts new file mode 100644 index 0000000000..2c78b7f9e2 --- /dev/null +++ b/packages/core/src/api/parsers/markdown/parseMarkdown.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it } from "vitest"; +import { BlockNoteEditor } from "../../.."; + +async function parseMarkdownAndCompareSnapshots( + md: string, + snapshotName: string +) { + const editor = BlockNoteEditor.create(); + const blocks = await editor.tryParseMarkdownToBlocks(md); + + const snapshotPath = "./__snapshots__/" + snapshotName + ".json"; + expect(JSON.stringify(blocks, undefined, 2)).toMatchFileSnapshot( + snapshotPath + ); +} + +describe("Parse Markdown", () => { + it("Convert non-nested Markdown to blocks", async () => { + const markdown = `# Heading + +Paragraph + +* Bullet List Item + +1. Numbered List Item + `; + await parseMarkdownAndCompareSnapshots(markdown, "non-nested"); + }); + + // Failing due to nested block parsing bug. + it("Convert nested Markdown to blocks", async () => { + const markdown = `# Heading + +Paragraph + +* Bullet List Item + + 1. Numbered List Item +`; + await parseMarkdownAndCompareSnapshots(markdown, "nested"); + }); + + it("Convert styled Markdown to blocks", async () => { + const markdown = `**Bold** *Italic* ~~Strikethrough~~ ***Multiple***`; + await parseMarkdownAndCompareSnapshots(markdown, "styled"); + }); + + it("Convert complex Markdown to blocks", async () => { + const markdown = `# Heading 1 + +## Heading 2 + +### Heading 3 + +Paragraph + +P**ara***grap*h + +P*ara*~~grap~~h + +* Bullet List Item + +* Bullet List Item + + * Bullet List Item + + * Bullet List Item + + Paragraph + + 1. Numbered List Item + + 2. Numbered List Item + + 3. Numbered List Item + + 1. Numbered List Item + + * Bullet List Item + + * Bullet List Item + +* Bullet List Item`; + await parseMarkdownAndCompareSnapshots(markdown, "complex"); + }); +}); + +describe("Issue 226", () => { + it("Case 1", async () => { + const markdown = ` +- 📝 item1 +- ⚙️ item2 +- 🔗 item3 + +# h1 +`; + await parseMarkdownAndCompareSnapshots(markdown, "issue-226-1"); + }); + + it("Case 2", async () => { + const markdown = `* a +* b +* c +* d + +anything + +[a link](http://example.com) + +* another +* list`; + await parseMarkdownAndCompareSnapshots(markdown, "issue-226-2"); + }); +}); diff --git a/packages/core/src/api/parsers/markdown/parseMarkdown.ts b/packages/core/src/api/parsers/markdown/parseMarkdown.ts index f81cb7a0b3..0bf367f166 100644 --- a/packages/core/src/api/parsers/markdown/parseMarkdown.ts +++ b/packages/core/src/api/parsers/markdown/parseMarkdown.ts @@ -46,7 +46,6 @@ function code(state: any, node: any) { return result; } -// TODO: add tests export function markdownToBlocks< BSchema extends BlockSchema, I extends InlineContentSchema, diff --git a/packages/core/src/api/testCases/cases/customBlocks.ts b/packages/core/src/api/testCases/cases/customBlocks.ts new file mode 100644 index 0000000000..1c435a5254 --- /dev/null +++ b/packages/core/src/api/testCases/cases/customBlocks.ts @@ -0,0 +1,285 @@ +import { EditorTestCases } from ".."; + +import { BlockNoteEditor } from "../../../BlockNoteEditor"; +import { createBlockSpec } from "../../../extensions/Blocks/api/blocks/createSpec"; +import { + BlockSchemaFromSpecs, + BlockSpecs, +} from "../../../extensions/Blocks/api/blocks/types"; +import { + DefaultInlineContentSchema, + DefaultStyleSchema, + defaultBlockSpecs, +} from "../../../extensions/Blocks/api/defaultBlocks"; +import { defaultProps } from "../../../extensions/Blocks/api/defaultProps"; +import { + imagePropSchema, + renderImage, +} from "../../../extensions/Blocks/nodes/BlockContent/ImageBlockContent/ImageBlockContent"; +import { uploadToTmpFilesDotOrg_DEV_ONLY } from "../../../extensions/Blocks/nodes/BlockContent/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY"; + +// This is a modified version of the default image block that does not implement +// a `serialize` function. It's used to test if the custom serializer by default +// serializes custom blocks using their `render` function. +const SimpleImage = createBlockSpec( + { + type: "simpleImage" as const, + propSchema: imagePropSchema, + content: "none", + }, + { render: renderImage as any } +); + +const CustomParagraph = createBlockSpec( + { + type: "customParagraph" as const, + propSchema: defaultProps, + content: "inline", + }, + { + render: () => { + const paragraph = document.createElement("p"); + paragraph.className = "custom-paragraph"; + + return { + dom: paragraph, + contentDOM: paragraph, + }; + }, + toExternalHTML: () => { + const paragraph = document.createElement("p"); + paragraph.className = "custom-paragraph"; + paragraph.innerHTML = "Hello World"; + + return { + dom: paragraph, + }; + }, + } +); + +const SimpleCustomParagraph = createBlockSpec( + { + type: "simpleCustomParagraph" as const, + propSchema: defaultProps, + content: "inline", + }, + { + render: () => { + const paragraph = document.createElement("p"); + paragraph.className = "simple-custom-paragraph"; + + return { + dom: paragraph, + contentDOM: paragraph, + }; + }, + } +); + +const customSpecs = { + ...defaultBlockSpecs, + simpleImage: SimpleImage, + customParagraph: CustomParagraph, + simpleCustomParagraph: SimpleCustomParagraph, +} satisfies BlockSpecs; + +export const customBlocksTestCases: EditorTestCases< + BlockSchemaFromSpecs, + DefaultInlineContentSchema, + DefaultStyleSchema +> = { + name: "custom blocks schema", + createEditor: () => { + return BlockNoteEditor.create({ + blockSpecs: customSpecs, + uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, + }); + }, + documents: [ + { + name: "simpleImage/button", + blocks: [ + { + type: "simpleImage" as const, + }, + ], + }, + { + name: "simpleImage/basic", + blocks: [ + { + type: "simpleImage" as const, + props: { + url: "exampleURL", + caption: "Caption", + width: 256, + } as const, + }, + ], + }, + { + name: "simpleImage/nested", + blocks: [ + { + type: "simpleImage" as const, + props: { + url: "exampleURL", + caption: "Caption", + width: 256, + } as const, + children: [ + { + type: "simpleImage" as const, + props: { + url: "exampleURL", + caption: "Caption", + width: 256, + } as const, + }, + ], + }, + ], + }, + { + name: "customParagraph/basic", + blocks: [ + { + type: "customParagraph" as const, + content: "Custom Paragraph", + }, + ], + }, + { + name: "customParagraph/styled", + blocks: [ + { + type: "customParagraph" as const, + props: { + textAlignment: "center", + textColor: "orange", + backgroundColor: "pink", + } as const, + content: [ + { + type: "text", + styles: {}, + text: "Plain ", + }, + { + type: "text", + styles: { + textColor: "red", + }, + text: "Red Text ", + }, + { + type: "text", + styles: { + backgroundColor: "blue", + }, + text: "Blue Background ", + }, + { + type: "text", + styles: { + textColor: "red", + backgroundColor: "blue", + }, + text: "Mixed Colors", + }, + ], + }, + ], + }, + { + name: "customParagraph/nested", + blocks: [ + { + type: "customParagraph" as const, + content: "Custom Paragraph", + children: [ + { + type: "customParagraph" as const, + content: "Nested Custom Paragraph 1", + }, + { + type: "customParagraph" as const, + content: "Nested Custom Paragraph 2", + }, + ], + }, + ], + }, + { + name: "simpleCustomParagraph/basic", + blocks: [ + { + type: "simpleCustomParagraph" as const, + content: "Custom Paragraph", + }, + ], + }, + { + name: "simpleCustomParagraph/styled", + blocks: [ + { + type: "simpleCustomParagraph" as const, + props: { + textAlignment: "center", + textColor: "orange", + backgroundColor: "pink", + } as const, + content: [ + { + type: "text", + styles: {}, + text: "Plain ", + }, + { + type: "text", + styles: { + textColor: "red", + }, + text: "Red Text ", + }, + { + type: "text", + styles: { + backgroundColor: "blue", + }, + text: "Blue Background ", + }, + { + type: "text", + styles: { + textColor: "red", + backgroundColor: "blue", + }, + text: "Mixed Colors", + }, + ], + }, + ], + }, + { + name: "simpleCustomParagraph/nested", + blocks: [ + { + type: "simpleCustomParagraph" as const, + content: "Custom Paragraph", + children: [ + { + type: "simpleCustomParagraph" as const, + content: "Nested Custom Paragraph 1", + }, + { + type: "simpleCustomParagraph" as const, + content: "Nested Custom Paragraph 2", + }, + ], + }, + ], + }, + ], +}; diff --git a/packages/core/src/api/testCases/cases/customInlineContent.ts b/packages/core/src/api/testCases/cases/customInlineContent.ts index a1603f4a87..e1d725ef08 100644 --- a/packages/core/src/api/testCases/cases/customInlineContent.ts +++ b/packages/core/src/api/testCases/cases/customInlineContent.ts @@ -82,7 +82,7 @@ export const customInlineContentTestCases: EditorTestCases< { type: "paragraph", content: [ - "I enjoy working with", + "I enjoy working with ", { type: "mention", props: { diff --git a/packages/react/src/test/__snapshots__/mention/basic/external.html b/packages/react/src/test/__snapshots__/mention/basic/external.html index dbec651469..e6ba4c7bfc 100644 --- a/packages/react/src/test/__snapshots__/mention/basic/external.html +++ b/packages/react/src/test/__snapshots__/mention/basic/external.html @@ -1 +1 @@ -

I enjoy working with@Matthew

\ No newline at end of file +

I enjoy working with @Matthew

\ No newline at end of file diff --git a/packages/react/src/test/__snapshots__/mention/basic/internal.html b/packages/react/src/test/__snapshots__/mention/basic/internal.html index 3e00d69a9c..fa3e3e8414 100644 --- a/packages/react/src/test/__snapshots__/mention/basic/internal.html +++ b/packages/react/src/test/__snapshots__/mention/basic/internal.html @@ -1 +1 @@ -

I enjoy working with@Matthew

\ No newline at end of file +

I enjoy working with @Matthew

\ No newline at end of file diff --git a/packages/react/src/test/__snapshots__/nodeConversion.test.tsx.snap b/packages/react/src/test/__snapshots__/nodeConversion.test.tsx.snap index d61a928c5a..40418cd364 100644 --- a/packages/react/src/test/__snapshots__/nodeConversion.test.tsx.snap +++ b/packages/react/src/test/__snapshots__/nodeConversion.test.tsx.snap @@ -346,7 +346,7 @@ exports[`Test React BlockNote-Prosemirror conversion > Case: custom react inline }, "content": [ { - "text": "I enjoy working with", + "text": "I enjoy working with ", "type": "text", }, { diff --git a/packages/react/src/test/testCases/customReactInlineContent.tsx b/packages/react/src/test/testCases/customReactInlineContent.tsx index 4b6db0e07e..f9a90dff65 100644 --- a/packages/react/src/test/testCases/customReactInlineContent.tsx +++ b/packages/react/src/test/testCases/customReactInlineContent.tsx @@ -69,7 +69,7 @@ export const customReactInlineContentTestCases: EditorTestCases< { type: "paragraph", content: [ - "I enjoy working with", + "I enjoy working with ", { type: "mention", props: { diff --git a/packages/website/docs/docs/converting-blocks.md b/packages/website/docs/docs/converting-blocks.md index 8ffed0314c..e83ba1186b 100644 --- a/packages/website/docs/docs/converting-blocks.md +++ b/packages/website/docs/docs/converting-blocks.md @@ -34,12 +34,12 @@ BlockNote can import / export Blocks to and from Markdown. Note that this is als // Definition class BlockNoteEditor { ... - public blocksToMarkdown(blocks: Block[]): string; + public blocksToMarkdownLossy(blocks: Block[]): string; ... } // Usage -const markdownFromBlocks = editor.blocksToMarkdown(blocks); +const markdownFromBlocks = editor.blocksToMarkdownLossy(blocks); ``` `returns:` The blocks, serialized as a Markdown string. @@ -64,17 +64,17 @@ export default function App() { const editor: BlockNoteEditor = useBlockNote({ // Listens for when the editor's contents change. onEditorContentChange: (editor) => { - // Converts the editor's contents from Block objects to Markdown and + // Converts the editor's contents from Block objects to Markdown and // saves them. const saveBlocksAsMarkdown = async () => { - const markdown: string = - await editor.blocksToMarkdown(editor.topLevelBlocks); + const markdown: string = + await editor.blocksToMarkdownLossy(editor.topLevelBlocks); setMarkdown(markdown); }; saveBlocksAsMarkdown(); } }); - + // Renders the editor instance, and its contents as Markdown below. return (
@@ -96,7 +96,7 @@ pre { ::: -### Converting Markdown to Blocks +### Parsing Markdown to Blocks `Block` objects can be parsed from a Markdown string using the following function: @@ -104,12 +104,12 @@ pre { // Definition class BlockNoteEditor { ... - public markdownToBlocks(markdown: string): Blocks[]; + public tryParseMarkdownToBlocks(markdown: string): Blocks[]; ... } // Usage -const blocksFromMarkdown = editor.markdownToBlocks(markdown); +const blocksFromMarkdown = editor.tryParseMarkdownToBlocks(markdown); ``` `returns:` The blocks parsed from the Markdown string. @@ -129,7 +129,7 @@ import "@blocknote/core/style.css"; export default function App() { // Stores the current Markdown content. const [markdown, setMarkdown] = useState(""); - + // Creates a new editor instance. const editor: BlockNoteEditor = useBlockNote({ // Makes the editor non-editable. @@ -141,7 +141,7 @@ export default function App() { // Whenever the current Markdown content changes, converts it to an array // of Block objects and replaces the editor's content with them. const getBlocks = async () => { - const blocks: Block[] = await editor.markdownToBlocks(markdown); + const blocks: Block[] = await editor.tryParseMarkdownToBlocks(markdown); editor.replaceBlocks(editor.topLevelBlocks, blocks); }; getBlocks(); @@ -181,21 +181,21 @@ We expose functions to convert Blocks to and from HTML. Converting Blocks to HTM ### Converting Blocks to HTML -`Block` objects can be serialized to an HTML string using the following function: +`Block` objects can be exported to an HTML string using the following function: ```typescript // Definition class BlockNoteEditor { ... - public blocksToHTML(blocks: Block[]): string; + public blocksToHTMLLossy(blocks: Block[]): string; ... } // Usage -const HTMLFromBlocks = editor.blocksToHTML(blocks); +const HTMLFromBlocks = editor.blocksToHTMLLossy(blocks); ``` -`returns:` The blocks, serialized as an HTML string. +`returns:` The blocks, exported to an HTML string. To better conform to HTML standards, children of blocks which aren't list items are un-nested in the output HTML. @@ -217,10 +217,10 @@ export default function App() { const editor: BlockNoteEditor = useBlockNote({ // Listens for when the editor's contents change. onEditorContentChange: (editor) => { - // Converts the editor's contents from Block objects to HTML and saves + // Converts the editor's contents from Block objects to HTML and saves // them. const saveBlocksAsHTML = async () => { - const html: string = await editor.blocksToHTML(editor.topLevelBlocks); + const html: string = await editor.blocksToHTMLLossy(editor.topLevelBlocks); setHTML(html); }; saveBlocksAsHTML(); @@ -248,7 +248,7 @@ pre { ::: -### Converting HTML to Blocks +### Parsing HTML to Blocks `Block` objects can be parsed from an HTML string using the following function: @@ -256,12 +256,12 @@ pre { // Definition class BlockNoteEditor { ... - public HTMLToBlocks(html: string): Blocks[]; + public tryParseHTMLToBlocks(html: string): Blocks[]; ... } // Usage -const blocksFromHTML = editor.HTMLToBlocks(html); +const blocksFromHTML = editor.tryParseHTMLToBlocks(html); ``` `returns:` The blocks parsed from the HTML string. @@ -281,7 +281,7 @@ import "@blocknote/core/style.css"; export default function App() { // Stores the current HTML content. const [html, setHTML] = useState(""); - + // Creates a new editor instance. const editor: BlockNoteEditor = useBlockNote({ // Makes the editor non-editable. @@ -290,10 +290,10 @@ export default function App() { useEffect(() => { if (editor) { - // Whenever the current HTML content changes, converts it to an array of + // Whenever the current HTML content changes, converts it to an array of // Block objects and replaces the editor's content with them. const getBlocks = async () => { - const blocks: Block[] = await editor.HTMLToBlocks(html); + const blocks: Block[] = await editor.tryParseHTMLToBlocks(html); editor.replaceBlocks(editor.topLevelBlocks, blocks); }; getBlocks(); @@ -325,4 +325,4 @@ textarea { } ``` -::: \ No newline at end of file +:::