diff --git a/ts_web/elements/wysiwyg/dees-input-wysiwyg.ts b/ts_web/elements/wysiwyg/dees-input-wysiwyg.ts index 13b7e97..d78fbf6 100644 --- a/ts_web/elements/wysiwyg/dees-input-wysiwyg.ts +++ b/ts_web/elements/wysiwyg/dees-input-wysiwyg.ts @@ -108,51 +108,6 @@ export class DeesInputWysiwyg extends DeesInputBase { // Add global selection listener console.log('Adding selectionchange listener'); document.addEventListener('selectionchange', this.selectionChangeHandler); - - // Set initial content for blocks after a brief delay to ensure DOM is ready - await this.updateComplete; - setTimeout(() => { - this.setBlockContents(); - }, 50); - } - - updated(changedProperties: Map) { - // When blocks change (e.g., from setValue), update DOM content - if (changedProperties.has('blocks')) { - // Wait for render to complete - setTimeout(() => { - this.setBlockContents(); - }, 50); - } - } - - private setBlockContents() { - // Set content for blocks that aren't being edited and don't already have content - this.blocks.forEach(block => { - const wrapperElement = this.shadowRoot!.querySelector(`[data-block-id="${block.id}"]`); - if (wrapperElement) { - const blockElement = wrapperElement.querySelector('.block') as HTMLDivElement; - if (blockElement && document.activeElement !== blockElement && block.type !== 'divider') { - // Only set content if the element is empty or has placeholder text - const currentContent = blockElement.textContent || ''; - const isEmpty = !currentContent || currentContent === 'Type \'/\' for commands...' || - currentContent === 'Heading 1' || currentContent === 'Heading 2' || - currentContent === 'Heading 3' || currentContent === 'Quote' || - currentContent === 'Code block'; - - if (isEmpty && block.content) { - if (block.type === 'list') { - blockElement.innerHTML = WysiwygBlocks.renderListContent(block.content, block.metadata); - } else if (block.content.includes('<') && block.content.includes('>')) { - // Content contains HTML formatting - blockElement.innerHTML = block.content; - } else { - blockElement.textContent = block.content; - } - } - } - } - }); } render(): TemplateResult { @@ -276,12 +231,17 @@ export class DeesInputWysiwyg extends DeesInputBase { if (listElement) { block.metadata = { listType: listElement.tagName.toLowerCase() === 'ol' ? 'ordered' : 'bullet' }; } - } else { + } else if (block.type === 'code') { + // For code blocks, preserve the exact text content block.content = target.textContent || ''; + } else { + // For other blocks, preserve HTML formatting + block.content = target.innerHTML || ''; } - // Check for block type change intents - const detectedType = this.detectBlockTypeIntent(block.content); + // Check for block type change intents (use text content for detection, not HTML) + const textContent = target.textContent || ''; + const detectedType = this.detectBlockTypeIntent(textContent); // Only process if the detected type is different from current type if (detectedType && detectedType.type !== block.type) { @@ -311,23 +271,8 @@ export class DeesInputWysiwyg extends DeesInputBase { block.content = ' '; // Create a new paragraph block after the divider - const blockIndex = this.blocks.findIndex(b => b.id === block.id); - const newBlock: IBlock = { - id: WysiwygShortcuts.generateBlockId(), - type: 'paragraph', - content: '', - }; - this.blocks = [...this.blocks.slice(0, blockIndex + 1), newBlock, ...this.blocks.slice(blockIndex + 1)]; - - setTimeout(() => { - const wrapperElement = this.shadowRoot!.querySelector(`[data-block-id="${newBlock.id}"]`); - if (wrapperElement) { - const blockElement = wrapperElement.querySelector('.block') as HTMLDivElement; - if (blockElement) { - blockElement.focus(); - } - } - }); + const newBlock = this.createNewBlock(); + this.insertBlockAfter(block, newBlock); this.updateValue(); this.requestUpdate(); @@ -366,9 +311,9 @@ export class DeesInputWysiwyg extends DeesInputBase { } // Check for slash commands at the beginning of any block - if (block.content === '/' || (block.content.startsWith('/') && this.showSlashMenu)) { + if (textContent === '/' || (textContent.startsWith('/') && this.showSlashMenu)) { // Only show menu on initial '/', or update filter if already showing - if (!this.showSlashMenu && block.content === '/') { + if (!this.showSlashMenu && textContent === '/') { this.showSlashMenu = true; this.slashMenuSelectedIndex = 0; const rect = target.getBoundingClientRect(); @@ -378,8 +323,8 @@ export class DeesInputWysiwyg extends DeesInputBase { y: rect.bottom - containerRect.top + 4 }; } - this.slashMenuFilter = block.content.slice(1); - } else if (!block.content.startsWith('/')) { + this.slashMenuFilter = textContent.slice(1); + } else if (!textContent.startsWith('/')) { this.closeSlashMenu(); } @@ -441,28 +386,8 @@ export class DeesInputWysiwyg extends DeesInputBase { if (e.shiftKey) { // Shift+Enter in code blocks creates a new block e.preventDefault(); - - const blockIndex = this.blocks.findIndex(b => b.id === block.id); - const newBlock: IBlock = { - id: WysiwygShortcuts.generateBlockId(), - type: 'paragraph', - content: '', - }; - - this.blocks = [...this.blocks.slice(0, blockIndex + 1), newBlock, ...this.blocks.slice(blockIndex + 1)]; - - this.updateValue(); - - setTimeout(() => { - const wrapperElement = this.shadowRoot!.querySelector(`[data-block-id="${newBlock.id}"]`); - if (wrapperElement) { - const blockElement = wrapperElement.querySelector('.block') as HTMLDivElement; - if (blockElement) { - blockElement.focus(); - WysiwygBlocks.setCursorToStart(blockElement); - } - } - }); + const newBlock = this.createNewBlock(); + this.insertBlockAfter(block, newBlock); } // For normal Enter in code blocks, let the browser handle it (creates new line) return; @@ -481,25 +406,8 @@ export class DeesInputWysiwyg extends DeesInputBase { if (currentLi && currentLi.textContent === '') { // Empty list item - exit list mode e.preventDefault(); - const blockIndex = this.blocks.findIndex(b => b.id === block.id); - const newBlock: IBlock = { - id: WysiwygShortcuts.generateBlockId(), - type: 'paragraph', - content: '', - }; - - this.blocks = [...this.blocks.slice(0, blockIndex + 1), newBlock, ...this.blocks.slice(blockIndex + 1)]; - - setTimeout(() => { - const wrapperElement = this.shadowRoot!.querySelector(`[data-block-id="${newBlock.id}"]`); - if (wrapperElement) { - const blockElement = wrapperElement.querySelector('.block') as HTMLDivElement; - if (blockElement) { - blockElement.focus(); - WysiwygBlocks.setCursorToStart(blockElement); - } - } - }); + const newBlock = this.createNewBlock(); + this.insertBlockAfter(block, newBlock); } // Otherwise, let the browser handle creating new list items } @@ -507,28 +415,8 @@ export class DeesInputWysiwyg extends DeesInputBase { } e.preventDefault(); - - const blockIndex = this.blocks.findIndex(b => b.id === block.id); - const newBlock: IBlock = { - id: WysiwygShortcuts.generateBlockId(), - type: 'paragraph', - content: '', - }; - - this.blocks = [...this.blocks.slice(0, blockIndex + 1), newBlock, ...this.blocks.slice(blockIndex + 1)]; - - this.updateValue(); - - setTimeout(() => { - const wrapperElement = this.shadowRoot!.querySelector(`[data-block-id="${newBlock.id}"]`); - if (wrapperElement) { - const blockElement = wrapperElement.querySelector('.block') as HTMLDivElement; - if (blockElement) { - blockElement.focus(); - WysiwygBlocks.setCursorToStart(blockElement); - } - } - }); + const newBlock = this.createNewBlock(); + this.insertBlockAfter(block, newBlock); } } else if (e.key === 'Backspace' && block.content === '' && this.blocks.length > 1) { e.preventDefault(); @@ -584,14 +472,17 @@ export class DeesInputWysiwyg extends DeesInputBase { if (this.showSlashMenu && this.selectedBlockId) { // Clear the slash command from the content if menu is closing without selection const currentBlock = this.blocks.find(b => b.id === this.selectedBlockId); - if (currentBlock && currentBlock.content.startsWith('/')) { - const blockElement = this.shadowRoot!.querySelector(`[data-block-id="${currentBlock.id}"]`) as HTMLDivElement; - if (blockElement) { - // Clear the slash command text - blockElement.textContent = ''; - currentBlock.content = ''; - // Ensure cursor stays in the block - blockElement.focus(); + if (currentBlock) { + const wrapperElement = this.shadowRoot!.querySelector(`[data-block-id="${currentBlock.id}"]`); + if (wrapperElement) { + const blockElement = wrapperElement.querySelector('.block') as HTMLDivElement; + if (blockElement && (blockElement.textContent || '').startsWith('/')) { + // Clear the slash command text + blockElement.textContent = ''; + currentBlock.content = ''; + // Ensure cursor stays in the block + blockElement.focus(); + } } } } @@ -672,11 +563,40 @@ export class DeesInputWysiwyg extends DeesInputBase { } } + private createNewBlock(type: IBlock['type'] = 'paragraph', content: string = '', metadata?: any): IBlock { + return { + id: WysiwygShortcuts.generateBlockId(), + type, + content, + ...(metadata && { metadata }) + }; + } + + private insertBlockAfter(afterBlock: IBlock, newBlock: IBlock, focusNewBlock: boolean = true): void { + const blockIndex = this.blocks.findIndex(b => b.id === afterBlock.id); + this.blocks = [...this.blocks.slice(0, blockIndex + 1), newBlock, ...this.blocks.slice(blockIndex + 1)]; + + this.updateValue(); + + if (focusNewBlock) { + setTimeout(() => { + const wrapperElement = this.shadowRoot!.querySelector(`[data-block-id="${newBlock.id}"]`); + if (wrapperElement && newBlock.type !== 'divider') { + const blockElement = wrapperElement.querySelector('.block') as HTMLDivElement; + if (blockElement) { + blockElement.focus(); + WysiwygBlocks.setCursorToStart(blockElement); + } + } + }, 50); + } + } + private async insertBlock(type: IBlock['type']) { const currentBlockIndex = this.blocks.findIndex(b => b.id === this.selectedBlockId); const currentBlock = this.blocks[currentBlockIndex]; - if (currentBlock && currentBlock.content.startsWith('/')) { + if (currentBlock) { // If it's a code block, ask for language if (type === 'code') { const language = await this.showLanguageSelectionModal(); @@ -693,22 +613,8 @@ export class DeesInputWysiwyg extends DeesInputBase { if (type === 'divider') { currentBlock.content = ' '; - const newBlock: IBlock = { - id: WysiwygShortcuts.generateBlockId(), - type: 'paragraph', - content: '', - }; - this.blocks = [...this.blocks.slice(0, currentBlockIndex + 1), newBlock, ...this.blocks.slice(currentBlockIndex + 1)]; - - setTimeout(() => { - const wrapperElement = this.shadowRoot!.querySelector(`[data-block-id="${newBlock.id}"]`); - if (wrapperElement) { - const blockElement = wrapperElement.querySelector('.block') as HTMLDivElement; - if (blockElement) { - blockElement.focus(); - } - } - }); + const newBlock = this.createNewBlock(); + this.insertBlockAfter(currentBlock, newBlock); } else if (type === 'list') { // Handle list type specially currentBlock.metadata = { listType: 'bullet' }; // Default to bullet list @@ -982,13 +888,6 @@ export class DeesInputWysiwyg extends DeesInputBase { } } - private getRootNodeOfNode(node: Node): Node { - let current: Node = node; - while (current.parentNode) { - current = current.parentNode; - } - return current; - } private updateFormattingMenuPosition(): void { console.log('updateFormattingMenuPosition called'); diff --git a/ts_web/elements/wysiwyg/wysiwyg.blocks.ts b/ts_web/elements/wysiwyg/wysiwyg.blocks.ts index bceeed9..1c3d581 100644 --- a/ts_web/elements/wysiwyg/wysiwyg.blocks.ts +++ b/ts_web/elements/wysiwyg/wysiwyg.blocks.ts @@ -74,12 +74,13 @@ export class WysiwygBlocks { console.log('Block mouseup event fired'); if (handlers.onMouseUp) handlers.onMouseUp(e); }}" + .textContent="${block.content || ''}" > `; } - return html` + const blockElement = html`
`; + + return blockElement; } static setCursorToEnd(element: HTMLElement): void {