@@ -11,6 +11,8 @@ import {
1111 Msg ,
1212 ShortcutItems ,
1313 WorkspaceSvg ,
14+ clipboard ,
15+ isSelectable ,
1416} from 'blockly' ;
1517import * as Constants from '../constants' ;
1618import { Navigation } from '../navigation' ;
@@ -31,14 +33,16 @@ const BASE_WEIGHT = 12;
3133 * In the long term, this will likely merge with the clipboard code in core.
3234 */
3335export class Clipboard {
34- /** The workspace a copy or cut keyboard shortcut happened in. */
35- private copyWorkspace : WorkspaceSvg | null = null ;
36-
3736 private oldCutShortcut : ShortcutRegistry . KeyboardShortcut | undefined ;
3837 private oldCopyShortcut : ShortcutRegistry . KeyboardShortcut | undefined ;
3938 private oldPasteShortcut : ShortcutRegistry . KeyboardShortcut | undefined ;
4039
41- constructor ( private navigation : Navigation ) { }
40+ constructor (
41+ private navigation : Navigation ,
42+ private options : { allowCrossWorkspacePaste : boolean } = {
43+ allowCrossWorkspacePaste : false ,
44+ } ,
45+ ) { }
4246
4347 /**
4448 * Install these actions as both keyboard shortcuts and context menu items.
@@ -84,8 +88,6 @@ export class Clipboard {
8488 name : Constants . SHORTCUT_NAMES . CUT ,
8589 preconditionFn : this . oldCutShortcut . preconditionFn ,
8690 callback : this . cutCallback . bind ( this ) ,
87- // The registry gives back keycodes as an object instead of an array
88- // See https://github.com/google/blockly/issues/9008
8991 keyCodes : this . oldCutShortcut . keyCodes ,
9092 allowCollision : false ,
9193 } ;
@@ -143,48 +145,6 @@ export class Clipboard {
143145 return 'disabled' ;
144146 }
145147
146- /**
147- * Precondition function for the copy context menu. This wraps the core copy
148- * precondition to support context menus.
149- *
150- * @param scope scope of the shortcut or context menu item
151- * @returns 'enabled' if the node can be copied, 'disabled' otherwise.
152- */
153- private copyPrecondition ( scope : ContextMenuRegistry . Scope ) : string {
154- const focused = scope . focusedNode ;
155- if ( ! focused || ! isCopyable ( focused ) ) return 'hidden' ;
156-
157- const workspace = focused . workspace ;
158- if ( ! ( workspace instanceof WorkspaceSvg ) ) return 'hidden' ;
159-
160- if (
161- this . oldCopyShortcut ?. preconditionFn &&
162- this . oldCopyShortcut . preconditionFn ( workspace , scope )
163- ) {
164- return 'enabled' ;
165- }
166- return 'disabled' ;
167- }
168-
169- /**
170- * Precondition function for the paste context menu. This wraps the core
171- * paste precondition to support context menus.
172- *
173- * @param scope scope of the shortcut or context menu item
174- * @returns 'enabled' if the node can be pasted, 'disabled' otherwise.
175- */
176- private pastePrecondition ( scope : ContextMenuRegistry . Scope ) : string {
177- if ( ! this . copyWorkspace ) return 'disabled' ;
178-
179- if (
180- this . oldPasteShortcut ?. preconditionFn &&
181- this . oldPasteShortcut . preconditionFn ( this . copyWorkspace , scope )
182- ) {
183- return 'enabled' ;
184- }
185- return 'disabled' ;
186- }
187-
188148 /**
189149 * The callback for the cut action. Uses the registered version of the cut callback
190150 * to perform the cut logic, then pops a toast if cut happened.
@@ -207,7 +167,6 @@ export class Clipboard {
207167 ! ! this . oldCutShortcut ?. callback &&
208168 this . oldCutShortcut . callback ( workspace , e , shortcut , scope ) ;
209169 if ( didCut ) {
210- this . copyWorkspace = workspace ;
211170 showCutHint ( workspace ) ;
212171 }
213172 return didCut ;
@@ -227,8 +186,6 @@ export class Clipboard {
227186 name : Constants . SHORTCUT_NAMES . COPY ,
228187 preconditionFn : this . oldCopyShortcut . preconditionFn ,
229188 callback : this . copyCallback . bind ( this ) ,
230- // The registry gives back keycodes as an object instead of an array
231- // See https://github.com/google/blockly/issues/9008
232189 keyCodes : this . oldCopyShortcut . keyCodes ,
233190 allowCollision : false ,
234191 } ;
@@ -263,6 +220,29 @@ export class Clipboard {
263220 ContextMenuRegistry . registry . register ( copyAction ) ;
264221 }
265222
223+ /**
224+ * Precondition function for the copy context menu. This wraps the core copy
225+ * precondition to support context menus.
226+ *
227+ * @param scope scope of the shortcut or context menu item
228+ * @returns 'enabled' if the node can be copied, 'disabled' otherwise.
229+ */
230+ private copyPrecondition ( scope : ContextMenuRegistry . Scope ) : string {
231+ const focused = scope . focusedNode ;
232+ if ( ! focused || ! isCopyable ( focused ) ) return 'hidden' ;
233+
234+ const workspace = focused . workspace ;
235+ if ( ! ( workspace instanceof WorkspaceSvg ) ) return 'hidden' ;
236+
237+ if (
238+ this . oldCopyShortcut ?. preconditionFn &&
239+ this . oldCopyShortcut . preconditionFn ( workspace , scope )
240+ ) {
241+ return 'enabled' ;
242+ }
243+ return 'disabled' ;
244+ }
245+
266246 /**
267247 * The callback for the copy action. Uses the registered version of the copy callback
268248 * to perform the copy logic, then pops a toast if copy happened.
@@ -285,9 +265,6 @@ export class Clipboard {
285265 ! ! this . oldCopyShortcut ?. callback &&
286266 this . oldCopyShortcut . callback ( workspace , e , shortcut , scope ) ;
287267 if ( didCopy ) {
288- this . copyWorkspace = workspace . isFlyout
289- ? workspace . targetWorkspace
290- : workspace ;
291268 showCopiedHint ( workspace ) ;
292269 }
293270 return didCopy ;
@@ -307,8 +284,6 @@ export class Clipboard {
307284 name : Constants . SHORTCUT_NAMES . PASTE ,
308285 preconditionFn : this . oldPasteShortcut . preconditionFn ,
309286 callback : this . pasteCallback . bind ( this ) ,
310- // The registry gives back keycodes as an object instead of an array
311- // See https://github.com/google/blockly/issues/9008
312287 keyCodes : this . oldPasteShortcut . keyCodes ,
313288 allowCollision : false ,
314289 } ;
@@ -330,8 +305,8 @@ export class Clipboard {
330305 getMenuItem ( Msg [ 'PASTE_SHORTCUT' ] , Constants . SHORTCUT_NAMES . PASTE ) ,
331306 preconditionFn : ( scope ) => this . pastePrecondition ( scope ) ,
332307 callback : ( scope : ContextMenuRegistry . Scope , menuOpenEvent : Event ) => {
333- const workspace = this . copyWorkspace ;
334- if ( ! workspace ) return ;
308+ const workspace = this . getPasteWorkspace ( scope ) ;
309+ if ( ! workspace ) return false ;
335310 return this . pasteCallback ( workspace , menuOpenEvent , undefined , scope ) ;
336311 } ,
337312 id : 'blockPasteFromContextMenu' ,
@@ -341,6 +316,59 @@ export class Clipboard {
341316 ContextMenuRegistry . registry . register ( pasteAction ) ;
342317 }
343318
319+ /**
320+ * Get the workspace to paste into based on which type of thing the menu was opened on.
321+ *
322+ * @param scope scope of shortcut or context menu item
323+ * @returns WorkspaceSvg to paste into or undefined
324+ */
325+ private getPasteWorkspace (
326+ scope : ContextMenuRegistry . Scope ,
327+ ) : WorkspaceSvg | undefined {
328+ let workspace ;
329+ if ( scope . focusedNode instanceof WorkspaceSvg ) {
330+ workspace = scope . focusedNode ;
331+ } else if ( isSelectable ( scope . focusedNode ) ) {
332+ workspace = scope . focusedNode . workspace ;
333+ }
334+
335+ if ( ! workspace || ! ( workspace instanceof WorkspaceSvg ) ) return undefined ;
336+ return workspace ;
337+ }
338+
339+ /**
340+ * Precondition function for the paste context menu. This wraps the core
341+ * paste precondition to support context menus.
342+ *
343+ * @param scope scope of the shortcut or context menu item
344+ * @returns 'enabled' if the node can be pasted, 'disabled' otherwise.
345+ */
346+ private pastePrecondition ( scope : ContextMenuRegistry . Scope ) : string {
347+ const workspace = this . getPasteWorkspace ( scope ) ;
348+ // If we can't identify what workspace to paste into, hide.
349+ if ( ! workspace ) return 'hidden' ;
350+
351+ // Don't paste into flyouts.
352+ if ( workspace . isFlyout ) return 'hidden' ;
353+
354+ if ( ! this . options . allowCrossWorkspacePaste ) {
355+ // Only paste into the same workspace that was copied from
356+ // or the parent workspace of a flyout that was copied from.
357+ let copiedWorkspace = clipboard . getLastCopiedWorkspace ( ) ;
358+ if ( copiedWorkspace ?. isFlyout )
359+ copiedWorkspace = copiedWorkspace . targetWorkspace ;
360+ if ( copiedWorkspace !== workspace ) return 'disabled' ;
361+ }
362+
363+ if (
364+ this . oldPasteShortcut ?. preconditionFn &&
365+ this . oldPasteShortcut . preconditionFn ( workspace , scope )
366+ ) {
367+ return 'enabled' ;
368+ }
369+ return 'disabled' ;
370+ }
371+
344372 /**
345373 * The callback for the paste action. Uses the registered version of the paste callback
346374 * to perform the paste logic, then clears any toasts about pasting.
0 commit comments