Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

execute undo on VDOM #239

Merged
merged 6 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions addon/commands/insert-xml-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { parseXmlSiblings } from '@lblod/ember-rdfa-editor/model/util/xml-utils'
import Model from '@lblod/ember-rdfa-editor/model/model';
import { logExecute } from '@lblod/ember-rdfa-editor/utils/logging-utils';
import ModelRange from '@lblod/ember-rdfa-editor/model/model-range';
import ModelNode from '../model/model-node';
import ModelElement from '../model/model-element';
import setNodeAndChildDirty from '@lblod/ember-rdfa-editor/model/util/set-node-and-child-dirty';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to a helper, because it turns out restoring a snapshot is also a special case


/**
* Allows you to insert model nodes from an xml string.
Expand All @@ -31,17 +30,9 @@ export default class InsertXmlCommand extends Command {
//All nodes are marked as dirty by default when inserted but not in the xml writer
// as we need to set dirtiness statuses in the tests, in order to solve bugs related to
// nodes not being properly inserted we set them all dirty in this function below
parsedModelNodes.forEach((node) => this.setNodeAndChildDirty(node));
parsedModelNodes.forEach((node) => setNodeAndChildDirty(node));
const newRange = mutator.insertNodes(range, ...parsedModelNodes);
this.model.selectRange(newRange);
});
}
setNodeAndChildDirty(node: ModelNode) {
node.addDirty('content');
if (ModelElement.isModelElement(node)) {
for (const child of node.children) {
this.setNodeAndChildDirty(child);
}
}
}
}
2 changes: 2 additions & 0 deletions addon/commands/undo-command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Command from '@lblod/ember-rdfa-editor/commands/command';
import Model from '@lblod/ember-rdfa-editor/model/model';
import { logExecute } from '../utils/logging-utils';

export default class UndoCommand extends Command {
name = 'undo';
Expand All @@ -8,6 +9,7 @@ export default class UndoCommand extends Command {
super(model, false);
}

@logExecute
execute(): void {
this.model.restoreSnapshot();
}
Expand Down
1 change: 1 addition & 0 deletions addon/editor/input-handlers/backspace-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ export default class BackspaceHandler extends InputHandler {
void this.backspace().then(() => {
this.rawEditor.updateSelectionAfterComplexInput(); // make sure currentSelection of editor is up to date with actual cursor position
this.rawEditor.model.read();
this.rawEditor.model.saveSnapshot();
this.rawEditor.eventBus.emit(
new ContentChangedEvent({
owner: CORE_OWNER,
Expand Down
5 changes: 2 additions & 3 deletions addon/editor/input-handlers/undo-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ export default class UndoHandler extends InputHandler {
}

handleEvent(_: KeyboardEvent): HandlerResponse {
this.rawEditor.undo();
this.rawEditor.model.read();
return { allowPropagation: false, allowBrowserDefault: true };
this.rawEditor.executeCommand('undo');
return { allowPropagation: false, allowBrowserDefault: false };
}
}
28 changes: 22 additions & 6 deletions addon/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import MarksRegistry from '@lblod/ember-rdfa-editor/model/marks-registry';
import { MarkSpec } from '@lblod/ember-rdfa-editor/model/mark';
import { CORE_OWNER } from '@lblod/ember-rdfa-editor/model/util/constants';
import NodeView from '@lblod/ember-rdfa-editor/model/node-view';
import setNodeAndChildDirty from './util/set-node-and-child-dirty';

/**
* Abstraction layer for the DOM. This is the only class that is allowed to call DOM methods.
Expand Down Expand Up @@ -195,11 +196,14 @@ export default class Model {
*/
change(
callback: (mutator: ImmediateModelMutator) => ModelElement | void,
writeBack = true
writeBack = true,
saveSnapshot = true
) {
const mutator = new ImmediateModelMutator(this._eventBus);
callback(mutator);

if (saveSnapshot) {
nvdk marked this conversation as resolved.
Show resolved Hide resolved
this.saveSnapshot();
}
if (writeBack) {
this.write();
}
Expand Down Expand Up @@ -248,11 +252,23 @@ export default class Model {
writeBack = true
) {
if (snapshot) {
this._rootModelNode = snapshot.rootModelNode;
this._selection = snapshot.modelSelection;

this.change(
(mutator) => {
const range = ModelRange.fromPaths(
this.rootModelNode,
[0],
[this.rootModelNode.getMaxOffset()]
);
setNodeAndChildDirty(snapshot.rootModelNode);
mutator.insertNodes(range, ...snapshot.rootModelNode.children);
},
false,
false
);
if (writeBack) {
this.write();
this.write(this.rootModelNode, false);
this._selection = snapshot.modelSelection.clone(this.rootModelNode);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't seem to work without this clone, though I don't really get why...

this.writeSelection();
}
} else {
this.logger('No snapshot to restore');
Expand Down
11 changes: 11 additions & 0 deletions addon/model/util/set-node-and-child-dirty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ModelElement from '../model-element';
import ModelNode from '../model-node';

export default function setNodeAndChildDirty(node: ModelNode) {
node.addDirty('content');
if (ModelElement.isModelElement(node)) {
for (const child of node.children) {
setNodeAndChildDirty(child);
}
}
}
62 changes: 1 addition & 61 deletions addon/utils/ce/pernet-raw-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,9 @@ import {
import nextTextNode from '@lblod/ember-rdfa-editor/utils/ce/next-text-node';
import MovementObserver from '@lblod/ember-rdfa-editor/utils/ce/movement-observers/movement-observer';
import getRichNodeMatchingDomNode from '@lblod/ember-rdfa-editor/utils/ce/get-rich-node-matching-dom-node';
import CappedHistory from '@lblod/ember-rdfa-editor/utils/ce/capped-history';
import RichNode from '@lblod/marawa/rich-node';
import { tracked } from '@glimmer/tracking';
import { Editor } from '@lblod/ember-rdfa-editor/editor/input-handlers/manipulation';
import { ModelError } from '@lblod/ember-rdfa-editor/utils/errors';
import { Region } from '@lblod/marawa/rdfa-block';
import { INVISIBLE_SPACE } from '@lblod/ember-rdfa-editor/model/util/constants';

Expand Down Expand Up @@ -82,9 +80,6 @@ export default class PernetRawEditor extends RawEditor implements Editor {
@tracked
private _currentSelection?: InternalSelection;

@tracked
history!: CappedHistory;

/**
* the domNode containing our caret
*
Expand All @@ -98,12 +93,7 @@ export default class PernetRawEditor extends RawEditor implements Editor {

constructor(properties: RawEditorProperties) {
super(properties);
this.history = new CappedHistory({ maxItems: 100 });
this.movementObservers = A();
document.addEventListener(
'editorModelWrite',
this.createSnapshot.bind(this)
);
this.eventBus.on(
'contentChanged',
() => {
Expand Down Expand Up @@ -305,9 +295,6 @@ export default class PernetRawEditor extends RawEditor implements Editor {
}

if (textHasChanges) {
if (!extraInfo.some((x) => x.noSnapshot)) {
this.createSnapshot();
}
for (const observer of contentObservers) {
// eslint-disable-next-line ember/no-observers
observer.handleFullContentUpdate(extraInfo);
Expand Down Expand Up @@ -636,7 +623,7 @@ export default class PernetRawEditor extends RawEditor implements Editor {
);
this.updateRichNode();
// eslint-disable-next-line @typescript-eslint/unbound-method
void taskFor(this.generateDiffEvents).perform([{ noSnapshot: true }]);
void taskFor(this.generateDiffEvents).perform();
return this.getRichNodeFor(textNode);
}

Expand Down Expand Up @@ -745,29 +732,6 @@ export default class PernetRawEditor extends RawEditor implements Editor {
}
}

/**
* create a snapshot for undo history
* @method createSnapshot
* @public
*/
createSnapshot() {
try {
const document = {
content: this.rootNode.innerHTML,
currentSelection: this.currentSelection,
};
this.history.push(document);
} catch (e) {
if (e instanceof ModelError) {
console.info(
'Failed to create snapshot because of uninitialized model. This is probably fine.'
);
} else {
throw e;
}
}
}

/**
* execute a DOM transformation on the editor content, ensures a consistent editor state
* @method externalDomUpdate
Expand Down Expand Up @@ -990,28 +954,6 @@ export default class PernetRawEditor extends RawEditor implements Editor {
return this.getRelativeCursorPosition();
}

/**
* restore a snapshot from undo history
* @method undo
* @public
*/
undo() {
const previousSnapshot = this.history.pop();
if (previousSnapshot) {
this.rootNode.innerHTML = previousSnapshot.content;
this.updateRichNode();
this.currentNode = null;
this.setCurrentPosition(previousSnapshot.currentSelection[0]);
// eslint-disable-next-line @typescript-eslint/unbound-method
void taskFor(this.generateDiffEvents).perform([{ noSnapshot: true }]);
this.model.read();
} else {
warn('no more history to undo', {
id: 'contenteditable-editor:history-empty',
});
}
}

selectCurrentSelection() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return
return selectCurrentSelection.bind(this)();
Expand All @@ -1034,7 +976,6 @@ export default class PernetRawEditor extends RawEditor implements Editor {
}

update(selection: unknown, options: Record<string, unknown>) {
this.createSnapshot();
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment
const rslt = update.bind(this)(selection, options);
if (this.tryOutVdom) {
Expand All @@ -1054,7 +995,6 @@ export default class PernetRawEditor extends RawEditor implements Editor {
motivation: string;
}
) {
this.createSnapshot();
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return
return replaceDomNode.bind(this)(domNode, options);
}
Expand Down
8 changes: 0 additions & 8 deletions addon/utils/ce/raw-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ import {
} from '@lblod/ember-rdfa-editor/model/mark';
import AddMarkToRangeCommand from '@lblod/ember-rdfa-editor/commands/add-mark-to-range-command';
import RemoveMarkFromRangeCommand from '@lblod/ember-rdfa-editor/commands/remove-mark-from-range-command';
import PernetRawEditor from '@lblod/ember-rdfa-editor/utils/ce/pernet-raw-editor';
import RemoveMarkCommand from '@lblod/ember-rdfa-editor/commands/remove-mark-command';
import MatchTextCommand from '@lblod/ember-rdfa-editor/commands/match-text-command';

Expand Down Expand Up @@ -255,13 +254,6 @@ export default class RawEditor {
* @param args
*/
executeCommand(commandName: string, ...args: unknown[]) {
//TODO this is a hack to keep legacy undo behavior, to be removed when vdom-based undo is solidified
if (commandName === 'undo') {
if (this instanceof PernetRawEditor) {
this.undo();
return;
}
}
try {
const command = this.getCommand(commandName);
if (command.canExecute(...args)) {
Expand Down