import { html, type TemplateResult } from '@design.estate/dees-element'; export interface IFormatButton { command: string; icon: string; label: string; shortcut?: string; action?: () => void; } export class WysiwygFormatting { static readonly formatButtons: IFormatButton[] = [ { command: 'bold', icon: 'B', label: 'Bold', shortcut: '⌘B' }, { command: 'italic', icon: 'I', label: 'Italic', shortcut: '⌘I' }, { command: 'underline', icon: 'U', label: 'Underline', shortcut: '⌘U' }, { command: 'strikeThrough', icon: 'S̶', label: 'Strikethrough' }, { command: 'code', icon: '{ }', label: 'Inline Code' }, { command: 'link', icon: '🔗', label: 'Link', shortcut: '⌘K' }, ]; static renderFormattingMenu( position: { x: number; y: number }, onFormat: (command: string) => void ): TemplateResult { return html`
${this.formatButtons.map(button => html` `)}
`; } static applyFormat(command: string, value?: string): void { // Save current selection const selection = window.getSelection(); if (!selection || selection.rangeCount === 0) return; const range = selection.getRangeAt(0); // Apply format based on command switch (command) { case 'bold': case 'italic': case 'underline': case 'strikeThrough': document.execCommand(command, false); break; case 'code': // For inline code, wrap selection in tags const codeElement = document.createElement('code'); try { codeElement.appendChild(range.extractContents()); range.insertNode(codeElement); // Select the newly created code element range.selectNodeContents(codeElement); selection.removeAllRanges(); selection.addRange(range); } catch (e) { // Fallback to execCommand if range manipulation fails document.execCommand('fontName', false, 'monospace'); } break; case 'link': const url = value || prompt('Enter URL:'); if (url) { document.execCommand('createLink', false, url); } break; } } static getSelectionCoordinates(shadowRoot?: ShadowRoot): { x: number, y: number } | null { // Try shadow root selection first, then window let selection = shadowRoot && (shadowRoot as any).getSelection ? (shadowRoot as any).getSelection() : null; if (!selection || selection.rangeCount === 0) { selection = window.getSelection(); } console.log('getSelectionCoordinates - selection:', selection); if (!selection || selection.rangeCount === 0) { console.log('No selection or no ranges'); return null; } const range = selection.getRangeAt(0); const rect = range.getBoundingClientRect(); console.log('Range rect:', rect); if (rect.width === 0) { console.log('Rect width is 0'); return null; } const coords = { x: rect.left + (rect.width / 2), y: Math.max(45, rect.top - 45) // Position above selection, but ensure it's not negative }; console.log('Returning coords:', coords); return coords; } static isFormattingApplied(command: string): boolean { try { return document.queryCommandState(command); } catch { return false; } } static hasSelection(): boolean { const selection = window.getSelection(); if (!selection || selection.rangeCount === 0) { console.log('No selection or no ranges'); return false; } // Check if we actually have selected text (not just collapsed cursor) const selectedText = selection.toString(); if (!selectedText || selectedText.length === 0) { console.log('No text selected'); return false; } return true; } static getSelectedText(): string { const selection = window.getSelection(); return selection ? selection.toString() : ''; } }