Skip to content

Commit a52ffce

Browse files
Playground custom elements (#430)
* Added playgrounds for vanilla blocks, React blocks, and vanilla inline content * fix renderHTML error * clean examples --------- Co-authored-by: yousefed <[email protected]>
1 parent b28c2c9 commit a52ffce

File tree

10 files changed

+498
-4
lines changed

10 files changed

+498
-4
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { defaultBlockSpecs, defaultProps } from "@blocknote/core";
2+
import "@blocknote/core/style.css";
3+
import {
4+
BlockNoteView,
5+
createReactBlockSpec,
6+
useBlockNote,
7+
} from "@blocknote/react";
8+
9+
type WindowWithProseMirror = Window & typeof globalThis & { ProseMirror: any };
10+
11+
// The types of alerts that users can choose from
12+
const alertTypes = {
13+
warning: {
14+
icon: "⚠️",
15+
color: "#e69819",
16+
backgroundColor: "#fff6e6",
17+
},
18+
error: {
19+
icon: "⛔",
20+
color: "#d80d0d",
21+
backgroundColor: "#ffe6e6",
22+
},
23+
info: {
24+
icon: "ℹ️",
25+
color: "#507aff",
26+
backgroundColor: "#e6ebff",
27+
},
28+
success: {
29+
icon: "✅",
30+
color: "#0bc10b",
31+
backgroundColor: "#e6ffe6",
32+
},
33+
};
34+
35+
export const alertBlock = createReactBlockSpec(
36+
{
37+
type: "alert",
38+
propSchema: {
39+
textAlignment: defaultProps.textAlignment,
40+
textColor: defaultProps.textColor,
41+
type: {
42+
default: "warning" as const,
43+
values: ["warning", "error", "info", "success"] as const,
44+
},
45+
},
46+
content: "inline",
47+
},
48+
{
49+
render: (props) => (
50+
<div
51+
style={{
52+
display: "flex",
53+
justifyContent: "center",
54+
alignItems: "center",
55+
flexGrow: "1",
56+
height: "48px",
57+
padding: "4px",
58+
maxWidth: "100%",
59+
backgroundColor: alertTypes[props.block.props.type].backgroundColor,
60+
}}>
61+
<select
62+
contentEditable={false}
63+
value={props.block.props.type}
64+
onChange={(event) => {
65+
props.editor.updateBlock(props.block, {
66+
type: "alert",
67+
props: { type: event.target.value as keyof typeof alertTypes },
68+
});
69+
}}>
70+
<option value="warning">{alertTypes["warning"].icon}</option>
71+
<option value="error">{alertTypes["error"].icon}</option>
72+
<option value="info">{alertTypes["info"].icon}</option>
73+
<option value="success">{alertTypes["success"].icon}</option>
74+
</select>
75+
<div style={{ flexGrow: 1 }} ref={props.contentRef} />
76+
</div>
77+
),
78+
}
79+
);
80+
81+
export const bracketsParagraphBlock = createReactBlockSpec(
82+
{
83+
type: "bracketsParagraph",
84+
content: "inline",
85+
propSchema: {
86+
...defaultProps,
87+
},
88+
},
89+
{
90+
render: (props) => (
91+
<div
92+
style={{
93+
display: "flex",
94+
justifyContent: "center",
95+
alignItems: "center",
96+
flexGrow: "1",
97+
height: "48px",
98+
padding: "4px",
99+
maxWidth: "100%",
100+
}}>
101+
<div contentEditable={"false"}>{"["}</div>
102+
<span contentEditable={"false"}>{"{"}</span>
103+
<div style={{ flexGrow: 1 }} ref={props.contentRef} />
104+
<span contentEditable={"false"}>{"}"}</span>
105+
<div contentEditable={"false"}>{"]"}</div>
106+
</div>
107+
),
108+
}
109+
);
110+
111+
export function ReactCustomBlocks() {
112+
const editor = useBlockNote({
113+
domAttributes: {
114+
editor: {
115+
class: "editor",
116+
"data-test": "editor",
117+
},
118+
},
119+
blockSpecs: {
120+
...defaultBlockSpecs,
121+
alert: alertBlock,
122+
bracketsParagraph: bracketsParagraphBlock,
123+
},
124+
initialContent: [
125+
{
126+
type: "alert",
127+
props: {
128+
type: "success",
129+
},
130+
content: "Alert",
131+
},
132+
{
133+
type: "bracketsParagraph",
134+
content: "Brackets Paragraph",
135+
},
136+
],
137+
});
138+
139+
// Give tests a way to get prosemirror instance
140+
(window as WindowWithProseMirror).ProseMirror = editor?._tiptapEditor;
141+
142+
return <BlockNoteView className="root" editor={editor} />;
143+
}

examples/editor/examples/ReactInlineContent.tsx renamed to examples/editor/examples/react-custom-inline-content/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ export function ReactInlineContent() {
7575
"I love ",
7676
{
7777
type: "tag",
78-
// props: {},
7978
content: "BlockNote",
8079
} as any,
8180
],
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import {
2+
createBlockSpec,
3+
defaultBlockSpecs,
4+
defaultProps,
5+
} from "@blocknote/core";
6+
import "@blocknote/core/style.css";
7+
import { BlockNoteView, useBlockNote } from "@blocknote/react";
8+
9+
type WindowWithProseMirror = Window & typeof globalThis & { ProseMirror: any };
10+
11+
// The types of alerts that users can choose from
12+
const alertTypes = {
13+
warning: {
14+
icon: "⚠️",
15+
color: "#e69819",
16+
backgroundColor: "#fff6e6",
17+
},
18+
error: {
19+
icon: "⛔",
20+
color: "#d80d0d",
21+
backgroundColor: "#ffe6e6",
22+
},
23+
info: {
24+
icon: "ℹ️",
25+
color: "#507aff",
26+
backgroundColor: "#e6ebff",
27+
},
28+
success: {
29+
icon: "✅",
30+
color: "#0bc10b",
31+
backgroundColor: "#e6ffe6",
32+
},
33+
};
34+
35+
const alertBlock = createBlockSpec(
36+
{
37+
type: "alert",
38+
propSchema: {
39+
textAlignment: defaultProps.textAlignment,
40+
textColor: defaultProps.textColor,
41+
type: {
42+
default: "warning" as const,
43+
values: ["warning", "error", "info", "success"] as const,
44+
},
45+
},
46+
content: "inline",
47+
},
48+
{
49+
render: (block, editor) => {
50+
const alert = document.createElement("div");
51+
Object.entries(alertStyles).forEach(([key, value]) => {
52+
alert.style[key as any] = value;
53+
});
54+
alert.style.backgroundColor =
55+
alertTypes[block.props.type].backgroundColor;
56+
57+
const dropdown = document.createElement("select");
58+
dropdown.contentEditable = "false";
59+
dropdown.addEventListener("change", () => {
60+
// TODO: Something is not quite right with the typing seems like
61+
editor.updateBlock(block, {
62+
type: "alert",
63+
props: { type: dropdown.value as keyof typeof alertTypes },
64+
});
65+
});
66+
dropdown.options.add(
67+
new Option(
68+
alertTypes["warning"].icon,
69+
"warning",
70+
block.props.type === "warning",
71+
block.props.type === "warning"
72+
)
73+
);
74+
dropdown.options.add(
75+
new Option(
76+
alertTypes["error"].icon,
77+
"error",
78+
block.props.type === "error",
79+
block.props.type === "error"
80+
)
81+
);
82+
dropdown.options.add(
83+
new Option(
84+
alertTypes["info"].icon,
85+
"info",
86+
block.props.type === "info",
87+
block.props.type === "info"
88+
)
89+
);
90+
dropdown.options.add(
91+
new Option(
92+
alertTypes["success"].icon,
93+
"success",
94+
block.props.type === "success",
95+
block.props.type === "success"
96+
)
97+
);
98+
alert.appendChild(dropdown);
99+
100+
const inlineContent = document.createElement("div");
101+
inlineContent.style.flexGrow = "1";
102+
103+
alert.appendChild(inlineContent);
104+
105+
return {
106+
dom: alert,
107+
contentDOM: inlineContent,
108+
};
109+
},
110+
}
111+
);
112+
113+
// TODO: use CSS?
114+
const alertStyles = {
115+
display: "flex",
116+
justifyContent: "center",
117+
alignItems: "center",
118+
flexGrow: "1",
119+
height: "48px",
120+
padding: "4px",
121+
maxWidth: "100%",
122+
};
123+
124+
const bracketsParagraphBlock = createBlockSpec(
125+
{
126+
type: "bracketsParagraph",
127+
content: "inline",
128+
propSchema: {
129+
...defaultProps,
130+
},
131+
},
132+
{
133+
render: () => {
134+
const bracketsParagraph = document.createElement("div");
135+
Object.entries(bracketsParagraphStyles).forEach(([key, value]) => {
136+
bracketsParagraph.style[key as any] = value;
137+
});
138+
139+
const leftBracket = document.createElement("div");
140+
leftBracket.contentEditable = "false";
141+
leftBracket.innerText = "[";
142+
bracketsParagraph.appendChild(leftBracket);
143+
const leftCurlyBracket = document.createElement("span");
144+
leftCurlyBracket.contentEditable = "false";
145+
leftCurlyBracket.innerText = "{";
146+
bracketsParagraph.appendChild(leftCurlyBracket);
147+
148+
const inlineContent = document.createElement("div");
149+
inlineContent.style.flexGrow = "1";
150+
151+
bracketsParagraph.appendChild(inlineContent);
152+
153+
const rightCurlyBracket = document.createElement("span");
154+
rightCurlyBracket.contentEditable = "false";
155+
rightCurlyBracket.innerText = "}";
156+
bracketsParagraph.appendChild(rightCurlyBracket);
157+
const rightBracket = document.createElement("div");
158+
rightBracket.contentEditable = "false";
159+
rightBracket.innerText = "]";
160+
bracketsParagraph.appendChild(rightBracket);
161+
162+
return {
163+
dom: bracketsParagraph,
164+
contentDOM: inlineContent,
165+
};
166+
},
167+
}
168+
);
169+
170+
// TODO: use CSS
171+
const bracketsParagraphStyles = {
172+
display: "flex",
173+
justifyContent: "center",
174+
alignItems: "center",
175+
flexGrow: "1",
176+
height: "48px",
177+
padding: "4px",
178+
maxWidth: "100%",
179+
};
180+
181+
export function CustomBlocks() {
182+
const editor = useBlockNote({
183+
domAttributes: {
184+
editor: {
185+
class: "editor",
186+
"data-test": "editor",
187+
},
188+
},
189+
blockSpecs: {
190+
...defaultBlockSpecs,
191+
alert: alertBlock,
192+
bracketsParagraph: bracketsParagraphBlock,
193+
},
194+
initialContent: [
195+
{
196+
type: "alert",
197+
props: {
198+
type: "success",
199+
},
200+
content: ["Alert"],
201+
},
202+
{
203+
type: "bracketsParagraph",
204+
content: "Brackets Paragraph",
205+
},
206+
],
207+
});
208+
209+
// Give tests a way to get prosemirror instance
210+
(window as WindowWithProseMirror).ProseMirror = editor?._tiptapEditor;
211+
212+
return <BlockNoteView className="root" editor={editor} />;
213+
}

0 commit comments

Comments
 (0)