Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions accessibility/keyboardui.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,16 @@ function getShortcuts() {
keys: `X`,
category: translate('shortcut_category_editor'),
},
{
label: translate('shortcut_comment_block'),
keys: `K`,
category: translate('shortcut_category_editor'),
},
{
label: translate('shortcut_delete_comment'),
keys: `Shift + K`,
category: translate('shortcut_category_editor'),
},
{
label: translate('shortcut_start_move_block'),
keys: `M`,
Expand Down
2 changes: 2 additions & 0 deletions locale/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,8 @@ export default {
shortcut_context_menu: 'Kontextmenü öffnen',
shortcut_duplicate_block: 'Block duplizieren',
shortcut_detach_block: 'Block trennen',
shortcut_comment_block: 'Kommentar ein-/ausblenden',
shortcut_delete_comment: 'Kommentar löschen',
shortcut_start_move_block: 'Block verschieben',
shortcut_move_arrows: 'Verschieben: zur Verbindung',
shortcut_move_anywhere: 'Verschieben: überall',
Expand Down
2 changes: 2 additions & 0 deletions locale/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,8 @@ export default {
shortcut_context_menu: 'Open context menu',
shortcut_duplicate_block: 'Duplicate block',
shortcut_detach_block: 'Detach block',
shortcut_comment_block: 'Show/hide comment',
shortcut_delete_comment: 'Delete comment',
shortcut_start_move_block: 'Move block',
shortcut_move_arrows: 'Move: to connection',
shortcut_move_anywhere: 'Move: anywhere',
Expand Down
2 changes: 2 additions & 0 deletions locale/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,8 @@ export default {
shortcut_context_menu: 'Abrir menú contextual',
shortcut_duplicate_block: 'Duplicar bloque',
shortcut_detach_block: 'Desconectar bloque',
shortcut_comment_block: 'Mostrar/ocultar comentario',
shortcut_delete_comment: 'Eliminar comentario',
shortcut_start_move_block: 'Mover bloque',
shortcut_move_arrows: 'Mover: a conexión',
shortcut_move_anywhere: 'Mover: a cualquier lugar',
Expand Down
2 changes: 2 additions & 0 deletions locale/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,8 @@ export default {
shortcut_context_menu: 'Ouvrir le menu contextuel',
shortcut_duplicate_block: 'Dupliquer le bloc',
shortcut_detach_block: 'Détacher le bloc',
shortcut_comment_block: 'Afficher/masquer le commentaire',
shortcut_delete_comment: 'Supprimer le commentaire',
shortcut_start_move_block: 'Déplacer le bloc',
shortcut_move_arrows: 'Déplacer : vers une connexion',
shortcut_move_anywhere: "Déplacer : n'importe où",
Expand Down
2 changes: 2 additions & 0 deletions locale/it.js
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,8 @@ export default {
shortcut_context_menu: 'Apri menu contestuale',
shortcut_duplicate_block: 'Duplica blocco',
shortcut_detach_block: 'Stacca blocco',
shortcut_comment_block: 'Mostra/nascondi commento',
shortcut_delete_comment: 'Elimina commento',
shortcut_start_move_block: 'Sposta blocco',
shortcut_move_arrows: 'Sposta: alla connessione',
shortcut_move_anywhere: 'Sposta: ovunque',
Expand Down
2 changes: 2 additions & 0 deletions locale/pl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,8 @@ export default {
shortcut_context_menu: 'Otwórz menu kontekstowe',
shortcut_duplicate_block: 'Duplikuj blok',
shortcut_detach_block: 'Odłącz blok',
shortcut_comment_block: 'Pokaż/ukryj komentarz',
shortcut_delete_comment: 'Usuń komentarz',
shortcut_start_move_block: 'Przesuń blok',
shortcut_move_arrows: 'Przesuń: do połączenia',
shortcut_move_anywhere: 'Przesuń: gdziekolwiek',
Expand Down
2 changes: 2 additions & 0 deletions locale/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,8 @@ export default {
shortcut_context_menu: 'Abrir menu de contexto',
shortcut_duplicate_block: 'Duplicar bloco',
shortcut_detach_block: 'Desconectar bloco',
shortcut_comment_block: 'Mostrar/ocultar comentário',
shortcut_delete_comment: 'Excluir comentário',
shortcut_start_move_block: 'Mover bloco',
shortcut_move_arrows: 'Mover: para ligação',
shortcut_move_anywhere: 'Mover: para qualquer lugar',
Expand Down
2 changes: 2 additions & 0 deletions locale/sv.js
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,8 @@ export default {
shortcut_context_menu: 'Öppna snabbmeny',
shortcut_duplicate_block: 'Duplicera block',
shortcut_detach_block: 'Koppla loss block',
shortcut_comment_block: 'Visa/dölj kommentar',
shortcut_delete_comment: 'Ta bort kommentar',
shortcut_start_move_block: 'Flytta block',
shortcut_move_arrows: 'Flytta: till anslutning',
shortcut_move_anywhere: 'Flytta: var som helst',
Expand Down
78 changes: 77 additions & 1 deletion main/blocklyinit.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ import { defineGenerators } from '../generators/generators.js';
import { registerCustomCommentIcon } from './customCommentIcon.js';
import { getMeshFromBlock } from '../ui/blockmesh.js';
import { initContextMenus } from '../ui/contextmenu.js';
import { applyBlockLockState, stripLockState } from '../ui/blocklyutil.js';
import {
applyBlockLockState,
stripLockState,
isBlockLocked,
toggleCommentBubble,
deleteBlockComment,
} from '../ui/blocklyutil.js';
import { toolbox as toolboxDef } from '../toolbox.js';

// Persist locked blocks as part of the workspace serialization. Lower priority
Expand Down Expand Up @@ -1593,6 +1599,76 @@ function installShadowNavigationPatch(ws) {
return true;
}
);

// Comment shortcuts. 'K' toggles the comment bubble open/closed (creating one
// and focusing it when none exists); 'Shift+K' deletes the comment. (N — the
// natural mnemonic for "note" — is already Blockly's next_stack nav key.)
// Comment has no built-in Blockly shortcut, so unlike X/D/Delete these must
// resolve the target themselves — from the focused block (scope.focusedNode),
// or a focused field's source block, falling back to the skippable-field
// resolver.
{
const commentTargetBlock = (scope) => {
const node = scope?.focusedNode;
// A focused block exposes getCommentText; a focused field exposes
// getSourceBlock and unwraps to the block that owns it.
if (node) {
if (typeof node.getCommentText === 'function') return node;
if (typeof node.getSourceBlock === 'function') {
const block = node.getSourceBlock();
if (block) return block;
}
}
return skippableFieldBlock();
};
const commentEditable = (ws, block) =>
!!block &&
!ws.isDragging?.() &&
!ws.isReadOnly?.() &&
!block.isShadow?.() &&
!isBlockLocked(block);

shortcutRegistry.register({
name: 'comment_block',
keyCodes: [shortcutRegistry.createSerializedKey(Blockly.utils.KeyCodes.K)],
preconditionFn: (ws, scope) => commentEditable(ws, commentTargetBlock(scope)),
callback: (_ws, event, _shortcut, scope) => {
const block = commentTargetBlock(scope);
if (!block || block.isShadow?.() || isBlockLocked(block)) return false;
// Cancel the keystroke's default text input; otherwise the 'k' that
// triggered this lands in the comment editor we're about to focus.
event?.preventDefault?.();
Blockly.Events.setGroup('comment_shortcut');
// The undoable create runs synchronously inside toggleCommentBubble
// before it awaits, so it lands in this group; the bubble open/focus
// that follows is UI state and needn't be grouped.
toggleCommentBubble(block);
Blockly.Events.setGroup(false);
return true;
},
});

shortcutRegistry.register({
name: 'delete_comment_block',
keyCodes: [
shortcutRegistry.createSerializedKey(Blockly.utils.KeyCodes.K, [
Blockly.utils.KeyCodes.SHIFT,
]),
],
preconditionFn: (ws, scope) => {
const block = commentTargetBlock(scope);
return commentEditable(ws, block) && block.getCommentText?.() !== null;
},
callback: (_ws, _event, _shortcut, scope) => {
const block = commentTargetBlock(scope);
if (!block || block.getCommentText?.() === null || isBlockLocked(block)) return false;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Blockly.Events.setGroup('delete_comment_shortcut');
deleteBlockComment(block);
Blockly.Events.setGroup(false);
return true;
},
});
}
}

export function createBlocklyWorkspace() {
Expand Down
32 changes: 32 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2255,6 +2255,38 @@ svg.blocklyTrashcanFlyout {
pointer-events: auto;
}

/* Keyboard-only shortcut-letter overlay for the floating block toolbar. */
.fc-toolbar-badges {
position: fixed;
inset: 0;
z-index: 201; /* above .fc-block-toolbar (200) */
pointer-events: none;
opacity: 0;
}

.fc-toolbar-badges.visible {
opacity: 1;
}

.fc-toolbar-key-badge {
position: absolute;
transform: translate(-50%, -50%); /* centre on the anchor point, below the icon */
background: #fff;
color: #000;
border: 1px solid #aaa;
border-radius: 4px;
box-shadow: 0 2px 0 #888;
padding: 2px 6px;
font-size: 12px;
font-weight: bold;
font-family: monospace;
pointer-events: none;
min-width: 10px;
text-align: center;
line-height: 1.4;
white-space: nowrap;
}

.fc-block-toolbar-btn {
display: flex;
align-items: center;
Expand Down
46 changes: 46 additions & 0 deletions ui/blocklyutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,52 @@ export function setBlockLocked(block, locked) {
}
}

// Toggle a comment on a block: remove it if present, otherwise add an empty one
// and open its bubble for editing. Used by the floating block toolbar's comment
// button (which is an add/remove affordance).
export function toggleBlockComment(block) {
if (!block) return;
if (block.getCommentText() !== null) {
block.setCommentText(null);
} else {
block.setCommentText("");
getCommentIcon(block)?.setBubbleVisible(true);
}
}

function getCommentIcon(block) {
return (
block?.getIcons?.().find((i) => typeof i.setBubbleVisible === "function") ??
null
);
}

// Toggle the comment bubble open/closed, creating the comment if the block
// doesn't have one yet. When opening, move keyboard focus into the comment
// editor so the user can type straight away. Used by the 'K' shortcut; never
// deletes (use deleteBlockComment for that). Async because Blockly's
// setBubbleVisible resolves once the bubble has been (re)rendered.
export async function toggleCommentBubble(block) {
if (!block) return;
if (block.getCommentText() === null) block.setCommentText("");
const icon = getCommentIcon(block);
if (!icon) return;
if (icon.bubbleIsVisible?.()) {
await icon.setBubbleVisible(false);
return;
}
await icon.setBubbleVisible(true);
// performAction() is the comment bubble's documented keyboard-navigation
// entry point: it focuses the editor's text area.
icon.getBubble?.()?.performAction?.();
}

// Remove a block's comment entirely (icon and text), if it has one.
export function deleteBlockComment(block) {
if (!block) return;
if (block.getCommentText() !== null) block.setCommentText(null);
}

function trackBlockHighlight(workspace, blockId) {
lastAddMenuHighlighted = { workspace, blockId };
const block = workspace.getBlockById(blockId);
Expand Down
Loading
Loading