fix(wysiwyg): cursor position
This commit is contained in:
@ -332,10 +332,8 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Update blockElement reference for code blocks
|
||||
if (this.block.type === 'code') {
|
||||
this.blockElement = editableBlock;
|
||||
}
|
||||
// For code blocks, we use the nested editableBlock
|
||||
// The blockElement getter will automatically find the right element
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
@ -394,33 +392,43 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
|
||||
|
||||
public focus(): void {
|
||||
if (!this.blockElement) return;
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
|
||||
if (!editableElement) return;
|
||||
|
||||
// Ensure the element is focusable
|
||||
if (!this.blockElement.hasAttribute('contenteditable')) {
|
||||
this.blockElement.setAttribute('contenteditable', 'true');
|
||||
if (!editableElement.hasAttribute('contenteditable')) {
|
||||
editableElement.setAttribute('contenteditable', 'true');
|
||||
}
|
||||
|
||||
this.blockElement.focus();
|
||||
editableElement.focus();
|
||||
|
||||
// If focus failed, try again after a microtask
|
||||
if (document.activeElement !== this.blockElement) {
|
||||
if (document.activeElement !== editableElement && this.shadowRoot?.activeElement !== editableElement) {
|
||||
Promise.resolve().then(() => {
|
||||
this.blockElement.focus();
|
||||
editableElement.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public focusWithCursor(position: 'start' | 'end' | number = 'end'): void {
|
||||
if (!this.blockElement) return;
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
|
||||
if (!editableElement) return;
|
||||
|
||||
// Ensure element is focusable first
|
||||
if (!this.blockElement.hasAttribute('contenteditable')) {
|
||||
this.blockElement.setAttribute('contenteditable', 'true');
|
||||
if (!editableElement.hasAttribute('contenteditable')) {
|
||||
editableElement.setAttribute('contenteditable', 'true');
|
||||
}
|
||||
|
||||
// Focus the element
|
||||
this.blockElement.focus();
|
||||
editableElement.focus();
|
||||
|
||||
// Set cursor position after focus is established
|
||||
const setCursor = () => {
|
||||
@ -430,17 +438,17 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
this.setCursorToEnd();
|
||||
} else if (typeof position === 'number') {
|
||||
// Use the new selection utility to set cursor position
|
||||
WysiwygSelection.setCursorPosition(this.blockElement, position);
|
||||
WysiwygSelection.setCursorPosition(editableElement, position);
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure cursor is set after focus
|
||||
if (document.activeElement === this.blockElement) {
|
||||
if (document.activeElement === editableElement || this.shadowRoot?.activeElement === editableElement) {
|
||||
setCursor();
|
||||
} else {
|
||||
// Wait for focus to be established
|
||||
Promise.resolve().then(() => {
|
||||
if (document.activeElement === this.blockElement) {
|
||||
if (document.activeElement === editableElement || this.shadowRoot?.activeElement === editableElement) {
|
||||
setCursor();
|
||||
}
|
||||
});
|
||||
@ -461,44 +469,64 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
public getContent(): string {
|
||||
if (!this.blockElement) return '';
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
|
||||
if (!editableElement) return '';
|
||||
|
||||
if (this.block.type === 'list') {
|
||||
const listItems = this.blockElement.querySelectorAll('li');
|
||||
const listItems = editableElement.querySelectorAll('li');
|
||||
return Array.from(listItems).map(li => li.innerHTML || '').join('\n');
|
||||
} else if (this.block.type === 'code') {
|
||||
return this.blockElement.textContent || '';
|
||||
return editableElement.textContent || '';
|
||||
} else {
|
||||
return this.blockElement.innerHTML || '';
|
||||
return editableElement.innerHTML || '';
|
||||
}
|
||||
}
|
||||
|
||||
public setContent(content: string): void {
|
||||
if (!this.blockElement) return;
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
|
||||
if (!editableElement) return;
|
||||
|
||||
// Store if we have focus
|
||||
const hadFocus = document.activeElement === this.blockElement;
|
||||
const hadFocus = document.activeElement === editableElement || this.shadowRoot?.activeElement === editableElement;
|
||||
|
||||
if (this.block.type === 'list') {
|
||||
this.blockElement.innerHTML = WysiwygBlocks.renderListContent(content, this.block.metadata);
|
||||
editableElement.innerHTML = WysiwygBlocks.renderListContent(content, this.block.metadata);
|
||||
} else if (this.block.type === 'code') {
|
||||
this.blockElement.textContent = content;
|
||||
editableElement.textContent = content;
|
||||
} else {
|
||||
this.blockElement.innerHTML = content;
|
||||
editableElement.innerHTML = content;
|
||||
}
|
||||
|
||||
// Restore focus if we had it
|
||||
if (hadFocus) {
|
||||
this.blockElement.focus();
|
||||
editableElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public setCursorToStart(): void {
|
||||
WysiwygBlocks.setCursorToStart(this.blockElement);
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
if (editableElement) {
|
||||
WysiwygBlocks.setCursorToStart(editableElement);
|
||||
}
|
||||
}
|
||||
|
||||
public setCursorToEnd(): void {
|
||||
WysiwygBlocks.setCursorToEnd(this.blockElement);
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
if (editableElement) {
|
||||
WysiwygBlocks.setCursorToEnd(editableElement);
|
||||
}
|
||||
}
|
||||
|
||||
public focusListItem(): void {
|
||||
@ -521,50 +549,84 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
blockType: this.block.type
|
||||
});
|
||||
|
||||
// Get selection info using the new utility that handles Shadow DOM
|
||||
const selectionInfo = WysiwygSelection.getSelectionInfo(this.shadowRoot!);
|
||||
if (!selectionInfo) {
|
||||
console.log('getSplitContent: No selection, returning all content as before');
|
||||
// Direct approach: Get selection from window
|
||||
const selection = window.getSelection();
|
||||
if (!selection || selection.rangeCount === 0) {
|
||||
console.log('getSplitContent: No selection found');
|
||||
return {
|
||||
before: fullContent,
|
||||
after: ''
|
||||
};
|
||||
}
|
||||
|
||||
// Check if selection is within this block
|
||||
if (!WysiwygSelection.isSelectionInElement(this.blockElement, this.shadowRoot!)) {
|
||||
console.log('getSplitContent: Selection not in this block');
|
||||
const range = selection.getRangeAt(0);
|
||||
console.log('getSplitContent: Range info:', {
|
||||
startContainer: range.startContainer,
|
||||
startOffset: range.startOffset,
|
||||
collapsed: range.collapsed,
|
||||
startContainerType: range.startContainer.nodeType,
|
||||
startContainerText: range.startContainer.textContent?.substring(0, 50)
|
||||
});
|
||||
|
||||
// Check if this block element has focus or contains the selection
|
||||
const activeElement = this.shadowRoot?.activeElement || document.activeElement;
|
||||
const hasFocus = this.blockElement === activeElement || this.blockElement?.contains(activeElement as Node);
|
||||
|
||||
// For contenteditable, check if selection is in our shadow DOM
|
||||
let selectionInThisBlock = false;
|
||||
try {
|
||||
// Walk up from the selection to see if we reach our block element
|
||||
let node: Node | null = range.startContainer;
|
||||
while (node) {
|
||||
if (node === this.blockElement || node === this.shadowRoot) {
|
||||
selectionInThisBlock = true;
|
||||
break;
|
||||
}
|
||||
node = node.parentNode || (node as any).host; // Check shadow host too
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error checking selection ancestry:', e);
|
||||
}
|
||||
|
||||
console.log('getSplitContent: Focus check:', {
|
||||
hasFocus,
|
||||
selectionInThisBlock,
|
||||
activeElement,
|
||||
blockElement: this.blockElement
|
||||
});
|
||||
|
||||
if (!hasFocus && !selectionInThisBlock) {
|
||||
console.log('getSplitContent: Block does not have focus/selection');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get cursor position as a number
|
||||
const cursorPosition = WysiwygSelection.getCursorPositionInElement(this.blockElement, this.shadowRoot!);
|
||||
console.log('getSplitContent: Cursor position:', {
|
||||
cursorPosition,
|
||||
contentLength: fullContent.length,
|
||||
startContainer: selectionInfo.startContainer,
|
||||
startOffset: selectionInfo.startOffset,
|
||||
collapsed: selectionInfo.collapsed
|
||||
});
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
|
||||
if (!editableElement) {
|
||||
console.log('getSplitContent: No editable element found');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle special cases for different block types
|
||||
if (this.block.type === 'code') {
|
||||
// For code blocks, split text content
|
||||
const fullText = this.blockElement.textContent || '';
|
||||
const textNode = this.getFirstTextNode(this.blockElement);
|
||||
const fullText = editableElement.textContent || '';
|
||||
const textNode = this.getFirstTextNode(editableElement);
|
||||
|
||||
if (textNode && selectionInfo.startContainer === textNode) {
|
||||
const before = fullText.substring(0, selectionInfo.startOffset);
|
||||
const after = fullText.substring(selectionInfo.startOffset);
|
||||
if (textNode && range.startContainer === textNode) {
|
||||
const before = fullText.substring(0, range.startOffset);
|
||||
const after = fullText.substring(range.startOffset);
|
||||
|
||||
console.log('getSplitContent: Code block split result:', {
|
||||
cursorPosition,
|
||||
contentLength: fullText.length,
|
||||
beforeContent: before,
|
||||
beforeLength: before.length,
|
||||
afterContent: after,
|
||||
afterLength: after.length,
|
||||
startOffset: selectionInfo.startOffset
|
||||
startOffset: range.startOffset
|
||||
});
|
||||
|
||||
return { before, after };
|
||||
@ -573,15 +635,38 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
|
||||
// For other block types, extract HTML content
|
||||
try {
|
||||
// If selection is not directly in our element, try to find cursor position by text
|
||||
if (!editableElement.contains(range.startContainer)) {
|
||||
// Simple approach: split at cursor position in text
|
||||
const textContent = editableElement.textContent || '';
|
||||
const cursorPos = range.startOffset; // Simplified cursor position
|
||||
|
||||
const beforeText = textContent.substring(0, cursorPos);
|
||||
const afterText = textContent.substring(cursorPos);
|
||||
|
||||
console.log('Splitting by text position (fallback):', {
|
||||
cursorPos,
|
||||
beforeText,
|
||||
afterText,
|
||||
totalLength: textContent.length
|
||||
});
|
||||
|
||||
// For now, return text-based split
|
||||
return {
|
||||
before: beforeText,
|
||||
after: afterText
|
||||
};
|
||||
}
|
||||
|
||||
// Create a temporary range to get content before cursor
|
||||
const beforeRange = document.createRange();
|
||||
beforeRange.selectNodeContents(this.blockElement);
|
||||
beforeRange.setEnd(selectionInfo.startContainer, selectionInfo.startOffset);
|
||||
beforeRange.selectNodeContents(editableElement);
|
||||
beforeRange.setEnd(range.startContainer, range.startOffset);
|
||||
|
||||
// Create a temporary range to get content after cursor
|
||||
const afterRange = document.createRange();
|
||||
afterRange.selectNodeContents(this.blockElement);
|
||||
afterRange.setStart(selectionInfo.startContainer, selectionInfo.startOffset);
|
||||
afterRange.selectNodeContents(editableElement);
|
||||
afterRange.setStart(range.startContainer, range.startOffset);
|
||||
|
||||
// Clone HTML content (not extract, to avoid modifying the DOM)
|
||||
const beforeContents = beforeRange.cloneContents();
|
||||
@ -602,7 +687,6 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
};
|
||||
|
||||
console.log('getSplitContent: Split result:', {
|
||||
cursorPosition,
|
||||
contentLength: fullContent.length,
|
||||
beforeContent: result.before,
|
||||
beforeLength: result.before.length,
|
||||
|
Reference in New Issue
Block a user