From 8799ce2799cabb224572b02648bafc4ae3736f41 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 20 Sep 2024 16:02:16 +0200 Subject: [PATCH 1/4] simple fix for text selection in non selectable nodes --- packages/react/src/schema/ReactBlockSpec.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index ba36009a62..3c55231fa7 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -140,8 +140,8 @@ export function createReactBlockSpec< }, addNodeView() { - return (props) => - ReactNodeViewRenderer( + return (props) => { + const nv = ReactNodeViewRenderer( (props: NodeViewProps) => { // Gets the BlockNote editor instance const editor = this.options.editor! as BlockNoteEditor; @@ -179,6 +179,22 @@ export function createReactBlockSpec< className: "bn-react-node-view-renderer", } )(props); + + nv.stopEvent = (event) => { + console.log("event", event); + if (event.type === "copy") { + return true; + } + if (event.type === "mousedown") { + setTimeout(() => { + this.editor.view.dom.blur(); + }, 10); + return true; + } + return false; + }; + return nv; + }; }, }); From a8cb0b89b8060926f9481c88903511707e3fc87f Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 20 Sep 2024 16:47:40 +0200 Subject: [PATCH 2/4] Added `isSelectable` field to `blockConfig` --- packages/core/src/schema/blocks/createSpec.ts | 28 +++++++++++++++++-- packages/core/src/schema/blocks/types.ts | 2 ++ packages/react/src/schema/ReactBlockSpec.tsx | 27 +++++++----------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/packages/core/src/schema/blocks/createSpec.ts b/packages/core/src/schema/blocks/createSpec.ts index 25fb94a9a5..f1dfaf7c65 100644 --- a/packages/core/src/schema/blocks/createSpec.ts +++ b/packages/core/src/schema/blocks/createSpec.ts @@ -1,4 +1,6 @@ +import { Editor } from "@tiptap/core"; import { TagParseRule } from "@tiptap/pm/model"; +import { NodeView } from "@tiptap/pm/view"; import type { BlockNoteEditor } from "../../editor/BlockNoteEditor"; import { InlineContentSchema } from "../inlineContent/types"; import { StyleSchema } from "../styles/types"; @@ -61,6 +63,22 @@ export type CustomBlockImplementation< ) => PartialBlockFromConfig["props"] | undefined; }; +export function applyNonSelectableBlockFix(nodeView: NodeView, editor: Editor) { + nodeView.stopEvent = (event) => { + // console.log("event", event); + if (event.type === "copy") { + return true; + } + if (event.type === "mousedown") { + setTimeout(() => { + editor.view.dom.blur(); + }, 10); + return true; + } + return false; + }; +} + // Function that uses the 'parse' function of a blockConfig to create a // TipTap node's `parseHTML` property. This is only used for parsing content // from the clipboard. @@ -125,7 +143,7 @@ export function createBlockSpec< ? "inline*" : "") as T["content"] extends "inline" ? "inline*" : "", group: "blockContent", - selectable: true, + selectable: blockConfig.isSelectable ?? true, addAttributes() { return propsToAttributes(blockConfig.propSchema); @@ -163,13 +181,19 @@ export function createBlockSpec< const output = blockImplementation.render(block as any, editor); - return wrapInBlockStructure( + const nodeView: NodeView = wrapInBlockStructure( output, block.type, block.props, blockConfig.propSchema, blockContentDOMAttributes ); + + if (blockConfig.isSelectable === false) { + applyNonSelectableBlockFix(nodeView, this.editor); + } + + return nodeView; }; }, }); diff --git a/packages/core/src/schema/blocks/types.ts b/packages/core/src/schema/blocks/types.ts index 1caf78db8c..e5712d08bc 100644 --- a/packages/core/src/schema/blocks/types.ts +++ b/packages/core/src/schema/blocks/types.ts @@ -49,6 +49,7 @@ export type FileBlockConfig = { }; }; content: "none"; + isSelectable?: boolean; isFileBlock: true; fileBlockAccept?: string[]; }; @@ -60,6 +61,7 @@ export type BlockConfig = type: string; readonly propSchema: PropSchema; content: "inline" | "none" | "table"; + isSelectable?: boolean; isFileBlock?: false; } | FileBlockConfig; diff --git a/packages/react/src/schema/ReactBlockSpec.tsx b/packages/react/src/schema/ReactBlockSpec.tsx index 3c55231fa7..687f93afef 100644 --- a/packages/react/src/schema/ReactBlockSpec.tsx +++ b/packages/react/src/schema/ReactBlockSpec.tsx @@ -1,4 +1,5 @@ import { + applyNonSelectableBlockFix, BlockFromConfig, BlockNoteEditor, BlockSchemaWithBlock, @@ -18,6 +19,7 @@ import { StyleSchema, } from "@blocknote/core"; import { + NodeView, NodeViewContent, NodeViewProps, NodeViewWrapper, @@ -118,7 +120,7 @@ export function createReactBlockSpec< ? "inline*" : "") as T["content"] extends "inline" ? "inline*" : "", group: "blockContent", - selectable: true, + selectable: blockConfig.isSelectable ?? true, addAttributes() { return propsToAttributes(blockConfig.propSchema); @@ -141,7 +143,7 @@ export function createReactBlockSpec< addNodeView() { return (props) => { - const nv = ReactNodeViewRenderer( + const nodeView = ReactNodeViewRenderer( (props: NodeViewProps) => { // Gets the BlockNote editor instance const editor = this.options.editor! as BlockNoteEditor; @@ -178,22 +180,13 @@ export function createReactBlockSpec< { className: "bn-react-node-view-renderer", } - )(props); + )(props) as NodeView; - nv.stopEvent = (event) => { - console.log("event", event); - if (event.type === "copy") { - return true; - } - if (event.type === "mousedown") { - setTimeout(() => { - this.editor.view.dom.blur(); - }, 10); - return true; - } - return false; - }; - return nv; + if (blockConfig.isSelectable === false) { + applyNonSelectableBlockFix(nodeView, this.editor); + } + + return nodeView; }; }, }); From 67646aa1243ffa205057d89b887f69f5eeac8560 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 20 Sep 2024 16:52:15 +0200 Subject: [PATCH 3/4] Added comments --- packages/core/src/schema/blocks/createSpec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/schema/blocks/createSpec.ts b/packages/core/src/schema/blocks/createSpec.ts index f1dfaf7c65..14a2726046 100644 --- a/packages/core/src/schema/blocks/createSpec.ts +++ b/packages/core/src/schema/blocks/createSpec.ts @@ -63,12 +63,17 @@ export type CustomBlockImplementation< ) => PartialBlockFromConfig["props"] | undefined; }; +// Function that enables copying of selected content within non-selectable +// blocks. export function applyNonSelectableBlockFix(nodeView: NodeView, editor: Editor) { nodeView.stopEvent = (event) => { - // console.log("event", event); + // Ensures copy events are handled by the browser and not by ProseMirror. if (event.type === "copy") { return true; } + // Blurs the editor on mouse down as the block is non-selectable. This is + // mainly done to prevent UI elements like the formatting toolbar from being + // visible while content within a non-selectable block is selected. if (event.type === "mousedown") { setTimeout(() => { editor.view.dom.blur(); From 839629529f2cf0038e01853043bf4eed85c0c5d8 Mon Sep 17 00:00:00 2001 From: matthewlipski Date: Fri, 20 Sep 2024 16:53:56 +0200 Subject: [PATCH 4/4] Added cut event handling --- packages/core/src/schema/blocks/createSpec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/schema/blocks/createSpec.ts b/packages/core/src/schema/blocks/createSpec.ts index 14a2726046..3689a108c8 100644 --- a/packages/core/src/schema/blocks/createSpec.ts +++ b/packages/core/src/schema/blocks/createSpec.ts @@ -68,7 +68,7 @@ export type CustomBlockImplementation< export function applyNonSelectableBlockFix(nodeView: NodeView, editor: Editor) { nodeView.stopEvent = (event) => { // Ensures copy events are handled by the browser and not by ProseMirror. - if (event.type === "copy") { + if (event.type === "copy" || event.type === "cut") { return true; } // Blurs the editor on mouse down as the block is non-selectable. This is