feat(wysiwyg): implement backspace

This commit is contained in:
2025-06-24 15:52:28 +00:00
parent 83f153f654
commit ca525ce7e3
7 changed files with 563 additions and 108 deletions

View File

@@ -23,6 +23,7 @@ import {
WysiwygKeyboardHandler,
WysiwygDragDropHandler,
WysiwygModalManager,
WysiwygHistory,
DeesSlashMenu,
DeesFormattingMenu
} from './index.js';
@@ -89,6 +90,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
private inputHandler: WysiwygInputHandler;
private keyboardHandler: WysiwygKeyboardHandler;
private dragDropHandler: WysiwygDragDropHandler;
private history: WysiwygHistory;
public static styles = [
...DeesInputBase.baseStyles,
@@ -103,6 +105,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
this.inputHandler = new WysiwygInputHandler(this);
this.keyboardHandler = new WysiwygKeyboardHandler(this);
this.dragDropHandler = new WysiwygDragDropHandler(this);
this.history = new WysiwygHistory();
}
async connectedCallback() {
@@ -143,6 +146,27 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
}
});
// Add global keyboard listener for undo/redo
this.addEventListener('keydown', (e: KeyboardEvent) => {
// Check if the event is from within our editor
const target = e.target as HTMLElement;
if (!this.contains(target) && !this.shadowRoot?.contains(target)) {
return;
}
// Handle undo/redo
if ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.key === 'z') {
e.preventDefault();
this.undo();
} else if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'z') {
e.preventDefault();
this.redo();
}
});
// Save initial state to history
this.history.saveState(this.blocks, this.selectedBlockId);
// Render blocks programmatically
this.renderBlocksProgrammatically();
}
@@ -516,6 +540,9 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
this.value = WysiwygConverters.getMarkdownOutput(this.blocks);
}
this.changeSubject.next(this.value);
// Save to history (debounced)
this.saveToHistory(true);
}
public getValue(): string {
@@ -879,4 +906,88 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
}, 100);
});
}
/**
* Undo the last action
*/
private undo(): void {
console.log('Undo triggered');
const state = this.history.undo();
if (state) {
this.restoreState(state);
}
}
/**
* Redo the next action
*/
private redo(): void {
console.log('Redo triggered');
const state = this.history.redo();
if (state) {
this.restoreState(state);
}
}
/**
* Restore editor state from history
*/
private restoreState(state: { blocks: IBlock[]; selectedBlockId: string | null; cursorPosition?: { blockId: string; offset: number } }): void {
// Update blocks
this.blocks = state.blocks;
this.selectedBlockId = state.selectedBlockId;
// Re-render blocks
this.renderBlocksProgrammatically();
// Restore cursor position if available
if (state.cursorPosition) {
setTimeout(() => {
const blockWrapper = this.shadowRoot?.querySelector(`[data-block-id="${state.cursorPosition!.blockId}"]`);
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block') as any;
if (blockComponent) {
blockComponent.focusWithCursor(state.cursorPosition!.offset);
}
}, 50);
} else if (state.selectedBlockId) {
// Just focus the selected block
setTimeout(() => {
this.blockOperations.focusBlock(state.selectedBlockId!);
}, 50);
}
// Update value
this.updateValue();
}
/**
* Save current state to history with cursor position
*/
public saveToHistory(debounce: boolean = true): void {
// Get current cursor position if a block is focused
let cursorPosition: { blockId: string; offset: number } | undefined;
if (this.selectedBlockId) {
const blockWrapper = this.shadowRoot?.querySelector(`[data-block-id="${this.selectedBlockId}"]`);
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block') as any;
if (blockComponent && typeof blockComponent.getCursorPosition === 'function') {
const editableElement = blockComponent.shadowRoot?.querySelector('.block') as HTMLElement;
if (editableElement) {
const offset = blockComponent.getCursorPosition(editableElement);
if (offset !== null) {
cursorPosition = {
blockId: this.selectedBlockId,
offset
};
}
}
}
}
if (debounce) {
this.history.saveState(this.blocks, this.selectedBlockId, cursorPosition);
} else {
this.history.saveCheckpoint(this.blocks, this.selectedBlockId, cursorPosition);
}
}
}