update navigation
This commit is contained in:
@ -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;
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user