Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Examples tab #322

Merged
merged 24 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2a3d9fa
Added pages for examples in docs
matthewlipski Aug 16, 2023
934670d
Added alert block example
matthewlipski Aug 16, 2023
d709898
Added keyboard shortcuts example and moved save/load example
matthewlipski Aug 16, 2023
33ae77a
Added block manipulation example
matthewlipski Aug 17, 2023
9f919c4
Added block from tiptap node example
matthewlipski Aug 17, 2023
eeddcfa
Updated examples frontmatter
matthewlipski Aug 18, 2023
4169c12
Updated saving & loading text
matthewlipski Aug 18, 2023
8a0271f
Fixed alert block example dropdown z-index
matthewlipski Aug 18, 2023
00b55c0
Updated Alert block example
matthewlipski Aug 18, 2023
df74a98
Updated Alert block example
matthewlipski Aug 18, 2023
20fe61a
Added custom UI docs
matthewlipski Aug 21, 2023
ab4193f
Small fix
matthewlipski Aug 21, 2023
1b34683
Merge branch 'main' into docs-examples
matthewlipski Aug 21, 2023
0b16095
Merge branch 'main' into docs-examples
matthewlipski Aug 21, 2023
31be20f
Fixes for blocks without inline content
matthewlipski Aug 22, 2023
8415313
Block type dropdown fix
matthewlipski Aug 22, 2023
fcc00e8
Block type dropdown fix
matthewlipski Aug 22, 2023
33a8132
Block from tiptap node example fix
matthewlipski Aug 22, 2023
8d73dac
Block type dropdown fix
matthewlipski Aug 22, 2023
4a5bfe2
Added docs to block type dropdown
matthewlipski Aug 22, 2023
fbbfcb8
Updated alert block example
matthewlipski Aug 22, 2023
c03355c
Implemented PR feedback
matthewlipski Aug 22, 2023
876e3d5
Reverted `BNUpdateBlock` changes
matthewlipski Aug 23, 2023
7a7f702
Removed block from TipTap node example
matthewlipski Sep 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions packages/core/src/extensions/Blocks/nodes/BlockContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,42 @@ export const BlockContainer = Node.create<{
);
}

// Changes the blockContent node type and adds the provided props as attributes. Also preserves all existing
// attributes that are compatible with the new type.
state.tr.setNodeMarkup(
startPos,
block.type === undefined
? undefined
: state.schema.nodes[block.type],
{
...contentNode.attrs,
...block.props,
}
);
// Since some block types contain inline content and others don't,
// we either need to call setNodeMarkup to just update type &
// attributes, or replaceWith to replace the whole blockContent.
const oldType = contentNode.type.name;
const newType = block.type || oldType;

const oldContentType = state.schema.nodes[oldType].spec.content;
const newContentType = state.schema.nodes[newType].spec.content;

if (oldContentType === "inline*" && newContentType === "") {
// Replaces the blockContent node with one of the new type and
// adds the provided props as attributes. Also preserves all
// existing attributes that are compatible with the new type.
state.tr.replaceWith(
matthewlipski marked this conversation as resolved.
Show resolved Hide resolved
startPos,
endPos,
state.schema.nodes[newType].create({
...contentNode.attrs,
...block.props,
})
);
} else {
// Changes the blockContent node type and adds the provided props
// as attributes. Also preserves all existing attributes that are
// compatible with the new type.
state.tr.setNodeMarkup(
startPos,
block.type === undefined
? undefined
: state.schema.nodes[block.type],
{
...contentNode.attrs,
...block.props,
}
);
}

