From bd223f77d06ff03f3bcd0ec069dfaa1e5674c6c5 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 24 Jun 2025 16:53:54 +0000 Subject: [PATCH] selection manipulation --- ts_web/elements/wysiwyg/dees-input-wysiwyg.ts | 116 ++++++++++++++++-- ts_web/elements/wysiwyg/dees-wysiwyg-block.ts | 5 +- 2 files changed, 109 insertions(+), 12 deletions(-) diff --git a/ts_web/elements/wysiwyg/dees-input-wysiwyg.ts b/ts_web/elements/wysiwyg/dees-input-wysiwyg.ts index 30370e6..e508a01 100644 --- a/ts_web/elements/wysiwyg/dees-input-wysiwyg.ts +++ b/ts_web/elements/wysiwyg/dees-input-wysiwyg.ts @@ -806,6 +806,9 @@ export class DeesInputWysiwyg extends DeesInputBase { if (!targetBlock || !targetBlockComponent) return; + // Create a range from our selection info + const range = WysiwygSelection.createRangeFromInfo(selectionInfo); + // Handle link command specially if (command === 'link') { const url = await this.showLinkDialog(); @@ -814,28 +817,28 @@ export class DeesInputWysiwyg extends DeesInputBase { targetBlockComponent.focus(); return; } - // Apply link format - WysiwygFormatting.applyFormat(command, url); + // Apply link format with Shadow DOM aware formatting + this.applyFormatInShadowDOM(range, command, targetBlockComponent, url); } else { - // Apply the format - WysiwygFormatting.applyFormat(command); + // Apply the format with Shadow DOM aware formatting + this.applyFormatInShadowDOM(range, command, targetBlockComponent); } - // Update content after a microtask to ensure DOM is updated - await new Promise(resolve => setTimeout(resolve, 10)); - - // Force content update + // Update content immediately targetBlock.content = targetBlockComponent.getContent(); // Update value to persist changes this.updateValue(); + // Restore focus to the block + targetBlockComponent.focus(); + // For link command, close the formatting menu if (command === 'link') { this.hideFormattingMenu(); - } else if (this.formattingMenu.visible) { - // Keep selection and update menu position - this.updateFormattingMenuPosition(); + } else { + // Let selection handler update menu position + this.selectedText = ''; } } @@ -967,6 +970,97 @@ export class DeesInputWysiwyg extends DeesInputBase { /** * Save current state to history with cursor position */ + /** + * Apply formatting within Shadow DOM context + */ + private applyFormatInShadowDOM(range: Range, command: string, blockComponent: any, value?: string): void { + const editableElement = blockComponent.shadowRoot?.querySelector('.block') as HTMLElement; + if (!editableElement) return; + + // Apply format based on command + switch (command) { + case 'bold': + this.wrapSelectionInShadowDOM(range, 'strong', editableElement); + break; + + case 'italic': + this.wrapSelectionInShadowDOM(range, 'em', editableElement); + break; + + case 'underline': + this.wrapSelectionInShadowDOM(range, 'u', editableElement); + break; + + case 'strikeThrough': + this.wrapSelectionInShadowDOM(range, 's', editableElement); + break; + + case 'code': + this.wrapSelectionInShadowDOM(range, 'code', editableElement); + break; + + case 'link': + if (value) { + this.wrapSelectionWithLinkInShadowDOM(range, value, editableElement); + } + break; + } + } + + /** + * Wrap selection with a tag within Shadow DOM + */ + private wrapSelectionInShadowDOM(range: Range, tagName: string, editableElement: HTMLElement): void { + try { + // Check if we're already wrapped in this tag + const parentElement = range.commonAncestorContainer.parentElement; + if (parentElement && parentElement.tagName.toLowerCase() === tagName) { + // Unwrap + const parent = parentElement.parentNode; + while (parentElement.firstChild) { + parent?.insertBefore(parentElement.firstChild, parentElement); + } + parent?.removeChild(parentElement); + } else { + // Wrap selection + const wrapper = editableElement.ownerDocument.createElement(tagName); + const contents = range.extractContents(); + wrapper.appendChild(contents); + range.insertNode(wrapper); + + // Select the wrapped content + range.selectNodeContents(wrapper); + + // Update selection using our Shadow DOM utilities + WysiwygSelection.setSelectionFromRange(range); + } + } catch (e) { + console.error('Failed to apply format:', e); + } + } + + /** + * Wrap selection with a link within Shadow DOM + */ + private wrapSelectionWithLinkInShadowDOM(range: Range, url: string, editableElement: HTMLElement): void { + try { + const link = editableElement.ownerDocument.createElement('a'); + link.href = url; + link.target = '_blank'; + link.rel = 'noopener noreferrer'; + + const contents = range.extractContents(); + link.appendChild(contents); + range.insertNode(link); + + // Select the link + range.selectNodeContents(link); + WysiwygSelection.setSelectionFromRange(range); + } catch (e) { + console.error('Failed to create link:', e); + } + } + public saveToHistory(debounce: boolean = true): void { // Get current cursor position if a block is focused let cursorPosition: { blockId: string; offset: number } | undefined; diff --git a/ts_web/elements/wysiwyg/dees-wysiwyg-block.ts b/ts_web/elements/wysiwyg/dees-wysiwyg-block.ts index 949b4f1..8c58453 100644 --- a/ts_web/elements/wysiwyg/dees-wysiwyg-block.ts +++ b/ts_web/elements/wysiwyg/dees-wysiwyg-block.ts @@ -667,7 +667,10 @@ export class DeesWysiwygBlock extends DeesElement { } else if (this.block.type === 'code') { return editableElement.textContent || ''; } else { - return editableElement.innerHTML || ''; + // For regular blocks, get the innerHTML which includes formatting tags + const content = editableElement.innerHTML || ''; + console.log('Getting content from block:', content); + return content; } }