update navigation

This commit is contained in:
Juergen Kunz
2025-06-26 13:45:00 +00:00
parent d48fd667a2
commit f72c9fad3a
2 changed files with 74 additions and 42 deletions

View File

@ -161,7 +161,7 @@ export class CodeBlockHandler extends BaseBlockHandler {
// Keydown handler
editor.addEventListener('keydown', (e) => {
// Handle Tab
// Handle Tab key for code blocks
if (e.key === 'Tab') {
e.preventDefault();
const selection = window.getSelection();
@ -179,6 +179,34 @@ export class CodeBlockHandler extends BaseBlockHandler {
return;
}
// Check cursor position for navigation keys
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
const cursorPos = this.getCursorPosition(element);
const textLength = editor.textContent?.length || 0;
// For ArrowLeft at position 0 or ArrowRight at end, let parent handle navigation
if ((e.key === 'ArrowLeft' && cursorPos === 0) ||
(e.key === 'ArrowRight' && cursorPos === textLength)) {
// Pass to parent handler for inter-block navigation
handlers.onKeyDown(e);
return;
}
// For ArrowUp/Down, check if we're at first/last line
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
const lines = (editor.textContent || '').split('\n');
const currentLine = this.getCurrentLineIndex(editor);
if ((e.key === 'ArrowUp' && currentLine === 0) ||
(e.key === 'ArrowDown' && currentLine === lines.length - 1)) {
// Let parent handle navigation to prev/next block
handlers.onKeyDown(e);
return;
}
}
}
// Pass other keys to parent handler
handlers.onKeyDown(e);
});
@ -233,6 +261,21 @@ export class CodeBlockHandler extends BaseBlockHandler {
lineNumbersContainer.innerHTML = lineNumbersHtml;
}
private getCurrentLineIndex(editor: HTMLElement): number {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) return 0;
const range = selection.getRangeAt(0);
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(editor);
preCaretRange.setEnd(range.startContainer, range.startOffset);
const textBeforeCursor = preCaretRange.toString();
const linesBeforeCursor = textBeforeCursor.split('\n');
return linesBeforeCursor.length - 1; // 0-indexed
}
private applyHighlighting(element: HTMLElement, block: IBlock): void {
const editor = element.querySelector('.code-editor') as HTMLElement;
if (!editor) return;

View File

@ -93,20 +93,9 @@ export class WysiwygKeyboardHandler {
*/
private handleTab(e: KeyboardEvent, block: IBlock): void {
if (block.type === 'code') {
// Allow tab in code blocks
e.preventDefault();
// Insert two spaces for tab
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
range.deleteContents();
const textNode = document.createTextNode(' ');
range.insertNode(textNode);
range.setStartAfter(textNode);
range.setEndAfter(textNode);
selection.removeAllRanges();
selection.addRange(range);
}
// Allow tab in code blocks - handled by CodeBlockHandler
// Let it bubble to the block handler
return;
} else if (block.type === 'list') {
// Future: implement list indentation
e.preventDefault();
@ -120,7 +109,7 @@ export class WysiwygKeyboardHandler {
const blockOps = this.component.blockOperations;
// For non-editable blocks, create a new paragraph after
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nonEditableTypes.includes(block.type)) {
e.preventDefault();
const newBlock = blockOps.createBlock();
@ -209,7 +198,7 @@ export class WysiwygKeyboardHandler {
const blockOps = this.component.blockOperations;
// Handle non-editable blocks
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nonEditableTypes.includes(block.type)) {
e.preventDefault();
@ -269,7 +258,7 @@ export class WysiwygKeyboardHandler {
// Get the actual editable element
const target = block.type === 'code'
? blockComponent.shadowRoot.querySelector('.block.code') as HTMLElement
? blockComponent.shadowRoot.querySelector('.code-editor') as HTMLElement
: blockComponent.shadowRoot.querySelector('.block') as HTMLElement;
if (!target) return;
@ -290,7 +279,7 @@ export class WysiwygKeyboardHandler {
if (prevBlock) {
// If previous block is non-editable, select it first
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nonEditableTypes.includes(prevBlock.type)) {
await blockOps.focusBlock(prevBlock.id);
return;
@ -382,7 +371,7 @@ export class WysiwygKeyboardHandler {
const blockOps = this.component.blockOperations;
// Handle non-editable blocks - same as backspace
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nonEditableTypes.includes(block.type)) {
e.preventDefault();
@ -420,7 +409,7 @@ export class WysiwygKeyboardHandler {
blockOps.removeBlock(block.id);
// Focus the appropriate block
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nextBlock && !nonEditableTypes.includes(nextBlock.type)) {
await blockOps.focusBlock(nextBlock.id, 'start');
} else if (prevBlock && !nonEditableTypes.includes(prevBlock.type)) {
@ -443,7 +432,7 @@ export class WysiwygKeyboardHandler {
// Get the actual editable element
const target = block.type === 'code'
? blockComponent.shadowRoot.querySelector('.block.code') as HTMLElement
? blockComponent.shadowRoot.querySelector('.code-editor') as HTMLElement
: blockComponent.shadowRoot.querySelector('.block') as HTMLElement;
if (!target) return;
@ -460,7 +449,7 @@ export class WysiwygKeyboardHandler {
if (cursorPos === textLength) {
const nextBlock = blockOps.getNextBlock(block.id);
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nextBlock && nonEditableTypes.includes(nextBlock.type)) {
e.preventDefault();
await blockOps.focusBlock(nextBlock.id);
@ -476,7 +465,7 @@ export class WysiwygKeyboardHandler {
*/
private async handleArrowUp(e: KeyboardEvent, block: IBlock): Promise<void> {
// For non-editable blocks, always navigate to previous block
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nonEditableTypes.includes(block.type)) {
e.preventDefault();
const blockOps = this.component.blockOperations;
@ -493,9 +482,9 @@ export class WysiwygKeyboardHandler {
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
if (!blockComponent || !blockComponent.shadowRoot) return;
// Get the actual editable element (code blocks have .block.code)
// Get the actual editable element - code blocks now use .code-editor
const target = block.type === 'code'
? blockComponent.shadowRoot.querySelector('.block.code') as HTMLElement
? blockComponent.shadowRoot.querySelector('.code-editor') as HTMLElement
: blockComponent.shadowRoot.querySelector('.block') as HTMLElement;
if (!target) return;
@ -515,7 +504,7 @@ export class WysiwygKeyboardHandler {
const prevBlock = blockOps.getPreviousBlock(block.id);
if (prevBlock) {
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
await blockOps.focusBlock(prevBlock.id, nonEditableTypes.includes(prevBlock.type) ? undefined : 'end');
}
}
@ -527,14 +516,14 @@ export class WysiwygKeyboardHandler {
*/
private async handleArrowDown(e: KeyboardEvent, block: IBlock): Promise<void> {
// For non-editable blocks, always navigate to next block
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nonEditableTypes.includes(block.type)) {
e.preventDefault();
const blockOps = this.component.blockOperations;
const nextBlock = blockOps.getNextBlock(block.id);
if (nextBlock) {
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
await blockOps.focusBlock(nextBlock.id, nonEditableTypes.includes(nextBlock.type) ? undefined : 'start');
}
return;
@ -545,9 +534,9 @@ export class WysiwygKeyboardHandler {
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
if (!blockComponent || !blockComponent.shadowRoot) return;
// Get the actual editable element (code blocks have .block.code)
// Get the actual editable element - code blocks now use .code-editor
const target = block.type === 'code'
? blockComponent.shadowRoot.querySelector('.block.code') as HTMLElement
? blockComponent.shadowRoot.querySelector('.code-editor') as HTMLElement
: blockComponent.shadowRoot.querySelector('.block') as HTMLElement;
if (!target) return;
@ -567,7 +556,7 @@ export class WysiwygKeyboardHandler {
const nextBlock = blockOps.getNextBlock(block.id);
if (nextBlock) {
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
await blockOps.focusBlock(nextBlock.id, nonEditableTypes.includes(nextBlock.type) ? undefined : 'start');
}
}
@ -595,14 +584,14 @@ export class WysiwygKeyboardHandler {
*/
private async handleArrowLeft(e: KeyboardEvent, block: IBlock): Promise<void> {
// For non-editable blocks, navigate to previous block
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nonEditableTypes.includes(block.type)) {
e.preventDefault();
const blockOps = this.component.blockOperations;
const prevBlock = blockOps.getPreviousBlock(block.id);
if (prevBlock) {
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
await blockOps.focusBlock(prevBlock.id, nonEditableTypes.includes(prevBlock.type) ? undefined : 'end');
}
return;
@ -613,9 +602,9 @@ export class WysiwygKeyboardHandler {
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
if (!blockComponent || !blockComponent.shadowRoot) return;
// Get the actual editable element (code blocks have .block.code)
// Get the actual editable element - code blocks now use .code-editor
const target = block.type === 'code'
? blockComponent.shadowRoot.querySelector('.block.code') as HTMLElement
? blockComponent.shadowRoot.querySelector('.code-editor') as HTMLElement
: blockComponent.shadowRoot.querySelector('.block') as HTMLElement;
if (!target) return;
@ -637,7 +626,7 @@ export class WysiwygKeyboardHandler {
if (prevBlock) {
e.preventDefault();
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
const position = nonEditableTypes.includes(prevBlock.type) ? undefined : 'end';
await blockOps.focusBlock(prevBlock.id, position);
}
@ -650,14 +639,14 @@ export class WysiwygKeyboardHandler {
*/
private async handleArrowRight(e: KeyboardEvent, block: IBlock): Promise<void> {
// For non-editable blocks, navigate to next block
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
if (nonEditableTypes.includes(block.type)) {
e.preventDefault();
const blockOps = this.component.blockOperations;
const nextBlock = blockOps.getNextBlock(block.id);
if (nextBlock) {
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
await blockOps.focusBlock(nextBlock.id, nonEditableTypes.includes(nextBlock.type) ? undefined : 'start');
}
return;
@ -668,9 +657,9 @@ export class WysiwygKeyboardHandler {
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
if (!blockComponent || !blockComponent.shadowRoot) return;
// Get the actual editable element (code blocks have .block.code)
// Get the actual editable element - code blocks now use .code-editor
const target = block.type === 'code'
? blockComponent.shadowRoot.querySelector('.block.code') as HTMLElement
? blockComponent.shadowRoot.querySelector('.code-editor') as HTMLElement
: blockComponent.shadowRoot.querySelector('.block') as HTMLElement;
if (!target) return;
@ -693,7 +682,7 @@ export class WysiwygKeyboardHandler {
if (nextBlock) {
e.preventDefault();
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
const nonEditableTypes = ['divider', 'image', 'youtube', 'attachment'];
await blockOps.focusBlock(nextBlock.id, nonEditableTypes.includes(nextBlock.type) ? undefined : 'start');
}
}