diff --git a/projects/ngx-editor/src/lib/Editor.ts b/projects/ngx-editor/src/lib/Editor.ts index 1965b6d..036ea12 100644 --- a/projects/ngx-editor/src/lib/Editor.ts +++ b/projects/ngx-editor/src/lib/Editor.ts @@ -138,22 +138,25 @@ class Editor { } const { state } = this.view; - const { tr, doc } = state; const newDoc = parseContent(content, this.schema, this.options.parseOptions); - tr.replaceWith(0, state.doc.content.size, newDoc); - // don't emit if both content is same - if (doc.eq(tr.doc)) { + if (state.doc.eq(newDoc)) { return; } - if (!tr.docChanged) { - return; - } + // Create a new state to avoid recording this change in the undo history. + // This prevents Ctrl/Cmd+Z from clearing the initial content. + const newState = EditorState.create({ + doc: newDoc, + schema: this.schema, + plugins: state.plugins, + }); + this.view.updateState(newState); - this.view.dispatch(tr); + const json = newState.doc.toJSON(); + this.valueChangesSubject.next(json); } registerPlugin(plugin: Plugin): void { diff --git a/projects/ngx-editor/src/lib/editor.component.spec.ts b/projects/ngx-editor/src/lib/editor.component.spec.ts index 006def4..5192fed 100644 --- a/projects/ngx-editor/src/lib/editor.component.spec.ts +++ b/projects/ngx-editor/src/lib/editor.component.spec.ts @@ -2,6 +2,7 @@ import { Component } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AbstractControl, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; +import { undo } from 'prosemirror-history'; import Editor from './Editor'; import { NgxEditorComponent } from './editor.component'; @@ -61,6 +62,51 @@ describe('NgxEditorComponent', () => { fixture.detectChanges(); expect(component.editor.view.state.doc.textContent).toBe(''); }); + + it('should not clear initial value on undo', () => { + component.writeValue('Hello world!'); + fixture.detectChanges(); + expect(component.editor.view.state.doc.textContent).toBe('Hello world!'); + + // undo with no user edits should not clear content + undo(component.editor.view.state, component.editor.view.dispatch); + fixture.detectChanges(); + expect(component.editor.view.state.doc.textContent).toBe('Hello world!'); + + // simulate a user edit by inserting text via a transaction + const { state } = component.editor.view; + const tr = state.tr.insertText(' Goodbye!', state.doc.content.size - 1); + component.editor.view.dispatch(tr); + fixture.detectChanges(); + expect(component.editor.view.state.doc.textContent).toBe('Hello world! Goodbye!'); + + // undo should revert the user edit back to the initial value + undo(component.editor.view.state, component.editor.view.dispatch); + fixture.detectChanges(); + expect(component.editor.view.state.doc.textContent).toBe('Hello world!'); + }); + + it('should reset undo history on programmatic setContent calls', () => { + component.writeValue('Initial'); + fixture.detectChanges(); + + // simulate a user edit + const { state } = component.editor.view; + const tr = state.tr.insertText(' edit', state.doc.content.size - 1); + component.editor.view.dispatch(tr); + fixture.detectChanges(); + expect(component.editor.view.state.doc.textContent).toBe('Initial edit'); + + // programmatic setContent resets history + component.writeValue('Replaced'); + fixture.detectChanges(); + expect(component.editor.view.state.doc.textContent).toBe('Replaced'); + + // undo should have no effect since history was reset + undo(component.editor.view.state, component.editor.view.dispatch); + fixture.detectChanges(); + expect(component.editor.view.state.doc.textContent).toBe('Replaced'); + }); }); describe('NgxEditorComponent: Reactive Forms API', () => {