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/ThemedExample.tsx b/docs/components/example/ThemedExample.tsx new file mode 100644 index 0000000000..2cf2c7f9d0 --- /dev/null +++ b/docs/components/example/ThemedExample.tsx @@ -0,0 +1,17 @@ +"use client"; +import { BlockNoteContext } from "@blocknote/react"; +import { useTheme } from "nextra-theme-docs"; +import { examples } from "./generated/exampleComponents.gen"; + +export default function ThemedExample(props: { name: keyof typeof examples }) { + const example = examples[props.name]; + const App = example.App; + const { theme } = useTheme(); + + return ( + + + + ); +} diff --git a/docs/components/pages/landing/hero/Demo.tsx b/docs/components/pages/landing/hero/Demo.tsx index 29c9e06185..6d7a6d472c 100644 --- a/docs/components/pages/landing/hero/Demo.tsx +++ b/docs/components/pages/landing/hero/Demo.tsx @@ -1,15 +1,12 @@ import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core"; import { BlockNoteView, useBlockNote } from "@blocknote/react"; import "@blocknote/react/style.css"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo } from "react"; import YPartyKitProvider from "y-partykit/provider"; import * as Y from "yjs"; import "./styles.css"; -const html = document.getElementsByTagName("html")[0]; -const getTheme = () => (html.className.includes("dark") ? "dark" : "light"); - const colors = [ "#958DF1", "#F98181", @@ -56,8 +53,10 @@ function getUTCDateYYYYMMDD() { return `${year}${formattedMonth}${formattedDay}`; } -export function ReactBlockNote() { - const [theme, setTheme] = useState<"dark" | "light">(() => getTheme()); +export function ReactBlockNote(props: { + theme: "light" | "dark"; +}) { + const [doc, provider] = useMemo(() => { console.log("create"); @@ -86,39 +85,24 @@ export function ReactBlockNote() { [], ); - 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]); - - useEffect(() => { - const observer = new MutationObserver((mutationList) => { - mutationList.forEach(function (mutation) { - if ( - mutation.type === "attributes" && - mutation.attributeName === "class" - ) { - setTheme(getTheme()); - } - }); - }); - observer.observe(html, { attributes: true }); - - return () => observer.disconnect(); - }, []); - - return ; + // 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; diff --git a/docs/components/pages/landing/hero/Hero.tsx b/docs/components/pages/landing/hero/Hero.tsx index 8d425763f3..8782f6e813 100644 --- a/docs/components/pages/landing/hero/Hero.tsx +++ b/docs/components/pages/landing/hero/Hero.tsx @@ -1,18 +1,20 @@ // import { PackLogo } from "../../logos/PackLogo"; import dynamic from "next/dynamic"; -import { FadeIn } from "../shared/FadeIn"; -import { HeroText, SectionSubtext } from "../../home-shared/Headings"; import Image from "next/image"; +import { HeroText, SectionSubtext } from "../../home-shared/Headings"; +import { FadeIn } from "../shared/FadeIn"; -import tryHereImageLight from "../../../../public/img/assets/try.svg"; +import { useTheme } from "nextra-theme-docs"; import tryHereImageDark from "../../../../public/img/assets/try.dark.svg"; +import tryHereImageLight from "../../../../public/img/assets/try.svg"; const Demo = dynamic(() => import("@/components/pages/landing/hero/Demo"), { ssr: false, }); export function Hero() { + const { theme } = useTheme(); return (
@@ -62,7 +64,7 @@ export function Hero() {
{/* TODO: Wait for editor & collab content to load before rendering. Show placeholder or delay loading?*/} - + {/*
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/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..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,32 +25,41 @@ 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; }>; ``` -`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. `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/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/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/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 9b7c25b83b..f5ac8d505e 100644 --- a/examples/01-basic/01-minimal/App.tsx +++ b/examples/01-basic/01-minimal/App.tsx @@ -1,19 +1,10 @@ -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({ - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, - uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, - }); + const editor = useBlockNote({}); // Renders the editor instance using a React component. - return ; + 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/01-minimal/main.tsx b/examples/01-basic/01-minimal/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/01-basic/01-minimal/main.tsx +++ b/examples/01-basic/01-minimal/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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 66% rename from examples/01-basic/block-objects/App.tsx rename to examples/01-basic/02-block-objects/App.tsx index a081a39aab..20c0b0f003 100644 --- a/examples/01-basic/block-objects/App.tsx +++ b/examples/01-basic/02-block-objects/App.tsx @@ -1,6 +1,5 @@ import { Block, - BlockNoteEditor, DefaultBlockSchema, DefaultInlineContentSchema, DefaultStyleSchema, @@ -14,22 +13,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 = 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/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-objects/main.tsx b/examples/01-basic/02-block-objects/main.tsx similarity index 77% rename from examples/01-basic/block-objects/main.tsx rename to examples/01-basic/02-block-objects/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/01-basic/block-objects/main.tsx +++ b/examples/01-basic/02-block-objects/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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/keyboard-shortcuts/main.tsx b/examples/01-basic/03-block-manipulation/main.tsx similarity index 77% rename from examples/01-basic/keyboard-shortcuts/main.tsx rename to examples/01-basic/03-block-manipulation/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/01-basic/keyboard-shortcuts/main.tsx +++ b/examples/01-basic/03-block-manipulation/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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/04-saving-loading/App.tsx b/examples/01-basic/04-saving-loading/App.tsx new file mode 100644 index 0000000000..1ed4eb1ee3 --- /dev/null +++ b/examples/01-basic/04-saving-loading/App.tsx @@ -0,0 +1,53 @@ +import { PartialBlock } from "@blocknote/core"; +import { BlockNoteView, createBlockNoteEditor } from "@blocknote/react"; +import "@blocknote/react/style.css"; +import { useEffect, useMemo, useState } from "react"; + +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 + const storageString = localStorage.getItem("editorContent"); + return storageString + ? (JSON.parse(storageString) as PartialBlock[]) + : undefined; +} + +export default function App() { + const [initialContent, setInitialContent] = useState< + PartialBlock[] | undefined | "loading" + >("loading"); + + // Loads the previously stored editor contents + useEffect(() => { + loadFromStorage().then((content) => { + setInitialContent(content); + }); + }, []); + + // 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 === "loading") { + return undefined; + } + return createBlockNoteEditor({ initialContent }); + }, [initialContent]); + + if (editor === undefined) { + return "Loading content..."; + } + + // Renders the editor instance. + return ( + { + saveToStorage(editor.topLevelBlocks); + }} + /> + ); +} 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/saving-loading/main.tsx b/examples/01-basic/04-saving-loading/main.tsx similarity index 77% rename from examples/01-basic/saving-loading/main.tsx rename to examples/01-basic/04-saving-loading/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/01-basic/saving-loading/main.tsx +++ b/examples/01-basic/04-saving-loading/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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/08-interoperability/converting-blocks-from-html/index.html b/examples/01-basic/05-file-uploading/index.html similarity index 88% rename from examples/08-interoperability/converting-blocks-from-html/index.html rename to examples/01-basic/05-file-uploading/index.html index bdbd33f2c6..f7f774c5f1 100644 --- a/examples/08-interoperability/converting-blocks-from-html/index.html +++ b/examples/01-basic/05-file-uploading/index.html @@ -5,7 +5,7 @@ - Converting HTML to Blocks + File / image uploading
    diff --git a/examples/01-basic/block-manipulation/main.tsx b/examples/01-basic/05-file-uploading/main.tsx similarity index 77% rename from examples/01-basic/block-manipulation/main.tsx rename to examples/01-basic/05-file-uploading/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/01-basic/block-manipulation/main.tsx +++ b/examples/01-basic/05-file-uploading/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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 68% rename from examples/01-basic/keyboard-shortcuts/App.tsx rename to examples/01-basic/06-keyboard-shortcuts/App.tsx index b526b5b468..66a0e43d0b 100644 --- a/examples/01-basic/keyboard-shortcuts/App.tsx +++ b/examples/01-basic/06-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,13 +35,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) - ), - }); + const editor = useBlockNote({}); - return ; + const onKeyDown = (event: React.KeyboardEvent) => { + cycleBlocksShortcut(event, editor); + }; + + 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/01-basic/06-keyboard-shortcuts/main.tsx b/examples/01-basic/06-keyboard-shortcuts/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/01-basic/06-keyboard-shortcuts/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/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 44663ca816..0000000000 --- a/examples/01-basic/block-manipulation/App.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { BlockNoteEditor } from "@blocknote/core"; -import { BlockNoteView, useBlockNote } from "@blocknote/react"; -import "@blocknote/react/style.css"; - -export default function App() { - const editor: BlockNoteEditor = 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/.bnexample.json b/examples/01-basic/block-objects/.bnexample.json deleted file mode 100644 index 178fd44ce0..0000000000 --- a/examples/01-basic/block-objects/.bnexample.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "playground": true, - "docs": false, - "group": "Basic Examples", - "order": 1 -} 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/App.tsx b/examples/01-basic/saving-loading/App.tsx deleted file mode 100644 index e61906c32f..0000000000 --- a/examples/01-basic/saving-loading/App.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { BlockNoteEditor } from "@blocknote/core"; -import { BlockNoteView, useBlockNote } from "@blocknote/react"; -import "@blocknote/react/style.css"; - -// Gets the previously stored editor contents. -const initialContent: string | null = localStorage.getItem("editorContent"); - -export default function App() { - // 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) - ); - }, - }); - - // Renders the editor instance. - return ; -} 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/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/custom-ui/main.tsx b/examples/02-ui-components/custom-ui/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/02-ui-components/custom-ui/main.tsx +++ b/examples/02-ui-components/custom-ui/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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/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 ( - - // -); \ No newline at end of file + + + +); 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-buttons/main.tsx b/examples/02-ui-components/side-menu-buttons/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/02-ui-components/side-menu-buttons/main.tsx +++ b/examples/02-ui-components/side-menu-buttons/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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/side-menu-drag-handle-items/main.tsx b/examples/02-ui-components/side-menu-drag-handle-items/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/02-ui-components/side-menu-drag-handle-items/main.tsx +++ b/examples/02-ui-components/side-menu-drag-handle-items/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/02-ui-components/slash-menu-items/main.tsx b/examples/02-ui-components/slash-menu-items/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/02-ui-components/slash-menu-items/main.tsx +++ b/examples/02-ui-components/slash-menu-items/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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-remove/main.tsx b/examples/02-ui-components/ui-elements-remove/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/02-ui-components/ui-elements-remove/main.tsx +++ b/examples/02-ui-components/ui-elements-remove/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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/02-ui-components/ui-elements-replace/main.tsx b/examples/02-ui-components/ui-elements-replace/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/02-ui-components/ui-elements-replace/main.tsx +++ b/examples/02-ui-components/ui-elements-replace/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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/04-theming/changing-font/main.tsx b/examples/04-theming/changing-font/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/04-theming/changing-font/main.tsx +++ b/examples/04-theming/changing-font/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/04-theming/theming-css-variables-code/main.tsx b/examples/04-theming/theming-css-variables-code/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/04-theming/theming-css-variables-code/main.tsx +++ b/examples/04-theming/theming-css-variables-code/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/04-theming/theming-css-variables/main.tsx b/examples/04-theming/theming-css-variables/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/04-theming/theming-css-variables/main.tsx +++ b/examples/04-theming/theming-css-variables/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/04-theming/theming-css/main.tsx b/examples/04-theming/theming-css/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/04-theming/theming-css/main.tsx +++ b/examples/04-theming/theming-css/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/04-theming/theming-dom-attributes/main.tsx b/examples/04-theming/theming-dom-attributes/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/04-theming/theming-dom-attributes/main.tsx +++ b/examples/04-theming/theming-dom-attributes/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/05-cursor-selections/selection-blocks/App.tsx b/examples/05-cursor-selections/selection-blocks/App.tsx index 00bbac52fe..cebb15dc61 100644 --- a/examples/05-cursor-selections/selection-blocks/App.tsx +++ b/examples/05-cursor-selections/selection-blocks/App.tsx @@ -1,54 +1,56 @@ import { BlockNoteView, useBlockNote } from "@blocknote/react"; 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 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/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/selection-blocks/main.tsx b/examples/05-cursor-selections/selection-blocks/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/05-cursor-selections/selection-blocks/main.tsx +++ b/examples/05-cursor-selections/selection-blocks/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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/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/05-cursor-selections/text-cursor-block/main.tsx b/examples/05-cursor-selections/text-cursor-block/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/05-cursor-selections/text-cursor-block/main.tsx +++ b/examples/05-cursor-selections/text-cursor-block/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/06-custom-schema/alert-block/main.tsx b/examples/06-custom-schema/alert-block/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/06-custom-schema/alert-block/main.tsx +++ b/examples/06-custom-schema/alert-block/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/06-custom-schema/react-custom-blocks/App.tsx b/examples/06-custom-schema/react-custom-blocks/App.tsx index 75f0a6e62a..da714765d7 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, @@ -150,5 +144,5 @@ export default function App() { ], }); - return ; + return ; } diff --git a/examples/06-custom-schema/react-custom-blocks/main.tsx b/examples/06-custom-schema/react-custom-blocks/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/06-custom-schema/react-custom-blocks/main.tsx +++ b/examples/06-custom-schema/react-custom-blocks/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); 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..ff053c77cf 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", @@ -80,5 +74,5 @@ export default function ReactInlineContent() { ], }); - return ; + return ; } diff --git a/examples/06-custom-schema/react-custom-inline-content/main.tsx b/examples/06-custom-schema/react-custom-inline-content/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/06-custom-schema/react-custom-inline-content/main.tsx +++ b/examples/06-custom-schema/react-custom-inline-content/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/06-custom-schema/react-custom-styles/App.tsx b/examples/06-custom-schema/react-custom-styles/App.tsx index 67366ef9c1..3c4a13c0d3 100644 --- a/examples/06-custom-schema/react-custom-styles/App.tsx +++ b/examples/06-custom-schema/react-custom-styles/App.tsx @@ -87,15 +87,6 @@ export default function App() { const editor = useBlockNote( { styleSpecs: customReactStyles, - onEditorContentChange: (editor) => { - console.log(editor.topLevelBlocks); - }, - domAttributes: { - editor: { - class: "editor", - "data-test": "editor", - }, - }, initialContent: [ { type: "paragraph", @@ -122,7 +113,7 @@ export default function App() { ); return ( - + - - // -); \ No newline at end of file + + + +); diff --git a/examples/07-collaboration/partykit/App.tsx b/examples/07-collaboration/partykit/App.tsx index 6a5217a5da..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"; @@ -16,12 +15,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, @@ -33,8 +26,7 @@ export default function App() { color: "#ff0000", }, }, - uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY, }); - return ; + return ; } diff --git a/examples/07-collaboration/partykit/main.tsx b/examples/07-collaboration/partykit/main.tsx index 8327213ef0..f88b490fbd 100644 --- a/examples/07-collaboration/partykit/main.tsx +++ b/examples/07-collaboration/partykit/main.tsx @@ -5,7 +5,7 @@ import App from "./App"; const root = createRoot(document.getElementById("root")!); root.render( - // - - // -); \ No newline at end of file + + + +); diff --git a/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json b/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json new file mode 100644 index 0000000000..0993be7115 --- /dev/null +++ b/examples/08-interoperability/01-converting-blocks-to-html/.bnexample.json @@ -0,0 +1,4 @@ +{ + "playground": true, + "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 new file mode 100644 index 0000000000..6a61fc2a75 --- /dev/null +++ b/examples/08-interoperability/01-converting-blocks-to-html/App.tsx @@ -0,0 +1,42 @@ +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 with some initial content. + const editor = useBlockNote({ + initialContent: [ + { + type: "paragraph", + content: [ + "Hello, ", + { + type: "text", + text: "world!", + styles: { + bold: true, + }, + }, + ], + }, + ], + }); + + 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. + return ( +
    + +
    {html}
    +
    + ); +} diff --git a/examples/08-interoperability/01-converting-blocks-to-html/README.md b/examples/08-interoperability/01-converting-blocks-to-html/README.md new file mode 100644 index 0000000000..75da7363eb --- /dev/null +++ b/examples/08-interoperability/01-converting-blocks-to-html/README.md @@ -0,0 +1,9 @@ +# 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/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/01-converting-blocks-to-html/main.tsx b/examples/08-interoperability/01-converting-blocks-to-html/main.tsx new file mode 100644 index 0000000000..f88b490fbd --- /dev/null +++ b/examples/08-interoperability/01-converting-blocks-to-html/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-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/02-converting-blocks-from-html/.bnexample.json b/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json new file mode 100644 index 0000000000..0993be7115 --- /dev/null +++ b/examples/08-interoperability/02-converting-blocks-from-html/.bnexample.json @@ -0,0 +1,4 @@ +{ + "playground": true, + "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 new file mode 100644 index 0000000000..712eeea796 --- /dev/null +++ b/examples/08-interoperability/02-converting-blocks-from-html/App.tsx @@ -0,0 +1,39 @@ +import { BlockNoteView, useBlockNote } from "@blocknote/react"; +import "@blocknote/react/style.css"; +import { ChangeEvent, useCallback, useEffect } from "react"; + +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(() => { + async function loadInitialHTML() { + const blocks = await editor.tryParseHTMLToBlocks(initialHTML); + editor.replaceBlocks(editor.topLevelBlocks, blocks); + } + 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 ( +
    +