Skip to content
New issue

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

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

Already on GitHub? # to your account

feat: Custom block serialization #257

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9aa6657
Added serialization for vanilla custom blocks
matthewlipski Jun 22, 2023
bc30dbd
Added serialization for React custom blocks
matthewlipski Jun 22, 2023
b4f3fb6
Cleaned up serializer implementation - no longer uses function override
matthewlipski Jun 23, 2023
a9baab2
Revert "Cleaned up serializer implementation - no longer uses functio…
matthewlipski Jun 23, 2023
736d4d9
Removed comment
matthewlipski Jun 23, 2023
21bce02
Added ability to set custom serialization and parse functions for cus…
matthewlipski Jun 23, 2023
eb44af4
Merge branch 'main' into custom-block-serialization
matthewlipski Oct 10, 2023
84463bf
Fixed build and most runtime issues
matthewlipski Oct 11, 2023
16a2948
Added `react-dom` dependency
matthewlipski Oct 11, 2023
1a38a60
Added PoC copy/paste handling
matthewlipski Oct 30, 2023
9cd58f6
Small changes & fixes
matthewlipski Nov 2, 2023
4c055f7
Added serialization tests
matthewlipski Nov 2, 2023
3a7d3dc
Changed copy/paste implementation
matthewlipski Nov 2, 2023
cb0b231
Small fix
matthewlipski Nov 3, 2023
e121c31
Implemented PR feedback
matthewlipski Nov 5, 2023
3d56aaf
Converted styles from modules to regular CSS
matthewlipski Nov 6, 2023
4569e36
Implemented PR feedback
matthewlipski Nov 7, 2023
b9b799a
Updated serialization test snapshots
matthewlipski Nov 7, 2023
adc9bce
Updated serialization tests to use BlockNote API
matthewlipski Nov 7, 2023
960a818
Commented out custom block parsing code (out of scope for this PR)
matthewlipski Nov 7, 2023
07765ff
Improved `nodeToBlock` typing
matthewlipski Nov 7, 2023
e3016be
Small fixes
matthewlipski Nov 7, 2023
98f5dc7
Fixed `destroy` function not getting passed to TipTap node view
matthewlipski Nov 7, 2023
b7b79b1
Updated comment regarding clipboard issues
matthewlipski Nov 7, 2023
30eed3c
Major restructure of copy/paste code
matthewlipski Nov 9, 2023
111e591
Reduced code duplication for HTML serializer & exporter
matthewlipski Nov 9, 2023
6229877
Implemented PR feedback & cleaned up CSS class names
matthewlipski Nov 9, 2023
0edc70c
Updated serialization unit test snapshots
matthewlipski Nov 9, 2023
f649447
Changed `DOMOutputSpec` implementation for default blocks
matthewlipski Nov 10, 2023
9d110fe
Fixed some CSS issues
matthewlipski Nov 10, 2023
cce7a36
Implemented PR feedback
matthewlipski Nov 10, 2023
3a7139f
Reverted `nodeToBlock` typing
matthewlipski Nov 10, 2023
9067c9e
Made external HTML conversions no longer `async` and fixed Firefox cl…
matthewlipski Nov 10, 2023
8f98022
Fixed image test issues and small changes
matthewlipski Nov 10, 2023
5cda4ea
Fixed remaining image test issues
matthewlipski Nov 10, 2023
28566e3
Merge branch 'main' into custom-block-serialization
matthewlipski Nov 10, 2023
b398795
Updated serialization unit test snapshots
matthewlipski Nov 10, 2023
4aa4871
Excluded `formatConversions` test
matthewlipski Nov 10, 2023
2d95f48
Fixed HTML export for custom blocks with inline content
matthewlipski Nov 13, 2023
07a3fba
Fixed duplicate `blockContainer` attributes getting added to custom b…
matthewlipski Nov 13, 2023
a391ac0
Added React serialization unit tests and extra vanilla tests
matthewlipski Nov 13, 2023
f2e21f6
Updated image e2e snapshots
matthewlipski Nov 13, 2023
50fdd67
Small e2e test fix
matthewlipski Nov 14, 2023
786b3d6
Added comments
matthewlipski Nov 14, 2023
755a574
Fixed error when copying only nested blocks
matthewlipski Nov 16, 2023
650a9ac
refactor types for blocks (#412)
YousefED Nov 29, 2023
f93e33a
Merge remote-tracking branch 'origin/main' into custom-block-serializ…
YousefED Nov 29, 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
3 changes: 0 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ on:
- main
pull_request:
types: [opened, synchronize, reopened, edited]
branches:
- main
- "project/**"

jobs:
build:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
// import logo from './logo.svg'
import "@blocknote/core/style.css";
import { BlockNoteView, useBlockNote } from "@blocknote/react";
import styles from "./App.module.css";

import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core";

type WindowWithProseMirror = Window & typeof globalThis & { ProseMirror: any };

function App() {
export function App() {
const editor = useBlockNote({
onEditorContentChange: (editor) => {
console.log(editor.topLevelBlocks);
},
domAttributes: {
editor: {
class: styles.editor,
class: "editor",
"data-test": "editor",
},
},
Expand All @@ -23,7 +19,7 @@ function App() {
// Give tests a way to get prosemirror instance
(window as WindowWithProseMirror).ProseMirror = editor?._tiptapEditor;

return <BlockNoteView editor={editor} />;
return <BlockNoteView className="root" editor={editor} />;
}

export default App;
48 changes: 48 additions & 0 deletions examples/editor/examples/Collaboration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import "@blocknote/core/style.css";
import { BlockNoteView, useBlockNote } from "@blocknote/react";

import { uploadToTmpFilesDotOrg_DEV_ONLY } from "@blocknote/core";

import YPartyKitProvider from "y-partykit/provider";
import * as Y from "yjs";

const doc = new Y.Doc();

const provider = new YPartyKitProvider(
"blocknote-dev.yousefed.partykit.dev",
// use a unique name as a "room" for your application:
"your-project-name",
doc
);

type WindowWithProseMirror = Window & typeof globalThis & { ProseMirror: any };

export function App() {
const editor = useBlockNote({
domAttributes: {
editor: {
class: "editor",
"data-test": "editor",
},
},
collaboration: {
// The Yjs Provider responsible for transporting updates:
provider,
// Where to store BlockNote data in the Y.Doc:
fragment: doc.getXmlFragment("document-storesss"),
// Information (name and color) for this user:
user: {
name: "My Username",
color: "#ff0000",
},
},
uploadFile: uploadToTmpFilesDotOrg_DEV_ONLY,
});

// Give tests a way to get prosemirror instance
(window as WindowWithProseMirror).ProseMirror = editor?._tiptapEditor;

return <BlockNoteView className="root" editor={editor} />;
}

export default App;
90 changes: 90 additions & 0 deletions examples/editor/examples/ReactInlineContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { defaultInlineContentSpecs } from "@blocknote/core";
import "@blocknote/core/style.css";
import {
BlockNoteView,
createReactInlineContentSpec,
useBlockNote,
} from "@blocknote/react";

type WindowWithProseMirror = Window & typeof globalThis & { ProseMirror: any };

const mention = createReactInlineContentSpec(
{
type: "mention",
propSchema: {
user: {
default: "",
},
},
content: "none",
},
{
render: (props) => {
return <span>@{props.inlineContent.props.user}</span>;
},
}
);

const tag = createReactInlineContentSpec(
{
type: "tag",
propSchema: {},
content: "styled",
},
{
render: (props) => {
return (
<span>
#<span ref={props.contentRef}></span>
</span>
);
},
}
);

export function ReactInlineContent() {
const editor = useBlockNote({
inlineContentSpecs: {
mention,
tag,
...defaultInlineContentSpecs,
},
domAttributes: {
editor: {
class: "editor",
"data-test": "editor",
},
},
initialContent: [
{
type: "paragraph",
content: [
"I enjoy working with ",
{
type: "mention",
props: {
user: "Matthew",
},
content: undefined,
} as any,
],
},
{
type: "paragraph",
content: [
"I love ",
{
type: "tag",
// props: {},
content: "BlockNote",
} as any,
],
},
],
});

// Give tests a way to get prosemirror instance
(window as WindowWithProseMirror).ProseMirror = editor?._tiptapEditor;

return <BlockNoteView className="root" editor={editor} />;
}
138 changes: 138 additions & 0 deletions examples/editor/examples/ReactStyles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import "@blocknote/core/style.css";
import {
BlockNoteView,
FormattingToolbarPositioner,
Toolbar,
ToolbarButton,
createReactStyleSpec,
useActiveStyles,
useBlockNote,
} from "@blocknote/react";

import {
BlockNoteEditor,
DefaultBlockSchema,
DefaultInlineContentSchema,
StyleSchemaFromSpecs,
defaultStyleSpecs,
} from "@blocknote/core";

type WindowWithProseMirror = Window & typeof globalThis & { ProseMirror: any };

const small = createReactStyleSpec(
{
type: "small",
propSchema: "boolean",
},
{
render: (props) => {
return <small ref={props.contentRef}></small>;
},
}
);

const fontSize = createReactStyleSpec(
{
type: "fontSize",
propSchema: "string",
},
{
render: (props) => {
return (
<span ref={props.contentRef} style={{ fontSize: props.value }}></span>
);
},
}
);

const customReactStyles = {
...defaultStyleSpecs,
small,
fontSize,
};

type MyEditorType = BlockNoteEditor<
DefaultBlockSchema,
DefaultInlineContentSchema,
StyleSchemaFromSpecs<typeof customReactStyles>
>;

const CustomFormattingToolbar = (props: { editor: MyEditorType }) => {
const activeStyles = useActiveStyles(props.editor);

return (
<Toolbar>
<ToolbarButton
mainTooltip={"small"}
onClick={() => {
props.editor.toggleStyles({
small: true,
});
}}
isSelected={activeStyles.small}>
Small
</ToolbarButton>
<ToolbarButton
mainTooltip={"font size"}
onClick={() => {
props.editor.toggleStyles({
fontSize: "30px",
});
}}
isSelected={!!activeStyles.fontSize}>
Font size
</ToolbarButton>
</Toolbar>
);
};

export function ReactStyles() {
const editor = useBlockNote(
{
styleSpecs: customReactStyles,
onEditorContentChange: (editor) => {
console.log(editor.topLevelBlocks);
},
domAttributes: {
editor: {
class: "editor",
"data-test": "editor",
},
},
initialContent: [
{
type: "paragraph",
content: [
{
type: "text",
text: "large text",
styles: {
fontSize: "30px",
},
},
{
type: "text",
text: "small text",
styles: {
small: true,
},
},
],
},
],
},
[]
);

// Give tests a way to get prosemirror instance
(window as WindowWithProseMirror).ProseMirror = editor?._tiptapEditor;

return (
<BlockNoteView className="root" editor={editor}>
<FormattingToolbarPositioner
editor={editor}
formattingToolbar={CustomFormattingToolbar}
/>
</BlockNoteView>
);
}
6 changes: 5 additions & 1 deletion examples/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
"dependencies": {
"@blocknote/core": "^0.9.6",
"@blocknote/react": "^0.9.6",
"@mantine/core": "^5.6.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.0",
"y-partykit": "^0.0.0-4c022c1",
"yjs": "^13.6.10"
},
"devDependencies": {
"@types/react": "^18.0.25",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,12 @@
margin: 0 calc((100% - 731px) / 2);
height: 100%;
}

body {
margin: 0;
}

.root {
height: 100%;
width: 100%;
}
Loading