@@ -724,22 +724,22 @@ export class Navigation {
724724 // Connect the moving block to the stationary connection using
725725 // the most plausible connection on the moving block.
726726 if (
727- movingType == Blockly . ASTNode . types . BLOCK ||
728- movingType == Blockly . ASTNode . types . STACK
727+ movingType === Blockly . ASTNode . types . BLOCK ||
728+ movingType === Blockly . ASTNode . types . STACK
729729 ) {
730730 const stationaryAsConnection =
731731 stationaryLoc as Blockly . RenderedConnection ;
732732 const movingAsBlock = movingLoc as Blockly . BlockSvg ;
733733 return this . insertBlock ( movingAsBlock , stationaryAsConnection ) ;
734734 }
735- } else if ( stationaryType == Blockly . ASTNode . types . WORKSPACE ) {
735+ } else if ( stationaryType === Blockly . ASTNode . types . WORKSPACE ) {
736736 const block = movingNode
737737 ? ( movingNode . getSourceBlock ( ) as Blockly . BlockSvg )
738738 : null ;
739739 return this . moveBlockToWorkspace ( block , stationaryNode ) ;
740740 } else if (
741- stationaryType == Blockly . ASTNode . types . BLOCK &&
742- movingType == Blockly . ASTNode . types . BLOCK
741+ stationaryType === Blockly . ASTNode . types . BLOCK &&
742+ movingType === Blockly . ASTNode . types . BLOCK
743743 ) {
744744 // Insert the moving block above the stationary block, if the
745745 // appropriate connections exist.
@@ -783,19 +783,19 @@ export class Navigation {
783783 const cursorType = cursorNode . getType ( ) ;
784784
785785 // Check the marker for invalid types.
786- if ( markerType == Blockly . ASTNode . types . FIELD ) {
786+ if ( markerType === Blockly . ASTNode . types . FIELD ) {
787787 this . warn ( 'Should not have been able to mark a field.' ) ;
788788 return false ;
789- } else if ( markerType == Blockly . ASTNode . types . STACK ) {
789+ } else if ( markerType === Blockly . ASTNode . types . STACK ) {
790790 this . warn ( 'Should not have been able to mark a stack.' ) ;
791791 return false ;
792792 }
793793
794794 // Check the cursor for invalid types.
795- if ( cursorType == Blockly . ASTNode . types . FIELD ) {
795+ if ( cursorType === Blockly . ASTNode . types . FIELD ) {
796796 this . warn ( 'Cannot attach a field to anything else.' ) ;
797797 return false ;
798- } else if ( cursorType == Blockly . ASTNode . types . WORKSPACE ) {
798+ } else if ( cursorType === Blockly . ASTNode . types . WORKSPACE ) {
799799 this . warn ( 'Cannot attach a workspace to anything else.' ) ;
800800 return false ;
801801 }
@@ -1255,9 +1255,9 @@ export class Navigation {
12551255 if ( ! cursor ) return ;
12561256 const curNode = cursor . getCurNode ( ) ;
12571257 const nodeType = curNode . getType ( ) ;
1258- if ( nodeType == Blockly . ASTNode . types . FIELD ) {
1258+ if ( nodeType === Blockly . ASTNode . types . FIELD ) {
12591259 ( curNode . getLocation ( ) as Blockly . Field ) . showEditor ( ) ;
1260- } else if ( nodeType == Blockly . ASTNode . types . BLOCK ) {
1260+ } else if ( nodeType === Blockly . ASTNode . types . BLOCK ) {
12611261 const block = curNode . getLocation ( ) as Blockly . Block ;
12621262 if ( ! tryShowFullBlockFieldEditor ( block ) ) {
12631263 const metaKey = navigator . platform . startsWith ( 'Mac' ) ? 'Cmd' : 'Ctrl' ;
@@ -1271,10 +1271,10 @@ export class Navigation {
12711271 }
12721272 } else if (
12731273 curNode . isConnection ( ) ||
1274- nodeType == Blockly . ASTNode . types . WORKSPACE
1274+ nodeType === Blockly . ASTNode . types . WORKSPACE
12751275 ) {
12761276 this . openToolboxOrFlyout ( workspace ) ;
1277- } else if ( nodeType == Blockly . ASTNode . types . STACK ) {
1277+ } else if ( nodeType === Blockly . ASTNode . types . STACK ) {
12781278 this . warn ( 'Cannot mark a stack.' ) ;
12791279 }
12801280 }
@@ -1294,55 +1294,90 @@ export class Navigation {
12941294 }
12951295
12961296 /**
1297- * Show the action menu for a given node.
1297+ * Show the action menu for the current node.
12981298 *
12991299 * The action menu will contain entries for relevant actions for the
13001300 * node's location. If the location is a block, this will include
13011301 * the contents of the block's context menu (if any).
1302+ *
1303+ * Returns true if it is possible to open the action menu in the
1304+ * current location, even if the menu was not opened due there being
1305+ * no applicable menu items.
13021306 */
1303- openActionMenu ( node : Blockly . ASTNode ) {
1304- const fakeEvent = fakeEventForNode ( node ) ;
1305- ( node . getLocation ( ) as Blockly . BlockSvg ) . showContextMenu ( fakeEvent ) ;
1306-
1307+ openActionMenu ( workspace : Blockly . WorkspaceSvg ) : boolean {
13071308 let menuOptions : Array <
13081309 | Blockly . ContextMenuRegistry . ContextMenuOption
13091310 | Blockly . ContextMenuRegistry . LegacyContextMenuOption
1310- > | null = null ;
1311+ > = [ ] ;
13111312 let rtl : boolean ;
1312- let workspace : Blockly . WorkspaceSvg ;
13131313
1314+ const cursor = workspace . getCursor ( ) ;
1315+ if ( ! cursor ) throw new Error ( 'workspace has no cursor' ) ;
1316+ const node = cursor . getCurNode ( ) ;
13141317 const nodeType = node . getType ( ) ;
13151318 switch ( nodeType ) {
13161319 case Blockly . ASTNode . types . BLOCK :
13171320 const block = node . getLocation ( ) as Blockly . BlockSvg ;
1318- workspace = block . workspace as Blockly . WorkspaceSvg ;
13191321 rtl = block . RTL ;
1320-
13211322 // Reimplement BlockSvg.prototype.generateContextMenu as that
13221323 // method is protected.
1323- if ( ! workspace . options . readOnly && ! block . contextMenu ) {
1324+ if ( ! workspace . options . readOnly && block . contextMenu ) {
13241325 menuOptions =
13251326 Blockly . ContextMenuRegistry . registry . getContextMenuOptions (
13261327 Blockly . ContextMenuRegistry . ScopeType . BLOCK ,
13271328 { block} ,
13281329 ) ;
13291330
13301331 // Allow the block to add or modify menuOptions.
1331- if ( block . customContextMenu ) {
1332- block . customContextMenu ( menuOptions ) ;
1333- }
1332+ block . customContextMenu ?.( menuOptions ) ;
13341333 }
13351334 // End reimplement.
13361335 break ;
1336+
1337+ // case Blockly.ASTNode.types.INPUT:
1338+ case Blockly . ASTNode . types . NEXT :
1339+ case Blockly . ASTNode . types . PREVIOUS :
1340+ const connection = node . getLocation ( ) as Blockly . Connection ;
1341+ rtl = connection . getSourceBlock ( ) . RTL ;
1342+
1343+ // Slightly hacky: get insert action from registry. Hacky
1344+ // because registry typings don't include {connection: ...} as
1345+ // a possible kind of scope.
1346+ const insertAction =
1347+ Blockly . ContextMenuRegistry . registry . getItem ( 'insert' ) ;
1348+ if ( ! insertAction ) throw new Error ( "can't find insert action" ) ;
1349+ const possibleOptions = [ insertAction /* etc.*/ ] ;
1350+
1351+ // Check preconditions and get menu texts.
1352+ const scope = {
1353+ connection,
1354+ } as unknown as Blockly . ContextMenuRegistry . Scope ;
1355+ for ( const option of possibleOptions ) {
1356+ const precondition = option . preconditionFn ( scope ) ;
1357+ if ( precondition === 'hidden' ) continue ;
1358+ const displayText =
1359+ typeof option . displayText === 'function'
1360+ ? option . displayText ( scope )
1361+ : option . displayText ;
1362+ menuOptions . push ( {
1363+ text : displayText ,
1364+ enabled : precondition === 'enabled' ,
1365+ callback : option . callback ,
1366+ scope,
1367+ weight : option . weight ,
1368+ } ) ;
1369+ }
1370+ break ;
1371+
13371372 default :
1338- throw new TypeError (
1339- `unable to show action menu for ASTNode of type ${ nodeType } ` ,
1340- ) ;
1373+ console . info ( `No action menu for ASTNode of type ${ nodeType } ` ) ;
1374+ return false ;
13411375 }
13421376
1343- if ( ! menuOptions || ! menuOptions . length ) return ;
1344-
1377+ if ( ! menuOptions ? .length ) return true ;
1378+ const fakeEvent = fakeEventForNode ( node ) ;
13451379 Blockly . ContextMenu . show ( fakeEvent , menuOptions , rtl , workspace ) ;
1380+ return true ;
13461381 }
13471382
13481383 /**
@@ -1411,12 +1446,29 @@ export class Navigation {
14111446 * Create a fake PointerEvent for opening the action menu for the
14121447 * given ASTNode.
14131448 *
1414- * Currently only works for block nodes.
1415- *
14161449 * @param node The node to open the action menu for.
14171450 * @returns A synthetic pointerdown PointerEvent.
14181451 */
14191452function fakeEventForNode ( node : Blockly . ASTNode ) : PointerEvent {
1453+ switch ( node . getType ( ) ) {
1454+ case Blockly . ASTNode . types . BLOCK :
1455+ return fakeEventForBlockNode ( node ) ;
1456+ case Blockly . ASTNode . types . NEXT :
1457+ case Blockly . ASTNode . types . PREVIOUS :
1458+ return fakeEventForStackNode ( node ) ;
1459+ default :
1460+ throw new TypeError ( 'unhandled node type' ) ;
1461+ }
1462+ }
1463+
1464+ /**
1465+ * Create a fake PointerEvent for opening the action menu for the
1466+ * given ASTNode of type BLOCK.
1467+ *
1468+ * @param node The node to open the action menu for.
1469+ * @returns A synthetic pointerdown PointerEvent.
1470+ */
1471+ function fakeEventForBlockNode ( node : Blockly . ASTNode ) : PointerEvent {
14201472 if ( node . getType ( ) !== Blockly . ASTNode . types . BLOCK ) {
14211473 throw new TypeError ( 'can only create PointerEvents for BLOCK nodes' ) ;
14221474 }
@@ -1449,6 +1501,36 @@ function fakeEventForNode(node: Blockly.ASTNode): PointerEvent {
14491501 } ) ;
14501502}
14511503
1504+ /**
1505+ * Create a fake PointerEvent for opening the action menu for the
1506+ * given ASTNode of type NEXT or PREVIOUS.
1507+ *
1508+ * For now this just puts the action menu in the same place as the
1509+ * context menu for the source block.
1510+ *
1511+ * @param node The node to open the action menu for.
1512+ * @returns A synthetic pointerdown PointerEvent.
1513+ */
1514+ function fakeEventForStackNode ( node : Blockly . ASTNode ) : PointerEvent {
1515+ if (
1516+ node . getType ( ) !== Blockly . ASTNode . types . NEXT &&
1517+ node . getType ( ) !== Blockly . ASTNode . types . PREVIOUS
1518+ ) {
1519+ throw new TypeError (
1520+ 'can only create PointerEvents for NEXT / PREVIOUS nodes' ,
1521+ ) ;
1522+ }
1523+
1524+ const connection = node . getLocation ( ) as Blockly . Connection ;
1525+
1526+ return fakeEventForBlockNode (
1527+ new Blockly . ASTNode (
1528+ Blockly . ASTNode . types . BLOCK ,
1529+ connection . getSourceBlock ( ) ,
1530+ ) ,
1531+ ) ;
1532+ }
1533+
14521534/**
14531535 * If this block has a full block field then show its editor.
14541536 *
0 commit comments