From 65fb330cc8fc6807584854096a2f669d5beb205a Mon Sep 17 00:00:00 2001 From: yousefed Date: Sat, 10 Feb 2024 06:23:05 +0100 Subject: [PATCH 01/17] cleanup mount system --- packages/core/src/editor/BlockNoteEditor.ts | 33 ++++++++++--------- .../core/src/editor/BlockNoteTipTapEditor.ts | 26 +++++++++++---- packages/react/src/editor/EditorContent.tsx | 6 ++++ packages/react/src/hooks/useBlockNote.ts | 24 +++----------- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 3da60922ab..545ab81c60 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -85,12 +85,6 @@ export type BlockNoteEditorOptions< */ slashMenuItems: BaseSlashMenuItem[]; - /** - * The HTML element that should be used as the parent element for the editor. - * - * @default: undefined, the editor is not attached to the DOM - */ - parentElement: HTMLElement; /** * An object containing attributes that should be added to HTML elements of the editor. * @@ -130,7 +124,7 @@ export type BlockNoteEditorOptions< /** * Locks the editor from being editable by the user if set to `false`. */ - editable: boolean; + editable: boolean; // TODO /** * The content that should be in the editor when it's created, represented as an array of partial block objects. */ @@ -395,9 +389,11 @@ export class BlockNoteEditor< // initial content, as the schema may contain custom blocks which need // it to render. if (initialContent !== undefined) { + // TODO: we can probably get rid of this now this.replaceBlocks(this.topLevelBlocks, initialContent as any); } + // TODO: remove? newOptions.onEditorReady?.(this); this.ready = true; }, @@ -409,8 +405,10 @@ export class BlockNoteEditor< return; } + // TODO: move to hook newOptions.onEditorContentChange?.(this); }, + onSelectionUpdate: (editor) => { newOptions._tiptapOptions?.onSelectionUpdate?.(editor); // This seems to be necessary due to a bug in TipTap: @@ -419,8 +417,10 @@ export class BlockNoteEditor< return; } + // TODO: move to hook newOptions.onTextCursorPositionChange?.(this); }, + // TODO: move to view prop and / or hook editable: options.editable !== undefined ? options.editable @@ -437,7 +437,7 @@ export class BlockNoteEditor< ...newOptions._tiptapOptions?.editorProps?.attributes, ...newOptions.domAttributes?.editor, class: mergeCSSClasses( - "bn-root", + "bn-root", // TODO: remove this class? "bn-editor", newOptions.defaultStyles ? "bn-default-styles" : "", newOptions.domAttributes?.editor?.class || "" @@ -447,14 +447,6 @@ export class BlockNoteEditor< }, }; - if (newOptions.parentElement) { - tiptapOptions.element = newOptions.parentElement; - } - - // (Editor.prototype as any).createView = () => { - // // no op - // }; - this._tiptapEditor = new BlockNoteTipTapEditor( tiptapOptions ) as BlockNoteTipTapEditor & { @@ -462,6 +454,15 @@ export class BlockNoteEditor< }; } + /** + * Mount the editor to a parent DOM element. Call mount(undefined) to clean up + * + * @warning Not needed for React, use BlockNoteView to take care of this + */ + public mount(parentElement?: HTMLElement | null) { + this._tiptapEditor.mount(parentElement); + } + public get prosemirrorView() { return this._tiptapEditor.view; } diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index d1915c1a8e..e1925a571c 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -4,10 +4,10 @@ import { Editor as TiptapEditor } from "@tiptap/core"; import { EditorView } from "@tiptap/pm/view"; import { EditorState } from "prosemirror-state"; -// TiptapEditor.prototype.createView = function () { -// debugger; -// }; - +/** + * Custom Editor class that extends TiptapEditor and separates + * the creation of the view from the constructor. + */ // @ts-ignore export class BlockNoteTipTapEditor extends TiptapEditor { private _state: EditorState; @@ -21,6 +21,10 @@ export class BlockNoteTipTapEditor extends TiptapEditor { this.options.parseOptions ); console.log("create state"); + + // Create state immediately, so that it's available independently from the View, + // the way Prosemirror "intends it to be". This also makes sure that we can access + // the state before the view is created / mounted. this._state = EditorState.create({ doc, // selection: selection || undefined, @@ -36,8 +40,13 @@ export class BlockNoteTipTapEditor extends TiptapEditor { createView() { // no-op + // Disable default call to `createView` in the Editor constructor. + // We should call `createView` manually only when a DOM element is available } + /** + * Replace the default `createView` method with a custom one - which we call on mount + */ private createViewAlternative() { this.view = new EditorView(this.options.element, { ...this.options.editorProps, @@ -57,9 +66,14 @@ export class BlockNoteTipTapEditor extends TiptapEditor { this.createNodeViews(); } - public mount = (element: HTMLElement | null) => { + /** + * Mounts / unmounts the editor to a dom element + * + * @param element DOM element to mount to, ur null / undefined to destroy + */ + public mount = (element?: HTMLElement | null) => { console.log("mount", element); - if (element === null) { + if (!element) { this.destroy(); } else { this.options.element = element; diff --git a/packages/react/src/editor/EditorContent.tsx b/packages/react/src/editor/EditorContent.tsx index e667876c53..11ca5bb2c9 100644 --- a/packages/react/src/editor/EditorContent.tsx +++ b/packages/react/src/editor/EditorContent.tsx @@ -15,6 +15,12 @@ const Portals: React.FC<{ renderers: Record }> = ({ ); }; +/** + * Replacement of https://github.com/ueberdosis/tiptap/blob/6676c7e034a46117afdde560a1b25fe75411a21d/packages/react/src/EditorContent.tsx + * that only takes care of the Portals. + * + * Original implementation is messy, and we use a "mount" system in BlockNoteTiptapEditor.tsx that makes this cleaner + */ export function EditorContent(props: { editor: BlockNoteEditor; children: any; diff --git a/packages/react/src/hooks/useBlockNote.ts b/packages/react/src/hooks/useBlockNote.ts index 996d42672c..fa1f93b815 100644 --- a/packages/react/src/hooks/useBlockNote.ts +++ b/packages/react/src/hooks/useBlockNote.ts @@ -1,18 +1,15 @@ import { BlockNoteEditor, BlockNoteEditorOptions, - BlockSchemaFromSpecs, BlockSpecs, - InlineContentSchemaFromSpecs, InlineContentSpecs, - StyleSchemaFromSpecs, StyleSpecs, defaultBlockSpecs, defaultInlineContentSpecs, defaultStyleSpecs, getBlockSchemaFromSpecs, } from "@blocknote/core"; -import { DependencyList, useMemo, useRef } from "react"; +import { DependencyList, useMemo } from "react"; import { getDefaultReactSlashMenuItems } from "../slashMenuItems/defaultReactSlashMenuItems"; const initEditor = < @@ -40,25 +37,12 @@ export const useBlockNote = < options: Partial> = {}, deps: DependencyList = [] ) => { - const editorRef = - useRef< - BlockNoteEditor< - BlockSchemaFromSpecs, - InlineContentSchemaFromSpecs, - StyleSchemaFromSpecs - > - >(); - return useMemo(() => { - if (editorRef.current) { - editorRef.current._tiptapEditor.destroy(); - } - - editorRef.current = initEditor(options); + const editor = initEditor(options); if (window) { // for testing / dev purposes - (window as any).ProseMirror = editorRef.current._tiptapEditor; + (window as any).ProseMirror = editor._tiptapEditor; } - return editorRef.current!; + return editor; }, deps); //eslint-disable-line react-hooks/exhaustive-deps }; From ca0391aac5e14203b687bc971eb7ff925fa1a793 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 07:03:07 +0100 Subject: [PATCH 02/17] demo --- docs/components/pages/landing/demo/Demo.tsx | 33 +++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/components/pages/landing/demo/Demo.tsx b/docs/components/pages/landing/demo/Demo.tsx index d2d7d1d27b..7858f91d7c 100644 --- a/docs/components/pages/landing/demo/Demo.tsx +++ b/docs/components/pages/landing/demo/Demo.tsx @@ -1,7 +1,7 @@ import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; -import { useEffect, useMemo } from "react"; +import { useMemo } from "react"; import YPartyKitProvider from "y-partykit/provider"; import * as Y from "yjs"; @@ -81,21 +81,22 @@ export function ReactBlockNote(props: { theme?: "light" | "dark" }) { [props.theme], ); - useEffect(() => { - let shownAlert = false; - const listener = () => { - if (!shownAlert) { - alert( - "Text you enter in this demo is displayed publicly on the internet to show multiplayer features. Be kind :)", - ); - shownAlert = true; - } - }; - editor?.domElement?.addEventListener("focus", listener); - return () => { - editor?.domElement?.removeEventListener("focus", listener); - }; - }, [editor?.domElement]); + // TODO + // useEffect(() => { + // let shownAlert = false; + // const listener = () => { + // if (!shownAlert) { + // alert( + // "Text you enter in this demo is displayed publicly on the internet to show multiplayer features. Be kind :)", + // ); + // shownAlert = true; + // } + // }; + // editor?.domElement?.addEventListener("focus", listener); + // return () => { + // editor?.domElement?.removeEventListener("focus", listener); + // }; + // }, [editor?.domElement]); return ; } From d0543184f66643472da8c605b88fb7f5f39a9d91 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 16:43:12 +0100 Subject: [PATCH 03/17] wip --- packages/core/src/editor/BlockNoteEditor.ts | 102 +++--------------- .../core/src/editor/BlockNoteTipTapEditor.ts | 50 ++++++++- packages/react/src/editor/EditorContent.tsx | 1 + 3 files changed, 64 insertions(+), 89 deletions(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 545ab81c60..3bf83abe1b 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -11,10 +11,7 @@ import { import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter"; import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter"; import { getBlockInfoFromPos } from "../api/getBlockInfoFromPos"; -import { - blockToNode, - nodeToBlock, -} from "../api/nodeConversions/nodeConversions"; +import { nodeToBlock } from "../api/nodeConversions/nodeConversions"; import { getNodeById } from "../api/nodeUtil"; import { HTMLToBlocks } from "../api/parsers/html/parseHTML"; import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown"; @@ -67,7 +64,10 @@ import { transformPasted } from "./transformPasted"; // CSS import "./Block.css"; -import { BlockNoteTipTapEditor } from "./BlockNoteTipTapEditor"; +import { + BlockNoteTipTapEditor, + BlockNoteTipTapEditorOptions, +} from "./BlockNoteTipTapEditor"; import "./editor.css"; export type BlockNoteEditorOptions< @@ -91,16 +91,7 @@ export type BlockNoteEditorOptions< * @example { editor: { class: "my-editor-class" } } */ domAttributes: Partial; - /** - * A callback function that runs when the editor is ready to be used. - */ - onEditorReady: ( - editor: BlockNoteEditor< - BlockSchemaFromSpecs, - InlineContentSchemaFromSpecs, - StyleSchemaFromSpecs - > - ) => void; + /** * A callback function that runs whenever the editor's contents change. */ @@ -208,8 +199,6 @@ export class BlockNoteEditor< public readonly inlineContentImplementations: InlineContentSpecs; public readonly styleImplementations: StyleSpecs; - public ready = false; - public readonly sideMenu: SideMenuProsemirrorPlugin< BSchema, ISchema, @@ -332,91 +321,31 @@ export class BlockNoteEditor< const initialContent = newOptions.initialContent || (options.collaboration - ? undefined + ? [ + { + type: "paragraph", + id: "initialBlockId", + }, + ] : [ { type: "paragraph", id: UniqueID.options.generateID(), }, ]); - const styleSchema = this.styleSchema; - const tiptapOptions: Partial = { + const tiptapOptions: BlockNoteTipTapEditorOptions = { ...blockNoteTipTapOptions, ...newOptions._tiptapOptions, - onBeforeCreate(editor) { - newOptions._tiptapOptions?.onBeforeCreate?.(editor); - // We always set the initial content to a single paragraph block. This - // allows us to easily replace it with the actual initial content once - // the TipTap editor is initialized. - const schema = editor.editor.schema; - - // This is a hack to make "initial content detection" by y-prosemirror (and also tiptap isEmpty) - // properly detect whether or not the document has changed. - // We change the doc.createAndFill function to make sure the initial block id is set, instead of null - let cache: any; - const oldCreateAndFill = schema.nodes.doc.createAndFill; - (schema.nodes.doc as any).createAndFill = (...args: any) => { - if (cache) { - return cache; - } - const ret = oldCreateAndFill.apply(schema.nodes.doc, args); - - // create a copy that we can mutate (otherwise, assigning attrs is not safe and corrupts the pm state) - const jsonNode = JSON.parse(JSON.stringify(ret!.toJSON())); - jsonNode.content[0].content[0].attrs.id = "initialBlockId"; - - cache = Node.fromJSON(schema, jsonNode); - return cache; - }; - - const root = schema.node( - "doc", - undefined, - schema.node("blockGroup", undefined, [ - blockToNode( - { id: "initialBlockId", type: "paragraph" }, - schema, - styleSchema - ), - ]) - ); - editor.editor.options.content = root.toJSON(); - }, - onCreate: (editor) => { - newOptions._tiptapOptions?.onCreate?.(editor); - // We need to wait for the TipTap editor to init before we can set the - // initial content, as the schema may contain custom blocks which need - // it to render. - if (initialContent !== undefined) { - // TODO: we can probably get rid of this now - this.replaceBlocks(this.topLevelBlocks, initialContent as any); - } - - // TODO: remove? - newOptions.onEditorReady?.(this); - this.ready = true; - }, + content: initialContent, onUpdate: (editor) => { newOptions._tiptapOptions?.onUpdate?.(editor); - // This seems to be necessary due to a bug in TipTap: - // https://github.com/ueberdosis/tiptap/issues/2583 - if (!this.ready) { - return; - } - // TODO: move to hook newOptions.onEditorContentChange?.(this); }, onSelectionUpdate: (editor) => { newOptions._tiptapOptions?.onSelectionUpdate?.(editor); - // This seems to be necessary due to a bug in TipTap: - // https://github.com/ueberdosis/tiptap/issues/2583 - if (!this.ready) { - return; - } - // TODO: move to hook newOptions.onTextCursorPositionChange?.(this); }, @@ -448,7 +377,8 @@ export class BlockNoteEditor< }; this._tiptapEditor = new BlockNoteTipTapEditor( - tiptapOptions + tiptapOptions, + this.styleSchema ) as BlockNoteTipTapEditor & { contentComponent: any; }; diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index e1925a571c..fc1e8c855a 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -1,9 +1,19 @@ import { EditorOptions, createDocument } from "@tiptap/core"; // import "./blocknote.css"; import { Editor as TiptapEditor } from "@tiptap/core"; +import { Node } from "@tiptap/pm/model"; import { EditorView } from "@tiptap/pm/view"; import { EditorState } from "prosemirror-state"; +import { blockToNode } from "../api/nodeConversions/nodeConversions"; +import { PartialBlock, StyleSchema } from "../schema"; + +export type BlockNoteTipTapEditorOptions = Partial< + Omit +> & { + content: PartialBlock[]; +}; + /** * Custom Editor class that extends TiptapEditor and separates * the creation of the view from the constructor. @@ -12,11 +22,44 @@ import { EditorState } from "prosemirror-state"; export class BlockNoteTipTapEditor extends TiptapEditor { private _state: EditorState; - constructor(options?: Partial) { - super(options); + constructor(options: BlockNoteTipTapEditorOptions, styleSchema: StyleSchema) { + super({ ...options, content: undefined }); + + // This is a hack to make "initial content detection" by y-prosemirror (and also tiptap isEmpty) + // properly detect whether or not the document has changed. + // We change the doc.createAndFill function to make sure the initial block id is set, instead of null + const schema = this.schema; + let cache: any; + const oldCreateAndFill = schema.nodes.doc.createAndFill; + (schema.nodes.doc as any).createAndFill = (...args: any) => { + console.log("createandfill"); + if (cache) { + return cache; + } + const ret = oldCreateAndFill.apply(schema.nodes.doc, args); + + // create a copy that we can mutate (otherwise, assigning attrs is not safe and corrupts the pm state) + const jsonNode = JSON.parse(JSON.stringify(ret!.toJSON())); + jsonNode.content[0].content[0].attrs.id = "initialBlockId"; + + cache = Node.fromJSON(schema, jsonNode); + return cache; + }; + + const pmNodes = options?.content.map((b) => + blockToNode(b, this.schema, styleSchema).toJSON() + ); const doc = createDocument( - this.options.content, + { + type: "doc", + content: [ + { + type: "blockGroup", + content: pmNodes, + }, + ], + }, this.schema, this.options.parseOptions ); @@ -27,6 +70,7 @@ export class BlockNoteTipTapEditor extends TiptapEditor { // the state before the view is created / mounted. this._state = EditorState.create({ doc, + schema: this.schema, // selection: selection || undefined, }); } diff --git a/packages/react/src/editor/EditorContent.tsx b/packages/react/src/editor/EditorContent.tsx index 11ca5bb2c9..1610a0af94 100644 --- a/packages/react/src/editor/EditorContent.tsx +++ b/packages/react/src/editor/EditorContent.tsx @@ -43,6 +43,7 @@ export function EditorContent(props: { }); }, }; + props.editor._tiptapEditor.createNodeViews(); return () => { props.editor._tiptapEditor.contentComponent = null; }; From a55085aafef1ad9aeee2c4da45072367c534cd26 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 20:15:27 +0100 Subject: [PATCH 04/17] fix build --- .../docs/custom-schemas/custom-blocks.mdx | 4 +- docs/pages/docs/editor-api/editor.mdx | 8 -- .../docs/ui-components/formatting-toolbar.mdx | 2 +- .../docs/ui-components/image-toolbar.mdx | 2 +- docs/pages/docs/ui-components/side-menu.mdx | 4 +- docs/pages/docs/ui-components/slash-menu.mdx | 2 +- docs/pages/docs/ui-components/theming.mdx | 8 +- docs/pages/docs/ui-components/ui-elements.mdx | 4 +- examples/01-basic/block-objects/App.tsx | 18 ++- examples/01-basic/keyboard-shortcuts/App.tsx | 9 +- examples/01-basic/saving-loading/App.tsx | 58 ++++++--- .../02-ui-components/custom-ui/ColorMenu.tsx | 11 +- .../custom-ui/CustomFormattingToolbar.tsx | 6 +- .../CustomButton.tsx | 12 +- .../selection-blocks/App.tsx | 86 ++++++------- .../text-cursor-block/App.tsx | 66 +++++----- .../react-custom-styles/App.tsx | 3 - .../converting-blocks-from-html/App.tsx | 25 ++-- .../converting-blocks-from-md/App.tsx | 25 ++-- .../converting-blocks-to-html/App.tsx | 26 ++-- .../converting-blocks-to-md/App.tsx | 26 ++-- .../react-vanilla-custom-styles/App.tsx | 3 - packages/core/src/editor/BlockNoteEditor.ts | 117 +++++++++++------- .../core/src/editor/BlockNoteTipTapEditor.ts | 1 - packages/dev-scripts/examples/genDocs.ts | 20 +-- .../DefaultButtons/ColorStyleButton.tsx | 6 +- .../DefaultButtons/CreateLinkButton.tsx | 14 +-- .../DefaultButtons/NestBlockButtons.tsx | 14 +-- .../DefaultButtons/ToggledStyleButton.tsx | 6 +- .../DefaultDropdowns/BlockTypeDropdown.tsx | 6 +- .../FormattingToolbarPositioner.tsx | 6 +- packages/react/src/editor/BlockNoteContext.ts | 29 +++++ packages/react/src/editor/BlockNoteView.tsx | 107 ++++++++++++---- packages/react/src/hooks/useActiveStyles.ts | 10 +- packages/react/src/hooks/useBlockNote.ts | 7 +- packages/react/src/hooks/useEditorChange.ts | 24 +++- .../react/src/hooks/useEditorContentChange.ts | 15 --- .../useEditorContentOrSelectionChange.ts | 11 ++ .../src/hooks/useEditorSelectionChange.ts | 23 ++-- packages/react/src/hooks/useSelectedBlocks.ts | 33 +++-- packages/react/src/index.ts | 4 +- 41 files changed, 502 insertions(+), 359 deletions(-) create mode 100644 packages/react/src/editor/BlockNoteContext.ts delete mode 100644 packages/react/src/hooks/useEditorContentChange.ts create mode 100644 packages/react/src/hooks/useEditorContentOrSelectionChange.ts diff --git a/docs/pages/docs/custom-schemas/custom-blocks.mdx b/docs/pages/docs/custom-schemas/custom-blocks.mdx index d90a04fa34..b475ae9f1b 100644 --- a/docs/pages/docs/custom-schemas/custom-blocks.mdx +++ b/docs/pages/docs/custom-schemas/custom-blocks.mdx @@ -1,9 +1,11 @@ +import { Callout } from "nextra/components"; +import { Example } from "@/components/example"; ## Custom Block Types In addition to the default block types that BlockNote offers, you can also make your own custom blocks. Take a look at the demo below, in which we add a custom alert block to a BlockNote editor, as well as a custom [Slash Menu Item](/docs/slash-menu#custom-items) to insert it. - + While custom blocks open a lot of doors for what you can do with BlockNote, we're still working on the API and there are a few limitations for the kinds of blocks you can create. We'd love to hear your feedback on GitHub or in our Discord community! diff --git a/docs/pages/docs/editor-api/editor.mdx b/docs/pages/docs/editor-api/editor.mdx index b55bcf58de..293c82cea4 100644 --- a/docs/pages/docs/editor-api/editor.mdx +++ b/docs/pages/docs/editor-api/editor.mdx @@ -35,18 +35,10 @@ export type BlockNoteEditorOptions = Partial<{ }>; ``` -`editable:` Locks the editor from being editable by the user if set to `false`. [Editor Functions](/docs/blocks#editor-functions) will still work. - `initialContent:` The content that should be in the editor when it's created, represented as an array of [partial block objects](/docs/manipulating-blocks#partial-blocks). `domAttributes:` An object containing HTML attributes that should be added to various DOM elements in the editor. See [Adding DOM Attributes](/docs/theming#adding-dom-attributes) for more. -`onEditorReady:` A callback function that runs when the editor is ready to be used. - -`onEditorContentChange:` A callback function that runs whenever the editor's contents change. - -`onTextCursorPositionChange:` A callback function that runs whenever the text cursor position changes. Head to [Text Cursor](/docs/cursor-selections#text-cursor) to see how you can make use of this. - `slashMenuItems:` The commands that are listed in the editor's [Slash Menu](/docs/slash-menu). If this option isn't defined, a default list of commands is loaded. `defaultStyles`: Whether to use the default font and reset the styles of `

