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`
`; } 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() : '';
}
}