1
1
import { Extension } from "@tiptap/core" ;
2
- import { Plugin } from "prosemirror-state" ;
2
+ import { NodeSelection , Plugin } from "prosemirror-state" ;
3
+ import { Node } from "prosemirror-model" ;
3
4
4
5
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor" ;
5
6
import { BlockSchema , InlineContentSchema , StyleSchema } from "../../schema" ;
6
7
import { createExternalHTMLExporter } from "./html/externalHTMLExporter" ;
7
8
import { createInternalHTMLSerializer } from "./html/internalHTMLSerializer" ;
8
9
import { cleanHTMLToMarkdown } from "./markdown/markdownExporter" ;
10
+ import { EditorView } from "prosemirror-view" ;
11
+
12
+ function selectedFragmentToHTML <
13
+ BSchema extends BlockSchema ,
14
+ I extends InlineContentSchema ,
15
+ S extends StyleSchema
16
+ > (
17
+ view : EditorView ,
18
+ editor : BlockNoteEditor < BSchema , I , S >
19
+ ) : {
20
+ internalHTML : string ;
21
+ externalHTML : string ;
22
+ plainText : string ;
23
+ } {
24
+ const selectedFragment = view . state . selection . content ( ) . content ;
25
+
26
+ const internalHTMLSerializer = createInternalHTMLSerializer (
27
+ view . state . schema ,
28
+ editor
29
+ ) ;
30
+ const internalHTML =
31
+ internalHTMLSerializer . serializeProseMirrorFragment ( selectedFragment ) ;
32
+
33
+ const externalHTMLExporter = createExternalHTMLExporter (
34
+ view . state . schema ,
35
+ editor
36
+ ) ;
37
+ const externalHTML =
38
+ externalHTMLExporter . exportProseMirrorFragment ( selectedFragment ) ;
39
+
40
+ const plainText = cleanHTMLToMarkdown ( externalHTML ) ;
41
+
42
+ return { internalHTML, externalHTML, plainText } ;
43
+ }
9
44
10
45
export const createCopyToClipboardExtension = <
11
46
BSchema extends BlockSchema ,
@@ -17,46 +52,84 @@ export const createCopyToClipboardExtension = <
17
52
Extension . create < { editor : BlockNoteEditor < BSchema , I , S > } , undefined > ( {
18
53
name : "copyToClipboard" ,
19
54
addProseMirrorPlugins ( ) {
20
- const tiptap = this . editor ;
21
- const schema = this . editor . schema ;
22
55
return [
23
56
new Plugin ( {
24
57
props : {
25
58
handleDOMEvents : {
26
- copy ( _view , event ) {
59
+ copy ( view , event ) {
27
60
// Stops the default browser copy behaviour.
28
61
event . preventDefault ( ) ;
29
62
event . clipboardData ! . clearData ( ) ;
30
63
31
- const selectedFragment =
32
- tiptap . state . selection . content ( ) . content ;
33
-
34
- const internalHTMLSerializer = createInternalHTMLSerializer (
35
- schema ,
36
- editor
37
- ) ;
38
- const internalHTML =
39
- internalHTMLSerializer . serializeProseMirrorFragment (
40
- selectedFragment
41
- ) ;
42
-
43
- const externalHTMLExporter = createExternalHTMLExporter (
44
- schema ,
45
- editor
46
- ) ;
47
- const externalHTML =
48
- externalHTMLExporter . exportProseMirrorFragment (
49
- selectedFragment
64
+ // Checks if a `blockContent` node is being copied and expands
65
+ // the selection to the parent `blockContainer` node. This is
66
+ // for the use-case in which only a block without content is
67
+ // selected, e.g. an image block.
68
+ if (
69
+ "node" in view . state . selection &&
70
+ ( view . state . selection . node as Node ) . type . spec . group ===
71
+ "blockContent"
72
+ ) {
73
+ view . dispatch (
74
+ view . state . tr . setSelection (
75
+ new NodeSelection (
76
+ view . state . doc . resolve ( view . state . selection . from - 1 )
77
+ )
78
+ )
50
79
) ;
80
+ }
51
81
52
- const plainText = cleanHTMLToMarkdown ( externalHTML ) ;
82
+ const { internalHTML, externalHTML, plainText } =
83
+ selectedFragmentToHTML ( view , editor ) ;
53
84
54
85
// TODO: Writing to other MIME types not working in Safari for
55
86
// some reason.
56
87
event . clipboardData ! . setData ( "blocknote/html" , internalHTML ) ;
57
88
event . clipboardData ! . setData ( "text/html" , externalHTML ) ;
58
89
event . clipboardData ! . setData ( "text/plain" , plainText ) ;
59
90
91
+ // Prevent default PM handler to be called
92
+ return true ;
93
+ } ,
94
+ // This is for the use-case in which only a block without content
95
+ // is selected, e.g. an image block, and dragged (not using the
96
+ // drag handle).
97
+ dragstart ( view , event ) {
98
+ // Checks if a `NodeSelection` is active.
99
+ if ( ! ( "node" in view . state . selection ) ) {
100
+ return ;
101
+ }
102
+
103
+ // Checks if a `blockContent` node is being dragged.
104
+ if (
105
+ ( view . state . selection . node as Node ) . type . spec . group !==
106
+ "blockContent"
107
+ ) {
108
+ return ;
109
+ }
110
+
111
+ // Expands the selection to the parent `blockContainer` node.
112
+ view . dispatch (
113
+ view . state . tr . setSelection (
114
+ new NodeSelection (
115
+ view . state . doc . resolve ( view . state . selection . from - 1 )
116
+ )
117
+ )
118
+ ) ;
119
+
120
+ // Stops the default browser drag start behaviour.
121
+ event . preventDefault ( ) ;
122
+ event . dataTransfer ! . clearData ( ) ;
123
+
124
+ const { internalHTML, externalHTML, plainText } =
125
+ selectedFragmentToHTML ( view , editor ) ;
126
+
127
+ // TODO: Writing to other MIME types not working in Safari for
128
+ // some reason.
129
+ event . dataTransfer ! . setData ( "blocknote/html" , internalHTML ) ;
130
+ event . dataTransfer ! . setData ( "text/html" , externalHTML ) ;
131
+ event . dataTransfer ! . setData ( "text/plain" , plainText ) ;
132
+
60
133
// Prevent default PM handler to be called
61
134
return true ;
62
135
} ,
0 commit comments