| 
									
										
										
										
											2025-06-24 22:45:50 +00:00
										 |  |  | import { BaseBlockHandler, type IBlockEventHandlers } from '../block.base.js'; | 
					
						
							|  |  |  | import type { IBlock } from '../../wysiwyg.types.js'; | 
					
						
							|  |  |  | import { cssManager } from '@design.estate/dees-element'; | 
					
						
							|  |  |  | import { WysiwygBlocks } from '../../wysiwyg.blocks.js'; | 
					
						
							|  |  |  | import { WysiwygSelection } from '../../wysiwyg.selection.js'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class QuoteBlockHandler extends BaseBlockHandler { | 
					
						
							|  |  |  |   type = 'quote'; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Track cursor position
 | 
					
						
							|  |  |  |   private lastKnownCursorPosition: number = 0; | 
					
						
							|  |  |  |   private lastSelectedText: string = ''; | 
					
						
							|  |  |  |   private selectionHandler: (() => void) | null = null; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   render(block: IBlock, isSelected: boolean): string { | 
					
						
							|  |  |  |     const selectedClass = isSelected ? ' selected' : ''; | 
					
						
							|  |  |  |     const placeholder = this.getPlaceholder(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     return `
 | 
					
						
							|  |  |  |       <div | 
					
						
							|  |  |  |         class="block quote${selectedClass}" | 
					
						
							|  |  |  |         contenteditable="true" | 
					
						
							|  |  |  |         data-placeholder="${placeholder}" | 
					
						
							|  |  |  |         data-block-id="${block.id}" | 
					
						
							|  |  |  |         data-block-type="${block.type}" | 
					
						
							| 
									
										
										
										
											2025-06-24 23:46:52 +00:00
										 |  |  |       ></div> | 
					
						
							| 
									
										
										
										
											2025-06-24 22:45:50 +00:00
										 |  |  |     `;
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   setup(element: HTMLElement, block: IBlock, handlers: IBlockEventHandlers): void { | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (!quoteBlock) { | 
					
						
							|  |  |  |       console.error('QuoteBlockHandler.setup: No quote block element found'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Set initial content if needed
 | 
					
						
							|  |  |  |     if (block.content && !quoteBlock.innerHTML) { | 
					
						
							|  |  |  |       quoteBlock.innerHTML = block.content; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Input handler with cursor tracking
 | 
					
						
							|  |  |  |     quoteBlock.addEventListener('input', (e) => { | 
					
						
							|  |  |  |       handlers.onInput(e as InputEvent); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Track cursor position after input
 | 
					
						
							|  |  |  |       const pos = this.getCursorPosition(element); | 
					
						
							|  |  |  |       if (pos !== null) { | 
					
						
							|  |  |  |         this.lastKnownCursorPosition = pos; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Keydown handler with cursor tracking
 | 
					
						
							|  |  |  |     quoteBlock.addEventListener('keydown', (e) => { | 
					
						
							|  |  |  |       // Track cursor position before keydown
 | 
					
						
							|  |  |  |       const pos = this.getCursorPosition(element); | 
					
						
							|  |  |  |       if (pos !== null) { | 
					
						
							|  |  |  |         this.lastKnownCursorPosition = pos; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       handlers.onKeyDown(e); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Focus handler
 | 
					
						
							|  |  |  |     quoteBlock.addEventListener('focus', () => { | 
					
						
							|  |  |  |       handlers.onFocus(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Blur handler
 | 
					
						
							|  |  |  |     quoteBlock.addEventListener('blur', () => { | 
					
						
							|  |  |  |       handlers.onBlur(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Composition handlers for IME support
 | 
					
						
							|  |  |  |     quoteBlock.addEventListener('compositionstart', () => { | 
					
						
							|  |  |  |       handlers.onCompositionStart(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     quoteBlock.addEventListener('compositionend', () => { | 
					
						
							|  |  |  |       handlers.onCompositionEnd(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Mouse up handler
 | 
					
						
							|  |  |  |     quoteBlock.addEventListener('mouseup', (e) => { | 
					
						
							|  |  |  |       const pos = this.getCursorPosition(element); | 
					
						
							|  |  |  |       if (pos !== null) { | 
					
						
							|  |  |  |         this.lastKnownCursorPosition = pos; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Selection will be handled by selectionchange event
 | 
					
						
							|  |  |  |       handlers.onMouseUp?.(e); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Click handler with delayed cursor tracking
 | 
					
						
							|  |  |  |     quoteBlock.addEventListener('click', (e: MouseEvent) => { | 
					
						
							|  |  |  |       // Small delay to let browser set cursor position
 | 
					
						
							|  |  |  |       setTimeout(() => { | 
					
						
							|  |  |  |         const pos = this.getCursorPosition(element); | 
					
						
							|  |  |  |         if (pos !== null) { | 
					
						
							|  |  |  |           this.lastKnownCursorPosition = pos; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, 0); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Keyup handler for additional cursor tracking
 | 
					
						
							|  |  |  |     quoteBlock.addEventListener('keyup', (e) => { | 
					
						
							|  |  |  |       const pos = this.getCursorPosition(element); | 
					
						
							|  |  |  |       if (pos !== null) { | 
					
						
							|  |  |  |         this.lastKnownCursorPosition = pos; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Set up selection change handler
 | 
					
						
							|  |  |  |     this.setupSelectionHandler(element, quoteBlock, block); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   private setupSelectionHandler(element: HTMLElement, quoteBlock: HTMLDivElement, block: IBlock): void { | 
					
						
							|  |  |  |     // Add selection change handler
 | 
					
						
							|  |  |  |     const checkSelection = () => { | 
					
						
							|  |  |  |       const selection = window.getSelection(); | 
					
						
							|  |  |  |       if (!selection || selection.rangeCount === 0) return; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       const selectedText = selection.toString(); | 
					
						
							|  |  |  |       if (selectedText.length === 0) { | 
					
						
							|  |  |  |         // Clear selection if no text
 | 
					
						
							|  |  |  |         if (this.lastSelectedText) { | 
					
						
							|  |  |  |           this.lastSelectedText = ''; | 
					
						
							|  |  |  |           this.dispatchSelectionEvent(element, { | 
					
						
							|  |  |  |             text: '', | 
					
						
							|  |  |  |             blockId: block.id, | 
					
						
							|  |  |  |             hasSelection: false | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Get parent wysiwyg component's shadow root - traverse from shadow root
 | 
					
						
							|  |  |  |       const wysiwygBlock = (quoteBlock.getRootNode() as ShadowRoot).host as any; | 
					
						
							|  |  |  |       const parentComponent = wysiwygBlock?.closest('dees-input-wysiwyg'); | 
					
						
							|  |  |  |       const parentShadowRoot = parentComponent?.shadowRoot; | 
					
						
							|  |  |  |       const blockShadowRoot = wysiwygBlock?.shadowRoot; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Use getComposedRanges with shadow roots as per MDN docs
 | 
					
						
							|  |  |  |       const shadowRoots: ShadowRoot[] = []; | 
					
						
							|  |  |  |       if (parentShadowRoot) shadowRoots.push(parentShadowRoot); | 
					
						
							|  |  |  |       if (blockShadowRoot) shadowRoots.push(blockShadowRoot); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Get selection info using our Shadow DOM-aware utility
 | 
					
						
							|  |  |  |       const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots); | 
					
						
							|  |  |  |       if (!selectionInfo) return; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // Check if selection is within this block
 | 
					
						
							|  |  |  |       const startInBlock = WysiwygSelection.containsAcrossShadowDOM(quoteBlock, selectionInfo.startContainer); | 
					
						
							|  |  |  |       const endInBlock = WysiwygSelection.containsAcrossShadowDOM(quoteBlock, selectionInfo.endContainer); | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       if (startInBlock || endInBlock) { | 
					
						
							|  |  |  |         if (selectedText !== this.lastSelectedText) { | 
					
						
							|  |  |  |           this.lastSelectedText = selectedText; | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           // Create range and get rect
 | 
					
						
							|  |  |  |           const range = WysiwygSelection.createRangeFromInfo(selectionInfo); | 
					
						
							|  |  |  |           const rect = range.getBoundingClientRect(); | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |           // Dispatch event
 | 
					
						
							|  |  |  |           this.dispatchSelectionEvent(element, { | 
					
						
							|  |  |  |             text: selectedText.trim(), | 
					
						
							|  |  |  |             blockId: block.id, | 
					
						
							|  |  |  |             range: range, | 
					
						
							|  |  |  |             rect: rect, | 
					
						
							|  |  |  |             hasSelection: true | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } else if (this.lastSelectedText) { | 
					
						
							|  |  |  |         // Clear selection if no longer in this block
 | 
					
						
							|  |  |  |         this.lastSelectedText = ''; | 
					
						
							|  |  |  |         this.dispatchSelectionEvent(element, { | 
					
						
							|  |  |  |           text: '', | 
					
						
							|  |  |  |           blockId: block.id, | 
					
						
							|  |  |  |           hasSelection: false | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Listen for selection changes
 | 
					
						
							|  |  |  |     document.addEventListener('selectionchange', checkSelection); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Store the handler for cleanup
 | 
					
						
							|  |  |  |     this.selectionHandler = checkSelection; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Clean up on disconnect (will be called by dees-wysiwyg-block)
 | 
					
						
							|  |  |  |     const wysiwygBlock = (quoteBlock.getRootNode() as ShadowRoot).host as any; | 
					
						
							|  |  |  |     if (wysiwygBlock) { | 
					
						
							|  |  |  |       const originalDisconnectedCallback = (wysiwygBlock as any).disconnectedCallback; | 
					
						
							|  |  |  |       (wysiwygBlock as any).disconnectedCallback = async function() { | 
					
						
							|  |  |  |         if (this.selectionHandler) { | 
					
						
							|  |  |  |           document.removeEventListener('selectionchange', this.selectionHandler); | 
					
						
							|  |  |  |           this.selectionHandler = null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (originalDisconnectedCallback) { | 
					
						
							|  |  |  |           await originalDisconnectedCallback.call(wysiwygBlock); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }.bind(this); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   private dispatchSelectionEvent(element: HTMLElement, detail: any): void { | 
					
						
							|  |  |  |     const event = new CustomEvent('block-text-selected', { | 
					
						
							|  |  |  |       detail, | 
					
						
							|  |  |  |       bubbles: true, | 
					
						
							|  |  |  |       composed: true | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     element.dispatchEvent(event); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   getStyles(): string { | 
					
						
							|  |  |  |     return `
 | 
					
						
							|  |  |  |       /* Quote specific styles */ | 
					
						
							|  |  |  |       .block.quote { | 
					
						
							|  |  |  |         border-left: 3px solid ${cssManager.bdTheme('#0066cc', '#4d94ff')}; | 
					
						
							|  |  |  |         padding-left: 20px; | 
					
						
							|  |  |  |         color: ${cssManager.bdTheme('#555', '#b0b0b0')}; | 
					
						
							|  |  |  |         font-style: italic; | 
					
						
							|  |  |  |         line-height: 1.6; | 
					
						
							|  |  |  |         margin: 16px 0; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     `;
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   getPlaceholder(): string { | 
					
						
							|  |  |  |     return 'Add a quote...'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   // Helper methods for quote functionality
 | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   getCursorPosition(element: HTMLElement, context?: any): number | null { | 
					
						
							|  |  |  |     // Get the actual quote element
 | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (!quoteBlock) { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get shadow roots from context
 | 
					
						
							|  |  |  |     const wysiwygBlock = context?.component; | 
					
						
							|  |  |  |     const parentComponent = wysiwygBlock?.closest('dees-input-wysiwyg'); | 
					
						
							|  |  |  |     const parentShadowRoot = parentComponent?.shadowRoot; | 
					
						
							|  |  |  |     const blockShadowRoot = context?.shadowRoot; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get selection info with both shadow roots for proper traversal
 | 
					
						
							|  |  |  |     const shadowRoots: ShadowRoot[] = []; | 
					
						
							|  |  |  |     if (parentShadowRoot) shadowRoots.push(parentShadowRoot); | 
					
						
							|  |  |  |     if (blockShadowRoot) shadowRoots.push(blockShadowRoot); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!selectionInfo) { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!WysiwygSelection.containsAcrossShadowDOM(quoteBlock, selectionInfo.startContainer)) { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Create a range from start of element to cursor position
 | 
					
						
							|  |  |  |     const preCaretRange = document.createRange(); | 
					
						
							|  |  |  |     preCaretRange.selectNodeContents(quoteBlock); | 
					
						
							|  |  |  |     preCaretRange.setEnd(selectionInfo.startContainer, selectionInfo.startOffset); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get the text content length up to cursor
 | 
					
						
							|  |  |  |     const position = preCaretRange.toString().length; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     return position; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   getContent(element: HTMLElement, context?: any): string { | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (!quoteBlock) return ''; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // For quotes, get the innerHTML which includes formatting tags
 | 
					
						
							|  |  |  |     const content = quoteBlock.innerHTML || ''; | 
					
						
							|  |  |  |     return content; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   setContent(element: HTMLElement, content: string, context?: any): void { | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (!quoteBlock) return; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Store if we have focus
 | 
					
						
							|  |  |  |     const hadFocus = document.activeElement === quoteBlock ||  | 
					
						
							|  |  |  |                      element.shadowRoot?.activeElement === quoteBlock; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     quoteBlock.innerHTML = content; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Restore focus if we had it
 | 
					
						
							|  |  |  |     if (hadFocus) { | 
					
						
							|  |  |  |       quoteBlock.focus(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   setCursorToStart(element: HTMLElement, context?: any): void { | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (quoteBlock) { | 
					
						
							|  |  |  |       WysiwygBlocks.setCursorToStart(quoteBlock); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   setCursorToEnd(element: HTMLElement, context?: any): void { | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (quoteBlock) { | 
					
						
							|  |  |  |       WysiwygBlocks.setCursorToEnd(quoteBlock); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   focus(element: HTMLElement, context?: any): void { | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (!quoteBlock) return; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Ensure the element is focusable
 | 
					
						
							|  |  |  |     if (!quoteBlock.hasAttribute('contenteditable')) { | 
					
						
							|  |  |  |       quoteBlock.setAttribute('contenteditable', 'true'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     quoteBlock.focus(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // If focus failed, try again after a microtask
 | 
					
						
							|  |  |  |     if (document.activeElement !== quoteBlock && element.shadowRoot?.activeElement !== quoteBlock) { | 
					
						
							|  |  |  |       Promise.resolve().then(() => { | 
					
						
							|  |  |  |         quoteBlock.focus(); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   focusWithCursor(element: HTMLElement, position: 'start' | 'end' | number = 'end', context?: any): void { | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (!quoteBlock) return; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Ensure element is focusable first
 | 
					
						
							|  |  |  |     if (!quoteBlock.hasAttribute('contenteditable')) { | 
					
						
							|  |  |  |       quoteBlock.setAttribute('contenteditable', 'true'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Focus the element
 | 
					
						
							|  |  |  |     quoteBlock.focus(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Set cursor position after focus is established
 | 
					
						
							|  |  |  |     const setCursor = () => { | 
					
						
							|  |  |  |       if (position === 'start') { | 
					
						
							|  |  |  |         this.setCursorToStart(element, context); | 
					
						
							|  |  |  |       } else if (position === 'end') { | 
					
						
							|  |  |  |         this.setCursorToEnd(element, context); | 
					
						
							|  |  |  |       } else if (typeof position === 'number') { | 
					
						
							|  |  |  |         // Use the selection utility to set cursor position
 | 
					
						
							|  |  |  |         WysiwygSelection.setCursorPosition(quoteBlock, position); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Ensure cursor is set after focus
 | 
					
						
							|  |  |  |     if (document.activeElement === quoteBlock || element.shadowRoot?.activeElement === quoteBlock) { | 
					
						
							|  |  |  |       setCursor(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       // Wait for focus to be established
 | 
					
						
							|  |  |  |       Promise.resolve().then(() => { | 
					
						
							|  |  |  |         if (document.activeElement === quoteBlock || element.shadowRoot?.activeElement === quoteBlock) { | 
					
						
							|  |  |  |           setCursor(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   getSplitContent(element: HTMLElement, context?: any): { before: string; after: string } | null { | 
					
						
							|  |  |  |     const quoteBlock = element.querySelector('.block.quote') as HTMLDivElement; | 
					
						
							|  |  |  |     if (!quoteBlock) { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get shadow roots from context
 | 
					
						
							|  |  |  |     const wysiwygBlock = context?.component; | 
					
						
							|  |  |  |     const parentComponent = wysiwygBlock?.closest('dees-input-wysiwyg'); | 
					
						
							|  |  |  |     const parentShadowRoot = parentComponent?.shadowRoot; | 
					
						
							|  |  |  |     const blockShadowRoot = context?.shadowRoot; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get selection info with both shadow roots for proper traversal
 | 
					
						
							|  |  |  |     const shadowRoots: ShadowRoot[] = []; | 
					
						
							|  |  |  |     if (parentShadowRoot) shadowRoots.push(parentShadowRoot); | 
					
						
							|  |  |  |     if (blockShadowRoot) shadowRoots.push(blockShadowRoot); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (!selectionInfo) { | 
					
						
							|  |  |  |       // Try using last known cursor position
 | 
					
						
							|  |  |  |       if (this.lastKnownCursorPosition !== null) { | 
					
						
							|  |  |  |         const fullText = quoteBlock.textContent || ''; | 
					
						
							|  |  |  |         const pos = Math.min(this.lastKnownCursorPosition, fullText.length); | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           before: fullText.substring(0, pos), | 
					
						
							|  |  |  |           after: fullText.substring(pos) | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Make sure the selection is within this block
 | 
					
						
							|  |  |  |     if (!WysiwygSelection.containsAcrossShadowDOM(quoteBlock, selectionInfo.startContainer)) { | 
					
						
							|  |  |  |       // Try using last known cursor position
 | 
					
						
							|  |  |  |       if (this.lastKnownCursorPosition !== null) { | 
					
						
							|  |  |  |         const fullText = quoteBlock.textContent || ''; | 
					
						
							|  |  |  |         const pos = Math.min(this.lastKnownCursorPosition, fullText.length); | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           before: fullText.substring(0, pos), | 
					
						
							|  |  |  |           after: fullText.substring(pos) | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Get cursor position first
 | 
					
						
							|  |  |  |     const cursorPos = this.getCursorPosition(element, context); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     if (cursorPos === null || cursorPos === 0) { | 
					
						
							|  |  |  |       // If cursor is at start or can't determine position, move all content
 | 
					
						
							|  |  |  |       return { | 
					
						
							|  |  |  |         before: '', | 
					
						
							|  |  |  |         after: quoteBlock.innerHTML | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // For HTML content, split using ranges to preserve formatting
 | 
					
						
							|  |  |  |     const beforeRange = document.createRange(); | 
					
						
							|  |  |  |     const afterRange = document.createRange(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Before range: from start of element to cursor
 | 
					
						
							|  |  |  |     beforeRange.setStart(quoteBlock, 0); | 
					
						
							|  |  |  |     beforeRange.setEnd(selectionInfo.startContainer, selectionInfo.startOffset); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // After range: from cursor to end of element
 | 
					
						
							|  |  |  |     afterRange.setStart(selectionInfo.startContainer, selectionInfo.startOffset); | 
					
						
							|  |  |  |     afterRange.setEnd(quoteBlock, quoteBlock.childNodes.length); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Extract HTML content
 | 
					
						
							|  |  |  |     const beforeFragment = beforeRange.cloneContents(); | 
					
						
							|  |  |  |     const afterFragment = afterRange.cloneContents(); | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // Convert to HTML strings
 | 
					
						
							|  |  |  |     const tempDiv = document.createElement('div'); | 
					
						
							|  |  |  |     tempDiv.appendChild(beforeFragment); | 
					
						
							|  |  |  |     const beforeHtml = tempDiv.innerHTML; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     tempDiv.innerHTML = ''; | 
					
						
							|  |  |  |     tempDiv.appendChild(afterFragment); | 
					
						
							|  |  |  |     const afterHtml = tempDiv.innerHTML; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     return {  | 
					
						
							|  |  |  |       before: beforeHtml,  | 
					
						
							|  |  |  |       after: afterHtml  | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |