Skip to content

Commit 5a74b58

Browse files
feat: HTML paste handling (#422)
* refactor parse * fix parse-divsc * add test case * add extra test (that should be fixed) * readd markdown functions * fix tests * remove unused file * remove comments * add comment * nested list handling * add todos * added comment * use refs for blocks (#424) * use refs for blocks * update react htmlConversion test * Custom inline content and styles commands/copy & paste fixes (#425) * Fixed commands and internal copy/paste for inline content * Fixed internal copy/paste for styles * Small cleanup * fix some tests --------- Co-authored-by: yousefed <[email protected]> --------- Co-authored-by: Matthew Lipski <[email protected]> * use processSync --------- Co-authored-by: Matthew Lipski <[email protected]>
1 parent 3ef4cc1 commit 5a74b58

File tree

154 files changed

+3770
-602
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

154 files changed

+3770
-602
lines changed

package-lock.json

Lines changed: 160 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"rehype-parse": "^8.0.4",
8383
"rehype-remark": "^9.1.2",
8484
"rehype-stringify": "^9.0.3",
85+
"rehype-format":"^5.0.0",
8586
"remark-gfm": "^3.0.1",
8687
"remark-parse": "^10.0.1",
8788
"remark-rehype": "^10.1.0",

packages/core/src/BlockNoteEditor.ts

Lines changed: 116 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Editor, EditorOptions, Extension } from "@tiptap/core";
2-
import { Node } from "prosemirror-model";
2+
import { Fragment, Node, Slice } from "prosemirror-model";
33
// import "./blocknote.css";
44
import { Editor as TiptapEditor } from "@tiptap/core/dist/packages/core/src/Editor";
55
import * as Y from "yjs";
@@ -24,7 +24,6 @@ import {
2424
BlockSpecs,
2525
PartialBlock,
2626
} from "./extensions/Blocks/api/blocks/types";
27-
import { TextCursorPosition } from "./extensions/Blocks/api/cursorPositionTypes";
2827
import {
2928
DefaultBlockSchema,
3029
DefaultInlineContentSchema,
@@ -44,8 +43,14 @@ import {
4443
import { getBlockInfoFromPos } from "./extensions/Blocks/helpers/getBlockInfoFromPos";
4544

4645
import "prosemirror-tables/style/tables.css";
46+
47+
import { createExternalHTMLExporter } from "./api/exporters/html/externalHTMLExporter";
48+
import { blocksToMarkdown } from "./api/exporters/markdown/markdownExporter";
49+
import { HTMLToBlocks } from "./api/parsers/html/parseHTML";
50+
import { markdownToBlocks } from "./api/parsers/markdown/parseMarkdown";
4751
import "./editor.css";
4852
import { getBlockSchemaFromSpecs } from "./extensions/Blocks/api/blocks/internal";
53+
import { TextCursorPosition } from "./extensions/Blocks/api/cursorPositionTypes";
4954
import { getInlineContentSchemaFromSpecs } from "./extensions/Blocks/api/inlineContent/internal";
5055
import {
5156
InlineContentSchema,
@@ -409,6 +414,50 @@ export class BlockNoteEditor<
409414
newOptions.domAttributes?.editor?.class || ""
410415
),
411416
},
417+
transformPasted(slice, view) {
418+
// helper function
419+
function removeChild(node: Fragment, n: number) {
420+
const children: any[] = [];
421+
node.forEach((child, _, i) => {
422+
if (i !== n) {
423+
children.push(child);
424+
}
425+
});
426+
return Fragment.from(children);
427+
}
428+
429+
// fix for https://github.com/ProseMirror/prosemirror/issues/1430#issuecomment-1822570821
430+
let f = Fragment.from(slice.content);
431+
for (let i = 0; i < f.childCount; i++) {
432+
if (f.child(i).type.spec.group === "blockContent") {
433+
const content = [f.child(i)];
434+
if (i + 1 < f.childCount) {
435+
// when there is a blockGroup, it should be nested in the new blockcontainer
436+
if (f.child(i + 1).type.spec.group === "blockGroup") {
437+
const nestedChild = f
438+
.child(i + 1)
439+
.child(0)
440+
.child(0);
441+
442+
if (
443+
nestedChild.type.name === "bulletListItem" ||
444+
nestedChild.type.name === "numberedListItem"
445+
) {
446+
content.push(f.child(i + 1));
447+
f = removeChild(f, i + 1);
448+
}
449+
}
450+
}
451+
const container = view.state.schema.nodes.blockContainer.create(
452+
undefined,
453+
content
454+
);
455+
f = f.replaceChild(i, container);
456+
}
457+
}
458+
459+
return new Slice(f, slice.openStart, slice.openEnd);
460+
},
412461
},
413462
};
414463

@@ -942,47 +991,71 @@ export class BlockNoteEditor<
942991
}
943992

944993
// TODO: Fix when implementing HTML/Markdown import & export
945-
// /**
946-
// * Serializes blocks into an HTML string. To better conform to HTML standards, children of blocks which aren't list
947-
// * items are un-nested in the output HTML.
948-
// * @param blocks An array of blocks that should be serialized into HTML.
949-
// * @returns The blocks, serialized as an HTML string.
950-
// */
951-
// public async blocksToHTML(blocks: Block<BSchema>[]): Promise<string> {
952-
// return blocksToHTML(blocks, this._tiptapEditor.schema, this);
953-
// }
954-
//
955-
// /**
956-
// * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and
957-
// * `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote
958-
// * doesn't recognize an HTML element's tag, it will parse it as a paragraph or plain text.
959-
// * @param html The HTML string to parse blocks from.
960-
// * @returns The blocks parsed from the HTML string.
961-
// */
962-
// public async HTMLToBlocks(html: string): Promise<Block<BSchema>[]> {
963-
// return HTMLToBlocks(html, this.schema, this._tiptapEditor.schema);
964-
// }
965-
//
966-
// /**
967-
// * Serializes blocks into a Markdown string. The output is simplified as Markdown does not support all features of
968-
// * BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed.
969-
// * @param blocks An array of blocks that should be serialized into Markdown.
970-
// * @returns The blocks, serialized as a Markdown string.
971-
// */
972-
// public async blocksToMarkdown(blocks: Block<BSchema>[]): Promise<string> {
973-
// return blocksToMarkdown(blocks, this._tiptapEditor.schema, this);
974-
// }
975-
//
976-
// /**
977-
// * Creates a list of blocks from a Markdown string. Tries to create `Block` and `InlineNode` objects based on
978-
// * Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it
979-
// * as text.
980-
// * @param markdown The Markdown string to parse blocks from.
981-
// * @returns The blocks parsed from the Markdown string.
982-
// */
983-
// public async markdownToBlocks(markdown: string): Promise<Block<BSchema>[]> {
984-
// return markdownToBlocks(markdown, this.schema, this._tiptapEditor.schema);
985-
// }
994+
/**
995+
* Serializes blocks into an HTML string. To better conform to HTML standards, children of blocks which aren't list
996+
* items are un-nested in the output HTML.
997+
* @param blocks An array of blocks that should be serialized into HTML.
998+
* @returns The blocks, serialized as an HTML string.
999+
*/
1000+
public async blocksToHTMLLossy(
1001+
blocks = this.topLevelBlocks
1002+
): Promise<string> {
1003+
const exporter = createExternalHTMLExporter(
1004+
this._tiptapEditor.schema,
1005+
this
1006+
);
1007+
return exporter.exportBlocks(blocks);
1008+
}
1009+
1010+
/**
1011+
* Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and
1012+
* `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote
1013+
* doesn't recognize an HTML element's tag, it will parse it as a paragraph or plain text.
1014+
* @param html The HTML string to parse blocks from.
1015+
* @returns The blocks parsed from the HTML string.
1016+
*/
1017+
public async tryParseHTMLToBlocks(
1018+
html: string
1019+
): Promise<Block<BSchema, ISchema, SSchema>[]> {
1020+
return HTMLToBlocks(
1021+
html,
1022+
this.blockSchema,
1023+
this.inlineContentSchema,
1024+
this.styleSchema,
1025+
this._tiptapEditor.schema
1026+
);
1027+
}
1028+
1029+
/**
1030+
* Serializes blocks into a Markdown string. The output is simplified as Markdown does not support all features of
1031+
* BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed.
1032+
* @param blocks An array of blocks that should be serialized into Markdown.
1033+
* @returns The blocks, serialized as a Markdown string.
1034+
*/
1035+
public async blocksToMarkdownLossy(
1036+
blocks = this.topLevelBlocks
1037+
): Promise<string> {
1038+
return blocksToMarkdown(blocks, this._tiptapEditor.schema, this);
1039+
}
1040+
1041+
/**
1042+
* Creates a list of blocks from a Markdown string. Tries to create `Block` and `InlineNode` objects based on
1043+
* Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it
1044+
* as text.
1045+
* @param markdown The Markdown string to parse blocks from.
1046+
* @returns The blocks parsed from the Markdown string.
1047+
*/
1048+
public async tryParseMarkdownToBlocks(
1049+
markdown: string
1050+
): Promise<Block<BSchema, ISchema, SSchema>[]> {
1051+
return markdownToBlocks(
1052+
markdown,
1053+
this.blockSchema,
1054+
this.inlineContentSchema,
1055+
this.styleSchema,
1056+
this._tiptapEditor.schema
1057+
);
1058+
}
9861059

9871060
/**
9881061
* Updates the user info for the current user that's shown to other collaborators.

0 commit comments

Comments
 (0)