This commit is contained in:
Juergen Kunz
2025-06-24 18:43:51 +00:00
parent fca3638f7f
commit 89a4a15e78
3 changed files with 275 additions and 42 deletions

View File

@ -35,6 +35,9 @@ export class WysiwygKeyboardHandler {
case 'Backspace':
await this.handleBackspace(e, block);
break;
case 'Delete':
await this.handleDelete(e, block);
break;
case 'ArrowUp':
await this.handleArrowUp(e, block);
break;
@ -116,6 +119,14 @@ export class WysiwygKeyboardHandler {
private async handleEnter(e: KeyboardEvent, block: IBlock): Promise<void> {
const blockOps = this.component.blockOperations;
// For non-editable blocks, create a new paragraph after
if (block.type === 'divider' || block.type === 'image') {
e.preventDefault();
const newBlock = blockOps.createBlock();
await blockOps.insertBlockAfter(block, newBlock);
return;
}
if (block.type === 'code') {
if (e.shiftKey) {
// Shift+Enter in code blocks creates a new block
@ -222,6 +233,41 @@ export class WysiwygKeyboardHandler {
private async handleBackspace(e: KeyboardEvent, block: IBlock): Promise<void> {
const blockOps = this.component.blockOperations;
// Handle non-editable blocks (divider, image)
if (block.type === 'divider' || block.type === 'image') {
e.preventDefault();
// Don't delete if it's the only block
if (this.component.blocks.length === 1) {
return;
}
// Save state for undo
this.component.saveToHistory(false);
// Find the previous block to focus
const prevBlock = blockOps.getPreviousBlock(block.id);
const nextBlock = blockOps.getNextBlock(block.id);
// Remove the block
blockOps.removeBlock(block.id);
// Focus the appropriate block
if (prevBlock && prevBlock.type !== 'divider' && prevBlock.type !== 'image') {
await blockOps.focusBlock(prevBlock.id, 'end');
} else if (nextBlock && nextBlock.type !== 'divider' && nextBlock.type !== 'image') {
await blockOps.focusBlock(nextBlock.id, 'start');
} else if (prevBlock) {
// If previous block is also non-editable, just select it
await blockOps.focusBlock(prevBlock.id);
} else if (nextBlock) {
// If next block is also non-editable, just select it
await blockOps.focusBlock(nextBlock.id);
}
return;
}
// Get the block component to check cursor position
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block') as any;
@ -321,10 +367,66 @@ export class WysiwygKeyboardHandler {
// Otherwise, let browser handle normal backspace
}
/**
* Handles Delete key
*/
private async handleDelete(e: KeyboardEvent, block: IBlock): Promise<void> {
const blockOps = this.component.blockOperations;
// Handle non-editable blocks (divider, image) - same as backspace
if (block.type === 'divider' || block.type === 'image') {
e.preventDefault();
// Don't delete if it's the only block
if (this.component.blocks.length === 1) {
return;
}
// Save state for undo
this.component.saveToHistory(false);
// Find the previous block to focus
const prevBlock = blockOps.getPreviousBlock(block.id);
const nextBlock = blockOps.getNextBlock(block.id);
// Remove the block
blockOps.removeBlock(block.id);
// Focus the appropriate block
if (nextBlock && nextBlock.type !== 'divider' && nextBlock.type !== 'image') {
await blockOps.focusBlock(nextBlock.id, 'start');
} else if (prevBlock && prevBlock.type !== 'divider' && prevBlock.type !== 'image') {
await blockOps.focusBlock(prevBlock.id, 'end');
} else if (nextBlock) {
// If next block is also non-editable, just select it
await blockOps.focusBlock(nextBlock.id);
} else if (prevBlock) {
// If previous block is also non-editable, just select it
await blockOps.focusBlock(prevBlock.id);
}
return;
}
// For editable blocks, let browser handle normal delete
}
/**
* Handles ArrowUp key - navigate to previous block if at beginning or first line
*/
private async handleArrowUp(e: KeyboardEvent, block: IBlock): Promise<void> {
// For non-editable blocks, always navigate to previous block
if (block.type === 'divider' || block.type === 'image') {
e.preventDefault();
const blockOps = this.component.blockOperations;
const prevBlock = blockOps.getPreviousBlock(block.id);
if (prevBlock) {
await blockOps.focusBlock(prevBlock.id, prevBlock.type === 'divider' || prevBlock.type === 'image' ? undefined : 'end');
}
return;
}
// Get the block component from the wysiwyg component's shadow DOM
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
@ -352,9 +454,9 @@ export class WysiwygKeyboardHandler {
const blockOps = this.component.blockOperations;
const prevBlock = blockOps.getPreviousBlock(block.id);
if (prevBlock && prevBlock.type !== 'divider') {
if (prevBlock) {
console.log('ArrowUp: Focusing previous block:', prevBlock.id);
await blockOps.focusBlock(prevBlock.id, 'end');
await blockOps.focusBlock(prevBlock.id, prevBlock.type === 'divider' || prevBlock.type === 'image' ? undefined : 'end');
}
}
// Otherwise, let browser handle normal navigation
@ -364,6 +466,18 @@ export class WysiwygKeyboardHandler {
* Handles ArrowDown key - navigate to next block if at end or last line
*/
private async handleArrowDown(e: KeyboardEvent, block: IBlock): Promise<void> {
// For non-editable blocks, always navigate to next block
if (block.type === 'divider' || block.type === 'image') {
e.preventDefault();
const blockOps = this.component.blockOperations;
const nextBlock = blockOps.getNextBlock(block.id);
if (nextBlock) {
await blockOps.focusBlock(nextBlock.id, nextBlock.type === 'divider' || nextBlock.type === 'image' ? undefined : 'start');
}
return;
}
// Get the block component from the wysiwyg component's shadow DOM
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
@ -391,9 +505,9 @@ export class WysiwygKeyboardHandler {
const blockOps = this.component.blockOperations;
const nextBlock = blockOps.getNextBlock(block.id);
if (nextBlock && nextBlock.type !== 'divider') {
if (nextBlock) {
console.log('ArrowDown: Focusing next block:', nextBlock.id);
await blockOps.focusBlock(nextBlock.id, 'start');
await blockOps.focusBlock(nextBlock.id, nextBlock.type === 'divider' || nextBlock.type === 'image' ? undefined : 'start');
}
}
// Otherwise, let browser handle normal navigation
@ -419,6 +533,18 @@ export class WysiwygKeyboardHandler {
* Handles ArrowLeft key - navigate to previous block if at beginning
*/
private async handleArrowLeft(e: KeyboardEvent, block: IBlock): Promise<void> {
// For non-editable blocks, navigate to previous block
if (block.type === 'divider' || block.type === 'image') {
e.preventDefault();
const blockOps = this.component.blockOperations;
const prevBlock = blockOps.getPreviousBlock(block.id);
if (prevBlock) {
await blockOps.focusBlock(prevBlock.id, prevBlock.type === 'divider' || prevBlock.type === 'image' ? undefined : 'end');
}
return;
}
// Get the block component from the wysiwyg component's shadow DOM
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
@ -448,9 +574,9 @@ export class WysiwygKeyboardHandler {
const prevBlock = blockOps.getPreviousBlock(block.id);
console.log('ArrowLeft: At start, previous block:', prevBlock?.id);
if (prevBlock && prevBlock.type !== 'divider') {
if (prevBlock) {
e.preventDefault();
await blockOps.focusBlock(prevBlock.id, 'end');
await blockOps.focusBlock(prevBlock.id, prevBlock.type === 'divider' || prevBlock.type === 'image' ? undefined : 'end');
}
}
// Otherwise, let the browser handle normal left arrow navigation
@ -460,6 +586,18 @@ export class WysiwygKeyboardHandler {
* Handles ArrowRight key - navigate to next block if at end
*/
private async handleArrowRight(e: KeyboardEvent, block: IBlock): Promise<void> {
// For non-editable blocks, navigate to next block
if (block.type === 'divider' || block.type === 'image') {
e.preventDefault();
const blockOps = this.component.blockOperations;
const nextBlock = blockOps.getNextBlock(block.id);
if (nextBlock) {
await blockOps.focusBlock(nextBlock.id, nextBlock.type === 'divider' || nextBlock.type === 'image' ? undefined : 'start');
}
return;
}
// Get the block component from the wysiwyg component's shadow DOM
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
@ -488,9 +626,9 @@ export class WysiwygKeyboardHandler {
const blockOps = this.component.blockOperations;
const nextBlock = blockOps.getNextBlock(block.id);
if (nextBlock && nextBlock.type !== 'divider') {
if (nextBlock) {
e.preventDefault();
await blockOps.focusBlock(nextBlock.id, 'start');
await blockOps.focusBlock(nextBlock.id, nextBlock.type === 'divider' || nextBlock.type === 'image' ? undefined : 'start');
}
}
// Otherwise, let the browser handle normal right arrow navigation