Improve Wysiwyg editor

This commit is contained in:
2025-06-24 08:19:53 +00:00
parent e4a042907a
commit 169f74aa2e
8 changed files with 982 additions and 27 deletions

View File

@@ -19,7 +19,12 @@ import {
WysiwygShortcuts,
WysiwygBlocks,
type ISlashMenuItem,
WysiwygFormatting
WysiwygFormatting,
WysiwygBlockOperations,
WysiwygInputHandler,
WysiwygKeyboardHandler,
WysiwygDragDropHandler,
WysiwygModalManager
} from './index.js';
declare global {
@@ -82,8 +87,13 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
private editorContentRef: HTMLDivElement;
private isComposing: boolean = false;
private saveTimeout: any = null;
private selectionChangeHandler = () => this.handleSelectionChange();
// Handler instances
private blockOperations: WysiwygBlockOperations;
private inputHandler: WysiwygInputHandler;
private keyboardHandler: WysiwygKeyboardHandler;
private dragDropHandler: WysiwygDragDropHandler;
public static styles = [
...DeesInputBase.baseStyles,
@@ -91,6 +101,15 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
wysiwygStyles
];
constructor() {
super();
// Initialize handlers
this.blockOperations = new WysiwygBlockOperations(this);
this.inputHandler = new WysiwygInputHandler(this);
this.keyboardHandler = new WysiwygKeyboardHandler(this);
this.dragDropHandler = new WysiwygDragDropHandler(this);
}
async connectedCallback() {
await super.connectedCallback();
}
@@ -99,6 +118,8 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
await super.disconnectedCallback();
// Remove selection listener
document.removeEventListener('selectionchange', this.selectionChangeHandler);
// Clean up handlers
this.inputHandler?.destroy();
}
async firstUpdated() {
@@ -132,28 +153,29 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
private renderBlock(block: IBlock): TemplateResult {
const isSelected = this.selectedBlockId === block.id;
const isDragging = this.draggedBlockId === block.id;
const isDragOver = this.dragOverBlockId === block.id;
const isDragging = this.dragDropHandler.isDragging(block.id);
const isDragOver = this.dragDropHandler.isDragOver(block.id);
const dragOverClasses = this.dragDropHandler.getDragOverClasses(block.id);
return html`
<div
class="block-wrapper ${isDragging ? 'dragging' : ''} ${isDragOver && this.dragOverPosition === 'before' ? 'drag-over-before' : ''} ${isDragOver && this.dragOverPosition === 'after' ? 'drag-over-after' : ''}"
class="block-wrapper ${isDragging ? 'dragging' : ''} ${dragOverClasses}"
data-block-id="${block.id}"
@dragover="${(e: DragEvent) => this.handleDragOver(e, block)}"
@drop="${(e: DragEvent) => this.handleDrop(e, block)}"
@dragleave="${() => this.handleDragLeave(block)}"
@dragover="${(e: DragEvent) => this.dragDropHandler.handleDragOver(e, block)}"
@drop="${(e: DragEvent) => this.dragDropHandler.handleDrop(e, block)}"
@dragleave="${() => this.dragDropHandler.handleDragLeave(block)}"
>
${block.type !== 'divider' ? html`
<div
class="drag-handle"
draggable="true"
@dragstart="${(e: DragEvent) => this.handleDragStart(e, block)}"
@dragend="${() => this.handleDragEnd()}"
@dragstart="${(e: DragEvent) => this.dragDropHandler.handleDragStart(e, block)}"
@dragend="${() => this.dragDropHandler.handleDragEnd()}"
></div>
` : ''}
${WysiwygBlocks.renderBlock(block, isSelected, {
onInput: (e: InputEvent) => this.handleBlockInput(e, block),
onKeyDown: (e: KeyboardEvent) => this.handleBlockKeyDown(e, block),
onInput: (e: InputEvent) => this.inputHandler.handleBlockInput(e, block),
onKeyDown: (e: KeyboardEvent) => this.keyboardHandler.handleBlockKeyDown(e, block),
onFocus: () => this.handleBlockFocus(block),
onBlur: () => this.handleBlockBlur(block),
onCompositionStart: () => this.isComposing = true,
@@ -166,7 +188,10 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
@click="${(e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
this.showBlockSettingsModal(block);
WysiwygModalManager.showBlockSettingsModal(block, (updatedBlock) => {
this.updateValue();
this.requestUpdate();
});
}}"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
@@ -180,7 +205,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
`;
}
private getFilteredMenuItems(): ISlashMenuItem[] {
public getFilteredMenuItems(): ISlashMenuItem[] {
const allItems = WysiwygShortcuts.getSlashMenuItems();
return allItems.filter(item =>
this.slashMenuFilter === '' ||
@@ -279,7 +304,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
return;
} else if (detectedType.type === 'code') {
// For code blocks, ask for language
this.showLanguageSelectionModal().then(language => {
WysiwygModalManager.showLanguageSelectionModal().then(language => {
if (language) {
block.type = 'code';
block.content = '';
@@ -330,12 +355,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
// Don't update value on every input - let the browser handle typing normally
// But schedule a save after a delay
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
this.saveTimeout = setTimeout(() => {
this.updateValue();
}, 1000); // Save after 1 second of inactivity
// Removed - now handled by inputHandler
}
private handleBlockKeyDown(e: KeyboardEvent, block: IBlock) {
@@ -468,7 +488,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
}
}
private closeSlashMenu() {
public closeSlashMenu() {
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);
@@ -592,14 +612,14 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
}
}
private async insertBlock(type: IBlock['type']) {
public async insertBlock(type: IBlock['type']) {
const currentBlockIndex = this.blocks.findIndex(b => b.id === this.selectedBlockId);
const currentBlock = this.blocks[currentBlockIndex];
if (currentBlock) {
// If it's a code block, ask for language
if (type === 'code') {
const language = await this.showLanguageSelectionModal();
const language = await WysiwygModalManager.showLanguageSelectionModal();
if (!language) {
// User cancelled
this.closeSlashMenu();
@@ -932,7 +952,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
this.selectedText = '';
}
private applyFormat(command: string): void {
public applyFormat(command: string): void {
// Save current selection before applying format
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) return;