Improve Wysiwyg editor
This commit is contained in:
@@ -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;
|
||||
|
Reference in New Issue
Block a user