@@ -734,22 +734,22 @@ export class Navigation {
734734 // Connect the moving block to the stationary connection using
735735 // the most plausible connection on the moving block.
736736 if (
737- movingType == Blockly . ASTNode . types . BLOCK ||
738- movingType == Blockly . ASTNode . types . STACK
737+ movingType === Blockly . ASTNode . types . BLOCK ||
738+ movingType === Blockly . ASTNode . types . STACK
739739 ) {
740740 const stationaryAsConnection =
741741 stationaryLoc as Blockly . RenderedConnection ;
742742 const movingAsBlock = movingLoc as Blockly . BlockSvg ;
743743 return this . insertBlock ( movingAsBlock , stationaryAsConnection ) ;
744744 }
745- } else if ( stationaryType == Blockly . ASTNode . types . WORKSPACE ) {
745+ } else if ( stationaryType === Blockly . ASTNode . types . WORKSPACE ) {
746746 const block = movingNode
747747 ? ( movingNode . getSourceBlock ( ) as Blockly . BlockSvg )
748748 : null ;
749749 return this . moveBlockToWorkspace ( block , stationaryNode ) ;
750750 } else if (
751- stationaryType == Blockly . ASTNode . types . BLOCK &&
752- movingType == Blockly . ASTNode . types . BLOCK
751+ stationaryType === Blockly . ASTNode . types . BLOCK &&
752+ movingType === Blockly . ASTNode . types . BLOCK
753753 ) {
754754 // Insert the moving block above the stationary block, if the
755755 // appropriate connections exist.
@@ -793,19 +793,19 @@ export class Navigation {
793793 const cursorType = cursorNode . getType ( ) ;
794794
795795 // Check the marker for invalid types.
796- if ( markerType == Blockly . ASTNode . types . FIELD ) {
796+ if ( markerType === Blockly . ASTNode . types . FIELD ) {
797797 this . warn ( 'Should not have been able to mark a field.' ) ;
798798 return false ;
799- } else if ( markerType == Blockly . ASTNode . types . STACK ) {
799+ } else if ( markerType === Blockly . ASTNode . types . STACK ) {
800800 this . warn ( 'Should not have been able to mark a stack.' ) ;
801801 return false ;
802802 }
803803
804804 // Check the cursor for invalid types.
805- if ( cursorType == Blockly . ASTNode . types . FIELD ) {
805+ if ( cursorType === Blockly . ASTNode . types . FIELD ) {
806806 this . warn ( 'Cannot attach a field to anything else.' ) ;
807807 return false ;
808- } else if ( cursorType == Blockly . ASTNode . types . WORKSPACE ) {
808+ } else if ( cursorType === Blockly . ASTNode . types . WORKSPACE ) {
809809 this . warn ( 'Cannot attach a workspace to anything else.' ) ;
810810 return false ;
811811 }
@@ -1265,9 +1265,9 @@ export class Navigation {
12651265 if ( ! cursor ) return ;
12661266 const curNode = cursor . getCurNode ( ) ;
12671267 const nodeType = curNode . getType ( ) ;
1268- if ( nodeType == Blockly . ASTNode . types . FIELD ) {
1268+ if ( nodeType === Blockly . ASTNode . types . FIELD ) {
12691269 ( curNode . getLocation ( ) as Blockly . Field ) . showEditor ( ) ;
1270- } else if ( nodeType == Blockly . ASTNode . types . BLOCK ) {
1270+ } else if ( nodeType === Blockly . ASTNode . types . BLOCK ) {
12711271 const block = curNode . getLocation ( ) as Blockly . Block ;
12721272 if ( ! tryShowFullBlockFieldEditor ( block ) ) {
12731273 const metaKey = navigator . platform . startsWith ( 'Mac' ) ? 'Cmd' : 'Ctrl' ;
@@ -1281,10 +1281,10 @@ export class Navigation {
12811281 }
12821282 } else if (
12831283 curNode . isConnection ( ) ||
1284- nodeType == Blockly . ASTNode . types . WORKSPACE
1284+ nodeType === Blockly . ASTNode . types . WORKSPACE
12851285 ) {
12861286 this . openToolboxOrFlyout ( workspace ) ;
1287- } else if ( nodeType == Blockly . ASTNode . types . STACK ) {
1287+ } else if ( nodeType === Blockly . ASTNode . types . STACK ) {
12881288 this . warn ( 'Cannot mark a stack.' ) ;
12891289 }
12901290 }
@@ -1304,55 +1304,90 @@ export class Navigation {
13041304 }
13051305
13061306 /**
1307- * Show the action menu for a given node.
1307+ * Show the action menu for the current node.
13081308 *
13091309 * The action menu will contain entries for relevant actions for the
13101310 * node's location. If the location is a block, this will include
13111311 * the contents of the block's context menu (if any).
1312+ *
1313+ * Returns true if it is possible to open the action menu in the
1314+ * current location, even if the menu was not opened due there being
1315+ * no applicable menu items.
13121316 */
1313- openActionMenu ( node : Blockly . ASTNode ) {
1314- const fakeEvent = fakeEventForNode ( node ) ;
1315- ( node . getLocation ( ) as Blockly . BlockSvg ) . showContextMenu ( fakeEvent ) ;
1316-
1317+ openActionMenu ( workspace : Blockly . WorkspaceSvg ) : boolean {
13171318 let menuOptions : Array <
13181319 | Blockly . ContextMenuRegistry . ContextMenuOption
13191320 | Blockly . ContextMenuRegistry . LegacyContextMenuOption
1320- > | null = null ;
1321+ > = [ ] ;
13211322 let rtl : boolean ;
1322- let workspace : Blockly . WorkspaceSvg ;
13231323
1324+ const cursor = workspace . getCursor ( ) ;
1325+ if ( ! cursor ) throw new Error ( 'workspace has no cursor' ) ;
1326+ const node = cursor . getCurNode ( ) ;
13241327 const nodeType = node . getType ( ) ;
13251328 switch ( nodeType ) {
13261329 case Blockly . ASTNode . types . BLOCK :
13271330 const block = node . getLocation ( ) as Blockly . BlockSvg ;
1328- workspace = block . workspace as Blockly . WorkspaceSvg ;
13291331 rtl = block . RTL ;
1330-
13311332 // Reimplement BlockSvg.prototype.generateContextMenu as that
13321333 // method is protected.
1333- if ( ! workspace . options . readOnly && ! block . contextMenu ) {
1334+ if ( ! workspace . options . readOnly && block . contextMenu ) {
13341335 menuOptions =
13351336 Blockly . ContextMenuRegistry . registry . getContextMenuOptions (
13361337 Blockly . ContextMenuRegistry . ScopeType . BLOCK ,
13371338 { block} ,
13381339 ) ;
13391340
13401341 // Allow the block to add or modify menuOptions.
1341- if ( block . customContextMenu ) {
1342- block . customContextMenu ( menuOptions ) ;
1343- }
1342+ block . customContextMenu ?.( menuOptions ) ;
13441343 }
13451344 // End reimplement.
13461345 break ;
1346+
1347+ // case Blockly.ASTNode.types.INPUT:
1348+ case Blockly . ASTNode . types . NEXT :
1349+ case Blockly . ASTNode . types . PREVIOUS :
1350+ const connection = node . getLocation ( ) as Blockly . Connection ;
1351+ rtl = connection . getSourceBlock ( ) . RTL ;
1352+
1353+ // Slightly hacky: get insert action from registry. Hacky
1354+ // because registry typings don't include {connection: ...} as
1355+ // a possible kind of scope.
1356+ const insertAction =
1357+ Blockly . ContextMenuRegistry . registry . getItem ( 'insert' ) ;
1358+ if ( ! insertAction ) throw new Error ( "can't find insert action" ) ;
1359+ const possibleOptions = [ insertAction /* etc.*/ ] ;
1360+
1361+ // Check preconditions and get menu texts.
1362+ const scope = {
1363+ connection,
1364+ } as unknown as Blockly . ContextMenuRegistry . Scope ;
1365+ for ( const option of possibleOptions ) {
1366+ const precondition = option . preconditionFn ( scope ) ;
1367+ if ( precondition === 'hidden' ) continue ;
1368+ const displayText =
1369+ typeof option . displayText === 'function'
1370+ ? option . displayText ( scope )
1371+ : option . displayText ;
1372+ menuOptions . push ( {
1373+ text : displayText ,
1374+ enabled : precondition === 'enabled' ,
1375+ callback : option . callback ,
1376+ scope,
1377+ weight : option . weight ,
1378+ } ) ;
1379+ }
1380+ break ;
1381+
13471382 default :
1348- throw new TypeError (
1349- `unable to show action menu for ASTNode of type ${ nodeType } ` ,
1350- ) ;
1383+ console . info ( `No action menu for ASTNode of type ${ nodeType } ` ) ;
1384+ return false ;
13511385 }
13521386
1353- if ( ! menuOptions || ! menuOptions . length ) return ;
1354-
1387+ if ( ! menuOptions ? .length ) return true ;
1388+ const fakeEvent = fakeEventForNode ( node ) ;
13551389 Blockly . ContextMenu . show ( fakeEvent , menuOptions , rtl , workspace ) ;
1390+ return true ;
13561391 }
13571392
13581393 /**
@@ -1421,12 +1456,29 @@ export class Navigation {
14211456 * Create a fake PointerEvent for opening the action menu for the
14221457 * given ASTNode.
14231458 *
1424- * Currently only works for block nodes.
1425- *
14261459 * @param node The node to open the action menu for.
14271460 * @returns A synthetic pointerdown PointerEvent.
14281461 */
14291462function fakeEventForNode ( node : Blockly . ASTNode ) : PointerEvent {
1463+ switch ( node . getType ( ) ) {
1464+ case Blockly . ASTNode . types . BLOCK :
1465+ return fakeEventForBlockNode ( node ) ;
1466+ case Blockly . ASTNode . types . NEXT :
1467+ case Blockly . ASTNode . types . PREVIOUS :
1468+ return fakeEventForStackNode ( node ) ;
1469+ default :
1470+ throw new TypeError ( 'unhandled node type' ) ;
1471+ }
1472+ }
1473+
1474+ /**
1475+ * Create a fake PointerEvent for opening the action menu for the
1476+ * given ASTNode of type BLOCK.
1477+ *
1478+ * @param node The node to open the action menu for.
1479+ * @returns A synthetic pointerdown PointerEvent.
1480+ */
1481+ function fakeEventForBlockNode ( node : Blockly . ASTNode ) : PointerEvent {
14301482 if ( node . getType ( ) !== Blockly . ASTNode . types . BLOCK ) {
14311483 throw new TypeError ( 'can only create PointerEvents for BLOCK nodes' ) ;
14321484 }
@@ -1459,6 +1511,36 @@ function fakeEventForNode(node: Blockly.ASTNode): PointerEvent {
14591511 } ) ;
14601512}
14611513
1514+ /**
1515+ * Create a fake PointerEvent for opening the action menu for the
1516+ * given ASTNode of type NEXT or PREVIOUS.
1517+ *
1518+ * For now this just puts the action menu in the same place as the
1519+ * context menu for the source block.
1520+ *
1521+ * @param node The node to open the action menu for.
1522+ * @returns A synthetic pointerdown PointerEvent.
1523+ */
1524+ function fakeEventForStackNode ( node : Blockly . ASTNode ) : PointerEvent {
1525+ if (
1526+ node . getType ( ) !== Blockly . ASTNode . types . NEXT &&
1527+ node . getType ( ) !== Blockly . ASTNode . types . PREVIOUS
1528+ ) {
1529+ throw new TypeError (
1530+ 'can only create PointerEvents for NEXT / PREVIOUS nodes' ,
1531+ ) ;
1532+ }
1533+
1534+ const connection = node . getLocation ( ) as Blockly . Connection ;
1535+
1536+ return fakeEventForBlockNode (
1537+ new Blockly . ASTNode (
1538+ Blockly . ASTNode . types . BLOCK ,
1539+ connection . getSourceBlock ( ) ,
1540+ ) ,
1541+ ) ;
1542+ }
1543+
14621544/**
14631545 * If this block has a full block field then show its editor.
14641546 *
0 commit comments