// Adds all provided props as attributes to the parent blockContainer node too, and also preserves existing
// attributes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
return [
"div",
mergeAttributes(HTMLAttributes, {
...blockContentDOMAttributes,
class: mergeCSSClasses(
styles.blockContent,
blockContentDOMAttributes.class
Expand All @@ -82,6 +83,7 @@ export const HeadingBlockContent = createTipTapBlock<"heading">({
[
"h" + node.attrs.level,
{
...inlineContentDOMAttributes,
class: mergeCSSClasses(
styles.inlineContent,
inlineContentDOMAttributes.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
return [
"div",
mergeAttributes(HTMLAttributes, {
...blockContentDOMAttributes,
class: mergeCSSClasses(
styles.blockContent,
blockContentDOMAttributes.class
Expand All @@ -100,6 +101,7 @@ export const BulletListItemBlockContent = createTipTapBlock<"bulletListItem">({
[
"p",
{
...inlineContentDOMAttributes,
class: mergeCSSClasses(
styles.inlineContent,
inlineContentDOMAttributes.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export const NumberedListItemBlockContent =
return [
"div",
mergeAttributes(HTMLAttributes, {
...blockContentDOMAttributes,
class: mergeCSSClasses(
styles.blockContent,
blockContentDOMAttributes.class
Expand All @@ -126,6 +127,7 @@ export const NumberedListItemBlockContent =
[
"p",
{
...inlineContentDOMAttributes,
class: mergeCSSClasses(
styles.inlineContent,
inlineContentDOMAttributes.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,19 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
if (!lastNode || lastNode.type.name !== "blockContainer") {
throw new Error("Expected blockContainer");
}
return lastNode.nodeSize > 4; // empty <block><content/></block> is length 4

const lastContentNode = lastNode.firstChild;

if (!lastContentNode) {
throw new Error("Expected blockContent");
}

// If last node is not empty (size > 4) or it doesn't contain
// inline content, we need to add a trailing node.
return (
matthewlipski marked this conversation as resolved.
Show resolved Hide resolved
lastNode.nodeSize > 4 ||
lastContentNode.type.spec.content !== "inline*"
);
},
},
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { useMemo, useState } from "react";
import { BlockNoteEditor, BlockSchema } from "@blocknote/core";
import {
Block,
BlockNoteEditor,
BlockSchema,
PartialBlock,
} from "@blocknote/core";
import { IconType } from "react-icons";
import {
RiH1,
Expand All @@ -20,41 +25,57 @@ export type BlockTypeDropdownItem = {
type: string;
props?: Record<string, string>;
icon: IconType;
isSelected: (block: Block<BlockSchema>) => boolean;
};

export const defaultBlockTypeDropdownItems: BlockTypeDropdownItem[] = [
{
name: "Paragraph",
type: "paragraph",
icon: RiText,
isSelected: (block) => block.type === "paragraph",
},
{
name: "Heading 1",
type: "heading",
props: { level: "1" },
icon: RiH1,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === "1",
},
{
name: "Heading 2",
type: "heading",
props: { level: "2" },
icon: RiH2,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === "2",
},
{
name: "Heading 3",
type: "heading",
props: { level: "3" },
icon: RiH3,
isSelected: (block) =>
block.type === "heading" &&
"level" in block.props &&
block.props.level === "3",
},
{
name: "Bullet List",
type: "bulletListItem",
icon: RiListUnordered,
isSelected: (block) => block.type === "bulletListItem",
},
{
name: "Numbered List",
type: "numberedListItem",
icon: RiListOrdered,
isSelected: (block) => block.type === "numberedListItem",
},
];

Expand Down Expand Up @@ -100,22 +121,22 @@ export const BlockTypeDropdown = <BSchema extends BlockSchema>(props: {
[block.type, filteredItems]
);

const fullItems: ToolbarDropdownItemProps[] = useMemo(
() =>
filteredItems.map((item) => ({
text: item.name,
icon: item.icon,
onClick: () => {
props.editor.focus();
props.editor.updateBlock(block, {
type: item.type,
props: {},
});
},
isSelected: block.type === item.type,
})),
[block, filteredItems, props.editor]
);
const fullItems: ToolbarDropdownItemProps[] = useMemo(() => {
const onClick = (item: BlockTypeDropdownItem) => {
props.editor.focus();
props.editor.updateBlock(block, {
type: item.type,
props: item.props,
} as PartialBlock<BlockSchema>);
};

return filteredItems.map((item) => ({
text: item.name,
icon: item.icon,
onClick: () => onClick(item),
isSelected: item.isSelected(block as Block<BlockSchema>),
}));
}, [block, filteredItems, props.editor]);

useEditorContentChange(props.editor, () => {
setBlock(props.editor.getTextCursorPosition().block);
Expand Down
36 changes: 35 additions & 1 deletion packages/website/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,36 @@ const SIDEBAR_DEFAULT = [
},
];

const EXAMPLES_SIDEBAR = [
{
text: "Basic Examples",
items: [
{ text: "Block Manipulation", link: "/examples/block-manipulation" },
{
text: "Keyboard Shortcuts",
link: "/examples/keyboard-shortcuts",
},
{
text: "Saving & Loading",
link: "/examples/saving-loading",
},
],
},
{
text: "Advanced Examples",
items: [
{
text: "Making UI Elements From Scratch",
link: "/examples/custom-ui",
},
{
text: "Alert Block",
link: "/examples/alert-block",
},
],
},
];

const METADATA_DEFAULT = {
title: "BlockNote",
description:
Expand Down Expand Up @@ -154,9 +184,13 @@ export default defineConfig({
light: "/img/logos/banner.svg",
dark: "/img/logos/banner.dark.svg",
},
nav: [{ text: "Documentation", link: "/docs/introduction" }],
nav: [
{ text: "Documentation", link: "/docs/introduction" },
{ text: "Examples", link: "/examples/block-manipulation" },
],
sidebar: {
"/docs/": SIDEBAR_DEFAULT,
"/examples/": EXAMPLES_SIDEBAR,
// "/tutorial/": SIDEBAR_DEFAULT,
// "/api": SIDEBAR_DEFAULT,
},
Expand Down
2 changes: 1 addition & 1 deletion packages/website/docs/docs/block-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ path: /docs/block-types

<script setup>
import { useData } from 'vitepress';
import { getTheme, getStyles } from "./demoUtils";
import { getTheme, getStyles } from "../demoUtils";

const { isDark } = useData();
</script>
Expand Down
2 changes: 1 addition & 1 deletion packages/website/docs/docs/blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ path: /docs/blocks

<script setup>
import { useData } from 'vitepress';
import { getTheme, getStyles } from "./demoUtils";
import { getTheme, getStyles } from "../demoUtils";

const { isDark } = useData();
</script>
Expand Down
2 changes: 1 addition & 1 deletion packages/website/docs/docs/converting-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ path: /docs/converting-blocks

<script setup>
import { useData } from 'vitepress';
import { getTheme, getStyles } from "./demoUtils";
import { getTheme, getStyles } from "../demoUtils";

const { isDark } = useData();
</script>
Expand Down
2 changes: 1 addition & 1 deletion packages/website/docs/docs/cursor-selections.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ path: /docs/cursor-selections

<script setup>
import { useData } from 'vitepress';
import { getTheme, getStyles } from "./demoUtils";
import { getTheme, getStyles } from "../demoUtils";

const { isDark } = useData();
</script>
Expand Down
51 changes: 2 additions & 49 deletions packages/website/docs/docs/editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ path: /docs/editor

<script setup>
import { useData } from 'vitepress';
import { getTheme, getStyles } from "./demoUtils";
import { getTheme, getStyles } from "../demoUtils";

const { isDark } = useData();
</script>
Expand All @@ -31,8 +31,6 @@ export type BlockNoteEditorOptions = Partial<{
onEditorContentChange: (editor: BlockNoteEditor) => void;
onTextCursorPositionChange: (editor: BlockNoteEditor) => void;
slashMenuItems: ReactSlashMenuItem[];
customElements: CustomElements;
uiFactories: UiFactories;
defaultStyles: boolean;
}>;
```
Expand All @@ -41,7 +39,7 @@ export type BlockNoteEditorOptions = Partial<{

`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).

`editorDOMAttributes:` An object containing attributes that should be added to the editor's HTML element. For example, you can pass `{ class: "my-editor-class" }` to set a custom class name.
`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.

Expand All @@ -51,49 +49,4 @@ export type BlockNoteEditorOptions = Partial<{

`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.

`customElements:` React components for a custom [Formatting Toolbar](/docs/formatting-toolbar#custom-formatting-toolbar) and/or [Drag Handle Menu](/docs/side-menu#custom-drag-handle-menu) to use.

`uiFactories:` UI element factories for creating a custom UI, including custom positioning & rendering. You can find out more about UI factories in [Creating Your Own UI Elements](/docs/vanilla-js#creating-your-own-ui-elements).

`defaultStyles`: Whether to use the default font and reset the styles of `<p>`, `<li>`, `<h1>`, etc. elements that are used in BlockNote. Defaults to true if undefined.

## Demo: Saving & Restoring Editor Contents

By default, BlockNote doesn't preserve the editor contents when your app is reopened or refreshed. However, using the editor options, you can change this by using the editor options.

In the example below, we use the `onEditorContentChange` option to save the editor contents in local storage whenever they change, then pass them to `initialContent` whenever the page is reloaded.

::: sandbox {template=react-ts}

```typescript-vue /App.tsx
import { BlockNoteEditor } from "@blocknote/core";
import { BlockNoteView, useBlockNote } from "@blocknote/react";
import "@blocknote/core/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 | null = 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 <BlockNoteView editor={editor} theme={"{{ getTheme(isDark) }}"} />;
}
```

```css-vue /styles.css [hidden]
{{ getStyles(isDark) }}
```

:::
Loading
Loading