Skip to content

Commit e9c682d

Browse files
committed
Merge branch 'main' into new-release
2 parents 50bc584 + 7ca714c commit e9c682d

File tree

16 files changed

+615
-181
lines changed

16 files changed

+615
-181
lines changed

examples/01-basic/04-default-blocks/App.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,5 @@ export default function App() {
138138
});
139139

140140
// Renders the editor instance using a React component.
141-
return (
142-
<div
143-
style={{
144-
height: "50vh",
145-
overflow: "scroll",
146-
}}>
147-
<BlockNoteView editor={editor} />
148-
</div>
149-
);
141+
return <BlockNoteView editor={editor} />;
150142
}

packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,17 @@ function updateBlockContentNode<
141141
content = inlineContentToNodes(
142142
[block.content],
143143
state.schema,
144-
editor.schema.styleSchema
144+
editor.schema.styleSchema,
145+
newNodeType.name
145146
);
146147
} else if (Array.isArray(block.content)) {
147148
// Adds a text node with the provided styles converted into marks to the content,
148149
// for each InlineContent object.
149150
content = inlineContentToNodes(
150151
block.content,
151152
state.schema,
152-
editor.schema.styleSchema
153+
editor.schema.styleSchema,
154+
newNodeType.name
153155
);
154156
} else if (block.content.type === "tableContent") {
155157
content = tableContentToNodes(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<pre><code class="bn-inline-content language-javascript">const hello = 'world';<br>console.log(hello);<br></code></pre>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="bn-block-group" data-node-type="blockGroup"><div class="bn-block-outer" data-node-type="blockOuter" data-id="1"><div class="bn-block" data-node-type="blockContainer" data-id="1"><div class="bn-block-content" data-content-type="codeBlock"><pre><code class="bn-inline-content language-javascript">const hello = 'world';
2+
console.log(hello);
3+
</code></pre></div></div></div></div>

packages/core/src/api/exporters/html/util/serializeBlocksInternalHTML.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export function serializeInlineContentInternalHTML<
2121
editor: BlockNoteEditor<any, I, S>,
2222
blockContent: PartialBlock<BSchema, I, S>["content"],
2323
serializer: DOMSerializer,
24+
blockType?: string,
2425
options?: { document?: Document }
2526
) {
2627
let nodes: any;
@@ -32,13 +33,15 @@ export function serializeInlineContentInternalHTML<
3233
nodes = inlineContentToNodes(
3334
[blockContent],
3435
editor.pmSchema,
35-
editor.schema.styleSchema
36+
editor.schema.styleSchema,
37+
blockType
3638
);
3739
} else if (Array.isArray(blockContent)) {
3840
nodes = inlineContentToNodes(
3941
blockContent,
4042
editor.pmSchema,
41-
editor.schema.styleSchema
43+
editor.schema.styleSchema,
44+
blockType
4245
);
4346
} else if (blockContent.type === "tableContent") {
4447
nodes = tableContentToNodes(
@@ -102,6 +105,7 @@ function serializeBlock<
102105
editor,
103106
block.content as any, // TODO
104107
serializer,
108+
block.type,
105109
options
106110
);
107111
ret.contentDOM.appendChild(ic);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
```javascript
2+
const hello = 'world';
3+
console.log(hello);
4+
```

packages/core/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,33 @@ exports[`Test BlockNote-Prosemirror conversion > Case: custom style schema > Con
662662
}
663663
`;
664664

665+
exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert codeBlock/contains-newlines to/from prosemirror 1`] = `
666+
{
667+
"attrs": {
668+
"backgroundColor": "default",
669+
"id": "1",
670+
"textColor": "default",
671+
},
672+
"content": [
673+
{
674+
"attrs": {
675+
"language": "javascript",
676+
},
677+
"content": [
678+
{
679+
"text": "const hello = 'world';
680+
console.log(hello);
681+
",
682+
"type": "text",
683+
},
684+
],
685+
"type": "codeBlock",
686+
},
687+
],
688+
"type": "blockContainer",
689+
}
690+
`;
691+
665692
exports[`Test BlockNote-Prosemirror conversion > Case: default schema > Convert codeBlock/defaultLanguage to/from prosemirror 1`] = `
666693
{
667694
"attrs": {

packages/core/src/api/nodeConversions/blockToNode.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import { UnreachableCaseError } from "../../util/typescript.js";
2525
function styledTextToNodes<T extends StyleSchema>(
2626
styledText: StyledText<T>,
2727
schema: Schema,
28-
styleSchema: T
28+
styleSchema: T,
29+
blockType?: string
2930
): Node[] {
3031
const marks: Mark[] = [];
3132

@@ -44,6 +45,12 @@ function styledTextToNodes<T extends StyleSchema>(
4445
}
4546
}
4647

48+
const parseHardBreaks = !blockType || !schema.nodes[blockType].spec.code;
49+
50+
if (!parseHardBreaks) {
51+
return [schema.text(styledText.text, marks)];
52+
}
53+
4754
return (
4855
styledText.text
4956
// Splits text & line breaks.
@@ -96,7 +103,8 @@ function linkToNodes(
96103
function styledTextArrayToNodes<S extends StyleSchema>(
97104
content: string | StyledText<S>[],
98105
schema: Schema,
99-
styleSchema: S
106+
styleSchema: S,
107+
blockType?: string
100108
): Node[] {
101109
const nodes: Node[] = [];
102110

@@ -105,14 +113,17 @@ function styledTextArrayToNodes<S extends StyleSchema>(
105113
...styledTextToNodes(
106114
{ type: "text", text: content, styles: {} },
107115
schema,
108-
styleSchema
116+
styleSchema,
117+
blockType
109118
)
110119
);
111120
return nodes;
112121
}
113122

114123
for (const styledText of content) {
115-
nodes.push(...styledTextToNodes(styledText, schema, styleSchema));
124+
nodes.push(
125+
...styledTextToNodes(styledText, schema, styleSchema, blockType)
126+
);
116127
}
117128
return nodes;
118129
}
@@ -126,17 +137,22 @@ export function inlineContentToNodes<
126137
>(
127138
blockContent: PartialInlineContent<I, S>,
128139
schema: Schema,
129-
styleSchema: S
140+
styleSchema: S,
141+
blockType?: string
130142
): Node[] {
131143
const nodes: Node[] = [];
132144

133145
for (const content of blockContent) {
134146
if (typeof content === "string") {
135-
nodes.push(...styledTextArrayToNodes(content, schema, styleSchema));
147+
nodes.push(
148+
...styledTextArrayToNodes(content, schema, styleSchema, blockType)
149+
);
136150
} else if (isPartialLinkInlineContent(content)) {
137151
nodes.push(...linkToNodes(content, schema, styleSchema));
138152
} else if (isStyledTextInlineContent(content)) {
139-
nodes.push(...styledTextArrayToNodes([content], schema, styleSchema));
153+
nodes.push(
154+
...styledTextArrayToNodes([content], schema, styleSchema, blockType)
155+
);
140156
} else {
141157
nodes.push(
142158
blockOrInlineContentToContentNode(content, schema, styleSchema)
@@ -217,10 +233,20 @@ function blockOrInlineContentToContentNode(
217233
if (!block.content) {
218234
contentNode = schema.nodes[type].createChecked(block.props);
219235
} else if (typeof block.content === "string") {
220-
const nodes = inlineContentToNodes([block.content], schema, styleSchema);
236+
const nodes = inlineContentToNodes(
237+
[block.content],
238+
schema,
239+
styleSchema,
240+
type
241+
);
221242
contentNode = schema.nodes[type].createChecked(block.props, nodes);
222243
} else if (Array.isArray(block.content)) {
223-
const nodes = inlineContentToNodes(block.content, schema, styleSchema);
244+
const nodes = inlineContentToNodes(
245+
block.content,
246+
schema,
247+
styleSchema,
248+
type
249+
);
224250
contentNode = schema.nodes[type].createChecked(block.props, nodes);
225251
} else if (block.content.type === "tableContent") {
226252
const nodes = tableContentToNodes(block.content, schema, styleSchema);

packages/core/src/api/testUtil/cases/defaultSchema.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ export const defaultSchemaTestCases: EditorTestCases<
208208
},
209209
],
210210
},
211+
{
212+
name: "codeBlock/contains-newlines",
213+
blocks: [
214+
{
215+
type: "codeBlock",
216+
content: "const hello = 'world';\nconsole.log(hello);\n",
217+
},
218+
],
219+
},
211220
{
212221
name: "pageBreak/basic",
213222
blocks: [

packages/core/src/editor/BlockNoteExtensions.ts

Lines changed: 4 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import { AnyExtension, Extension, extensions } from "@tiptap/core";
2-
import { Awareness } from "y-protocols/awareness";
3-
4-
import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
5-
6-
import Collaboration from "@tiptap/extension-collaboration";
7-
import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
82
import { Gapcursor } from "@tiptap/extension-gapcursor";
93
import { HardBreak } from "@tiptap/extension-hard-break";
104
import { History } from "@tiptap/extension-history";
115
import { Link } from "@tiptap/extension-link";
126
import { Text } from "@tiptap/extension-text";
137
import { Plugin } from "prosemirror-state";
148
import * as Y from "yjs";
9+
10+
import type { BlockNoteEditor, BlockNoteExtension } from "./BlockNoteEditor.js";
1511
import { createDropFileExtension } from "../api/clipboard/fromClipboard/fileDropExtension.js";
1612
import { createPasteFromClipboardExtension } from "../api/clipboard/fromClipboard/pasteExtension.js";
1713
import { createCopyToClipboardExtension } from "../api/clipboard/toClipboard/copyExtension.js";
@@ -44,6 +40,7 @@ import {
4440
StyleSchema,
4541
StyleSpecs,
4642
} from "../schema/index.js";
43+
import { createCollaborationExtensions } from "../extensions/Collaboration/createCollaborationExtensions.js";
4744

4845
type ExtensionOptions<
4946
BSchema extends BlockSchema,
@@ -247,128 +244,7 @@ const getTipTapExtensions = <
247244
];
248245

249246
if (opts.collaboration) {
250-
tiptapExtensions.push(
251-
Collaboration.configure({
252-
fragment: opts.collaboration.fragment,
253-
})
254-
);
255-
256-
const awareness = opts.collaboration?.provider.awareness as Awareness;
257-
258-
if (awareness) {
259-
const cursors = new Map<
260-
number,
261-
{ element: HTMLElement; hideTimeout: NodeJS.Timeout | undefined }
262-
>();
263-
264-
if (opts.collaboration.showCursorLabels !== "always") {
265-
awareness.on(
266-
"change",
267-
({
268-
updated,
269-
}: {
270-
added: Array<number>;
271-
updated: Array<number>;
272-
removed: Array<number>;
273-
}) => {
274-
for (const clientID of updated) {
275-
const cursor = cursors.get(clientID);
276-
277-
if (cursor) {
278-
cursor.element.setAttribute("data-active", "");
279-
280-
if (cursor.hideTimeout) {
281-
clearTimeout(cursor.hideTimeout);
282-
}
283-
284-
cursors.set(clientID, {
285-
element: cursor.element,
286-
hideTimeout: setTimeout(() => {
287-
cursor.element.removeAttribute("data-active");
288-
}, 2000),
289-
});
290-
}
291-
}
292-
}
293-
);
294-
}
295-
296-
const createCursor = (clientID: number, name: string, color: string) => {
297-
const cursorElement = document.createElement("span");
298-
299-
cursorElement.classList.add("collaboration-cursor__caret");
300-
cursorElement.setAttribute("style", `border-color: ${color}`);
301-
if (opts.collaboration?.showCursorLabels === "always") {
302-
cursorElement.setAttribute("data-active", "");
303-
}
304-
305-
const labelElement = document.createElement("span");
306-
307-
labelElement.classList.add("collaboration-cursor__label");
308-
labelElement.setAttribute("style", `background-color: ${color}`);
309-
labelElement.insertBefore(document.createTextNode(name), null);
310-
311-
cursorElement.insertBefore(document.createTextNode("\u2060"), null); // Non-breaking space
312-
cursorElement.insertBefore(labelElement, null);
313-
cursorElement.insertBefore(document.createTextNode("\u2060"), null); // Non-breaking space
314-
315-
cursors.set(clientID, {
316-
element: cursorElement,
317-
hideTimeout: undefined,
318-
});
319-
320-
if (opts.collaboration?.showCursorLabels !== "always") {
321-
cursorElement.addEventListener("mouseenter", () => {
322-
const cursor = cursors.get(clientID)!;
323-
cursor.element.setAttribute("data-active", "");
324-
325-
if (cursor.hideTimeout) {
326-
clearTimeout(cursor.hideTimeout);
327-
cursors.set(clientID, {
328-
element: cursor.element,
329-
hideTimeout: undefined,
330-
});
331-
}
332-
});
333-
334-
cursorElement.addEventListener("mouseleave", () => {
335-
const cursor = cursors.get(clientID)!;
336-
337-
cursors.set(clientID, {
338-
element: cursor.element,
339-
hideTimeout: setTimeout(() => {
340-
cursor.element.removeAttribute("data-active");
341-
}, 2000),
342-
});
343-
});
344-
}
345-
346-
return cursors.get(clientID)!;
347-
};
348-
349-
const defaultRender = (user: { color: string; name: string }) => {
350-
const clientState = [...awareness.getStates().entries()].find(
351-
(state) => state[1].user === user
352-
);
353-
354-
if (!clientState) {
355-
throw new Error("Could not find client state for user");
356-
}
357-
358-
const clientID = clientState[0];
359-
360-
return (
361-
cursors.get(clientID) || createCursor(clientID, user.name, user.color)
362-
).element;
363-
};
364-
tiptapExtensions.push(
365-
CollaborationCursor.configure({
366-
user: opts.collaboration.user,
367-
render: opts.collaboration.renderCursor || defaultRender,
368-
provider: opts.collaboration.provider,
369-
})
370-
);
371-
}
247+
tiptapExtensions.push(...createCollaborationExtensions(opts.collaboration));
372248
} else {
373249
// disable history extension when collaboration is enabled as Yjs takes care of undo / redo
374250
tiptapExtensions.push(History);

0 commit comments

Comments
 (0)