`, `

  • `, `

    `, etc. elements that are used in BlockNote. Defaults to true if undefined. diff --git a/docs/pages/docs/ui-components/formatting-toolbar.mdx b/docs/pages/docs/ui-components/formatting-toolbar.mdx index 1a434516ec..fe0bb64ed8 100644 --- a/docs/pages/docs/ui-components/formatting-toolbar.mdx +++ b/docs/pages/docs/ui-components/formatting-toolbar.mdx @@ -19,7 +19,7 @@ If you want to change the buttons/dropdowns in the Formatting Toolbar, or replac You can see how this is done in the example below, which has a custom Formatting Toolbar. It contains the same items as the default Formatting Toolbar, with and added blue text/background color and code style button. - + `CustomFormattingToolbar` is the component we use to replace the default Formatting Toolbar. You can see it's made up of a bunch of other components that are exported by BlockNote. Read on to [Components](/docs/formatting-toolbar#components) to find out more about these. diff --git a/docs/pages/docs/ui-components/image-toolbar.mdx b/docs/pages/docs/ui-components/image-toolbar.mdx index 70a138225a..8de6aaf246 100644 --- a/docs/pages/docs/ui-components/image-toolbar.mdx +++ b/docs/pages/docs/ui-components/image-toolbar.mdx @@ -27,4 +27,4 @@ type uploadFile = (file: File) => Promise; You can use the provided `uploadToTempFilesOrg` function to as a starting point, which uploads files to [tmpfiles.org](https://tmpfiles.org/). However, it's not recommended to use this in a production environment - you should use your own backend: - + diff --git a/docs/pages/docs/ui-components/side-menu.mdx b/docs/pages/docs/ui-components/side-menu.mdx index ba27841a00..f8fde000d0 100644 --- a/docs/pages/docs/ui-components/side-menu.mdx +++ b/docs/pages/docs/ui-components/side-menu.mdx @@ -23,7 +23,7 @@ If you want to change the items in the Side Menu, or replace it altogether, you You can see how this is done in the example below, which has a custom Side Menu with two items. The first one deletes the selected block, while the second one is a drag handle, which opens a menu on click. - + `CustomDragHandleMenu` is the component we use to replace the default Side Menu. You can see it's made up of a bunch of other components that are exported by BlockNote. Read on to [Components](/docs/side-menu#components) to find out more about these. @@ -35,7 +35,7 @@ If you want to change the items in the Drag Handle Menu, or replace it altogethe You can see how this is done in the example below, which has a custom Drag Handle Menu. It contains the default items, as well as a custom item which opens an alert. - + `CustomDragHandleMenu` is the component we use to replace the default Drag Handle Menu. You can see it's made up of a bunch of other components that are exported by BlockNote. Read on to [Components](/docs/side-menu#components) to find out more about these. diff --git a/docs/pages/docs/ui-components/slash-menu.mdx b/docs/pages/docs/ui-components/slash-menu.mdx index 8ab901d073..76c944a9b6 100644 --- a/docs/pages/docs/ui-components/slash-menu.mdx +++ b/docs/pages/docs/ui-components/slash-menu.mdx @@ -19,7 +19,7 @@ If you want to change the items that appear in the Slash Menu, you can do that u You can see how this is done in the example below, which has a custom Slash Menu item list. It includes all the default Slash Menu items, as well as a custom item, which inserts a new block below with "Hello World" in bold. - + To find out how to get the default Slash Menu items, as well as how to make custom items, read on to [Slash Menu Items](/docs/slash-menu#slash-menu-items) diff --git a/docs/pages/docs/ui-components/theming.mdx b/docs/pages/docs/ui-components/theming.mdx index ad6b60387d..40f7d3d732 100644 --- a/docs/pages/docs/ui-components/theming.mdx +++ b/docs/pages/docs/ui-components/theming.mdx @@ -17,7 +17,7 @@ BlockNote's styling is defined in CSS, and you can find the default styles in th In the demo below, we create additional CSS rules to add some basic styling to the editor, and also make all hovered slash menu items blue: - + ## Theme CSS Variables @@ -70,7 +70,7 @@ Setting these variables on the `.bn-container[data-color-scheme]` selector will In the demo below, we set a red theme for the editor which changes based on if light or dark mode is used: - + ### Changing CSS Variables Through Code @@ -125,7 +125,7 @@ type LightAndDarkThemes = { In the demo below, we create the same red theme as from the previous demo, but this time we set it via the `theme` prop in `BlockNoteView`: - + ## Adding DOM Attributes @@ -133,7 +133,7 @@ You can set additional HTML attributes on most DOM elements inside the editor, w In the demo below, we set a custom class on the `blockContainer` element to add a border to each block: - + There are a number of elements that you can set classes for: diff --git a/docs/pages/docs/ui-components/ui-elements.mdx b/docs/pages/docs/ui-components/ui-elements.mdx index e449b2cf50..2264a8afc8 100644 --- a/docs/pages/docs/ui-components/ui-elements.mdx +++ b/docs/pages/docs/ui-components/ui-elements.mdx @@ -47,7 +47,7 @@ Explicitly adding `Positioner` components as children of `BlockNoteView` allows In the following example, we remove the Side Menu from the editor. This is done by adding all `Positioner` components as children of `BlockNoteView`, for each UI element except the Side Menu: - + Each further `Positioner` component you remove will remove its corresponding UI element from the editor. If you only want to keep the editor itself, add only an empty fragment (`<>`) to `BlockNoteView`'s children. @@ -55,7 +55,7 @@ Each further `Positioner` component you remove will remove its corresponding UI In the following example, the Side Menu is replaced with a simple component which just displays the name of the element: - + As you can see, this is done by passing a React component to the `sideMenu` prop of `SideMenuPositioner`. Each `Positioner` element has a prop through which you can pass the component you want to render (`formattingToolbar` for the Formatting Toolbar, etc.). If nothing is passed, the `Positioner` will render the default UI element. diff --git a/examples/01-basic/block-objects/App.tsx b/examples/01-basic/block-objects/App.tsx index a081a39aab..7df07d3053 100644 --- a/examples/01-basic/block-objects/App.tsx +++ b/examples/01-basic/block-objects/App.tsx @@ -14,22 +14,20 @@ export default function App() { const [blocks, setBlocks] = useState< Block[] >([]); - - // TODO: revise API to use a simple hook? - // Creates a new editor instance. - const editor: BlockNoteEditor = useBlockNote({ - // Listens for when the editor's contents change. - onEditorContentChange: (editor) => - // Converts the editor's contents to an array of Block objects. - setBlocks(editor.topLevelBlocks), - }); + const editor: BlockNoteEditor = useBlockNote({}); // Renders the editor instance and its contents, as an array of Block // objects, below. return (
    - + { + // Converts the editor's contents to an array of Block objects. + setBlocks(editor.topLevelBlocks); + }} + />

    Document JSON:

    {JSON.stringify(blocks, null, 2)}
    diff --git a/examples/01-basic/keyboard-shortcuts/App.tsx b/examples/01-basic/keyboard-shortcuts/App.tsx index b526b5b468..94717d71a3 100644 --- a/examples/01-basic/keyboard-shortcuts/App.tsx +++ b/examples/01-basic/keyboard-shortcuts/App.tsx @@ -34,10 +34,11 @@ const cycleBlocksShortcut = (event: KeyboardEvent, editor: BlockNoteEditor) => { export default function App() { const editor: BlockNoteEditor = useBlockNote({ // Adds event handler on key down when the editor is ready - onEditorReady: (editor) => - editor.domElement.addEventListener("keydown", (event) => - cycleBlocksShortcut(event, editor) - ), + // TODO: useful? + // onEditorReady: (editor) => + // editor.domElement.addEventListener("keydown", (event) => + // cycleBlocksShortcut(event, editor) + // ), }); return ; diff --git a/examples/01-basic/saving-loading/App.tsx b/examples/01-basic/saving-loading/App.tsx index e61906c32f..b91c18b067 100644 --- a/examples/01-basic/saving-loading/App.tsx +++ b/examples/01-basic/saving-loading/App.tsx @@ -1,24 +1,50 @@ -import { BlockNoteEditor } from "@blocknote/core"; -import { BlockNoteView, useBlockNote } from "@blocknote/react"; +import { PartialBlock } from "@blocknote/core"; +import { BlockNoteView, createBlockNoteEditor } from "@blocknote/react"; import "@blocknote/react/style.css"; +import { useEffect, useMemo, useState } from "react"; -// Gets the previously stored editor contents. -const initialContent: string | null = localStorage.getItem("editorContent"); +async function saveToStorage(jsonBlocks: any[]) { + // Save contents to local storage. You might want to debounce this or replace with a call to your API / database + localStorage.setItem("editorContent", JSON.stringify(jsonBlocks)); +} + +async function loadFromStorage() { + // Gets the previously stored editor contents + return JSON.parse(localStorage.getItem("editorContent") || "[]"); +} export default function App() { + const [initialContent, setInitialContent] = useState< + PartialBlock[] | undefined + >(); + + // Loads the previously stored editor contents + useEffect(() => { + loadFromStorage().then((content) => { + setInitialContent(content); + }); + }, []); + // Creates a new editor instance. - const editor: BlockNoteEditor = useBlockNote({ - // If the editor contents were previously saved, restores them. - initialContent: initialContent ? JSON.parse(initialContent) : undefined, - // Serializes and saves the editor contents to local storage. - onEditorContentChange: (editor) => { - localStorage.setItem( - "editorContent", - JSON.stringify(editor.topLevelBlocks) - ); - }, - }); + // We use useMemo + createBlockNoteEditor instead of useBlockNote so we can delay the creation of the editor until the initial content is loaded. + const editor = useMemo(() => { + if (initialContent === undefined) { + return undefined; + } + return createBlockNoteEditor({ initialContent }); + }, [initialContent]); + + if (editor === undefined) { + return "Loading content..."; + } // Renders the editor instance. - return ; + return ( + { + saveToStorage(editor.topLevelBlocks); + }} + /> + ); } diff --git a/examples/02-ui-components/custom-ui/ColorMenu.tsx b/examples/02-ui-components/custom-ui/ColorMenu.tsx index d8a2120c43..439f8d45f2 100644 --- a/examples/02-ui-components/custom-ui/ColorMenu.tsx +++ b/examples/02-ui-components/custom-ui/ColorMenu.tsx @@ -1,6 +1,6 @@ import { FormattingToolbarProps, - useEditorContentChange, + useEditorChange, useEditorSelectionChange, } from "@blocknote/react"; import { HTMLAttributes, useState } from "react"; @@ -32,22 +32,23 @@ export const ColorMenu = ( ); // Update the colors when the editor content or selection changes - useEditorContentChange(props.editor, () => { + useEditorChange(() => { setTextColor( (props.editor.getActiveStyles().textColor as string) || "default" ); setCurrentColor( (props.editor.getActiveStyles().backgroundColor as string) || "default" ); - }); - useEditorSelectionChange(props.editor, () => { + }, props.editor); + + useEditorSelectionChange(() => { setTextColor( (props.editor.getActiveStyles().textColor as string) || "default" ); setCurrentColor( (props.editor.getActiveStyles().backgroundColor as string) || "default" ); - }); + }, props.editor); return (
    diff --git a/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx b/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx index 30b0423f88..6c860cbd37 100644 --- a/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx +++ b/examples/02-ui-components/custom-ui/CustomFormattingToolbar.tsx @@ -1,6 +1,6 @@ import { FormattingToolbarProps, - useEditorContentChange, + useEditorChange, useEditorSelectionChange, } from "@blocknote/react"; import { useState } from "react"; @@ -77,8 +77,8 @@ export const CustomFormattingToolbar = (props: FormattingToolbarProps) => { const [linkMenuOpen, setLinkMenuOpen] = useState(false); // Updates toolbar state when the editor content or selection changes - useEditorContentChange(props.editor, () => setState(getState())); - useEditorSelectionChange(props.editor, () => setState(getState())); + useEditorChange(() => setState(getState()), props.editor); + useEditorSelectionChange(() => setState(getState()), props.editor); return (
    diff --git a/examples/02-ui-components/formatting-toolbar-buttons/CustomButton.tsx b/examples/02-ui-components/formatting-toolbar-buttons/CustomButton.tsx index 01d18ea0f1..e9e6b16dcd 100644 --- a/examples/02-ui-components/formatting-toolbar-buttons/CustomButton.tsx +++ b/examples/02-ui-components/formatting-toolbar-buttons/CustomButton.tsx @@ -1,10 +1,10 @@ -import { useState } from "react"; import { BlockNoteEditor } from "@blocknote/core"; import { ToolbarButton, - useEditorContentChange, + useEditorChange, useEditorSelectionChange, } from "@blocknote/react"; +import { useState } from "react"; export const CustomButton = (props: { editor: BlockNoteEditor }) => { // Tracks whether the text & background are both blue. @@ -14,20 +14,20 @@ export const CustomButton = (props: { editor: BlockNoteEditor }) => { ); // Updates state on content change. - useEditorContentChange(props.editor, () => { + useEditorChange(() => { setIsSelected( props.editor.getActiveStyles().textColor === "blue" && props.editor.getActiveStyles().backgroundColor === "blue" ); - }); + }, props.editor); // Updates state on selection change. - useEditorSelectionChange(props.editor, () => { + useEditorSelectionChange(() => { setIsSelected( props.editor.getActiveStyles().textColor === "blue" && props.editor.getActiveStyles().backgroundColor === "blue" ); - }); + }, props.editor); return ( { - // Gets the blocks currently spanned by the selection. - const selectedBlocks = editor.getSelection()?.blocks; - // Converts array of blocks to set of block IDs for more efficient comparison. - const selectedBlockIds = new Set( - selectedBlocks?.map((block) => block.id) || [] - ); + const editor = useBlockNote({}); - // Traverses all blocks. - editor.forEachBlock((block) => { - // If no selection is active, resets the background color of each block. - if (selectedBlockIds.size === 0) { - editor.updateBlock(block, { - props: { backgroundColor: "default" }, - }); + const onSelectionChange = useCallback(() => { + // Gets the blocks currently spanned by the selection. + const selectedBlocks = editor.getSelection()?.blocks; + // Converts array of blocks to set of block IDs for more efficient comparison. + const selectedBlockIds = new Set( + selectedBlocks?.map((block) => block.id) || [] + ); - return true; - } - - if ( - selectedBlockIds.has(block.id) && - block.props.backgroundColor !== "blue" - ) { - // If the block is currently spanned by the selection, makes its - // background blue if it isn't already. - editor.updateBlock(block, { - props: { backgroundColor: "blue" }, - }); - } else if ( - !selectedBlockIds.has(block.id) && - block.props.backgroundColor === "blue" - ) { - // If the block is not currently spanned by the selection, resets - // its background if it's blue. - editor.updateBlock(block, { - props: { backgroundColor: "default" }, - }); - } + // Traverses all blocks. + editor.forEachBlock((block) => { + // If no selection is active, resets the background color of each block. + if (selectedBlockIds.size === 0) { + editor.updateBlock(block, { + props: { backgroundColor: "default" }, + }); return true; - }); - }, - }); + } + + if ( + selectedBlockIds.has(block.id) && + block.props.backgroundColor !== "blue" + ) { + // If the block is currently spanned by the selection, makes its + // background blue if it isn't already. + editor.updateBlock(block, { + props: { backgroundColor: "blue" }, + }); + } else if ( + !selectedBlockIds.has(block.id) && + block.props.backgroundColor === "blue" + ) { + // If the block is not currently spanned by the selection, resets + // its background if it's blue. + editor.updateBlock(block, { + props: { backgroundColor: "default" }, + }); + } + + return true; + }); + }, [editor]); // Renders the editor instance. - return ; + return ( + + ); } diff --git a/examples/05-cursor-selections/text-cursor-block/App.tsx b/examples/05-cursor-selections/text-cursor-block/App.tsx index db526865fd..cf76d03ae7 100644 --- a/examples/05-cursor-selections/text-cursor-block/App.tsx +++ b/examples/05-cursor-selections/text-cursor-block/App.tsx @@ -1,41 +1,45 @@ import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; +import "@blocknote/react/style.css"; +import { useCallback } from "react"; + export default function App() { // Creates a new editor instance. - const editor = useBlockNote({ - // Listens for when the text cursor position changes. - onTextCursorPositionChange: (editor) => { - // Gets the block currently hovered by the text cursor. - const hoveredBlock = editor.getTextCursorPosition().block; + const editor = useBlockNote({}); + + const onSelectionChange = useCallback(() => { + // Gets the block currently hovered by the text cursor. + const hoveredBlock = editor.getTextCursorPosition().block; - // Traverses all blocks. - editor.forEachBlock((block) => { - if ( - block.id === hoveredBlock.id && - block.props.backgroundColor !== "blue" - ) { - // If the block is currently hovered by the text cursor, makes its - // background blue if it isn't already. - editor.updateBlock(block, { - props: { backgroundColor: "blue" }, - }); - } else if ( - block.id !== hoveredBlock.id && - block.props.backgroundColor === "blue" - ) { - // If the block is not currently hovered by the text cursor, resets - // its background if it's blue. - editor.updateBlock(block, { - props: { backgroundColor: "default" }, - }); - } + // Traverses all blocks. + editor.forEachBlock((block) => { + if ( + block.id === hoveredBlock.id && + block.props.backgroundColor !== "blue" + ) { + // If the block is currently hovered by the text cursor, makes its + // background blue if it isn't already. + editor.updateBlock(block, { + props: { backgroundColor: "blue" }, + }); + } else if ( + block.id !== hoveredBlock.id && + block.props.backgroundColor === "blue" + ) { + // If the block is not currently hovered by the text cursor, resets + // its background if it's blue. + editor.updateBlock(block, { + props: { backgroundColor: "default" }, + }); + } - return true; - }); - }, - }); + return true; + }); + }, [editor]); // Renders the editor instance. - return ; + return ( + + ); } diff --git a/examples/06-custom-schema/react-custom-styles/App.tsx b/examples/06-custom-schema/react-custom-styles/App.tsx index 67366ef9c1..f9844469b7 100644 --- a/examples/06-custom-schema/react-custom-styles/App.tsx +++ b/examples/06-custom-schema/react-custom-styles/App.tsx @@ -87,9 +87,6 @@ export default function App() { const editor = useBlockNote( { styleSpecs: customReactStyles, - onEditorContentChange: (editor) => { - console.log(editor.topLevelBlocks); - }, domAttributes: { editor: { class: "editor", diff --git a/examples/08-interoperability/converting-blocks-from-html/App.tsx b/examples/08-interoperability/converting-blocks-from-html/App.tsx index d00dce62b1..fb3eee93f6 100644 --- a/examples/08-interoperability/converting-blocks-from-html/App.tsx +++ b/examples/08-interoperability/converting-blocks-from-html/App.tsx @@ -1,27 +1,22 @@ -import { useEffect, useState } from "react"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; +import { useEffect, useState } from "react"; export default function App() { // Stores the current HTML content. const [html, setHTML] = useState(""); // Creates a new editor instance. - const editor = useBlockNote({ - // Makes the editor non-editable. - editable: false, - }); + const editor = useBlockNote(); useEffect(() => { - if (editor) { - // 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 = await editor.tryParseHTMLToBlocks(html); - editor.replaceBlocks(editor.topLevelBlocks, blocks); - }; - getBlocks(); - } + // 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 = await editor.tryParseHTMLToBlocks(html); + editor.replaceBlocks(editor.topLevelBlocks, blocks); + }; + getBlocks(); }, [editor, html]); // Renders a text area for you to write/paste HTML in, and the editor instance @@ -32,7 +27,7 @@ export default function App() { value={html} onChange={(event) => setHTML(event.target.value)} /> - +
    ); } diff --git a/examples/08-interoperability/converting-blocks-from-md/App.tsx b/examples/08-interoperability/converting-blocks-from-md/App.tsx index 59790c90af..3ba28dc9bb 100644 --- a/examples/08-interoperability/converting-blocks-from-md/App.tsx +++ b/examples/08-interoperability/converting-blocks-from-md/App.tsx @@ -1,27 +1,22 @@ -import { useEffect, useState } from "react"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; +import { useEffect, useState } from "react"; export default function App() { // Stores the current Markdown content. const [markdown, setMarkdown] = useState(""); // Creates a new editor instance. - const editor = useBlockNote({ - // Makes the editor non-editable. - editable: false, - }); + const editor = useBlockNote({}); useEffect(() => { - if (editor) { - // 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 = await editor.tryParseMarkdownToBlocks(markdown); - editor.replaceBlocks(editor.topLevelBlocks, blocks); - }; - getBlocks(); - } + // 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 = await editor.tryParseMarkdownToBlocks(markdown); + editor.replaceBlocks(editor.topLevelBlocks, blocks); + }; + getBlocks(); }, [editor, markdown]); // Renders a text area for you to write/paste Markdown in, and the editor @@ -32,7 +27,7 @@ export default function App() { value={markdown} onChange={(event) => setMarkdown(event.target.value)} /> - +
    ); } diff --git a/examples/08-interoperability/converting-blocks-to-html/App.tsx b/examples/08-interoperability/converting-blocks-to-html/App.tsx index f5ef167b9c..1f6af7cf8a 100644 --- a/examples/08-interoperability/converting-blocks-to-html/App.tsx +++ b/examples/08-interoperability/converting-blocks-to-html/App.tsx @@ -1,29 +1,27 @@ -import { useState } from "react"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; +import { useState } from "react"; export default function App() { // Stores the editor's contents as HTML. const [html, setHTML] = useState(""); // Creates a new editor instance. - const editor = useBlockNote({ - // Listens for when the editor's contents change. - onEditorContentChange: (editor) => { - // Converts the editor's contents from Block objects to HTML and saves - // them. - const saveBlocksAsHTML = async () => { - const html = await editor.blocksToHTMLLossy(editor.topLevelBlocks); - setHTML(html); - }; - saveBlocksAsHTML(); - }, - }); + const editor = useBlockNote({}); + + const onChange = () => { + // Converts the editor's contents from Block objects to HTML and saves them. + const saveBlocksAsHTML = async () => { + const html = await editor.blocksToHTMLLossy(editor.topLevelBlocks); + setHTML(html); + }; + saveBlocksAsHTML(); + }; // Renders the editor instance, and its contents as HTML below. return (
    - +
    {html}
    ); diff --git a/examples/08-interoperability/converting-blocks-to-md/App.tsx b/examples/08-interoperability/converting-blocks-to-md/App.tsx index e6ebc000a0..fb48958bdb 100644 --- a/examples/08-interoperability/converting-blocks-to-md/App.tsx +++ b/examples/08-interoperability/converting-blocks-to-md/App.tsx @@ -1,31 +1,23 @@ -import { useState } from "react"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; +import { useState } from "react"; export default function App() { - // Stores the editor's contents as Markdown. const [markdown, setMarkdown] = useState(""); // Creates a new editor instance. - const editor = useBlockNote({ - // Listens for when the editor's contents change. - onEditorContentChange: (editor) => { - // Converts the editor's contents from Block objects to Markdown and - // saves them. - const saveBlocksAsMarkdown = async () => { - const markdown = await editor.blocksToMarkdownLossy( - editor.topLevelBlocks - ); - setMarkdown(markdown); - }; - saveBlocksAsMarkdown(); - }, - }); + const editor = useBlockNote({}); + + const onChange = async () => { + // Save markdown version of document (blocks) on state + const markdown = await editor.blocksToMarkdownLossy(editor.topLevelBlocks); + setMarkdown(markdown); + }; // Renders the editor instance, and its contents as Markdown below. return (
    - +
    {markdown}
    ); diff --git a/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx index 6b9c6628ec..e6b5d90368 100644 --- a/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx +++ b/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx @@ -96,9 +96,6 @@ export default function App() { small, fontSize, }, - onEditorContentChange: (editor) => { - console.log(editor.topLevelBlocks); - }, domAttributes: { editor: { class: "editor", diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 3bf83abe1b..46f99231f2 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -92,38 +92,20 @@ export type BlockNoteEditorOptions< */ domAttributes: Partial; - /** - * A callback function that runs whenever the editor's contents change. - */ - onEditorContentChange: ( - editor: BlockNoteEditor< - BlockSchemaFromSpecs, - InlineContentSchemaFromSpecs, - StyleSchemaFromSpecs - > - ) => void; - /** - * A callback function that runs whenever the text cursor position changes. - */ - onTextCursorPositionChange: ( - editor: BlockNoteEditor< - BlockSchemaFromSpecs, - InlineContentSchemaFromSpecs, - StyleSchemaFromSpecs - > - ) => void; - /** - * Locks the editor from being editable by the user if set to `false`. - */ - editable: boolean; // TODO /** * The content that should be in the editor when it's created, represented as an array of partial block objects. */ - initialContent: PartialBlock< - BlockSchemaFromSpecs, - InlineContentSchemaFromSpecs, - StyleSchemaFromSpecs - >[]; + initialContent: + | PartialBlock< + BlockSchemaFromSpecs, + InlineContentSchemaFromSpecs, + StyleSchemaFromSpecs + >[] + | Block< + BlockSchemaFromSpecs, + InlineContentSchemaFromSpecs, + StyleSchemaFromSpecs + >[]; /** * Use default BlockNote font and reset the styles of

  • elements etc., that are used in BlockNote. * @@ -251,6 +233,25 @@ export class BlockNoteEditor< private constructor( private readonly options: Partial> ) { + const anyOpts = options as any; + if (anyOpts.onEditorContentChange) { + throw new Error( + "onEditorContentChange initialization option is deprecated, use , the useEditorChange(...) hook, or editor.onChange(...)" + ); + } + + if (anyOpts.onTextCursorPositionChange) { + throw new Error( + "onTextCursorPositionChange initialization option is deprecated, use , the useEditorSelectionChange(...) hook, or editor.onSelectionChange(...)" + ); + } + + if (anyOpts.editable) { + throw new Error( + "editable initialization option is deprecated, use , or alternatively editor.isEditable = true/false" + ); + } + // apply defaults const newOptions = { defaultStyles: true, @@ -338,24 +339,6 @@ export class BlockNoteEditor< ...blockNoteTipTapOptions, ...newOptions._tiptapOptions, content: initialContent, - onUpdate: (editor) => { - newOptions._tiptapOptions?.onUpdate?.(editor); - // TODO: move to hook - newOptions.onEditorContentChange?.(this); - }, - - onSelectionUpdate: (editor) => { - newOptions._tiptapOptions?.onSelectionUpdate?.(editor); - // TODO: move to hook - newOptions.onTextCursorPositionChange?.(this); - }, - // TODO: move to view prop and / or hook - editable: - options.editable !== undefined - ? options.editable - : newOptions._tiptapOptions?.editable !== undefined - ? newOptions._tiptapOptions?.editable - : true, extensions: newOptions.enableBlockNoteExtensions === false ? newOptions._tiptapOptions?.extensions || [] @@ -991,4 +974,44 @@ export class BlockNoteEditor< } this._tiptapEditor.commands.updateUser(user); } + + /** + * A callback function that runs whenever the editor's contents change. + * + * @param callback The callback to execute. + * @returns A function to remove the callback. + */ + public onChange( + callback: (editor: BlockNoteEditor) => void + ) { + const cb = () => { + callback(this); + }; + + this._tiptapEditor.on("update", cb); + + return () => { + this._tiptapEditor.off("update", cb); + }; + } + + /** + * A callback function that runs whenever the text cursor position or selection changes. + * + * @param callback The callback to execute. + * @returns A function to remove the callback. + */ + public onSelectionChange( + callback: (editor: BlockNoteEditor) => void + ) { + const cb = () => { + callback(this); + }; + + this._tiptapEditor.on("selectionUpdate", cb); + + return () => { + this._tiptapEditor.off("selectionUpdate", cb); + }; + } } diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index fc1e8c855a..622dd30f90 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -32,7 +32,6 @@ export class BlockNoteTipTapEditor extends TiptapEditor { let cache: any; const oldCreateAndFill = schema.nodes.doc.createAndFill; (schema.nodes.doc as any).createAndFill = (...args: any) => { - console.log("createandfill"); if (cache) { return cache; } diff --git a/packages/dev-scripts/examples/genDocs.ts b/packages/dev-scripts/examples/genDocs.ts index a3cd17b17c..dbdf41db9c 100644 --- a/packages/dev-scripts/examples/genDocs.ts +++ b/packages/dev-scripts/examples/genDocs.ts @@ -82,16 +82,6 @@ ${readme} * Consists of the contents of the readme + the interactive example */ async function generatePageForExample(project: Project) { - if ( - !fs.existsSync( - path.resolve(dir, "../../../docs/pages/examples/" + project.group.slug) - ) - ) { - fs.mkdirSync( - path.resolve(dir, "../../../docs/pages/examples/" + project.group.slug) - ); - } - const target = path.resolve( dir, "../../../docs/pages/examples/" + project.fullSlug + ".mdx" @@ -112,6 +102,16 @@ async function generateMetaForExampleGroup(group: { slug: string; projects: Project[]; }) { + if ( + !fs.existsSync( + path.resolve(dir, "../../../docs/pages/examples/" + group.slug) + ) + ) { + fs.mkdirSync( + path.resolve(dir, "../../../docs/pages/examples/" + group.slug) + ); + } + const target = path.resolve( dir, "../../../docs/pages/examples/" + group.slug + "/_meta.json" diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx index 3b2b3fba80..bf2f52efb2 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/ColorStyleButton.tsx @@ -5,7 +5,7 @@ import { useCallback, useMemo, useState } from "react"; import { ColorIcon } from "../../../components-shared/ColorPicker/ColorIcon"; import { ColorPicker } from "../../../components-shared/ColorPicker/ColorPicker"; import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton"; -import { useEditorChange } from "../../../hooks/useEditorChange"; +import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange"; import { usePreventMenuOverflow } from "../../../hooks/usePreventMenuOverflow"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks"; @@ -21,12 +21,12 @@ export const ColorStyleButton = (props: { props.editor.getActiveStyles().backgroundColor || "default" ); - useEditorChange(props.editor, () => { + useEditorContentOrSelectionChange(() => { setCurrentTextColor(props.editor.getActiveStyles().textColor || "default"); setCurrentBackgroundColor( props.editor.getActiveStyles().backgroundColor || "default" ); - }); + }, props.editor); const { ref, updateMaxHeight } = usePreventMenuOverflow(); diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx index 1276c8ff06..b3e29c5ba8 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/CreateLinkButton.tsx @@ -1,13 +1,13 @@ -import { useCallback, useMemo, useState } from "react"; import { BlockNoteEditor, BlockSchema } from "@blocknote/core"; +import { useCallback, useMemo, useState } from "react"; import { RiLink } from "react-icons/ri"; -import { ToolbarInputDropdownButton } from "../../../components-shared/Toolbar/ToolbarInputDropdownButton"; +import { formatKeyboardShortcut } from "@blocknote/core"; import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton"; -import { EditHyperlinkMenu } from "../../HyperlinkToolbar/EditHyperlinkMenu/components/EditHyperlinkMenu"; +import { ToolbarInputDropdownButton } from "../../../components-shared/Toolbar/ToolbarInputDropdownButton"; +import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks"; -import { useEditorChange } from "../../../hooks/useEditorChange"; -import { formatKeyboardShortcut } from "@blocknote/core"; +import { EditHyperlinkMenu } from "../../HyperlinkToolbar/EditHyperlinkMenu/components/EditHyperlinkMenu"; export const CreateLinkButton = (props: { editor: BlockNoteEditor; @@ -19,10 +19,10 @@ export const CreateLinkButton = (props: { ); const [text, setText] = useState(props.editor.getSelectedText()); - useEditorChange(props.editor, () => { + useEditorContentOrSelectionChange(() => { setText(props.editor.getSelectedText() || ""); setUrl(props.editor.getSelectedLinkUrl() || ""); - }); + }, props.editor); const update = useCallback( (url: string, text: string) => { diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx index efdda0b7b7..28abeafaa3 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/NestBlockButtons.tsx @@ -1,10 +1,10 @@ -import { useCallback, useState } from "react"; import { BlockNoteEditor, BlockSchema } from "@blocknote/core"; +import { useCallback, useState } from "react"; import { RiIndentDecrease, RiIndentIncrease } from "react-icons/ri"; -import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton"; -import { useEditorChange } from "../../../hooks/useEditorChange"; import { formatKeyboardShortcut } from "@blocknote/core"; +import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton"; +import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange"; export const NestBlockButton = (props: { editor: BlockNoteEditor; @@ -13,10 +13,10 @@ export const NestBlockButton = (props: { props.editor.canNestBlock() ); - useEditorChange(props.editor, () => { + useEditorContentOrSelectionChange(() => { props.editor.canNestBlock(); setCanNestBlock(props.editor.canNestBlock()); - }); + }, props.editor); const nestBlock = useCallback(() => { props.editor.focus(); @@ -41,9 +41,9 @@ export const UnnestBlockButton = (props: { props.editor.canUnnestBlock() ); - useEditorChange(props.editor, () => { + useEditorContentOrSelectionChange(() => { setCanUnnestBlock(props.editor.canUnnestBlock()); - }); + }, props.editor); const unnestBlock = useCallback(() => { props.editor.focus(); diff --git a/packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx b/packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx index 98e2212842..8c537844e0 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultButtons/ToggledStyleButton.tsx @@ -15,7 +15,7 @@ import { } from "react-icons/ri"; import { ToolbarButton } from "../../../components-shared/Toolbar/ToolbarButton"; -import { useEditorChange } from "../../../hooks/useEditorChange"; +import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks"; const shortcuts = { @@ -48,9 +48,9 @@ export const ToggledStyleButton = < props.toggledStyle in props.editor.getActiveStyles() ); - useEditorChange(props.editor, () => { + useEditorContentOrSelectionChange(() => { setActive(props.toggledStyle in props.editor.getActiveStyles()); - }); + }, props.editor); const toggleStyle = (style: typeof props.toggledStyle) => { props.editor.focus(); diff --git a/packages/react/src/components/FormattingToolbar/DefaultDropdowns/BlockTypeDropdown.tsx b/packages/react/src/components/FormattingToolbar/DefaultDropdowns/BlockTypeDropdown.tsx index f14c61490d..f1e8b92fae 100644 --- a/packages/react/src/components/FormattingToolbar/DefaultDropdowns/BlockTypeDropdown.tsx +++ b/packages/react/src/components/FormattingToolbar/DefaultDropdowns/BlockTypeDropdown.tsx @@ -12,7 +12,7 @@ import { import { ToolbarDropdown } from "../../../components-shared/Toolbar/ToolbarDropdown"; import type { ToolbarDropdownItemProps } from "../../../components-shared/Toolbar/ToolbarDropdownItem"; -import { useEditorChange } from "../../../hooks/useEditorChange"; +import { useEditorContentOrSelectionChange } from "../../../hooks/useEditorContentOrSelectionChange"; import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks"; export type BlockTypeDropdownItem = { @@ -138,9 +138,9 @@ export const BlockTypeDropdown = (props: { })); }, [block, filteredItems, props.editor, selectedBlocks]); - useEditorChange(props.editor, () => { + useEditorContentOrSelectionChange(() => { setBlock(props.editor.getTextCursorPosition().block); - }); + }, props.editor); if (!shouldShow) { return null; diff --git a/packages/react/src/components/FormattingToolbar/FormattingToolbarPositioner.tsx b/packages/react/src/components/FormattingToolbar/FormattingToolbarPositioner.tsx index 24f52f1937..008b65e14b 100644 --- a/packages/react/src/components/FormattingToolbar/FormattingToolbarPositioner.tsx +++ b/packages/react/src/components/FormattingToolbar/FormattingToolbarPositioner.tsx @@ -12,7 +12,7 @@ import { } from "@floating-ui/react"; import { FC, useEffect, useRef, useState } from "react"; -import { useEditorChange } from "../../hooks/useEditorChange"; +import { useEditorContentOrSelectionChange } from "../../hooks/useEditorContentOrSelectionChange"; import { DefaultFormattingToolbar } from "./DefaultFormattingToolbar"; const textAlignmentToPlacement = ( @@ -77,7 +77,7 @@ export const FormattingToolbarPositioner = < }); }, [props.editor, update]); - useEditorChange(props.editor, () => { + useEditorContentOrSelectionChange(() => { const block = props.editor.getTextCursorPosition().block; if (!("textAlignment" in block.props)) { @@ -89,7 +89,7 @@ export const FormattingToolbarPositioner = < ) ); } - }); + }, props.editor); useEffect(() => { refs.setReference({ diff --git a/packages/react/src/editor/BlockNoteContext.ts b/packages/react/src/editor/BlockNoteContext.ts new file mode 100644 index 0000000000..947e9d557f --- /dev/null +++ b/packages/react/src/editor/BlockNoteContext.ts @@ -0,0 +1,29 @@ +import { + BlockNoteEditor, + BlockSchema, + DefaultBlockSchema, + DefaultInlineContentSchema, + DefaultStyleSchema, + InlineContentSchema, + StyleSchema, +} from "@blocknote/core"; +import { createContext, useContext } from "react"; + +export const BlockNoteContext = createContext< + BlockNoteEditor | undefined +>(undefined); + +export function useBlockNoteContext< + BSchema extends BlockSchema = DefaultBlockSchema, + ISchema extends InlineContentSchema = DefaultInlineContentSchema, + SSchema extends StyleSchema = DefaultStyleSchema +>(): BlockNoteEditor { + const context = useContext(BlockNoteContext); + + if (!context) { + throw new Error( + "useBlockNoteContext must be used within a BlockNoteProvider" + ); + } + return context; +} diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index 7b985bada6..145afe0203 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -7,7 +7,14 @@ import { } from "@blocknote/core"; import { MantineProvider } from "@mantine/core"; -import { HTMLAttributes, ReactNode, useCallback, useState } from "react"; +import { + HTMLAttributes, + ReactNode, + useCallback, + useEffect, + useMemo, + useState, +} from "react"; import usePrefersColorScheme from "use-prefers-color-scheme"; import { FormattingToolbarPositioner } from "../components/FormattingToolbar/FormattingToolbarPositioner"; import { HyperlinkToolbarPositioner } from "../components/HyperlinkToolbar/HyperlinkToolbarPositioner"; @@ -15,6 +22,9 @@ import { ImageToolbarPositioner } from "../components/ImageToolbar/ImageToolbarP import { SideMenuPositioner } from "../components/SideMenu/SideMenuPositioner"; import { SlashMenuPositioner } from "../components/SlashMenu/SlashMenuPositioner"; import { TableHandlesPositioner } from "../components/TableHandles/TableHandlePositioner"; +import { useEditorChange } from "../hooks/useEditorChange"; +import { useEditorSelectionChange } from "../hooks/useEditorSelectionChange"; +import { BlockNoteContext } from "./BlockNoteContext"; import { Theme, applyBlockNoteCSSVariablesFromTheme, @@ -28,6 +38,10 @@ const mantineTheme = { activeClassName: "", }; +const emptyFn = (_editor: any) => { + // noop +}; + export function BlockNoteView< BSchema extends BlockSchema, ISchema extends InlineContentSchema, @@ -43,10 +57,38 @@ export function BlockNoteView< light: Theme; dark: Theme; }; + /** + * Locks the editor from being editable by the user if set to `false`. + */ + editable?: boolean; + /** + * A callback function that runs whenever the text cursor position or selection changes. + */ + onSelectionChange?: ( + editor: BlockNoteEditor + ) => void; + + /** + * A callback function that runs whenever the editor's contents change. + */ + onChange?: (editor: BlockNoteEditor) => void; + children?: ReactNode; - } & HTMLAttributes + } & Omit< + HTMLAttributes, + "onChange" | "onSelectionChange" | "children" + > ) { - const { editor, className, theme, children, ...rest } = props; + const { + editor, + className, + theme, + children, + editable, + onSelectionChange, + onChange, + ...rest + } = props; const systemColorScheme = usePrefersColorScheme(); @@ -95,30 +137,49 @@ export function BlockNoteView< [systemColorScheme, theme, editor._tiptapEditor] ); + useEditorChange(onChange || emptyFn, editor); + useEditorSelectionChange(onSelectionChange || emptyFn, editor); + + useEffect(() => { + if (editable === false) { + editor.isEditable = false; + } else { + editor.isEditable = true; + } + }, [editable, editor]); + + const renderChildren = useMemo(() => { + return ( + children || ( + <> + + + + + + {editor.blockSchema.table && ( + + )} + + ) + ); + }, [editor, children]); + return ( // `cssVariablesSelector` scopes Mantine CSS variables to only the editor, // as proposed here: https://github.com/orgs/mantinedev/discussions/5685 - -
    - {children || ( - <> - - - - - - {editor.blockSchema.table && ( - - )} - - )} -
    -
    + + +
    + {renderChildren} +
    +
    +
    ); } diff --git a/packages/react/src/hooks/useActiveStyles.ts b/packages/react/src/hooks/useActiveStyles.ts index 93e4ad41ac..b53a3a26e8 100644 --- a/packages/react/src/hooks/useActiveStyles.ts +++ b/packages/react/src/hooks/useActiveStyles.ts @@ -1,6 +1,6 @@ import { BlockNoteEditor, StyleSchema } from "@blocknote/core"; import { useState } from "react"; -import { useEditorContentChange } from "./useEditorContentChange"; +import { useEditorChange } from "./useEditorChange"; import { useEditorSelectionChange } from "./useEditorSelectionChange"; export function useActiveStyles( @@ -9,14 +9,14 @@ export function useActiveStyles( const [styles, setStyles] = useState(() => editor.getActiveStyles()); // Updates state on editor content change. - useEditorContentChange(editor, () => { + useEditorChange((editor) => { setStyles(editor.getActiveStyles()); - }); + }, editor); // Updates state on selection change. - useEditorSelectionChange(editor, () => { + useEditorSelectionChange(() => { setStyles(editor.getActiveStyles()); - }); + }, editor); return styles; } diff --git a/packages/react/src/hooks/useBlockNote.ts b/packages/react/src/hooks/useBlockNote.ts index fa1f93b815..a1f32437ee 100644 --- a/packages/react/src/hooks/useBlockNote.ts +++ b/packages/react/src/hooks/useBlockNote.ts @@ -12,7 +12,8 @@ import { import { DependencyList, useMemo } from "react"; import { getDefaultReactSlashMenuItems } from "../slashMenuItems/defaultReactSlashMenuItems"; -const initEditor = < +// TODO: document in docs +export const createBlockNoteEditor = < BSpecs extends BlockSpecs, ISpecs extends InlineContentSpecs, SSpecs extends StyleSpecs @@ -28,6 +29,8 @@ const initEditor = < /** * Main hook for importing a BlockNote editor into a React project + * + * TODO: document in docs */ export const useBlockNote = < BSpecs extends BlockSpecs = typeof defaultBlockSpecs, @@ -38,7 +41,7 @@ export const useBlockNote = < deps: DependencyList = [] ) => { return useMemo(() => { - const editor = initEditor(options); + const editor = createBlockNoteEditor(options); if (window) { // for testing / dev purposes (window as any).ProseMirror = editor._tiptapEditor; diff --git a/packages/react/src/hooks/useEditorChange.ts b/packages/react/src/hooks/useEditorChange.ts index 517f980205..bf20b7c6b6 100644 --- a/packages/react/src/hooks/useEditorChange.ts +++ b/packages/react/src/hooks/useEditorChange.ts @@ -1,11 +1,23 @@ import type { BlockNoteEditor } from "@blocknote/core"; -import { useEditorContentChange } from "./useEditorContentChange"; -import { useEditorSelectionChange } from "./useEditorSelectionChange"; +import { useEffect } from "react"; +import { useBlockNoteContext } from "../editor/BlockNoteContext"; export function useEditorChange( - editor: BlockNoteEditor, - callback: () => void + callback: (editor: BlockNoteEditor) => void, + editor?: BlockNoteEditor ) { - useEditorContentChange(editor, callback); - useEditorSelectionChange(editor, callback); + const editorContext = useBlockNoteContext(); + if (!editor) { + editor = editorContext; + } + + useEffect(() => { + if (!editor) { + throw new Error( + "'editor' is required, either from BlockNoteContext or as a function argument" + ); + } + + return editor.onChange(callback); + }, [callback, editor]); } diff --git a/packages/react/src/hooks/useEditorContentChange.ts b/packages/react/src/hooks/useEditorContentChange.ts deleted file mode 100644 index 2922258a60..0000000000 --- a/packages/react/src/hooks/useEditorContentChange.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { BlockNoteEditor } from "@blocknote/core"; -import { useEffect } from "react"; - -export function useEditorContentChange( - editor: BlockNoteEditor, - callback: () => void -) { - useEffect(() => { - editor._tiptapEditor.on("update", callback); - - return () => { - editor._tiptapEditor.off("update", callback); - }; - }, [callback, editor._tiptapEditor]); -} diff --git a/packages/react/src/hooks/useEditorContentOrSelectionChange.ts b/packages/react/src/hooks/useEditorContentOrSelectionChange.ts new file mode 100644 index 0000000000..ca48c68fb9 --- /dev/null +++ b/packages/react/src/hooks/useEditorContentOrSelectionChange.ts @@ -0,0 +1,11 @@ +import type { BlockNoteEditor } from "@blocknote/core"; +import { useEditorChange } from "./useEditorChange"; +import { useEditorSelectionChange } from "./useEditorSelectionChange"; + +export function useEditorContentOrSelectionChange( + callback: (editor: BlockNoteEditor) => void, + editor?: BlockNoteEditor +) { + useEditorChange(callback, editor); + useEditorSelectionChange(callback, editor); +} diff --git a/packages/react/src/hooks/useEditorSelectionChange.ts b/packages/react/src/hooks/useEditorSelectionChange.ts index dab5c227cf..a63b3e6e80 100644 --- a/packages/react/src/hooks/useEditorSelectionChange.ts +++ b/packages/react/src/hooks/useEditorSelectionChange.ts @@ -1,15 +1,22 @@ import type { BlockNoteEditor } from "@blocknote/core"; import { useEffect } from "react"; +import { useBlockNoteContext } from "../editor/BlockNoteContext"; export function useEditorSelectionChange( - editor: BlockNoteEditor, - callback: () => void + callback: (editor: BlockNoteEditor) => void, + editor?: BlockNoteEditor ) { - useEffect(() => { - editor._tiptapEditor.on("selectionUpdate", callback); + const editorContext = useBlockNoteContext(); + if (!editor) { + editor = editorContext; + } - return () => { - editor._tiptapEditor.off("selectionUpdate", callback); - }; - }, [callback, editor._tiptapEditor]); + useEffect(() => { + if (!editor) { + throw new Error( + "'editor' is required, either from BlockNoteContext or as a function argument" + ); + } + return editor.onSelectionChange(callback); + }, [callback, editor]); } diff --git a/packages/react/src/hooks/useSelectedBlocks.ts b/packages/react/src/hooks/useSelectedBlocks.ts index 63ab136ed7..69411e04a2 100644 --- a/packages/react/src/hooks/useSelectedBlocks.ts +++ b/packages/react/src/hooks/useSelectedBlocks.ts @@ -6,24 +6,37 @@ import { StyleSchema, } from "@blocknote/core"; import { useState } from "react"; -import { useEditorChange } from "./useEditorChange"; +import { useBlockNoteContext } from "../editor/BlockNoteContext"; +import { useEditorContentOrSelectionChange } from "./useEditorContentOrSelectionChange"; export function useSelectedBlocks< BSchema extends BlockSchema, ISchema extends InlineContentSchema, SSchema extends StyleSchema ->(editor: BlockNoteEditor) { +>(editor?: BlockNoteEditor) { + const editorContext = useBlockNoteContext(); + if (!editor) { + editor = editorContext; + } + + if (!editor) { + throw new Error( + "'editor' is required, either from BlockNoteContext or as a function argument" + ); + } + + const e = editor; + const [selectedBlocks, setSelectedBlocks] = useState< Block[] - >( - () => - editor.getSelection()?.blocks || [editor.getTextCursorPosition().block] - ); + >(() => e.getSelection()?.blocks || [e.getTextCursorPosition().block]); - useEditorChange(editor, () => - setSelectedBlocks( - editor.getSelection()?.blocks || [editor.getTextCursorPosition().block] - ) + useEditorContentOrSelectionChange( + () => + setSelectedBlocks( + e.getSelection()?.blocks || [e.getTextCursorPosition().block] + ), + e ); return selectedBlocks; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 258765b26b..0fa7d78c02 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -29,10 +29,10 @@ export * from "./components/SideMenu/DragHandleMenu/DefaultDragHandleMenu"; export * from "./components/SideMenu/DragHandleMenu/DragHandleMenu"; export * from "./components/SideMenu/DragHandleMenu/DragHandleMenuItem"; -export * from "./slashMenuItems/ReactSlashMenuItem"; export * from "./components/SlashMenu/DefaultSlashMenu"; export * from "./components/SlashMenu/SlashMenuItem"; export * from "./components/SlashMenu/SlashMenuPositioner"; +export * from "./slashMenuItems/ReactSlashMenuItem"; export * from "./slashMenuItems/defaultReactSlashMenuItems"; export * from "./components/ImageToolbar/DefaultImageToolbar"; @@ -48,7 +48,7 @@ export * from "./components-shared/Toolbar/ToolbarDropdown"; export * from "./hooks/useActiveStyles"; export * from "./hooks/useBlockNote"; export * from "./hooks/useEditorChange"; -export * from "./hooks/useEditorContentChange"; +export * from "./hooks/useEditorContentOrSelectionChange"; export * from "./hooks/useEditorForceUpdate"; export * from "./hooks/useEditorSelectionChange"; export * from "./hooks/useSelectedBlocks"; From fa9975036af9d70b5e2a6011068656772a37e274 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 20:19:34 +0100 Subject: [PATCH 05/17] fix --- packages/react/src/editor/BlockNoteContext.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/react/src/editor/BlockNoteContext.ts b/packages/react/src/editor/BlockNoteContext.ts index 947e9d557f..98a54faf99 100644 --- a/packages/react/src/editor/BlockNoteContext.ts +++ b/packages/react/src/editor/BlockNoteContext.ts @@ -17,13 +17,8 @@ export function useBlockNoteContext< BSchema extends BlockSchema = DefaultBlockSchema, ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema ->(): BlockNoteEditor { +>(): BlockNoteEditor | undefined { const context = useContext(BlockNoteContext); - if (!context) { - throw new Error( - "useBlockNoteContext must be used within a BlockNoteProvider" - ); - } return context; } From 25b221b1861ff5ac285c40de9c4d9db2f2e70dad Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 20:47:10 +0100 Subject: [PATCH 06/17] fix light / dark mode --- docs/components/example/ExampleBlock.tsx | 13 +++++++--- docs/components/example/ExampleWrapper.tsx | 16 ++++++++++++ docs/components/pages/landing/PackHero.tsx | 4 ++- packages/react/src/editor/BlockNoteContext.ts | 15 ++++++++--- packages/react/src/editor/BlockNoteView.tsx | 25 ++++++++++++++----- packages/react/src/hooks/useEditorChange.ts | 2 +- .../src/hooks/useEditorSelectionChange.ts | 2 +- packages/react/src/hooks/useSelectedBlocks.ts | 2 +- packages/react/src/index.ts | 1 + 9 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 docs/components/example/ExampleWrapper.tsx diff --git a/docs/components/example/ExampleBlock.tsx b/docs/components/example/ExampleBlock.tsx index e1db6c2e6f..55b5803932 100644 --- a/docs/components/example/ExampleBlock.tsx +++ b/docs/components/example/ExampleBlock.tsx @@ -1,13 +1,19 @@ -import { AiFillGithub, AiFillCodeSandboxCircle } from "react-icons/ai"; +import dynamic from "next/dynamic"; +import { AiFillCodeSandboxCircle, AiFillGithub } from "react-icons/ai"; import { examples } from "./generated/exampleComponents.gen"; import "./styles.css"; + const baseGitHubURL = "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/TypeCellOS/BlockNote/tree/main/examples/"; const baseCodeSandboxURL = "https://githubbox.com/TypeCellOS/BlockNote/tree/main/examples/"; +const ExampleWrapper = dynamic(() => import("./ExampleWrapper"), { + ssr: false, +}); + export function ExampleBlock(props: { name: keyof typeof examples; children: any; @@ -16,8 +22,7 @@ export function ExampleBlock(props: { // if (!example) { // throw new Error("invalid example"); // } - const example = examples[props.name]; - const App = example.App; + return (
    @@ -40,7 +45,7 @@ export function ExampleBlock(props: {
    - +
    {props.children} diff --git a/docs/components/example/ExampleWrapper.tsx b/docs/components/example/ExampleWrapper.tsx new file mode 100644 index 0000000000..94566c0808 --- /dev/null +++ b/docs/components/example/ExampleWrapper.tsx @@ -0,0 +1,16 @@ +"use client"; +import { BlockNoteContext } from "@blocknote/react"; +import { useTheme } from "nextra-theme-docs"; +import { examples } from "./generated/exampleComponents.gen"; + +export default function ExampleWrapper(props: { + name: keyof typeof examples + }) { + const example = examples[props.name]; + const App = example.App; + const { theme } = useTheme(); + + return + + ; + } \ No newline at end of file diff --git a/docs/components/pages/landing/PackHero.tsx b/docs/components/pages/landing/PackHero.tsx index 4c42448eb4..842d60399c 100644 --- a/docs/components/pages/landing/PackHero.tsx +++ b/docs/components/pages/landing/PackHero.tsx @@ -1,12 +1,14 @@ // import { PackLogo } from "../../logos/PackLogo"; import dynamic from "next/dynamic"; +import { useTheme } from "nextra-theme-docs"; import { FadeIn } from "../home-shared/FadeIn"; import { HeroText, SectionSubtext } from "../home-shared/Headings"; const Demo = dynamic(() => import("./demo/Demo"), { ssr: false }); export function PackHero() { + const { theme } = useTheme(); return (
    @@ -50,7 +52,7 @@ export function PackHero() {
    {/* TODO: Wait for editor & collab content to load before rendering. Show placeholder or delay loading?*/} - + {/*
    diff --git a/packages/react/src/editor/BlockNoteContext.ts b/packages/react/src/editor/BlockNoteContext.ts index 98a54faf99..5de44dc28b 100644 --- a/packages/react/src/editor/BlockNoteContext.ts +++ b/packages/react/src/editor/BlockNoteContext.ts @@ -9,16 +9,25 @@ import { } from "@blocknote/core"; import { createContext, useContext } from "react"; +type BlockNoteContextValue< + BSchema extends BlockSchema = DefaultBlockSchema, + ISchema extends InlineContentSchema = DefaultInlineContentSchema, + SSchema extends StyleSchema = DefaultStyleSchema +> = { + editor?: BlockNoteEditor; + colorSchemePreference?: "light" | "dark"; +}; + export const BlockNoteContext = createContext< - BlockNoteEditor | undefined + BlockNoteContextValue | undefined >(undefined); export function useBlockNoteContext< BSchema extends BlockSchema = DefaultBlockSchema, ISchema extends InlineContentSchema = DefaultInlineContentSchema, SSchema extends StyleSchema = DefaultStyleSchema ->(): BlockNoteEditor | undefined { - const context = useContext(BlockNoteContext); +>(): BlockNoteContextValue | undefined { + const context = useContext(BlockNoteContext) as any; return context; } diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index 145afe0203..c65cfcdea5 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -24,7 +24,7 @@ import { SlashMenuPositioner } from "../components/SlashMenu/SlashMenuPositioner import { TableHandlesPositioner } from "../components/TableHandles/TableHandlePositioner"; import { useEditorChange } from "../hooks/useEditorChange"; import { useEditorSelectionChange } from "../hooks/useEditorSelectionChange"; -import { BlockNoteContext } from "./BlockNoteContext"; +import { BlockNoteContext, useBlockNoteContext } from "./BlockNoteContext"; import { Theme, applyBlockNoteCSSVariablesFromTheme, @@ -90,7 +90,11 @@ export function BlockNoteView< ...rest } = props; + const existingContext = useBlockNoteContext(); + const systemColorScheme = usePrefersColorScheme(); + const defaultColorScheme = + existingContext?.colorSchemePreference || systemColorScheme; const [editorColorScheme, setEditorColorScheme] = useState< "light" | "dark" | undefined @@ -120,10 +124,12 @@ export function BlockNoteView< if (typeof theme === "object") { if ("light" in theme && "dark" in theme) { applyBlockNoteCSSVariablesFromTheme( - theme[systemColorScheme === "dark" ? "dark" : "light"], + theme[defaultColorScheme === "dark" ? "dark" : "light"], node ); - setEditorColorScheme(systemColorScheme === "dark" ? "dark" : "light"); + setEditorColorScheme( + defaultColorScheme === "dark" ? "dark" : "light" + ); return; } @@ -132,9 +138,9 @@ export function BlockNoteView< return; } - setEditorColorScheme(systemColorScheme === "dark" ? "dark" : "light"); + setEditorColorScheme(defaultColorScheme === "dark" ? "dark" : "light"); }, - [systemColorScheme, theme, editor._tiptapEditor] + [defaultColorScheme, theme, editor._tiptapEditor] ); useEditorChange(onChange || emptyFn, editor); @@ -165,11 +171,18 @@ export function BlockNoteView< ); }, [editor, children]); + const context = useMemo(() => { + return { + ...existingContext, + editor, + }; + }, [existingContext, editor]); + return ( // `cssVariablesSelector` scopes Mantine CSS variables to only the editor, // as proposed here: https://github.com/orgs/mantinedev/discussions/5685 - +
    { diff --git a/packages/react/src/hooks/useEditorSelectionChange.ts b/packages/react/src/hooks/useEditorSelectionChange.ts index a63b3e6e80..ff9d298bd9 100644 --- a/packages/react/src/hooks/useEditorSelectionChange.ts +++ b/packages/react/src/hooks/useEditorSelectionChange.ts @@ -8,7 +8,7 @@ export function useEditorSelectionChange( ) { const editorContext = useBlockNoteContext(); if (!editor) { - editor = editorContext; + editor = editorContext?.editor; } useEffect(() => { diff --git a/packages/react/src/hooks/useSelectedBlocks.ts b/packages/react/src/hooks/useSelectedBlocks.ts index 69411e04a2..49d90dece0 100644 --- a/packages/react/src/hooks/useSelectedBlocks.ts +++ b/packages/react/src/hooks/useSelectedBlocks.ts @@ -16,7 +16,7 @@ export function useSelectedBlocks< >(editor?: BlockNoteEditor) { const editorContext = useBlockNoteContext(); if (!editor) { - editor = editorContext; + editor = editorContext?.editor; } if (!editor) { diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 0fa7d78c02..1cd5d0e958 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,4 +1,5 @@ // TODO: review directories +export * from "./editor/BlockNoteContext"; export * from "./editor/BlockNoteTheme"; export * from "./editor/BlockNoteView"; export * from "./editor/defaultThemes"; From d959e5fa43282e2db8c2644f5455a1d6c930f584 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 21:02:39 +0100 Subject: [PATCH 07/17] clean examples --- examples/01-basic/01-minimal/App.tsx | 6 ------ examples/06-custom-schema/react-custom-blocks/App.tsx | 6 ------ .../06-custom-schema/react-custom-inline-content/App.tsx | 6 ------ examples/06-custom-schema/react-custom-styles/App.tsx | 6 ------ examples/07-collaboration/partykit/App.tsx | 6 ------ examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx | 6 ------ .../react-vanilla-custom-inline-content/App.tsx | 6 ------ examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx | 6 ------ playground/src/style.css | 6 +----- tests/src/end-to-end/basics/basics.test.ts | 7 +++++-- tests/src/end-to-end/placeholder/placeholder.test.ts | 7 +++++-- tests/src/utils/components/Editor.tsx | 4 ---- tests/src/utils/const.ts | 2 +- 13 files changed, 12 insertions(+), 62 deletions(-) diff --git a/examples/01-basic/01-minimal/App.tsx b/examples/01-basic/01-minimal/App.tsx index 9b7c25b83b..dd37d4aece 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -5,12 +5,6 @@ import "@blocknote/react/style.css"; export default function App() { // Creates a new editor instance. const editor = useBlockNote({ - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, }); diff --git a/examples/06-custom-schema/react-custom-blocks/App.tsx b/examples/06-custom-schema/react-custom-blocks/App.tsx index 75f0a6e62a..ca16b6b3e7 100644 --- a/examples/06-custom-schema/react-custom-blocks/App.tsx +++ b/examples/06-custom-schema/react-custom-blocks/App.tsx @@ -117,12 +117,6 @@ export const bracketsParagraphBlock = createReactBlockSpec( export default function App() { const editor = useBlockNote({ - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, blockSpecs: { ...defaultBlockSpecs, alert: alertBlock, diff --git a/examples/06-custom-schema/react-custom-inline-content/App.tsx b/examples/06-custom-schema/react-custom-inline-content/App.tsx index a98d862c0d..ca8c55c6f4 100644 --- a/examples/06-custom-schema/react-custom-inline-content/App.tsx +++ b/examples/06-custom-schema/react-custom-inline-content/App.tsx @@ -47,12 +47,6 @@ export default function ReactInlineContent() { tag, ...defaultInlineContentSpecs, }, - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, initialContent: [ { type: "paragraph", diff --git a/examples/06-custom-schema/react-custom-styles/App.tsx b/examples/06-custom-schema/react-custom-styles/App.tsx index f9844469b7..45cba15875 100644 --- a/examples/06-custom-schema/react-custom-styles/App.tsx +++ b/examples/06-custom-schema/react-custom-styles/App.tsx @@ -87,12 +87,6 @@ export default function App() { const editor = useBlockNote( { styleSpecs: customReactStyles, - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, initialContent: [ { type: "paragraph", diff --git a/examples/07-collaboration/partykit/App.tsx b/examples/07-collaboration/partykit/App.tsx index 6a5217a5da..d624f3bec1 100644 --- a/examples/07-collaboration/partykit/App.tsx +++ b/examples/07-collaboration/partykit/App.tsx @@ -16,12 +16,6 @@ const provider = new YPartyKitProvider( export default function App() { const editor = useBlockNote({ - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, collaboration: { // The Yjs Provider responsible for transporting updates: provider, diff --git a/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx index 0e968ae3ba..e563db3996 100644 --- a/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx +++ b/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx @@ -179,12 +179,6 @@ const bracketsParagraphBlock = createBlockSpec( export default function App() { const editor = useBlockNote({ - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, blockSpecs: { ...defaultBlockSpecs, alert: alertBlock, diff --git a/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx index d2e819f0bb..6f82c584e8 100644 --- a/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx +++ b/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx @@ -56,12 +56,6 @@ export default function App() { tag, ...defaultInlineContentSpecs, }, - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, initialContent: [ { type: "paragraph", diff --git a/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx index e6b5d90368..fa196ae6b7 100644 --- a/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx +++ b/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx @@ -96,12 +96,6 @@ export default function App() { small, fontSize, }, - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, initialContent: [ { type: "paragraph", diff --git a/playground/src/style.css b/playground/src/style.css index 2d6f7390c7..67f8b81395 100644 --- a/playground/src/style.css +++ b/playground/src/style.css @@ -5,13 +5,9 @@ body { } .bn-container { - margin-top: 8px; + margin: 8px calc((100% - 731px) / 2) 0; } .mantine-AppShell-navbar { background-color: #f7f7f5; } - -.editor { - margin: 8px calc((100% - 731px) / 2) 0; -} diff --git a/tests/src/end-to-end/basics/basics.test.ts b/tests/src/end-to-end/basics/basics.test.ts index e835649b06..bbe16e2ae9 100644 --- a/tests/src/end-to-end/basics/basics.test.ts +++ b/tests/src/end-to-end/basics/basics.test.ts @@ -1,6 +1,6 @@ import { expect } from "@playwright/test"; import { test } from "../../setup/setupScript"; -import { BASE_URL } from "../../utils/const"; +import { BASE_URL, EDITOR_SELECTOR } from "../../utils/const"; test.beforeEach(async ({ page }) => { await page.goto(BASE_URL); @@ -9,7 +9,10 @@ test.beforeEach(async ({ page }) => { test.describe("Basic typing functionality", () => { test("should allow me to type content", async ({ page }) => { const editor = await page.waitForSelector("[data-test='editor']"); - await page.locator('[data-test="editor"] div').nth(3).click(); + await page + .locator(EDITOR_SELECTOR + " div") + .nth(3) + .click(); await page.keyboard.insertText("hello world"); // await page.pause(); expect(await editor.textContent()).toBe("hello world"); diff --git a/tests/src/end-to-end/placeholder/placeholder.test.ts b/tests/src/end-to-end/placeholder/placeholder.test.ts index 2c93a19608..dc1be47610 100644 --- a/tests/src/end-to-end/placeholder/placeholder.test.ts +++ b/tests/src/end-to-end/placeholder/placeholder.test.ts @@ -1,6 +1,6 @@ import { expect } from "@playwright/test"; import { test } from "../../setup/setupScript"; -import { BASE_URL } from "../../utils/const"; +import { BASE_URL, EDITOR_SELECTOR } from "../../utils/const"; test.beforeEach(async ({ page }) => { await page.goto(BASE_URL); @@ -9,7 +9,10 @@ test.beforeEach(async ({ page }) => { test.describe("Basic placeholder functionality", () => { test("should show placeholder on load", async ({ page }) => { // const editor = await page.waitForSelector("[data-test='editor']"); - await page.locator('[data-test="editor"] div').nth(3).hover(); + await page + .locator(EDITOR_SELECTOR + " div") + .nth(3) + .hover(); // TODO: doesn't work. No way to access text of ::before element? // expect(await editor.textContent()).toBe( diff --git a/tests/src/utils/components/Editor.tsx b/tests/src/utils/components/Editor.tsx index b70567a89f..39c072d177 100644 --- a/tests/src/utils/components/Editor.tsx +++ b/tests/src/utils/components/Editor.tsx @@ -10,7 +10,6 @@ import { Button, insertButton } from "../customblocks/Button"; import { Embed, insertEmbed } from "../customblocks/Embed"; import { Image, insertImage } from "../customblocks/Image"; import { Separator, insertSeparator } from "../customblocks/Separator"; -import styles from "./Editor.module.css"; export default function Editor() { const blockSpecs = { @@ -33,9 +32,6 @@ export default function Editor() { ]; const editor = useBlockNote({ - domAttributes: { - editor: { class: styles.editor, "data-test": "editor" }, - }, blockSpecs, slashMenuItems: [...getDefaultReactSlashMenuItems(), ...slashMenuItems], }); diff --git a/tests/src/utils/const.ts b/tests/src/utils/const.ts index bb536f8083..22cb53a56f 100644 --- a/tests/src/utils/const.ts +++ b/tests/src/utils/const.ts @@ -5,7 +5,7 @@ export const BASE_URL = !process.env.RUN_IN_DOCKER export const PASTE_ZONE_SELECTOR = "#pasteZone"; -export const EDITOR_SELECTOR = `[data-test="editor"]`; +export const EDITOR_SELECTOR = `.ProseMirror`; export const BLOCK_CONTAINER_SELECTOR = `[data-node-type="blockContainer"]`; export const BLOCK_GROUP_SELECTOR = `[data-node-type="blockGroup"]`; From 97251a852a1af69ab48906443f6eb1e4fd69ddfb Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 21:04:45 +0100 Subject: [PATCH 08/17] clean examples --- examples/01-basic/01-minimal/App.tsx | 2 +- examples/06-custom-schema/react-custom-blocks/App.tsx | 2 +- examples/06-custom-schema/react-custom-inline-content/App.tsx | 2 +- examples/06-custom-schema/react-custom-styles/App.tsx | 2 +- examples/07-collaboration/partykit/App.tsx | 2 +- examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx | 2 +- .../09-vanilla-js/react-vanilla-custom-inline-content/App.tsx | 2 +- examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/01-basic/01-minimal/App.tsx b/examples/01-basic/01-minimal/App.tsx index dd37d4aece..a4ad0ac607 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -9,5 +9,5 @@ export default function App() { }); // Renders the editor instance using a React component. - return ; + return ; } diff --git a/examples/06-custom-schema/react-custom-blocks/App.tsx b/examples/06-custom-schema/react-custom-blocks/App.tsx index ca16b6b3e7..da714765d7 100644 --- a/examples/06-custom-schema/react-custom-blocks/App.tsx +++ b/examples/06-custom-schema/react-custom-blocks/App.tsx @@ -144,5 +144,5 @@ export default function App() { ], }); - return ; + return ; } diff --git a/examples/06-custom-schema/react-custom-inline-content/App.tsx b/examples/06-custom-schema/react-custom-inline-content/App.tsx index ca8c55c6f4..ff053c77cf 100644 --- a/examples/06-custom-schema/react-custom-inline-content/App.tsx +++ b/examples/06-custom-schema/react-custom-inline-content/App.tsx @@ -74,5 +74,5 @@ export default function ReactInlineContent() { ], }); - return ; + return ; } diff --git a/examples/06-custom-schema/react-custom-styles/App.tsx b/examples/06-custom-schema/react-custom-styles/App.tsx index 45cba15875..3c4a13c0d3 100644 --- a/examples/06-custom-schema/react-custom-styles/App.tsx +++ b/examples/06-custom-schema/react-custom-styles/App.tsx @@ -113,7 +113,7 @@ export default function App() { ); return ( - + ; + return ; } diff --git a/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx index e563db3996..df9b462034 100644 --- a/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx +++ b/examples/09-vanilla-js/react-vanilla-custom-blocks/App.tsx @@ -206,5 +206,5 @@ export default function App() { ], }); - return ; + return ; } diff --git a/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx index 6f82c584e8..aa39042c34 100644 --- a/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx +++ b/examples/09-vanilla-js/react-vanilla-custom-inline-content/App.tsx @@ -83,5 +83,5 @@ export default function App() { ], }); - return ; + return ; } diff --git a/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx b/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx index fa196ae6b7..32f0ad4d60 100644 --- a/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx +++ b/examples/09-vanilla-js/react-vanilla-custom-styles/App.tsx @@ -122,7 +122,7 @@ export default function App() { ); return ( - + Date: Mon, 12 Feb 2024 21:17:18 +0100 Subject: [PATCH 09/17] docs --- docs/pages/docs/editor-api/editor.mdx | 43 +++++++++++++++------ packages/core/src/editor/BlockNoteEditor.ts | 12 ++++-- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/docs/pages/docs/editor-api/editor.mdx b/docs/pages/docs/editor-api/editor.mdx index 293c82cea4..8714426d9f 100644 --- a/docs/pages/docs/editor-api/editor.mdx +++ b/docs/pages/docs/editor-api/editor.mdx @@ -1,17 +1,19 @@ --- -title: Customizing the Editor +title: Editor Setup description: While you can get started with BlockNote in minutes, it's likely that you'll want to customize its features and functionality to better suit your app. imageTitle: Customizing the Editor path: /docs/editor --- TODO: -- review API -- what needs to be part of useBlockNote vs the component? -- expose / document hooks -- controlled / uncontrolled +[x] review API +[x] what needs to be part of useBlockNote vs the component? +[ ] document instantiation / useBlockNote +[ ] expose / document hooks +[ ] document events +[ ] explain controlled / uncontrolled -# Customizing the Editor +# Editor Setup While you can get started with BlockNote in minutes, it's likely that you'll want to customize its features and functionality to better suit your app. @@ -23,15 +25,15 @@ can use to customize the editor. You can find the full list of these below: ```typescript export type BlockNoteEditorOptions = Partial<{ - editable: boolean; initialContent: PartialBlock[]; - editorDOMAttributes: Record; - onEditorReady: (editor: BlockNoteEditor) => void; - onEditorContentChange: (editor: BlockNoteEditor) => void; - onTextCursorPositionChange: (editor: BlockNoteEditor) => void; + domAttributes: Record; slashMenuItems: ReactSlashMenuItem[]; defaultStyles: boolean; - uploadFile: (file: File) => Promise + uploadFile: (file: File) => Promise; + collaboration: CollaborationOptions; + blockSpecs: BlockSpecs; + inlineContentSpecs: InlineContentSpecs; + styleSpecs: StyleSpecs; }>; ``` @@ -44,3 +46,20 @@ export type BlockNoteEditorOptions = Partial<{ `defaultStyles`: Whether to use the default font and reset the styles of `

    `, `

  • `, `

    `, etc. elements that are used in BlockNote. Defaults to true if undefined. `uploadFile`: A function which handles file uploads and eventually returns the URL to the uploaded file. Used by the [Image Toolbar](/docs/image-toolbar). + +`collaboration`: Options for enabling real-time collaboration. See [Collaboration](/docs/collaboration) for more info. + +`blockSpecs` (_advanced_): _advanced_ Specifications for Custom Blocks. See [Block Specs](/docs/block-specs) more info. + +`inlineContentSpecs` (_advanced_): Specifications for Custom Inline Content. See [Inline Content Specs](/docs/inline-content-specs) for more info. + +`styleSpecs` (_advanced_): Specifications for Custom Styles. See [Style Specs](/docs/style-specs) for more info. + + +## `useBlockNote` + +## `BlockNoteView` + +- editable +- events + diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 46f99231f2..95f2bc7323 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -114,14 +114,20 @@ export type BlockNoteEditorOptions< defaultStyles: boolean; /** - * A list of block types that should be available in the editor. + * A list of custom block types that should be available in the editor. */ blockSpecs: BSpecs; - styleSpecs: SSpecs; - + /** + * A list of custom InlineContent types that should be available in the editor. + */ inlineContentSpecs: ISpecs; + /** + * A list of custom Styles that should be available in the editor. + */ + styleSpecs: SSpecs; + /** * A custom function to handle file uploads. * @param file The file that should be uploaded. From 8ac74e2d326fcddcb4c32b97e4ba85781c9812a3 Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 22:16:23 +0100 Subject: [PATCH 10/17] misc --- docs/next.config.mjs | 2 +- .../core/src/editor/BlockNoteTipTapEditor.ts | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/next.config.mjs b/docs/next.config.mjs index d7c4f80e1b..a07f955480 100644 --- a/docs/next.config.mjs +++ b/docs/next.config.mjs @@ -54,7 +54,7 @@ const nextConfig = withAnalyzer( cacheGroups: { vendor: { test: (module) => { - console.log(module.resource); + // console.log(module.resource); if (module.resource?.includes("blocknote") || module.resource?.includes("mantine")) { return true; diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index 622dd30f90..e9dd7675f2 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -23,8 +23,29 @@ export class BlockNoteTipTapEditor extends TiptapEditor { private _state: EditorState; constructor(options: BlockNoteTipTapEditorOptions, styleSchema: StyleSchema) { + // possible fix for next.js server side rendering + // const d = globalThis.document; + // const w = globalThis.window; + // if (!globalThis.document) { + // globalThis.document = { + // createElement: () => {}, + // }; + // } + // if (!globalThis.window) { + // globalThis.window = { + // setTimeout: () => {}, + // }; + // } + // options.injectCSS = false super({ ...options, content: undefined }); + // try { + // globalThis.window = w; + // } catch(e) {} + // try { + // globalThis.document = d; + // } catch(e) {} + // This is a hack to make "initial content detection" by y-prosemirror (and also tiptap isEmpty) // properly detect whether or not the document has changed. // We change the doc.createAndFill function to make sure the initial block id is set, instead of null From 7714c3711c82457cf6157724873f2ab137c8315b Mon Sep 17 00:00:00 2001 From: yousefed Date: Mon, 12 Feb 2024 23:25:27 +0100 Subject: [PATCH 11/17] refs --- examples/01-basic/block-manipulation/App.tsx | 3 +-- examples/01-basic/block-objects/App.tsx | 3 +-- examples/01-basic/keyboard-shortcuts/App.tsx | 23 +++++++++------- .../formatting-toolbar-buttons/App.tsx | 3 +-- .../side-menu-buttons/App.tsx | 2 +- .../side-menu-drag-handle-items/App.tsx | 2 +- .../ui-elements-remove/App.tsx | 2 +- .../ui-elements-replace/App.tsx | 2 +- examples/04-theming/changing-font/App.tsx | 3 +-- .../converting-blocks-to-html/App.tsx | 2 +- packages/react/src/editor/BlockNoteView.tsx | 26 ++++++++++++++----- packages/react/src/util/mergeRefs.ts | 14 ++++++++++ 12 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 packages/react/src/util/mergeRefs.ts diff --git a/examples/01-basic/block-manipulation/App.tsx b/examples/01-basic/block-manipulation/App.tsx index 44663ca816..fc9d2fe74d 100644 --- a/examples/01-basic/block-manipulation/App.tsx +++ b/examples/01-basic/block-manipulation/App.tsx @@ -1,9 +1,8 @@ -import { BlockNoteEditor } from "@blocknote/core"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; export default function App() { - const editor: BlockNoteEditor = useBlockNote(); + const editor = useBlockNote(); return (
    diff --git a/examples/01-basic/block-objects/App.tsx b/examples/01-basic/block-objects/App.tsx index 7df07d3053..20c0b0f003 100644 --- a/examples/01-basic/block-objects/App.tsx +++ b/examples/01-basic/block-objects/App.tsx @@ -1,6 +1,5 @@ import { Block, - BlockNoteEditor, DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, @@ -15,7 +14,7 @@ export default function App() { Block[] >([]); // Creates a new editor instance. - const editor: BlockNoteEditor = useBlockNote({}); + const editor = useBlockNote({}); // Renders the editor instance and its contents, as an array of Block // objects, below. diff --git a/examples/01-basic/keyboard-shortcuts/App.tsx b/examples/01-basic/keyboard-shortcuts/App.tsx index 94717d71a3..5f3f9c75a3 100644 --- a/examples/01-basic/keyboard-shortcuts/App.tsx +++ b/examples/01-basic/keyboard-shortcuts/App.tsx @@ -8,7 +8,10 @@ import { import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; -const cycleBlocksShortcut = (event: KeyboardEvent, editor: BlockNoteEditor) => { +const cycleBlocksShortcut = ( + event: React.KeyboardEvent, + editor: BlockNoteEditor +) => { // Checks for Ctrl+G shortcut if (event.ctrlKey && event.key === "g") { // Needs type cast as Object.keys doesn't preserve type @@ -32,14 +35,14 @@ const cycleBlocksShortcut = (event: KeyboardEvent, editor: BlockNoteEditor) => { }; export default function App() { - const editor: BlockNoteEditor = useBlockNote({ - // Adds event handler on key down when the editor is ready - // TODO: useful? - // onEditorReady: (editor) => - // editor.domElement.addEventListener("keydown", (event) => - // cycleBlocksShortcut(event, editor) - // ), - }); + const editor = useBlockNote({}); - return ; + const onKeyDown = (event: React.KeyboardEvent) => { + cycleBlocksShortcut(event, editor); + }; + const r = (el: any) => { + console.log("EL", el); + }; + + return ; } diff --git a/examples/02-ui-components/formatting-toolbar-buttons/App.tsx b/examples/02-ui-components/formatting-toolbar-buttons/App.tsx index 380923af85..8e48bbf2d1 100644 --- a/examples/02-ui-components/formatting-toolbar-buttons/App.tsx +++ b/examples/02-ui-components/formatting-toolbar-buttons/App.tsx @@ -1,4 +1,3 @@ -import { BlockNoteEditor } from "@blocknote/core"; import { BlockNoteView, FormattingToolbarPositioner, @@ -12,7 +11,7 @@ import { CustomFormattingToolbar } from "./CustomFormattingToolbar"; export default function App() { // Creates a new editor instance. - const editor: BlockNoteEditor = useBlockNote(); + const editor = useBlockNote(); // Renders the editor instance. return ( diff --git a/examples/02-ui-components/side-menu-buttons/App.tsx b/examples/02-ui-components/side-menu-buttons/App.tsx index 76c1d6f459..e7789d6a55 100644 --- a/examples/02-ui-components/side-menu-buttons/App.tsx +++ b/examples/02-ui-components/side-menu-buttons/App.tsx @@ -16,7 +16,7 @@ export default function App() { // Renders the editor instance. return ( - + diff --git a/examples/02-ui-components/side-menu-drag-handle-items/App.tsx b/examples/02-ui-components/side-menu-drag-handle-items/App.tsx index dd675ef056..ef6009df4d 100644 --- a/examples/02-ui-components/side-menu-drag-handle-items/App.tsx +++ b/examples/02-ui-components/side-menu-drag-handle-items/App.tsx @@ -17,7 +17,7 @@ export default function App() { // Renders the editor instance. return ( - + diff --git a/examples/02-ui-components/ui-elements-remove/App.tsx b/examples/02-ui-components/ui-elements-remove/App.tsx index b8f8c4ee9b..6c8ff5de49 100644 --- a/examples/02-ui-components/ui-elements-remove/App.tsx +++ b/examples/02-ui-components/ui-elements-remove/App.tsx @@ -14,7 +14,7 @@ export default function App() { // Renders the editor instance. return ( - + diff --git a/examples/02-ui-components/ui-elements-replace/App.tsx b/examples/02-ui-components/ui-elements-replace/App.tsx index 3c61df749e..82dc3d4845 100644 --- a/examples/02-ui-components/ui-elements-replace/App.tsx +++ b/examples/02-ui-components/ui-elements-replace/App.tsx @@ -15,7 +15,7 @@ export default function App() { // Renders the editor instance. return ( - + diff --git a/examples/04-theming/changing-font/App.tsx b/examples/04-theming/changing-font/App.tsx index c8931f9a58..249f964521 100644 --- a/examples/04-theming/changing-font/App.tsx +++ b/examples/04-theming/changing-font/App.tsx @@ -1,10 +1,9 @@ -import { BlockNoteEditor } from "@blocknote/core"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; export default function App() { // Creates a new editor instance. - const editor: BlockNoteEditor = useBlockNote(); + const editor = useBlockNote(); // Renders the editor instance using a React component. return ; diff --git a/examples/08-interoperability/converting-blocks-to-html/App.tsx b/examples/08-interoperability/converting-blocks-to-html/App.tsx index 1f6af7cf8a..b5bc18ca40 100644 --- a/examples/08-interoperability/converting-blocks-to-html/App.tsx +++ b/examples/08-interoperability/converting-blocks-to-html/App.tsx @@ -21,7 +21,7 @@ export default function App() { // Renders the editor instance, and its contents as HTML below. return (
    - +
    {html}
    ); diff --git a/packages/react/src/editor/BlockNoteView.tsx b/packages/react/src/editor/BlockNoteView.tsx index c65cfcdea5..451a7749e7 100644 --- a/packages/react/src/editor/BlockNoteView.tsx +++ b/packages/react/src/editor/BlockNoteView.tsx @@ -7,9 +7,10 @@ import { } from "@blocknote/core"; import { MantineProvider } from "@mantine/core"; -import { +import React, { HTMLAttributes, ReactNode, + Ref, useCallback, useEffect, useMemo, @@ -24,6 +25,7 @@ import { SlashMenuPositioner } from "../components/SlashMenu/SlashMenuPositioner import { TableHandlesPositioner } from "../components/TableHandles/TableHandlePositioner"; import { useEditorChange } from "../hooks/useEditorChange"; import { useEditorSelectionChange } from "../hooks/useEditorSelectionChange"; +import { mergeRefs } from "../util/mergeRefs"; import { BlockNoteContext, useBlockNoteContext } from "./BlockNoteContext"; import { Theme, @@ -42,13 +44,14 @@ const emptyFn = (_editor: any) => { // noop }; -export function BlockNoteView< +function BlockNoteViewComponent< BSchema extends BlockSchema, ISchema extends InlineContentSchema, SSchema extends StyleSchema >( props: { editor: BlockNoteEditor; + theme?: | "light" | "dark" @@ -74,10 +77,13 @@ export function BlockNoteView< onChange?: (editor: BlockNoteEditor) => void; children?: ReactNode; + + ref?: Ref | undefined; // only here to get types working with the generics. Regular form doesn't work } & Omit< HTMLAttributes, "onChange" | "onSelectionChange" | "children" - > + >, + ref: React.Ref ) { const { editor, @@ -102,8 +108,6 @@ export function BlockNoteView< const containerRef = useCallback( (node: HTMLDivElement | null) => { - editor._tiptapEditor.mount(node); // maybe cleaner to use "mergeRefs" - if (!node) { // todo: clean variables? return; @@ -140,7 +144,7 @@ export function BlockNoteView< setEditorColorScheme(defaultColorScheme === "dark" ? "dark" : "light"); }, - [defaultColorScheme, theme, editor._tiptapEditor] + [defaultColorScheme, theme] ); useEditorChange(onChange || emptyFn, editor); @@ -178,6 +182,10 @@ export function BlockNoteView< }; }, [existingContext, editor]); + const refs = useMemo(() => { + return mergeRefs([containerRef, editor._tiptapEditor.mount, ref]); + }, [containerRef, editor._tiptapEditor.mount, ref]); + return ( // `cssVariablesSelector` scopes Mantine CSS variables to only the editor, // as proposed here: https://github.com/orgs/mantinedev/discussions/5685 @@ -188,7 +196,7 @@ export function BlockNoteView< className={mergeCSSClasses("bn-container", className || "")} data-color-scheme={editorColorScheme} {...rest} - ref={containerRef}> + ref={refs}> {renderChildren}
    @@ -196,3 +204,7 @@ export function BlockNoteView< ); } + +export const BlockNoteView = React.forwardRef( + BlockNoteViewComponent +) as typeof BlockNoteViewComponent; // need hack to get types working with generics diff --git a/packages/react/src/util/mergeRefs.ts b/packages/react/src/util/mergeRefs.ts new file mode 100644 index 0000000000..969732f585 --- /dev/null +++ b/packages/react/src/util/mergeRefs.ts @@ -0,0 +1,14 @@ +// https://github.com/gregberge/react-merge-refs/blob/main/src/index.tsx +export function mergeRefs( + refs: Array | React.LegacyRef | undefined | null> +): React.RefCallback { + return (value) => { + refs.forEach((ref) => { + if (typeof ref === "function") { + ref(value); + } else if (ref != null) { + (ref as React.MutableRefObject).current = value; + } + }); + }; +} From 33fec47aa6ffecb75f552bb8011dd78a8821341f Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 13 Feb 2024 06:02:55 +0100 Subject: [PATCH 12/17] better errors --- packages/core/src/editor/BlockNoteEditor.ts | 6 +++ .../core/src/editor/BlockNoteTipTapEditor.ts | 47 ++++++++++++------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 95f2bc7323..e17f723545 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -252,6 +252,12 @@ export class BlockNoteEditor< ); } + if (anyOpts.onEditorReady) { + throw new Error( + "onEditorReady is deprecated. Editor is immediately ready for use after creation." + ); + } + if (anyOpts.editable) { throw new Error( "editable initialization option is deprecated, use , or alternatively editor.isEditable = true/false" diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index e9dd7675f2..a8c08e7cf7 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -66,24 +66,35 @@ export class BlockNoteTipTapEditor extends TiptapEditor { return cache; }; - const pmNodes = options?.content.map((b) => - blockToNode(b, this.schema, styleSchema).toJSON() - ); - - const doc = createDocument( - { - type: "doc", - content: [ - { - type: "blockGroup", - content: pmNodes, - }, - ], - }, - this.schema, - this.options.parseOptions - ); - console.log("create state"); + let doc: Node; + + try { + const pmNodes = options?.content.map((b) => + blockToNode(b, this.schema, styleSchema).toJSON() + ); + doc = createDocument( + { + type: "doc", + content: [ + { + type: "blockGroup", + content: pmNodes, + }, + ], + }, + this.schema, + this.options.parseOptions + ); + } catch (e) { + console.error( + "Error creating document from blocks passed as `initialContent`. Caused by exception: ", + e + ); + throw new Error( + "Error creating document from blocks passed as `initialContent`:\n" + + +JSON.stringify(options.content) + ); + } // Create state immediately, so that it's available independently from the View, // the way Prosemirror "intends it to be". This also makes sure that we can access From 3a14157699760a3ecd88aaf7e9c9aee7d24c06f5 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 13 Feb 2024 06:16:09 +0100 Subject: [PATCH 13/17] add default block / partialblock types --- .../blockManipulation.test.ts | 3 ++- .../blockManipulation/blockManipulation.ts | 5 ++--- .../exporters/html/externalHTMLExporter.ts | 8 ++------ .../api/exporters/html/htmlConversion.test.ts | 3 ++- .../exporters/html/internalHTMLSerializer.ts | 8 ++------ .../markdown/markdownExporter.test.ts | 5 +++-- .../exporters/markdown/markdownExporter.ts | 8 ++------ .../nodeConversions/nodeConversions.test.ts | 8 ++++++-- .../api/nodeConversions/nodeConversions.ts | 3 +-- .../core/src/api/parsers/html/parseHTML.ts | 8 ++------ .../src/api/parsers/markdown/parseMarkdown.ts | 8 ++------ packages/core/src/api/testUtil/index.ts | 3 ++- .../src/api/testUtil/partialBlockTestUtil.ts | 8 ++------ .../core/src/blocks/defaultBlockHelpers.ts | 8 ++------ packages/core/src/blocks/defaultBlocks.ts | 17 ++++++++++++++++ packages/core/src/editor/BlockNoteEditor.ts | 4 ++-- .../core/src/editor/BlockNoteTipTapEditor.ts | 3 ++- .../core/src/editor/cursorPositionTypes.ts | 8 ++------ packages/core/src/editor/selectionTypes.ts | 8 ++------ .../src/extensions/SideMenu/SideMenuPlugin.ts | 8 ++------ .../SlashMenu/defaultSlashMenuItems.ts | 8 +++++--- .../TableHandles/TableHandlesPlugin.ts | 10 ++++++---- packages/core/src/pm-nodes/BlockContainer.ts | 2 +- packages/core/src/schema/blocks/types.ts | 20 +++++++++---------- 24 files changed, 81 insertions(+), 93 deletions(-) diff --git a/packages/core/src/api/blockManipulation/blockManipulation.test.ts b/packages/core/src/api/blockManipulation/blockManipulation.test.ts index f7d0521e2a..df471d8092 100644 --- a/packages/core/src/api/blockManipulation/blockManipulation.test.ts +++ b/packages/core/src/api/blockManipulation/blockManipulation.test.ts @@ -1,11 +1,12 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { + Block, DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, + PartialBlock, } from "../../blocks/defaultBlocks"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor"; -import { Block, PartialBlock } from "../../schema/blocks/types"; let editor: BlockNoteEditor; diff --git a/packages/core/src/api/blockManipulation/blockManipulation.ts b/packages/core/src/api/blockManipulation/blockManipulation.ts index 892a8be8bb..e1e44e834b 100644 --- a/packages/core/src/api/blockManipulation/blockManipulation.ts +++ b/packages/core/src/api/blockManipulation/blockManipulation.ts @@ -1,17 +1,16 @@ import { Node } from "prosemirror-model"; +import { Transaction } from "prosemirror-state"; +import { Block, PartialBlock } from "../../blocks/defaultBlocks"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; import { - Block, BlockIdentifier, BlockSchema, InlineContentSchema, - PartialBlock, StyleSchema, } from "../../schema"; import { blockToNode, nodeToBlock } from "../nodeConversions/nodeConversions"; import { getNodeById } from "../nodeUtil"; -import { Transaction } from "prosemirror-state"; export function insertBlocks< BSchema extends BlockSchema, diff --git a/packages/core/src/api/exporters/html/externalHTMLExporter.ts b/packages/core/src/api/exporters/html/externalHTMLExporter.ts index 43b591b610..932a6c086e 100644 --- a/packages/core/src/api/exporters/html/externalHTMLExporter.ts +++ b/packages/core/src/api/exporters/html/externalHTMLExporter.ts @@ -3,13 +3,9 @@ import rehypeParse from "rehype-parse"; import rehypeStringify from "rehype-stringify"; import { unified } from "unified"; +import { PartialBlock } from "../../../blocks/defaultBlocks"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor"; -import { - BlockSchema, - InlineContentSchema, - PartialBlock, - StyleSchema, -} from "../../../schema"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema"; import { blockToNode } from "../../nodeConversions/nodeConversions"; import { serializeNodeInner, diff --git a/packages/core/src/api/exporters/html/htmlConversion.test.ts b/packages/core/src/api/exporters/html/htmlConversion.test.ts index 6671037f19..bc02e76d02 100644 --- a/packages/core/src/api/exporters/html/htmlConversion.test.ts +++ b/packages/core/src/api/exporters/html/htmlConversion.test.ts @@ -2,7 +2,8 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor"; import { addIdsToBlocks, partialBlocksToBlocksForTesting } from "../../.."; -import { BlockSchema, PartialBlock } from "../../../schema/blocks/types"; +import { PartialBlock } from "../../../blocks/defaultBlocks"; +import { BlockSchema } from "../../../schema/blocks/types"; import { InlineContentSchema } from "../../../schema/inlineContent/types"; import { StyleSchema } from "../../../schema/styles/types"; import { customBlocksTestCases } from "../../testUtil/cases/customBlocks"; diff --git a/packages/core/src/api/exporters/html/internalHTMLSerializer.ts b/packages/core/src/api/exporters/html/internalHTMLSerializer.ts index a635819caa..9f2a55612d 100644 --- a/packages/core/src/api/exporters/html/internalHTMLSerializer.ts +++ b/packages/core/src/api/exporters/html/internalHTMLSerializer.ts @@ -1,11 +1,7 @@ import { DOMSerializer, Fragment, Node, Schema } from "prosemirror-model"; +import { PartialBlock } from "../../../blocks/defaultBlocks"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor"; -import { - BlockSchema, - InlineContentSchema, - PartialBlock, - StyleSchema, -} from "../../../schema"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema"; import { blockToNode } from "../../nodeConversions/nodeConversions"; import { serializeNodeInner, diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.test.ts b/packages/core/src/api/exporters/markdown/markdownExporter.test.ts index a4f391bc11..894f4663cd 100644 --- a/packages/core/src/api/exporters/markdown/markdownExporter.test.ts +++ b/packages/core/src/api/exporters/markdown/markdownExporter.test.ts @@ -1,15 +1,16 @@ import fs from "node:fs"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { PartialBlock } from "../../../blocks/defaultBlocks"; import { BlockNoteEditor } from "../../../editor/BlockNoteEditor"; -import { BlockSchema, PartialBlock } from "../../../schema/blocks/types"; +import { BlockSchema } from "../../../schema/blocks/types"; import { InlineContentSchema } from "../../../schema/inlineContent/types"; import { StyleSchema } from "../../../schema/styles/types"; -import { partialBlocksToBlocksForTesting } from "../../testUtil/partialBlockTestUtil"; import { customBlocksTestCases } from "../../testUtil/cases/customBlocks"; import { customInlineContentTestCases } from "../../testUtil/cases/customInlineContent"; import { customStylesTestCases } from "../../testUtil/cases/customStyles"; import { defaultSchemaTestCases } from "../../testUtil/cases/defaultSchema"; +import { partialBlocksToBlocksForTesting } from "../../testUtil/partialBlockTestUtil"; async function convertToMarkdownAndCompareSnapshots< B extends BlockSchema, diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.ts b/packages/core/src/api/exporters/markdown/markdownExporter.ts index 841ff6381e..24990edee3 100644 --- a/packages/core/src/api/exporters/markdown/markdownExporter.ts +++ b/packages/core/src/api/exporters/markdown/markdownExporter.ts @@ -4,13 +4,9 @@ import rehypeRemark from "rehype-remark"; import remarkGfm from "remark-gfm"; import remarkStringify from "remark-stringify"; import { unified } from "unified"; +import { Block } from "../../../blocks/defaultBlocks"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor"; -import { - Block, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../../../schema"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema"; import { createExternalHTMLExporter } from "../html/externalHTMLExporter"; import { removeUnderlines } from "./removeUnderlinesRehypePlugin"; diff --git a/packages/core/src/api/nodeConversions/nodeConversions.test.ts b/packages/core/src/api/nodeConversions/nodeConversions.test.ts index 68d583dd55..4bdfded9af 100644 --- a/packages/core/src/api/nodeConversions/nodeConversions.test.ts +++ b/packages/core/src/api/nodeConversions/nodeConversions.test.ts @@ -1,12 +1,16 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor"; -import { PartialBlock } from "../../schema/blocks/types"; + +import { PartialBlock } from "../../blocks/defaultBlocks"; import { customInlineContentTestCases } from "../testUtil/cases/customInlineContent"; import { customStylesTestCases } from "../testUtil/cases/customStyles"; import { defaultSchemaTestCases } from "../testUtil/cases/defaultSchema"; +import { + addIdsToBlock, + partialBlockToBlockForTesting, +} from "../testUtil/partialBlockTestUtil"; import { blockToNode, nodeToBlock } from "./nodeConversions"; -import { addIdsToBlock, partialBlockToBlockForTesting } from "../testUtil/partialBlockTestUtil"; function validateConversion( block: PartialBlock, diff --git a/packages/core/src/api/nodeConversions/nodeConversions.ts b/packages/core/src/api/nodeConversions/nodeConversions.ts index 3f82377c5a..fba56c71ca 100644 --- a/packages/core/src/api/nodeConversions/nodeConversions.ts +++ b/packages/core/src/api/nodeConversions/nodeConversions.ts @@ -2,14 +2,12 @@ import { Mark, Node, Schema } from "@tiptap/pm/model"; import UniqueID from "../../extensions/UniqueID/UniqueID"; import type { - Block, BlockSchema, CustomInlineContentConfig, CustomInlineContentFromConfig, InlineContent, InlineContentFromConfig, InlineContentSchema, - PartialBlock, PartialCustomInlineContentFromConfig, PartialInlineContent, PartialLink, @@ -21,6 +19,7 @@ import type { } from "../../schema"; import { getBlockInfo } from "../getBlockInfoFromPos"; +import type { Block, PartialBlock } from "../../blocks/defaultBlocks"; import { isLinkInlineContent, isPartialLinkInlineContent, diff --git a/packages/core/src/api/parsers/html/parseHTML.ts b/packages/core/src/api/parsers/html/parseHTML.ts index dad025f9dc..97f743c2cd 100644 --- a/packages/core/src/api/parsers/html/parseHTML.ts +++ b/packages/core/src/api/parsers/html/parseHTML.ts @@ -1,11 +1,7 @@ import { DOMParser, Schema } from "prosemirror-model"; -import { - Block, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../../../schema"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema"; +import { Block } from "../../../blocks/defaultBlocks"; import { nodeToBlock } from "../../nodeConversions/nodeConversions"; import { nestedListsToBlockNoteStructure } from "./util/nestedLists"; export async function HTMLToBlocks< diff --git a/packages/core/src/api/parsers/markdown/parseMarkdown.ts b/packages/core/src/api/parsers/markdown/parseMarkdown.ts index 6e4fab74e2..c3418fc5e3 100644 --- a/packages/core/src/api/parsers/markdown/parseMarkdown.ts +++ b/packages/core/src/api/parsers/markdown/parseMarkdown.ts @@ -4,12 +4,8 @@ import remarkGfm from "remark-gfm"; import remarkParse from "remark-parse"; import remarkRehype, { defaultHandlers } from "remark-rehype"; import { unified } from "unified"; -import { - Block, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../../../schema"; +import { Block } from "../../../blocks/defaultBlocks"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema"; import { HTMLToBlocks } from "../html/parseHTML"; // modified version of https://github.com/syntax-tree/mdast-util-to-hast/blob/main/lib/handlers/code.js diff --git a/packages/core/src/api/testUtil/index.ts b/packages/core/src/api/testUtil/index.ts index d3269f3e86..d52a871781 100644 --- a/packages/core/src/api/testUtil/index.ts +++ b/packages/core/src/api/testUtil/index.ts @@ -1,5 +1,6 @@ +import { PartialBlock } from "../../blocks/defaultBlocks"; import { BlockNoteEditor } from "../../editor/BlockNoteEditor"; -import { BlockSchema, PartialBlock } from "../../schema/blocks/types"; +import { BlockSchema } from "../../schema/blocks/types"; import { InlineContentSchema } from "../../schema/inlineContent/types"; import { StyleSchema } from "../../schema/styles/types"; diff --git a/packages/core/src/api/testUtil/partialBlockTestUtil.ts b/packages/core/src/api/testUtil/partialBlockTestUtil.ts index 6d91a204f3..3be300c7bd 100644 --- a/packages/core/src/api/testUtil/partialBlockTestUtil.ts +++ b/packages/core/src/api/testUtil/partialBlockTestUtil.ts @@ -1,10 +1,6 @@ +import { Block, PartialBlock } from "../../blocks/defaultBlocks"; import UniqueID from "../../extensions/UniqueID/UniqueID"; -import { - Block, - BlockSchema, - PartialBlock, - TableContent, -} from "../../schema/blocks/types"; +import { BlockSchema, TableContent } from "../../schema/blocks/types"; import { InlineContent, InlineContentSchema, diff --git a/packages/core/src/blocks/defaultBlockHelpers.ts b/packages/core/src/blocks/defaultBlockHelpers.ts index 710ca57aa0..7769e6a492 100644 --- a/packages/core/src/blocks/defaultBlockHelpers.ts +++ b/packages/core/src/blocks/defaultBlockHelpers.ts @@ -1,12 +1,8 @@ import { blockToNode } from "../api/nodeConversions/nodeConversions"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor"; -import type { - Block, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../schema"; +import type { BlockSchema, InlineContentSchema, StyleSchema } from "../schema"; import { mergeCSSClasses } from "../util/browser"; +import { Block } from "./defaultBlocks"; // Function that creates a ProseMirror `DOMOutputSpec` for a default block. // Since all default blocks have the same structure (`blockContent` div with a diff --git a/packages/core/src/blocks/defaultBlocks.ts b/packages/core/src/blocks/defaultBlocks.ts index 36ebc50f7d..df16a70fcf 100644 --- a/packages/core/src/blocks/defaultBlocks.ts +++ b/packages/core/src/blocks/defaultBlocks.ts @@ -6,8 +6,13 @@ import Underline from "@tiptap/extension-underline"; import { BackgroundColor } from "../extensions/BackgroundColor/BackgroundColorMark"; import { TextColor } from "../extensions/TextColor/TextColorMark"; import { + BlockNoDefaults, + BlockSchema, BlockSpecs, + InlineContentSchema, InlineContentSpecs, + PartialBlockNoDefaults, + StyleSchema, StyleSpecs, createStyleSpecFromTipTapMark, getBlockSchemaFromSpecs, @@ -58,3 +63,15 @@ export const defaultInlineContentSchema = getInlineContentSchemaFromSpecs( ); export type DefaultInlineContentSchema = typeof defaultInlineContentSchema; + +export type PartialBlock< + BSchema extends BlockSchema = DefaultBlockSchema, + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +> = PartialBlockNoDefaults; + +export type Block< + BSchema extends BlockSchema = DefaultBlockSchema, + I extends InlineContentSchema = DefaultInlineContentSchema, + S extends StyleSchema = DefaultStyleSchema +> = BlockNoDefaults; diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index e17f723545..70519c1ff3 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -16,9 +16,11 @@ import { getNodeById } from "../api/nodeUtil"; import { HTMLToBlocks } from "../api/parsers/html/parseHTML"; import { markdownToBlocks } from "../api/parsers/markdown/parseMarkdown"; import { + Block, DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, + PartialBlock, defaultBlockSchema, defaultBlockSpecs, defaultInlineContentSpecs, @@ -34,7 +36,6 @@ import { getDefaultSlashMenuItems } from "../extensions/SlashMenu/defaultSlashMe import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin"; import { UniqueID } from "../extensions/UniqueID/UniqueID"; import { - Block, BlockIdentifier, BlockNoteDOMAttributes, BlockSchema, @@ -44,7 +45,6 @@ import { InlineContentSchema, InlineContentSchemaFromSpecs, InlineContentSpecs, - PartialBlock, StyleSchema, StyleSchemaFromSpecs, StyleSpecs, diff --git a/packages/core/src/editor/BlockNoteTipTapEditor.ts b/packages/core/src/editor/BlockNoteTipTapEditor.ts index a8c08e7cf7..6bd1b35998 100644 --- a/packages/core/src/editor/BlockNoteTipTapEditor.ts +++ b/packages/core/src/editor/BlockNoteTipTapEditor.ts @@ -6,7 +6,8 @@ import { EditorView } from "@tiptap/pm/view"; import { EditorState } from "prosemirror-state"; import { blockToNode } from "../api/nodeConversions/nodeConversions"; -import { PartialBlock, StyleSchema } from "../schema"; +import { PartialBlock } from "../blocks/defaultBlocks"; +import { StyleSchema } from "../schema"; export type BlockNoteTipTapEditorOptions = Partial< Omit diff --git a/packages/core/src/editor/cursorPositionTypes.ts b/packages/core/src/editor/cursorPositionTypes.ts index b7fa932475..7f82cce855 100644 --- a/packages/core/src/editor/cursorPositionTypes.ts +++ b/packages/core/src/editor/cursorPositionTypes.ts @@ -1,9 +1,5 @@ -import { - Block, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../schema"; +import { Block } from "../blocks/defaultBlocks"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../schema"; export type TextCursorPosition< BSchema extends BlockSchema, diff --git a/packages/core/src/editor/selectionTypes.ts b/packages/core/src/editor/selectionTypes.ts index aef65b5f08..a96b26945f 100644 --- a/packages/core/src/editor/selectionTypes.ts +++ b/packages/core/src/editor/selectionTypes.ts @@ -1,9 +1,5 @@ -import { - Block, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../schema"; +import { Block } from "../blocks/defaultBlocks"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../schema"; export type Selection< BSchema extends BlockSchema, diff --git a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts index 2eb687e7cb..76815c5ab0 100644 --- a/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts +++ b/packages/core/src/extensions/SideMenu/SideMenuPlugin.ts @@ -6,14 +6,10 @@ import { createExternalHTMLExporter } from "../../api/exporters/html/externalHTM import { createInternalHTMLSerializer } from "../../api/exporters/html/internalHTMLSerializer"; import { cleanHTMLToMarkdown } from "../../api/exporters/markdown/markdownExporter"; import { getBlockInfoFromPos } from "../../api/getBlockInfoFromPos"; +import { Block } from "../../blocks/defaultBlocks"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; import { BaseUiElementState } from "../../extensions-shared/BaseUiElementTypes"; -import { - Block, - BlockSchema, - InlineContentSchema, - StyleSchema, -} from "../../schema"; +import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema"; import { EventEmitter } from "../../util/EventEmitter"; import { slashMenuPluginKey } from "../SlashMenu/SlashMenuPlugin"; import { MultipleNodeSelection } from "./MultipleNodeSelection"; diff --git a/packages/core/src/extensions/SlashMenu/defaultSlashMenuItems.ts b/packages/core/src/extensions/SlashMenu/defaultSlashMenuItems.ts index b6b3aa0115..bdc88415c1 100644 --- a/packages/core/src/extensions/SlashMenu/defaultSlashMenuItems.ts +++ b/packages/core/src/extensions/SlashMenu/defaultSlashMenuItems.ts @@ -1,10 +1,12 @@ -import { defaultBlockSchema } from "../../blocks/defaultBlocks"; -import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; import { Block, + PartialBlock, + defaultBlockSchema, +} from "../../blocks/defaultBlocks"; +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { BlockSchema, InlineContentSchema, - PartialBlock, StyleSchema, isStyledTextInlineContent, } from "../../schema"; diff --git a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts index 520ae5718e..d4532e98d3 100644 --- a/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts +++ b/packages/core/src/extensions/TableHandles/TableHandlesPlugin.ts @@ -1,18 +1,20 @@ import { Plugin, PluginKey, PluginView } from "prosemirror-state"; import { Decoration, DecorationSet, EditorView } from "prosemirror-view"; -import { EventEmitter } from "../../util/EventEmitter"; import { nodeToBlock } from "../../api/nodeConversions/nodeConversions"; -import { DefaultBlockSchema } from "../../blocks/defaultBlocks"; -import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; import { Block, + DefaultBlockSchema, + PartialBlock, +} from "../../blocks/defaultBlocks"; +import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; +import { BlockFromConfigNoChildren, BlockSchemaWithBlock, InlineContentSchema, - PartialBlock, SpecificBlock, StyleSchema, } from "../../schema"; +import { EventEmitter } from "../../util/EventEmitter"; import { getDraggableBlockFromCoords } from "../SideMenu/SideMenuPlugin"; let dragImageElement: HTMLElement | undefined; diff --git a/packages/core/src/pm-nodes/BlockContainer.ts b/packages/core/src/pm-nodes/BlockContainer.ts index 77e3e3dab8..b4467ffd01 100644 --- a/packages/core/src/pm-nodes/BlockContainer.ts +++ b/packages/core/src/pm-nodes/BlockContainer.ts @@ -8,6 +8,7 @@ import { inlineContentToNodes, tableContentToNodes, } from "../api/nodeConversions/nodeConversions"; +import { PartialBlock } from "../blocks/defaultBlocks"; import type { BlockNoteEditor } from "../editor/BlockNoteEditor"; import { NonEditableBlockPlugin } from "../extensions/NonEditableBlocks/NonEditableBlockPlugin"; import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin"; @@ -15,7 +16,6 @@ import { BlockNoteDOMAttributes, BlockSchema, InlineContentSchema, - PartialBlock, StyleSchema, } from "../schema"; import { mergeCSSClasses } from "../util/browser"; diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 65b231c3a9..6872e48e39 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -41,7 +41,7 @@ export type TiptapBlockImplementation< node: Node; toInternalHTML: ( block: BlockFromConfigNoChildren & { - children: Block[]; + children: BlockNoDefaults[]; }, editor: BlockNoteEditor ) => { @@ -50,7 +50,7 @@ export type TiptapBlockImplementation< }; toExternalHTML: ( block: BlockFromConfigNoChildren & { - children: Block[]; + children: BlockNoDefaults[]; }, editor: BlockNoteEditor ) => { @@ -142,7 +142,7 @@ export type BlockFromConfig< I extends InlineContentSchema, S extends StyleSchema > = BlockFromConfigNoChildren & { - children: Block[]; + children: BlockNoDefaults[]; }; // Converts each block spec into a Block object without children. We later merge @@ -158,12 +158,12 @@ type BlocksWithoutChildren< // Converts each block spec into a Block object without children, merges them // into a union type, and adds a children property -export type Block< +export type BlockNoDefaults< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema > = BlocksWithoutChildren[keyof BSchema] & { - children: Block[]; + children: BlockNoDefaults[]; }; export type SpecificBlock< @@ -172,7 +172,7 @@ export type SpecificBlock< I extends InlineContentSchema, S extends StyleSchema > = BlocksWithoutChildren[BType] & { - children: Block[]; + children: BlockNoDefaults[]; }; /** CODE FOR PARTIAL BLOCKS, analogous to above @@ -219,7 +219,7 @@ type PartialBlocksWithoutChildren< >; }; -export type PartialBlock< +export type PartialBlockNoDefaults< BSchema extends BlockSchema, I extends InlineContentSchema, S extends StyleSchema @@ -229,7 +229,7 @@ export type PartialBlock< S >[keyof PartialBlocksWithoutChildren] & Partial<{ - children: PartialBlock[]; + children: PartialBlockNoDefaults[]; }>; export type SpecificPartialBlock< @@ -238,7 +238,7 @@ export type SpecificPartialBlock< BType extends keyof BSchema, S extends StyleSchema > = PartialBlocksWithoutChildren[BType] & { - children?: Block[]; + children?: BlockNoDefaults[]; }; export type PartialBlockFromConfig< @@ -246,7 +246,7 @@ export type PartialBlockFromConfig< I extends InlineContentSchema, S extends StyleSchema > = PartialBlockFromConfigNoChildren & { - children?: Block[]; + children?: BlockNoDefaults[]; }; export type BlockIdentifier = { id: string } | string; From 00ed85b8ed32c154db1f011858568733913695b9 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 13 Feb 2024 07:11:01 +0100 Subject: [PATCH 14/17] wip: examples --- examples/01-basic/01-minimal/.bnexample.json | 4 +- examples/01-basic/01-minimal/App.tsx | 5 +- examples/01-basic/01-minimal/README.md | 2 + .../01-basic/02-block-objects/.bnexample.json | 4 ++ .../App.tsx | 0 examples/01-basic/02-block-objects/README.md | 5 ++ .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../styles.css | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../03-block-manipulation/.bnexample.json | 4 ++ .../01-basic/03-block-manipulation/App.tsx | 59 ++++++++++++++++++ .../01-basic/03-block-manipulation/README.md | 10 ++++ .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../04-saving-loading/.bnexample.json | 4 ++ .../App.tsx | 11 ++-- examples/01-basic/04-saving-loading/README.md | 12 ++++ .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../05-file-uploading/.bnexample.json | 4 ++ examples/01-basic/05-file-uploading/App.tsx | 31 ++++++++++ examples/01-basic/05-file-uploading/README.md | 9 +++ .../01-basic/05-file-uploading/index.html | 14 +++++ .../main.tsx | 0 .../01-basic/05-file-uploading/package.json | 34 +++++++++++ .../tsconfig.json | 0 .../vite.config.ts | 0 .../06-keyboard-shortcuts/.bnexample.json | 4 ++ .../App.tsx | 5 +- .../README.md | 4 +- .../index.html | 0 .../06-keyboard-shortcuts}/main.tsx | 0 .../package.json | 0 .../06-keyboard-shortcuts}/tsconfig.json | 0 .../06-keyboard-shortcuts}/vite.config.ts | 0 .../block-manipulation/.bnexample.json | 6 -- examples/01-basic/block-manipulation/App.tsx | 53 ---------------- .../01-basic/block-manipulation/README.md | 8 --- examples/01-basic/block-objects/README.md | 1 - .../keyboard-shortcuts/.bnexample.json | 6 -- .../01-basic/saving-loading/.bnexample.json | 6 -- examples/01-basic/saving-loading/README.md | 10 ---- .../selection-blocks/README.md | 4 +- .../text-cursor-block/README.md | 6 +- examples/07-collaboration/partykit/App.tsx | 2 - .../.bnexample.json | 0 .../App.tsx | 0 .../README.md | 0 .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../App.tsx | 0 .../README.md | 0 .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../App.tsx | 0 .../README.md | 0 .../index.html | 0 .../main.tsx | 0 .../package.json | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../.bnexample.json | 0 .../App.tsx | 0 .../README.md | 0 .../index.html | 0 .../04-converting-blocks-from-md/main.tsx | 11 ++++ .../package.json | 0 .../tsconfig.json | 36 +++++++++++ .../vite.config.ts | 32 ++++++++++ .../converting-blocks-to-md/.bnexample.json | 6 -- .../uploadToTmpFilesDotOrg_DEV_ONLY.ts | 5 ++ packages/core/src/editor/BlockNoteEditor.ts | 7 +++ playground/src/examples.gen.tsx | 60 ++++++++++--------- 90 files changed, 340 insertions(+), 144 deletions(-) create mode 100644 examples/01-basic/02-block-objects/.bnexample.json rename examples/01-basic/{block-objects => 02-block-objects}/App.tsx (100%) create mode 100644 examples/01-basic/02-block-objects/README.md rename examples/01-basic/{block-objects => 02-block-objects}/index.html (100%) rename examples/01-basic/{block-manipulation => 02-block-objects}/main.tsx (100%) rename examples/01-basic/{block-objects => 02-block-objects}/package.json (100%) rename examples/01-basic/{block-objects => 02-block-objects}/styles.css (100%) rename examples/01-basic/{block-manipulation => 02-block-objects}/tsconfig.json (100%) rename examples/01-basic/{block-manipulation => 02-block-objects}/vite.config.ts (100%) create mode 100644 examples/01-basic/03-block-manipulation/.bnexample.json create mode 100644 examples/01-basic/03-block-manipulation/App.tsx create mode 100644 examples/01-basic/03-block-manipulation/README.md rename examples/01-basic/{block-manipulation => 03-block-manipulation}/index.html (100%) rename examples/01-basic/{block-objects => 03-block-manipulation}/main.tsx (100%) rename examples/01-basic/{block-manipulation => 03-block-manipulation}/package.json (100%) rename examples/01-basic/{block-objects => 03-block-manipulation}/tsconfig.json (100%) rename examples/01-basic/{block-objects => 03-block-manipulation}/vite.config.ts (100%) create mode 100644 examples/01-basic/04-saving-loading/.bnexample.json rename examples/01-basic/{saving-loading => 04-saving-loading}/App.tsx (84%) create mode 100644 examples/01-basic/04-saving-loading/README.md rename examples/01-basic/{saving-loading => 04-saving-loading}/index.html (100%) rename examples/01-basic/{keyboard-shortcuts => 04-saving-loading}/main.tsx (100%) rename examples/01-basic/{saving-loading => 04-saving-loading}/package.json (100%) rename examples/01-basic/{keyboard-shortcuts => 04-saving-loading}/tsconfig.json (100%) rename examples/01-basic/{keyboard-shortcuts => 04-saving-loading}/vite.config.ts (100%) create mode 100644 examples/01-basic/05-file-uploading/.bnexample.json create mode 100644 examples/01-basic/05-file-uploading/App.tsx create mode 100644 examples/01-basic/05-file-uploading/README.md create mode 100644 examples/01-basic/05-file-uploading/index.html rename examples/01-basic/{saving-loading => 05-file-uploading}/main.tsx (100%) create mode 100644 examples/01-basic/05-file-uploading/package.json rename examples/01-basic/{saving-loading => 05-file-uploading}/tsconfig.json (100%) rename examples/01-basic/{saving-loading => 05-file-uploading}/vite.config.ts (100%) create mode 100644 examples/01-basic/06-keyboard-shortcuts/.bnexample.json rename examples/01-basic/{keyboard-shortcuts => 06-keyboard-shortcuts}/App.tsx (89%) rename examples/01-basic/{keyboard-shortcuts => 06-keyboard-shortcuts}/README.md (72%) rename examples/01-basic/{keyboard-shortcuts => 06-keyboard-shortcuts}/index.html (100%) rename examples/{08-interoperability/converting-blocks-from-html => 01-basic/06-keyboard-shortcuts}/main.tsx (100%) rename examples/01-basic/{keyboard-shortcuts => 06-keyboard-shortcuts}/package.json (100%) rename examples/{08-interoperability/converting-blocks-from-html => 01-basic/06-keyboard-shortcuts}/tsconfig.json (100%) rename examples/{08-interoperability/converting-blocks-from-html => 01-basic/06-keyboard-shortcuts}/vite.config.ts (100%) delete mode 100644 examples/01-basic/block-manipulation/.bnexample.json delete mode 100644 examples/01-basic/block-manipulation/App.tsx delete mode 100644 examples/01-basic/block-manipulation/README.md delete mode 100644 examples/01-basic/block-objects/README.md delete mode 100644 examples/01-basic/keyboard-shortcuts/.bnexample.json delete mode 100644 examples/01-basic/saving-loading/.bnexample.json delete mode 100644 examples/01-basic/saving-loading/README.md rename examples/{01-basic/block-objects => 08-interoperability/01-converting-blocks-to-html}/.bnexample.json (100%) rename examples/08-interoperability/{converting-blocks-to-html => 01-converting-blocks-to-html}/App.tsx (100%) rename examples/08-interoperability/{converting-blocks-to-html => 01-converting-blocks-to-html}/README.md (100%) rename examples/08-interoperability/{converting-blocks-to-html => 01-converting-blocks-to-html}/index.html (100%) rename examples/08-interoperability/{converting-blocks-from-md => 01-converting-blocks-to-html}/main.tsx (100%) rename examples/08-interoperability/{converting-blocks-to-html => 01-converting-blocks-to-html}/package.json (100%) rename examples/08-interoperability/{converting-blocks-from-md => 01-converting-blocks-to-html}/tsconfig.json (100%) rename examples/08-interoperability/{converting-blocks-from-md => 01-converting-blocks-to-html}/vite.config.ts (100%) rename examples/08-interoperability/{converting-blocks-from-html => 02-converting-blocks-from-html}/.bnexample.json (100%) rename examples/08-interoperability/{converting-blocks-from-html => 02-converting-blocks-from-html}/App.tsx (100%) rename examples/08-interoperability/{converting-blocks-from-html => 02-converting-blocks-from-html}/README.md (100%) rename examples/08-interoperability/{converting-blocks-from-html => 02-converting-blocks-from-html}/index.html (100%) rename examples/08-interoperability/{converting-blocks-to-html => 02-converting-blocks-from-html}/main.tsx (100%) rename examples/08-interoperability/{converting-blocks-from-html => 02-converting-blocks-from-html}/package.json (100%) rename examples/08-interoperability/{converting-blocks-to-html => 02-converting-blocks-from-html}/tsconfig.json (100%) rename examples/08-interoperability/{converting-blocks-to-html => 02-converting-blocks-from-html}/vite.config.ts (100%) rename examples/08-interoperability/{converting-blocks-from-md => 03-converting-blocks-to-md}/.bnexample.json (100%) rename examples/08-interoperability/{converting-blocks-to-md => 03-converting-blocks-to-md}/App.tsx (100%) rename examples/08-interoperability/{converting-blocks-to-md => 03-converting-blocks-to-md}/README.md (100%) rename examples/08-interoperability/{converting-blocks-to-md => 03-converting-blocks-to-md}/index.html (100%) rename examples/08-interoperability/{converting-blocks-to-md => 03-converting-blocks-to-md}/main.tsx (100%) rename examples/08-interoperability/{converting-blocks-to-md => 03-converting-blocks-to-md}/package.json (100%) rename examples/08-interoperability/{converting-blocks-to-md => 03-converting-blocks-to-md}/tsconfig.json (100%) rename examples/08-interoperability/{converting-blocks-to-md => 03-converting-blocks-to-md}/vite.config.ts (100%) rename examples/08-interoperability/{converting-blocks-to-html => 04-converting-blocks-from-md}/.bnexample.json (100%) rename examples/08-interoperability/{converting-blocks-from-md => 04-converting-blocks-from-md}/App.tsx (100%) rename examples/08-interoperability/{converting-blocks-from-md => 04-converting-blocks-from-md}/README.md (100%) rename examples/08-interoperability/{converting-blocks-from-md => 04-converting-blocks-from-md}/index.html (100%) create mode 100644 examples/08-interoperability/04-converting-blocks-from-md/main.tsx rename examples/08-interoperability/{converting-blocks-from-md => 04-converting-blocks-from-md}/package.json (100%) create mode 100644 examples/08-interoperability/04-converting-blocks-from-md/tsconfig.json create mode 100644 examples/08-interoperability/04-converting-blocks-from-md/vite.config.ts delete mode 100644 examples/08-interoperability/converting-blocks-to-md/.bnexample.json diff --git a/examples/01-basic/01-minimal/.bnexample.json b/examples/01-basic/01-minimal/.bnexample.json index 178fd44ce0..0993be7115 100644 --- a/examples/01-basic/01-minimal/.bnexample.json +++ b/examples/01-basic/01-minimal/.bnexample.json @@ -1,6 +1,4 @@ { "playground": true, - "docs": false, - "group": "Basic Examples", - "order": 1 + "docs": true } diff --git a/examples/01-basic/01-minimal/App.tsx b/examples/01-basic/01-minimal/App.tsx index a4ad0ac607..f5ac8d505e 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -1,12 +1,9 @@ -import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; export default function App() { // Creates a new editor instance. - const editor = useBlockNote({ - uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, - }); + const editor = useBlockNote({}); // Renders the editor instance using a React component. return ; diff --git a/examples/01-basic/01-minimal/README.md b/examples/01-basic/01-minimal/README.md index c1764b7e34..c8fcefae14 100644 --- a/examples/01-basic/01-minimal/README.md +++ b/examples/01-basic/01-minimal/README.md @@ -1 +1,3 @@ # Basic Editor Setup + +This example shows the minimal code required to setup your React rich text editor with BlockNote. diff --git a/examples/01-basic/02-block-objects/.bnexample.json b/examples/01-basic/02-block-objects/.bnexample.json new file mode 100644 index 0000000000..0993be7115 --- /dev/null +++ b/examples/01-basic/02-block-objects/.bnexample.json @@ -0,0 +1,4 @@ +{ + "playground": true, + "docs": true +} diff --git a/examples/01-basic/block-objects/App.tsx b/examples/01-basic/02-block-objects/App.tsx similarity index 100% rename from examples/01-basic/block-objects/App.tsx rename to examples/01-basic/02-block-objects/App.tsx diff --git a/examples/01-basic/02-block-objects/README.md b/examples/01-basic/02-block-objects/README.md new file mode 100644 index 0000000000..7b78c8e438 --- /dev/null +++ b/examples/01-basic/02-block-objects/README.md @@ -0,0 +1,5 @@ +# Displaying Block Objects + +Here, we use the `onChange` handler to listen to updates to the BlockNote document and display them below the editor. + +**Try it out:** Type in the editor and see the JSON representation of the document. diff --git a/examples/01-basic/block-objects/index.html b/examples/01-basic/02-block-objects/index.html similarity index 100% rename from examples/01-basic/block-objects/index.html rename to examples/01-basic/02-block-objects/index.html diff --git a/examples/01-basic/block-manipulation/main.tsx b/examples/01-basic/02-block-objects/main.tsx similarity index 100% rename from examples/01-basic/block-manipulation/main.tsx rename to examples/01-basic/02-block-objects/main.tsx diff --git a/examples/01-basic/block-objects/package.json b/examples/01-basic/02-block-objects/package.json similarity index 100% rename from examples/01-basic/block-objects/package.json rename to examples/01-basic/02-block-objects/package.json diff --git a/examples/01-basic/block-objects/styles.css b/examples/01-basic/02-block-objects/styles.css similarity index 100% rename from examples/01-basic/block-objects/styles.css rename to examples/01-basic/02-block-objects/styles.css diff --git a/examples/01-basic/block-manipulation/tsconfig.json b/examples/01-basic/02-block-objects/tsconfig.json similarity index 100% rename from examples/01-basic/block-manipulation/tsconfig.json rename to examples/01-basic/02-block-objects/tsconfig.json diff --git a/examples/01-basic/block-manipulation/vite.config.ts b/examples/01-basic/02-block-objects/vite.config.ts similarity index 100% rename from examples/01-basic/block-manipulation/vite.config.ts rename to examples/01-basic/02-block-objects/vite.config.ts diff --git a/examples/01-basic/03-block-manipulation/.bnexample.json b/examples/01-basic/03-block-manipulation/.bnexample.json new file mode 100644 index 0000000000..0993be7115 --- /dev/null +++ b/examples/01-basic/03-block-manipulation/.bnexample.json @@ -0,0 +1,4 @@ +{ + "playground": true, + "docs": true +} diff --git a/examples/01-basic/03-block-manipulation/App.tsx b/examples/01-basic/03-block-manipulation/App.tsx new file mode 100644 index 0000000000..2101732367 --- /dev/null +++ b/examples/01-basic/03-block-manipulation/App.tsx @@ -0,0 +1,59 @@ +import { BlockNoteView, useBlockNote } from "@blocknote/react"; +import "@blocknote/react/style.css"; + +export default function App() { + const editor = useBlockNote(); + + return ( +
    + + {/*Inserts a new block at end of document.*/} + + {/*Updates the currently selected block*/} + + {/*Removes the currently selected block*/} + + {/*Replaces the currently selected block*/} + +
    + ); +} diff --git a/examples/01-basic/03-block-manipulation/README.md b/examples/01-basic/03-block-manipulation/README.md new file mode 100644 index 0000000000..aea7dbb93e --- /dev/null +++ b/examples/01-basic/03-block-manipulation/README.md @@ -0,0 +1,10 @@ +# Block Manipulation + +TODO: fix styles + +This example shows 4 buttons to manipulate the currently selected block using the `insertBlocks`, `updateBlock`, `removeBlocks` and `replaceBlocks` methods. + +**Relevant Docs:** + +- [Block Manipulation](/docs/manipulating-blocks) +- [Text Cursor](/docs/cursor-selections#text-cursor) diff --git a/examples/01-basic/block-manipulation/index.html b/examples/01-basic/03-block-manipulation/index.html similarity index 100% rename from examples/01-basic/block-manipulation/index.html rename to examples/01-basic/03-block-manipulation/index.html diff --git a/examples/01-basic/block-objects/main.tsx b/examples/01-basic/03-block-manipulation/main.tsx similarity index 100% rename from examples/01-basic/block-objects/main.tsx rename to examples/01-basic/03-block-manipulation/main.tsx diff --git a/examples/01-basic/block-manipulation/package.json b/examples/01-basic/03-block-manipulation/package.json similarity index 100% rename from examples/01-basic/block-manipulation/package.json rename to examples/01-basic/03-block-manipulation/package.json diff --git a/examples/01-basic/block-objects/tsconfig.json b/examples/01-basic/03-block-manipulation/tsconfig.json similarity index 100% rename from examples/01-basic/block-objects/tsconfig.json rename to examples/01-basic/03-block-manipulation/tsconfig.json diff --git a/examples/01-basic/block-objects/vite.config.ts b/examples/01-basic/03-block-manipulation/vite.config.ts similarity index 100% rename from examples/01-basic/block-objects/vite.config.ts rename to examples/01-basic/03-block-manipulation/vite.config.ts diff --git a/examples/01-basic/04-saving-loading/.bnexample.json b/examples/01-basic/04-saving-loading/.bnexample.json new file mode 100644 index 0000000000..0993be7115 --- /dev/null +++ b/examples/01-basic/04-saving-loading/.bnexample.json @@ -0,0 +1,4 @@ +{ + "playground": true, + "docs": true +} diff --git a/examples/01-basic/saving-loading/App.tsx b/examples/01-basic/04-saving-loading/App.tsx similarity index 84% rename from examples/01-basic/saving-loading/App.tsx rename to examples/01-basic/04-saving-loading/App.tsx index b91c18b067..1ed4eb1ee3 100644 --- a/examples/01-basic/saving-loading/App.tsx +++ b/examples/01-basic/04-saving-loading/App.tsx @@ -10,13 +10,16 @@ async function saveToStorage(jsonBlocks: any[]) { async function loadFromStorage() { // Gets the previously stored editor contents - return JSON.parse(localStorage.getItem("editorContent") || "[]"); + const storageString = localStorage.getItem("editorContent"); + return storageString + ? (JSON.parse(storageString) as PartialBlock[]) + : undefined; } export default function App() { const [initialContent, setInitialContent] = useState< - PartialBlock[] | undefined - >(); + PartialBlock[] | undefined | "loading" + >("loading"); // Loads the previously stored editor contents useEffect(() => { @@ -28,7 +31,7 @@ export default function App() { // Creates a new editor instance. // We use useMemo + createBlockNoteEditor instead of useBlockNote so we can delay the creation of the editor until the initial content is loaded. const editor = useMemo(() => { - if (initialContent === undefined) { + if (initialContent === "loading") { return undefined; } return createBlockNoteEditor({ initialContent }); diff --git a/examples/01-basic/04-saving-loading/README.md b/examples/01-basic/04-saving-loading/README.md new file mode 100644 index 0000000000..9cdae10159 --- /dev/null +++ b/examples/01-basic/04-saving-loading/README.md @@ -0,0 +1,12 @@ +# Saving & Loading + +This example shows how to save the editor contents to local storage whenever a change is made, and load the saved contents when the editor is created. + +You can replace the `saveToStorage` and `loadFromStorage` functions with calls to your backend or database. + +**Try it out:** Try typing in the editor and reloading the page! + +**Relevant Docs:** + +- [Editor Options](/docs/editor#editor-options) +- [Accessing Blocks](/docs/manipulating-blocks#accessing-blocks) diff --git a/examples/01-basic/saving-loading/index.html b/examples/01-basic/04-saving-loading/index.html similarity index 100% rename from examples/01-basic/saving-loading/index.html rename to examples/01-basic/04-saving-loading/index.html diff --git a/examples/01-basic/keyboard-shortcuts/main.tsx b/examples/01-basic/04-saving-loading/main.tsx similarity index 100% rename from examples/01-basic/keyboard-shortcuts/main.tsx rename to examples/01-basic/04-saving-loading/main.tsx diff --git a/examples/01-basic/saving-loading/package.json b/examples/01-basic/04-saving-loading/package.json similarity index 100% rename from examples/01-basic/saving-loading/package.json rename to examples/01-basic/04-saving-loading/package.json diff --git a/examples/01-basic/keyboard-shortcuts/tsconfig.json b/examples/01-basic/04-saving-loading/tsconfig.json similarity index 100% rename from examples/01-basic/keyboard-shortcuts/tsconfig.json rename to examples/01-basic/04-saving-loading/tsconfig.json diff --git a/examples/01-basic/keyboard-shortcuts/vite.config.ts b/examples/01-basic/04-saving-loading/vite.config.ts similarity index 100% rename from examples/01-basic/keyboard-shortcuts/vite.config.ts rename to examples/01-basic/04-saving-loading/vite.config.ts diff --git a/examples/01-basic/05-file-uploading/.bnexample.json b/examples/01-basic/05-file-uploading/.bnexample.json new file mode 100644 index 0000000000..0993be7115 --- /dev/null +++ b/examples/01-basic/05-file-uploading/.bnexample.json @@ -0,0 +1,4 @@ +{ + "playground": true, + "docs": true +} diff --git a/examples/01-basic/05-file-uploading/App.tsx b/examples/01-basic/05-file-uploading/App.tsx new file mode 100644 index 0000000000..170d7000b1 --- /dev/null +++ b/examples/01-basic/05-file-uploading/App.tsx @@ -0,0 +1,31 @@ +import { BlockNoteView, useBlockNote } from "@blocknote/react"; +import "@blocknote/react/style.css"; + +/** + * Uploads a file to tmpfiles.org and returns the URL to the uploaded file. + * + * @warning This function should only be used for development purposes, replace with your own backend! + */ +async function uploadFile(file: File) { + const body = new FormData(); + body.append("file", file); + + const ret = await fetch("https://tmpfiles.org/api/v1/upload", { + method: "POST", + body: body, + }); + return (await ret.json()).data.url.replace( + "tmpfiles.org/", + "tmpfiles.org/dl/" + ); +} + +export default function App() { + // Creates a new editor instance. + const editor = useBlockNote({ + uploadFile, + }); + + // Renders the editor instance using a React component. + return ; +} diff --git a/examples/01-basic/05-file-uploading/README.md b/examples/01-basic/05-file-uploading/README.md new file mode 100644 index 0000000000..00bc2b4fd2 --- /dev/null +++ b/examples/01-basic/05-file-uploading/README.md @@ -0,0 +1,9 @@ +# File / image uploading + +This example registers an `uploadFile` handler. This makes it possible for users to upload files to the editor. + +**Try it out:** Insert an image (by typing `/` and selecting image), and notice how it's now possible to upload a file from your local device. + +**Relevant Docs:** + +TODO diff --git a/examples/01-basic/05-file-uploading/index.html b/examples/01-basic/05-file-uploading/index.html new file mode 100644 index 0000000000..f7f774c5f1 --- /dev/null +++ b/examples/01-basic/05-file-uploading/index.html @@ -0,0 +1,14 @@ + + + + + + File / image uploading + + +
    + + + diff --git a/examples/01-basic/saving-loading/main.tsx b/examples/01-basic/05-file-uploading/main.tsx similarity index 100% rename from examples/01-basic/saving-loading/main.tsx rename to examples/01-basic/05-file-uploading/main.tsx diff --git a/examples/01-basic/05-file-uploading/package.json b/examples/01-basic/05-file-uploading/package.json new file mode 100644 index 0000000000..f7908f6fb8 --- /dev/null +++ b/examples/01-basic/05-file-uploading/package.json @@ -0,0 +1,34 @@ +{ + "name": "@blocknote/example-file-uploading", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "private": true, + "version": "0.11.1", + "scripts": { + "start": "vite", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint . --max-warnings 0" + }, + "dependencies": { + "@blocknote/core": "^0.11.1", + "@blocknote/react": "^0.11.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^4.0.4", + "eslint": "^8.10.0", + "vite": "^4.4.8" + }, + "eslintConfig": { + "extends": [ + "../../../.eslintrc.js" + ] + }, + "eslintIgnore": [ + "dist" + ] +} \ No newline at end of file diff --git a/examples/01-basic/saving-loading/tsconfig.json b/examples/01-basic/05-file-uploading/tsconfig.json similarity index 100% rename from examples/01-basic/saving-loading/tsconfig.json rename to examples/01-basic/05-file-uploading/tsconfig.json diff --git a/examples/01-basic/saving-loading/vite.config.ts b/examples/01-basic/05-file-uploading/vite.config.ts similarity index 100% rename from examples/01-basic/saving-loading/vite.config.ts rename to examples/01-basic/05-file-uploading/vite.config.ts diff --git a/examples/01-basic/06-keyboard-shortcuts/.bnexample.json b/examples/01-basic/06-keyboard-shortcuts/.bnexample.json new file mode 100644 index 0000000000..0993be7115 --- /dev/null +++ b/examples/01-basic/06-keyboard-shortcuts/.bnexample.json @@ -0,0 +1,4 @@ +{ + "playground": true, + "docs": true +} diff --git a/examples/01-basic/keyboard-shortcuts/App.tsx b/examples/01-basic/06-keyboard-shortcuts/App.tsx similarity index 89% rename from examples/01-basic/keyboard-shortcuts/App.tsx rename to examples/01-basic/06-keyboard-shortcuts/App.tsx index 5f3f9c75a3..66a0e43d0b 100644 --- a/examples/01-basic/keyboard-shortcuts/App.tsx +++ b/examples/01-basic/06-keyboard-shortcuts/App.tsx @@ -40,9 +40,6 @@ export default function App() { const onKeyDown = (event: React.KeyboardEvent) => { cycleBlocksShortcut(event, editor); }; - const r = (el: any) => { - console.log("EL", el); - }; - return ; + return ; } diff --git a/examples/01-basic/keyboard-shortcuts/README.md b/examples/01-basic/06-keyboard-shortcuts/README.md similarity index 72% rename from examples/01-basic/keyboard-shortcuts/README.md rename to examples/01-basic/06-keyboard-shortcuts/README.md index 3f3000a3e8..86a75a0096 100644 --- a/examples/01-basic/keyboard-shortcuts/README.md +++ b/examples/01-basic/06-keyboard-shortcuts/README.md @@ -1,9 +1,11 @@ # Keyboard Shortcuts +TODO: useful example? Non-basic? + In this example, we create a keyboard shortcut which cycles the current block type when Ctrl+G is pressed. **Relevant Docs:** - [Editor Options](/docs/editor#editor-options) - [Text Cursor](/docs/cursor-selections#text-cursor) -- [Updating Blocks](/docs/manipulating-blocks#updating-blocks) \ No newline at end of file +- [Updating Blocks](/docs/manipulating-blocks#updating-blocks) diff --git a/examples/01-basic/keyboard-shortcuts/index.html b/examples/01-basic/06-keyboard-shortcuts/index.html similarity index 100% rename from examples/01-basic/keyboard-shortcuts/index.html rename to examples/01-basic/06-keyboard-shortcuts/index.html diff --git a/examples/08-interoperability/converting-blocks-from-html/main.tsx b/examples/01-basic/06-keyboard-shortcuts/main.tsx similarity index 100% rename from examples/08-interoperability/converting-blocks-from-html/main.tsx rename to examples/01-basic/06-keyboard-shortcuts/main.tsx diff --git a/examples/01-basic/keyboard-shortcuts/package.json b/examples/01-basic/06-keyboard-shortcuts/package.json similarity index 100% rename from examples/01-basic/keyboard-shortcuts/package.json rename to examples/01-basic/06-keyboard-shortcuts/package.json diff --git a/examples/08-interoperability/converting-blocks-from-html/tsconfig.json b/examples/01-basic/06-keyboard-shortcuts/tsconfig.json similarity index 100% rename from examples/08-interoperability/converting-blocks-from-html/tsconfig.json rename to examples/01-basic/06-keyboard-shortcuts/tsconfig.json diff --git a/examples/08-interoperability/converting-blocks-from-html/vite.config.ts b/examples/01-basic/06-keyboard-shortcuts/vite.config.ts similarity index 100% rename from examples/08-interoperability/converting-blocks-from-html/vite.config.ts rename to examples/01-basic/06-keyboard-shortcuts/vite.config.ts diff --git a/examples/01-basic/block-manipulation/.bnexample.json b/examples/01-basic/block-manipulation/.bnexample.json deleted file mode 100644 index c244176150..0000000000 --- a/examples/01-basic/block-manipulation/.bnexample.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "playground": true, - "docs": true, - "group": "Basic Examples", - "order": 1 -} \ No newline at end of file diff --git a/examples/01-basic/block-manipulation/App.tsx b/examples/01-basic/block-manipulation/App.tsx deleted file mode 100644 index fc9d2fe74d..0000000000 --- a/examples/01-basic/block-manipulation/App.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { BlockNoteView, useBlockNote } from "@blocknote/react"; -import "@blocknote/react/style.css"; - -export default function App() { - const editor = useBlockNote(); - - return ( -
    - - {/*Inserts a new block below the currently selected block.*/} - - {/*Updates the currently selected block*/} - - {/*Removes the currently selected block*/} - - {/*Replaces the currently selected block*/} - -
    - ); -} diff --git a/examples/01-basic/block-manipulation/README.md b/examples/01-basic/block-manipulation/README.md deleted file mode 100644 index 981f233e1a..0000000000 --- a/examples/01-basic/block-manipulation/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Block Manipulation - -In this example, we create 4 buttons under the editor to manipulate the currently selected block. - -**Relevant Docs:** - -- [Block Manipulation](/docs/manipulating-blocks) -- [Text Cursor](/docs/cursor-selections#text-cursor) \ No newline at end of file diff --git a/examples/01-basic/block-objects/README.md b/examples/01-basic/block-objects/README.md deleted file mode 100644 index 0daa252125..0000000000 --- a/examples/01-basic/block-objects/README.md +++ /dev/null @@ -1 +0,0 @@ -# Displaying Block Objects diff --git a/examples/01-basic/keyboard-shortcuts/.bnexample.json b/examples/01-basic/keyboard-shortcuts/.bnexample.json deleted file mode 100644 index e79f342c1b..0000000000 --- a/examples/01-basic/keyboard-shortcuts/.bnexample.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "playground": true, - "docs": true, - "group": "Basic Examples", - "order": 5 -} \ No newline at end of file diff --git a/examples/01-basic/saving-loading/.bnexample.json b/examples/01-basic/saving-loading/.bnexample.json deleted file mode 100644 index f394113b94..0000000000 --- a/examples/01-basic/saving-loading/.bnexample.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "playground": true, - "docs": true, - "group": "Basic Examples", - "order": 2 -} diff --git a/examples/01-basic/saving-loading/README.md b/examples/01-basic/saving-loading/README.md deleted file mode 100644 index f8862fcebf..0000000000 --- a/examples/01-basic/saving-loading/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Saving & Loading - -In this example, we save the editor contents to local storage whenever a change is made, and load the saved contents when the editor is created. - -See this in action by typing in the editor and reloading the page! - -**Relevant Docs:** - -- [Editor Options](/docs/editor#editor-options) -- [Accessing Blocks](/docs/manipulating-blocks#accessing-blocks) diff --git a/examples/05-cursor-selections/selection-blocks/README.md b/examples/05-cursor-selections/selection-blocks/README.md index f0ce29294b..9f1c7c719c 100644 --- a/examples/05-cursor-selections/selection-blocks/README.md +++ b/examples/05-cursor-selections/selection-blocks/README.md @@ -1 +1,3 @@ -# Highlighting Blocks in Selection \ No newline at end of file +# Highlighting Blocks in Selection + +TODO: same as text-cursor block. Perhaps replace both by 1 single example diff --git a/examples/05-cursor-selections/text-cursor-block/README.md b/examples/05-cursor-selections/text-cursor-block/README.md index dcef483c5f..51bf7844d7 100644 --- a/examples/05-cursor-selections/text-cursor-block/README.md +++ b/examples/05-cursor-selections/text-cursor-block/README.md @@ -1 +1,5 @@ -# Highlighting Block with the Text Cursor \ No newline at end of file +# Highlighting Block with the Text Cursor + +TODO: remove. I don't really see a scenario where this example makes sense in an application (selection related info should never be stored in the document; as this would also be saved in database, multiplayer, etc.) + +Let's replace with an example similar to "basic/block-objects" that just outputs the relevant info diff --git a/examples/07-collaboration/partykit/App.tsx b/examples/07-collaboration/partykit/App.tsx index 5cfdaef96f..81a4f2a527 100644 --- a/examples/07-collaboration/partykit/App.tsx +++ b/examples/07-collaboration/partykit/App.tsx @@ -1,4 +1,3 @@ -import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; @@ -27,7 +26,6 @@ export default function App() { color: "#ff0000", }, }, - uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, }); return ; diff --git a/examples/01-basic/block-objects/.bnexample.json b/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json similarity index 100% rename from examples/01-basic/block-objects/.bnexample.json rename to examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json diff --git a/examples/08-interoperability/converting-blocks-to-html/App.tsx b/examples/08-interoperability/01-converting-blocks-to-html/App.tsx similarity index 100% rename from examples/08-interoperability/converting-blocks-to-html/App.tsx rename to examples/08-interoperability/01-converting-blocks-to-html/App.tsx diff --git a/examples/08-interoperability/converting-blocks-to-html/README.md b/examples/08-interoperability/01-converting-blocks-to-html/README.md similarity index 100% rename from examples/08-interoperability/converting-blocks-to-html/README.md rename to examples/08-interoperability/01-converting-blocks-to-html/README.md diff --git a/examples/08-interoperability/converting-blocks-to-html/index.html b/examples/08-interoperability/01-converting-blocks-to-html/index.html similarity index 100% rename from examples/08-interoperability/converting-blocks-to-html/index.html rename to examples/08-interoperability/01-converting-blocks-to-html/index.html diff --git a/examples/08-interoperability/converting-blocks-from-md/main.tsx b/examples/08-interoperability/01-converting-blocks-to-html/main.tsx similarity index 100% rename from examples/08-interoperability/converting-blocks-from-md/main.tsx rename to examples/08-interoperability/01-converting-blocks-to-html/main.tsx diff --git a/examples/08-interoperability/converting-blocks-to-html/package.json b/examples/08-interoperability/01-converting-blocks-to-html/package.json similarity index 100% rename from examples/08-interoperability/converting-blocks-to-html/package.json rename to examples/08-interoperability/01-converting-blocks-to-html/package.json diff --git a/examples/08-interoperability/converting-blocks-from-md/tsconfig.json b/examples/08-interoperability/01-converting-blocks-to-html/tsconfig.json similarity index 100% rename from examples/08-interoperability/converting-blocks-from-md/tsconfig.json rename to examples/08-interoperability/01-converting-blocks-to-html/tsconfig.json diff --git a/examples/08-interoperability/converting-blocks-from-md/vite.config.ts b/examples/08-interoperability/01-converting-blocks-to-html/vite.config.ts similarity index 100% rename from examples/08-interoperability/converting-blocks-from-md/vite.config.ts rename to examples/08-interoperability/01-converting-blocks-to-html/vite.config.ts diff --git a/examples/08-interoperability/converting-blocks-from-html/.bnexample.json b/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json similarity index 100% rename from examples/08-interoperability/converting-blocks-from-html/.bnexample.json rename to examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json diff --git a/examples/08-interoperability/converting-blocks-from-html/App.tsx b/examples/08-interoperability/02-converting-blocks-from-html/App.tsx similarity index 100% rename from examples/08-interoperability/converting-blocks-from-html/App.tsx rename to examples/08-interoperability/02-converting-blocks-from-html/App.tsx diff --git a/examples/08-interoperability/converting-blocks-from-html/README.md b/examples/08-interoperability/02-converting-blocks-from-html/README.md similarity index 100% rename from examples/08-interoperability/converting-blocks-from-html/README.md rename to examples/08-interoperability/02-converting-blocks-from-html/README.md diff --git a/examples/08-interoperability/converting-blocks-from-html/index.html b/examples/08-interoperability/02-converting-blocks-from-html/index.html similarity index 100% rename from examples/08-interoperability/converting-blocks-from-html/index.html rename to examples/08-interoperability/02-converting-blocks-from-html/index.html diff --git a/examples/08-interoperability/converting-blocks-to-html/main.tsx b/examples/08-interoperability/02-converting-blocks-from-html/main.tsx similarity index 100% rename from examples/08-interoperability/converting-blocks-to-html/main.tsx rename to examples/08-interoperability/02-converting-blocks-from-html/main.tsx diff --git a/examples/08-interoperability/converting-blocks-from-html/package.json b/examples/08-interoperability/02-converting-blocks-from-html/package.json similarity index 100% rename from examples/08-interoperability/converting-blocks-from-html/package.json rename to examples/08-interoperability/02-converting-blocks-from-html/package.json diff --git a/examples/08-interoperability/converting-blocks-to-html/tsconfig.json b/examples/08-interoperability/02-converting-blocks-from-html/tsconfig.json similarity index 100% rename from examples/08-interoperability/converting-blocks-to-html/tsconfig.json rename to examples/08-interoperability/02-converting-blocks-from-html/tsconfig.json diff --git a/examples/08-interoperability/converting-blocks-to-html/vite.config.ts b/examples/08-interoperability/02-converting-blocks-from-html/vite.config.ts similarity index 100% rename from examples/08-interoperability/converting-blocks-to-html/vite.config.ts rename to examples/08-interoperability/02-converting-blocks-from-html/vite.config.ts diff --git a/examples/08-interoperability/converting-blocks-from-md/.bnexample.json b/examples/08-interoperability/03-converting-blocks-to-md/.bnexample.json similarity index 100% rename from examples/08-interoperability/converting-blocks-from-md/.bnexample.json rename to examples/08-interoperability/03-converting-blocks-to-md/.bnexample.json diff --git a/examples/08-interoperability/converting-blocks-to-md/App.tsx b/examples/08-interoperability/03-converting-blocks-to-md/App.tsx similarity index 100% rename from examples/08-interoperability/converting-blocks-to-md/App.tsx rename to examples/08-interoperability/03-converting-blocks-to-md/App.tsx diff --git a/examples/08-interoperability/converting-blocks-to-md/README.md b/examples/08-interoperability/03-converting-blocks-to-md/README.md similarity index 100% rename from examples/08-interoperability/converting-blocks-to-md/README.md rename to examples/08-interoperability/03-converting-blocks-to-md/README.md diff --git a/examples/08-interoperability/converting-blocks-to-md/index.html b/examples/08-interoperability/03-converting-blocks-to-md/index.html similarity index 100% rename from examples/08-interoperability/converting-blocks-to-md/index.html rename to examples/08-interoperability/03-converting-blocks-to-md/index.html diff --git a/examples/08-interoperability/converting-blocks-to-md/main.tsx b/examples/08-interoperability/03-converting-blocks-to-md/main.tsx similarity index 100% rename from examples/08-interoperability/converting-blocks-to-md/main.tsx rename to examples/08-interoperability/03-converting-blocks-to-md/main.tsx diff --git a/examples/08-interoperability/converting-blocks-to-md/package.json b/examples/08-interoperability/03-converting-blocks-to-md/package.json similarity index 100% rename from examples/08-interoperability/converting-blocks-to-md/package.json rename to examples/08-interoperability/03-converting-blocks-to-md/package.json diff --git a/examples/08-interoperability/converting-blocks-to-md/tsconfig.json b/examples/08-interoperability/03-converting-blocks-to-md/tsconfig.json similarity index 100% rename from examples/08-interoperability/converting-blocks-to-md/tsconfig.json rename to examples/08-interoperability/03-converting-blocks-to-md/tsconfig.json diff --git a/examples/08-interoperability/converting-blocks-to-md/vite.config.ts b/examples/08-interoperability/03-converting-blocks-to-md/vite.config.ts similarity index 100% rename from examples/08-interoperability/converting-blocks-to-md/vite.config.ts rename to examples/08-interoperability/03-converting-blocks-to-md/vite.config.ts diff --git a/examples/08-interoperability/converting-blocks-to-html/.bnexample.json b/examples/08-interoperability/04-converting-blocks-from-md/.bnexample.json similarity index 100% rename from examples/08-interoperability/converting-blocks-to-html/.bnexample.json rename to examples/08-interoperability/04-converting-blocks-from-md/.bnexample.json diff --git a/examples/08-interoperability/converting-blocks-from-md/App.tsx b/examples/08-interoperability/04-converting-blocks-from-md/App.tsx similarity index 100% rename from examples/08-interoperability/converting-blocks-from-md/App.tsx rename to examples/08-interoperability/04-converting-blocks-from-md/App.tsx diff --git a/examples/08-interoperability/converting-blocks-from-md/README.md b/examples/08-interoperability/04-converting-blocks-from-md/README.md similarity index 100% rename from examples/08-interoperability/converting-blocks-from-md/README.md rename to examples/08-interoperability/04-converting-blocks-from-md/README.md diff --git a/examples/08-interoperability/converting-blocks-from-md/index.html b/examples/08-interoperability/04-converting-blocks-from-md/index.html similarity index 100% rename from examples/08-interoperability/converting-blocks-from-md/index.html rename to examples/08-interoperability/04-converting-blocks-from-md/index.html diff --git a/examples/08-interoperability/04-converting-blocks-from-md/main.tsx b/examples/08-interoperability/04-converting-blocks-from-md/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/08-interoperability/04-converting-blocks-from-md/main.tsx @@ -0,0 +1,11 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App"; + +const root = createRoot(document.getElementById("root")!); +root.render( + + + +); diff --git a/examples/08-interoperability/converting-blocks-from-md/package.json b/examples/08-interoperability/04-converting-blocks-from-md/package.json similarity index 100% rename from examples/08-interoperability/converting-blocks-from-md/package.json rename to examples/08-interoperability/04-converting-blocks-from-md/package.json diff --git a/examples/08-interoperability/04-converting-blocks-from-md/tsconfig.json b/examples/08-interoperability/04-converting-blocks-from-md/tsconfig.json new file mode 100644 index 0000000000..bb6637c459 --- /dev/null +++ b/examples/08-interoperability/04-converting-blocks-from-md/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "composite": true + }, + "include": [ + "." + ], + "references": [ + { + "path": "../../../packages/core/" + }, + { + "path": "../../../packages/react/" + } + ] +} \ No newline at end of file diff --git a/examples/08-interoperability/04-converting-blocks-from-md/vite.config.ts b/examples/08-interoperability/04-converting-blocks-from-md/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/08-interoperability/04-converting-blocks-from-md/vite.config.ts @@ -0,0 +1,32 @@ +// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY +import react from "@vitejs/plugin-react"; +import * as fs from "fs"; +import * as path from "path"; +import { defineConfig } from "vite"; +// import eslintPlugin from "vite-plugin-eslint"; +// https://vitejs.dev/config/ +export default defineConfig((conf) => ({ + plugins: [react()], + optimizeDeps: {}, + build: { + sourcemap: true, + }, + resolve: { + alias: + conf.command === "build" || + !fs.existsSync(path.resolve(__dirname, "../../packages/core/src")) + ? {} + : ({ + // Comment out the lines below to load a built version of blocknote + // or, keep as is to load live from sources with live reload working + "@blocknote/core": path.resolve( + __dirname, + "../../packages/core/src/" + ), + "@blocknote/react": path.resolve( + __dirname, + "../../packages/react/src/" + ), + } as any), + }, +})); diff --git a/examples/08-interoperability/converting-blocks-to-md/.bnexample.json b/examples/08-interoperability/converting-blocks-to-md/.bnexample.json deleted file mode 100644 index 178fd44ce0..0000000000 --- a/examples/08-interoperability/converting-blocks-to-md/.bnexample.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "playground": true, - "docs": false, - "group": "Basic Examples", - "order": 1 -} diff --git a/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts b/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts index 21c09bb645..406dc7e8d9 100644 --- a/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts +++ b/packages/core/src/blocks/ImageBlockContent/uploadToTmpFilesDotOrg_DEV_ONLY.ts @@ -1,3 +1,8 @@ +/** + * Uploads a file to tmpfiles.org and returns the URL to the uploaded file. + * + * @warning This function should only be used for development purposes, replace with your own backend! + */ export const uploadToTmpFilesDotOrg_DEV_ONLY = async (file: File) => { const body = new FormData(); body.append("file", file); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 70519c1ff3..68ac0d15ef 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -347,6 +347,13 @@ export class BlockNoteEditor< }, ]); + if (!Array.isArray(initialContent) || initialContent.length === 0) { + throw new Error( + "initialContent must be a non-empty array of blocks, received: " + + initialContent + ); + } + const tiptapOptions: BlockNoteTipTapEditorOptions = { ...blockNoteTipTapOptions, ...newOptions._tiptapOptions, diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx index aff1e2ec96..0b92bc70f2 100644 --- a/playground/src/examples.gen.tsx +++ b/playground/src/examples.gen.tsx @@ -11,9 +11,7 @@ "pathFromRoot": "examples/01-basic/01-minimal", "config": { "playground": true, - "docs": false, - "group": "Basic Examples", - "order": 1 + "docs": true }, "title": "Basic Editor Setup", "group": { @@ -21,15 +19,27 @@ "slug": "basic" } }, + { + "projectSlug": "block-objects", + "fullSlug": "basic/block-objects", + "pathFromRoot": "examples/01-basic/02-block-objects", + "config": { + "playground": true, + "docs": true + }, + "title": "Displaying Block Objects", + "group": { + "pathFromRoot": "examples/01-basic", + "slug": "basic" + } + }, { "projectSlug": "block-manipulation", "fullSlug": "basic/block-manipulation", - "pathFromRoot": "examples/01-basic/block-manipulation", + "pathFromRoot": "examples/01-basic/03-block-manipulation", "config": { "playground": true, - "docs": true, - "group": "Basic Examples", - "order": 1 + "docs": true }, "title": "Block Manipulation", "group": { @@ -38,48 +48,42 @@ } }, { - "projectSlug": "block-objects", - "fullSlug": "basic/block-objects", - "pathFromRoot": "examples/01-basic/block-objects", + "projectSlug": "saving-loading", + "fullSlug": "basic/saving-loading", + "pathFromRoot": "examples/01-basic/04-saving-loading", "config": { "playground": true, - "docs": false, - "group": "Basic Examples", - "order": 1 + "docs": true }, - "title": "Displaying Block Objects", + "title": "Saving & Loading", "group": { "pathFromRoot": "examples/01-basic", "slug": "basic" } }, { - "projectSlug": "keyboard-shortcuts", - "fullSlug": "basic/keyboard-shortcuts", - "pathFromRoot": "examples/01-basic/keyboard-shortcuts", + "projectSlug": "file-uploading", + "fullSlug": "basic/file-uploading", + "pathFromRoot": "examples/01-basic/05-file-uploading", "config": { "playground": true, - "docs": true, - "group": "Basic Examples", - "order": 5 + "docs": true }, - "title": "Keyboard Shortcuts", + "title": "File / image uploading", "group": { "pathFromRoot": "examples/01-basic", "slug": "basic" } }, { - "projectSlug": "saving-loading", - "fullSlug": "basic/saving-loading", - "pathFromRoot": "examples/01-basic/saving-loading", + "projectSlug": "keyboard-shortcuts", + "fullSlug": "basic/keyboard-shortcuts", + "pathFromRoot": "examples/01-basic/06-keyboard-shortcuts", "config": { "playground": true, - "docs": true, - "group": "Basic Examples", - "order": 2 + "docs": true }, - "title": "Saving & Loading", + "title": "Keyboard Shortcuts", "group": { "pathFromRoot": "examples/01-basic", "slug": "basic" From 18c3c5bab65fb1a04744cb6d2485dbdf315a9d2a Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 13 Feb 2024 09:14:39 +0100 Subject: [PATCH 15/17] fix theme --- docs/components/pages/landing/hero/Demo.tsx | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/components/pages/landing/hero/Demo.tsx diff --git a/docs/components/pages/landing/hero/Demo.tsx b/docs/components/pages/landing/hero/Demo.tsx new file mode 100644 index 0000000000..6d7a6d472c --- /dev/null +++ b/docs/components/pages/landing/hero/Demo.tsx @@ -0,0 +1,108 @@ +import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core"; +import { BlockNoteView, useBlockNote } from "@blocknote/react"; +import "@blocknote/react/style.css"; +import { useMemo } from "react"; +import YPartyKitProvider from "y-partykit/provider"; +import * as Y from "yjs"; + +import "./styles.css"; + +const colors = [ + "#958DF1", + "#F98181", + "#FBBC88", + "#FAF594", + "#70CFF8", + "#94FADB", + "#B9F18D", +]; +const names = [ + "Lorem Ipsumovich", + "Typy McTypeface", + "Collabo Rative", + "Edito Von Editz", + "Wordsworth Writywrite", + "Docu D. Mentor", + "Scrivener Scribblesworth", + "Digi Penman", + "Ernest Wordway", + "Sir Typalot", + "Comic Sans-Serif", + "Miss Spellcheck", + "Bullet Liston", + "Autonomy Backspace", + "Ctrl Zedson", +]; + +const getRandomElement = (list: any[]) => + list[Math.floor(Math.random() * list.length)]; + +const getRandomColor = () => getRandomElement(colors); +const getRandomName = () => getRandomElement(names); + +function getUTCDateYYYYMMDD() { + const now = new Date(); + const year = now.getUTCFullYear(); + const month = now.getUTCMonth() + 1; // January is 0 + const day = now.getUTCDate(); + + // Add leading zeros to month and day if needed + const formattedMonth = month < 10 ? `0${month}` : `${month}`; + const formattedDay = day < 10 ? `0${day}` : `${day}`; + + return `${year}${formattedMonth}${formattedDay}`; +} + +export function ReactBlockNote(props: { + theme: "light" | "dark"; +}) { + + + const [doc, provider] = useMemo(() => { + console.log("create"); + const doc = new Y.Doc(); + const provider = new YPartyKitProvider( + "blocknote.yousefed.partykit.dev", + // "127.0.0.1:1999", // (dev server) + "homepage-" + getUTCDateYYYYMMDD(), + doc, + ); + return [doc, provider]; + }, []); + + const editor = useBlockNote( + { + collaboration: { + provider, + fragment: doc.getXmlFragment("blocknote"), + user: { + name: getRandomName(), + color: getRandomColor(), + }, + }, + uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, + }, + [], + ); + + // TODO + // useEffect(() => { + // let shownAlert = false; + // const listener = () => { + // if (!shownAlert) { + // alert( + // "Text you enter in this demo is displayed publicly on the internet to show multiplayer features. Be kind :)", + // ); + // shownAlert = true; + // } + // }; + // editor?.domElement?.addEventListener("focus", listener); + // return () => { + // editor?.domElement?.removeEventListener("focus", listener); + // }; + // }, [editor?.domElement]); + + return ; +} + +export default ReactBlockNote; From 961134a773a5cd5b18c6abb87907d247a9900c23 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 13 Feb 2024 10:08:01 +0100 Subject: [PATCH 16/17] update interop examples --- .../.bnexample.json | 4 +- .../01-converting-blocks-to-html/App.tsx | 32 +++++++++---- .../01-converting-blocks-to-html/README.md | 10 +++- .../.bnexample.json | 4 +- .../02-converting-blocks-from-html/App.tsx | 36 ++++++++------ .../02-converting-blocks-from-html/README.md | 12 ++++- .../02-converting-blocks-from-html/index.html | 2 +- .../.bnexample.json | 4 +- .../03-converting-blocks-to-md/App.tsx | 24 ++++++++-- .../03-converting-blocks-to-md/README.md | 10 +++- .../.bnexample.json | 4 +- .../04-converting-blocks-from-md/App.tsx | 41 +++++++++------- .../04-converting-blocks-from-md/README.md | 12 ++++- .../04-converting-blocks-from-md/index.html | 2 +- package-lock.json | 8 ++-- package.json | 2 +- packages/core/package.json | 2 +- packages/core/src/editor/BlockNoteEditor.ts | 19 ++++---- packages/dev-scripts/package.json | 2 +- packages/react/package.json | 2 +- playground/src/examples.gen.tsx | 48 ++++++++----------- 21 files changed, 172 insertions(+), 108 deletions(-) diff --git a/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json b/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json index 178fd44ce0..0993be7115 100644 --- a/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json +++ b/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json @@ -1,6 +1,4 @@ { "playground": true, - "docs": false, - "group": "Basic Examples", - "order": 1 + "docs": true } diff --git a/examples/08-interoperability/01-converting-blocks-to-html/App.tsx b/examples/08-interoperability/01-converting-blocks-to-html/App.tsx index b5bc18ca40..6a61fc2a75 100644 --- a/examples/08-interoperability/01-converting-blocks-to-html/App.tsx +++ b/examples/08-interoperability/01-converting-blocks-to-html/App.tsx @@ -2,20 +2,34 @@ import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; import { useState } from "react"; +// TODO: better design? export default function App() { // Stores the editor's contents as HTML. const [html, setHTML] = useState(""); - // Creates a new editor instance. - const editor = useBlockNote({}); + // Creates a new editor instance with some initial content. + const editor = useBlockNote({ + initialContent: [ + { + type: "paragraph", + content: [ + "Hello, ", + { + type: "text", + text: "world!", + styles: { + bold: true, + }, + }, + ], + }, + ], + }); - const onChange = () => { - // Converts the editor's contents from Block objects to HTML and saves them. - const saveBlocksAsHTML = async () => { - const html = await editor.blocksToHTMLLossy(editor.topLevelBlocks); - setHTML(html); - }; - saveBlocksAsHTML(); + const onChange = async () => { + // Converts the editor's contents from Block objects to HTML and store to state. + const html = await editor.blocksToHTMLLossy(editor.topLevelBlocks); + setHTML(html); }; // Renders the editor instance, and its contents as HTML below. diff --git a/examples/08-interoperability/01-converting-blocks-to-html/README.md b/examples/08-interoperability/01-converting-blocks-to-html/README.md index e3d69ff3fd..75da7363eb 100644 --- a/examples/08-interoperability/01-converting-blocks-to-html/README.md +++ b/examples/08-interoperability/01-converting-blocks-to-html/README.md @@ -1 +1,9 @@ -# Converting Blocks to HTML \ No newline at end of file +# Converting Blocks to HTML + +This example exports the current document (all blocks) as HTML and displays it below the editor. + +**Try it out:** Edit the document to see the HTML representation. + +**Relevant Docs:** + +TODO diff --git a/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json b/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json index 178fd44ce0..0993be7115 100644 --- a/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json +++ b/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json @@ -1,6 +1,4 @@ { "playground": true, - "docs": false, - "group": "Basic Examples", - "order": 1 + "docs": true } diff --git a/examples/08-interoperability/02-converting-blocks-from-html/App.tsx b/examples/08-interoperability/02-converting-blocks-from-html/App.tsx index fb3eee93f6..712eeea796 100644 --- a/examples/08-interoperability/02-converting-blocks-from-html/App.tsx +++ b/examples/08-interoperability/02-converting-blocks-from-html/App.tsx @@ -1,32 +1,38 @@ import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; -import { useEffect, useState } from "react"; +import { ChangeEvent, useCallback, useEffect } from "react"; -export default function App() { - // Stores the current HTML content. - const [html, setHTML] = useState(""); +const initialHTML = "

    Hello, world!

    "; +// TODO: better design? +export default function App() { // Creates a new editor instance. const editor = useBlockNote(); + const htmlInputChanged = useCallback( + async (e: ChangeEvent) => { + // Whenever the current HTML content changes, converts it to an array of + // Block objects and replaces the editor's content with them. + const blocks = await editor.tryParseHTMLToBlocks(e.target.value); + editor.replaceBlocks(editor.topLevelBlocks, blocks); + }, + [editor] + ); + + // For initialization; on mount, convert the initial HTML to blocks and replace the default editor's content useEffect(() => { - // 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 = await editor.tryParseHTMLToBlocks(html); + async function loadInitialHTML() { + const blocks = await editor.tryParseHTMLToBlocks(initialHTML); editor.replaceBlocks(editor.topLevelBlocks, blocks); - }; - getBlocks(); - }, [editor, html]); + } + loadInitialHTML(); + }, [editor]); // Renders a text area for you to write/paste HTML in, and the editor instance // below, which displays the current HTML as blocks. return (
    -