Skip to content
This repository was archived by the owner on May 5, 2021. It is now read-only.

Commit ddc85d3

Browse files
committed
memory integration
1 parent 4f2b41c commit ddc85d3

File tree

15 files changed

+179
-28
lines changed

15 files changed

+179
-28
lines changed

packages/core/src/JWEditor.ts

+25
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ModeError } from '../../utils/src/errors';
1414
import { ContainerNode } from './VNodes/ContainerNode';
1515
import { AtomicNode } from './VNodes/AtomicNode';
1616
import { SeparatorNode } from './VNodes/SeparatorNode';
17+
import { Memory } from './Memory/Memory';
1718

1819
export enum Mode {
1920
CONFIGURATION = 'configuration',
@@ -64,6 +65,8 @@ export class JWEditor {
6465
plugins: [],
6566
loadables: {},
6667
};
68+
memory: Memory;
69+
private memoryID = 0;
6770
vDocument: VDocument;
6871
selection = new VSelection();
6972
loaders: Record<string, Loader> = {};
@@ -106,6 +109,13 @@ export class JWEditor {
106109

107110
this.vDocument = new VDocument(new FragmentNode());
108111

112+
// create memory
113+
this.memory = new Memory();
114+
this.memory.linkToMemory(this.vDocument);
115+
// create the next memory slice (and freeze the current memory)
116+
this.memoryID++;
117+
this.memory.create(this.memoryID.toString());
118+
109119
document.body.prepend(this.el);
110120

111121
for (const plugin of this.plugins.values()) {
@@ -119,6 +129,10 @@ export class JWEditor {
119129
domPlugin.editable.addEventListener('keydown', this.processKeydown.bind(this));
120130
this.eventManager = new EventManager(this, domPlugin);
121131
}
132+
133+
// create the next memory slice (and freeze the current memory)
134+
this.memoryID++;
135+
this.memory.create(this.memoryID.toString());
122136
}
123137

124138
//--------------------------------------------------------------------------
@@ -298,7 +312,18 @@ export class JWEditor {
298312
commandName: C,
299313
params?: CommandParams<P, C>,
300314
): Promise<void> {
315+
// switch to the next memory slice (unfreeze the memory)
316+
this.memory.switchTo(this.memoryID.toString());
317+
318+
// TODO:
319+
// create an intermediate slice and switch on it
320+
// create memory for each plugin who use the command then
321+
// use squashInto(winnerSliceKey, winnerSliceKey, newMasterSliceKey)
301322
await this.dispatcher.dispatch(commandName, params);
323+
324+
// create the next memory slice (and freeze the current memory)
325+
this.memoryID++;
326+
this.memory.create(this.memoryID.toString());
302327
}
303328

304329
/**

packages/core/src/Memory/test/memory.test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -842,7 +842,7 @@ describe('core', () => {
842842
expect(array.indexOf(-3)).to.equal(3);
843843
});
844844
it('array indexOf value several times', () => {
845-
const array = new VersionableArray(0, 1, 2, 3, 1, 4);
845+
const array = new VersionableArray<number>(0, 1, 2, 3, 1, 4);
846846
const memory = new Memory();
847847
memory.create('test');
848848
memory.switchTo('test');
@@ -1294,7 +1294,7 @@ describe('core', () => {
12941294
});
12951295
it('default value for array', () => {
12961296
const memory = new Memory();
1297-
const array = new VersionableArray(1, 2, 3, 4, 5);
1297+
const array = new VersionableArray<number>(1, 2, 3, 4, 5);
12981298
memory.linkToMemory(array);
12991299
memory.create('a');
13001300
memory.switchTo('a');
@@ -1310,7 +1310,7 @@ describe('core', () => {
13101310
memory.create('test');
13111311
memory.switchTo('test');
13121312

1313-
const array = new VersionableArray(1, 2, 3, 4, 5);
1313+
const array = new VersionableArray<number>(1, 2, 3, 4, 5);
13141314
memory.linkToMemory(array);
13151315
delete array[0];
13161316
array[0] = 1;
@@ -1324,7 +1324,7 @@ describe('core', () => {
13241324
memory.create('test');
13251325
memory.switchTo('test');
13261326

1327-
const array = new VersionableArray(1, 2, 3, 4, 5);
1327+
const array = new VersionableArray<number>(1, 2, 3, 4, 5);
13281328
memory.linkToMemory(array);
13291329

13301330
memory.create('1-1');
@@ -1348,7 +1348,7 @@ describe('core', () => {
13481348
});
13491349
it('array push and pop in same slide memory have a clean memory slice values', () => {
13501350
const memory = new Memory();
1351-
const array = new VersionableArray(1, 2, 3, 4, 5);
1351+
const array = new VersionableArray<number>(1, 2, 3, 4, 5);
13521352
memory.linkToMemory(array);
13531353
memory.create('1');
13541354
memory.switchTo('1');

packages/core/src/VDocument.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { VNode } from './VNodes/VNode';
22
import { FragmentNode } from './VNodes/FragmentNode';
3+
import { VersionableObject } from './Memory/VersionableObject';
34

4-
export class VDocument {
5+
export class VDocument extends VersionableObject {
56
root: FragmentNode;
67

78
constructor(root: FragmentNode) {
9+
super();
810
this.root = root;
911
}
1012

packages/core/src/VNodes/AbstractNode.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import { VNode, RelativePosition, Predicate, Typeguard, isLeaf } from './VNode';
22
import { Constructor, nodeLength } from '../../../utils/src/utils';
33
import { ContainerNode } from './ContainerNode';
44
import { AtomicNode } from './AtomicNode';
5+
import { VersionableObject } from './../Memory/VersionableObject';
6+
import { markAsDiffRoot } from './../Memory/Memory';
7+
import { makeVersionable } from './../Memory/Versionable';
58

69
let id = 0;
7-
export abstract class AbstractNode {
8-
readonly id = id;
10+
export abstract class AbstractNode extends VersionableObject {
11+
readonly id = id++;
912
tangible = true;
1013
breakable = true;
1114
parent: VNode;
12-
attributes: Record<string, string | Record<string, string>> = {};
15+
attributes: Record<string, string | Record<string, string>> = makeVersionable({});
1316
childVNodes: VNode[];
1417
/**
1518
* Return whether the given predicate is a constructor of a VNode class.
@@ -23,7 +26,8 @@ export abstract class AbstractNode {
2326
}
2427

2528
constructor() {
26-
id++;
29+
super();
30+
markAsDiffRoot(this);
2731
}
2832

2933
get name(): string {
@@ -61,7 +65,7 @@ export abstract class AbstractNode {
6165
*/
6266
clone(): this {
6367
const clone = new this.constructor();
64-
clone.attributes = { ...this.attributes };
68+
clone.attributes = makeVersionable({ ...this.attributes });
6569
return clone;
6670
}
6771

packages/core/src/VNodes/ContainerNode.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { AbstractNode } from './AbstractNode';
22
import { VNode, Predicate, isLeaf } from './VNode';
33
import { ChildError } from '../../../utils/src/errors';
4+
import { VersionableArray } from '../Memory/VersionableArray';
45

56
export class ContainerNode extends AbstractNode {
6-
readonly childVNodes = [];
7+
readonly childVNodes = new VersionableArray<VNode>();
78

89
//--------------------------------------------------------------------------
910
// Browsing children.

packages/core/src/VNodes/VElement.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ContainerNode } from './ContainerNode';
2+
import { makeVersionable } from '../Memory/Versionable';
23

34
export class VElement extends ContainerNode {
45
htmlTag: string;
@@ -16,7 +17,7 @@ export class VElement extends ContainerNode {
1617
*/
1718
clone(): this {
1819
const clone = new this.constructor<typeof VElement>(this.htmlTag);
19-
clone.attributes = { ...this.attributes };
20+
clone.attributes = makeVersionable({ ...this.attributes });
2021
return clone;
2122
}
2223
}
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/* eslint-disable max-nested-callbacks */
2+
import { expect } from 'chai';
3+
import { BasicEditor } from '../../../bundles/BasicEditor';
4+
import { Dom } from '../../plugin-dom/src/Dom';
5+
6+
describe('test performances', () => {
7+
describe('stores', () => {
8+
describe('Memory / VDocument', () => {
9+
let wrapper: HTMLElement;
10+
let editor: BasicEditor;
11+
12+
beforeEach(async () => {
13+
wrapper = document.createElement('test-wrapper');
14+
wrapper.style.display = 'block';
15+
document.body.appendChild(wrapper);
16+
const root = document.createElement('div');
17+
root.innerHTML = `<h1>Jabberwocky</h1>
18+
<h3>by Lewis Carroll</h3>
19+
<p><i>’Twas brillig, and the slithy toves<br/>
20+
Did gyre and gimble in the wabe:<br/>
21+
All mimsy were the borogoves,<br/>
22+
And the mome raths outgrabe.<br/>
23+
<br/>
24+
“Beware the Jabberwock, my son!<br/>
25+
The jaws that bite, the claws that catch!<br/>
26+
Beware the Jubjub bird, and shun<br/>
27+
The frumious Bandersnatch!”<br/>
28+
<br/>
29+
He took his vorpal sword in hand;<br/>
30+
Long time the manxome foe he sought—<br/>
31+
So rested he by the Tumtum tree<br/>
32+
And stood awhile in thought.<br/>
33+
<br/>
34+
And, as in uffish thought he stood,<br/>
35+
The Jabberwock, with eyes of flame,<br/>
36+
Came whiffling through the tulgey wood,<br/>
37+
And burbled as it came!<br/>
38+
<br/>
39+
One, two! One, two! And through and through<br/>
40+
The vorpal blade went snicker-snack!<br/>
41+
He left it dead, and with its head<br/>
42+
He went galumphing back.<br/>
43+
<br/>
44+
“And hast thou slain the Jabberwock?<br/>
45+
Come to my arms, my beamish boy!<br/>
46+
O frabjous day! Callooh! Callay!”<br/>
47+
He chortled in his joy.<br/>
48+
<br/>
49+
’Twas brillig, and the slithy toves<br/>
50+
Did gyre and gimble in the wabe:<br/>
51+
All mimsy were the borogoves,<br/>
52+
And the mome raths outgrabe.<br/></i></p>`;
53+
wrapper.appendChild(root);
54+
55+
editor = new BasicEditor();
56+
editor.configure(Dom, { target: root });
57+
await editor.start();
58+
});
59+
afterEach(async () => {
60+
editor.stop();
61+
document.body.removeChild(wrapper);
62+
});
63+
64+
it('should split a paragraph in two', async () => {
65+
// Parse the editable in the internal format of the editor.
66+
const memory = editor.memory;
67+
const vDocument = editor.vDocument;
68+
memory.linkToMemory(vDocument);
69+
editor.selection.setAt(vDocument.root.children[2].children[500]);
70+
memory.create('0').switchTo('0');
71+
72+
expect(vDocument.root.children.length).to.equal(3);
73+
memory.create('test').switchTo('test');
74+
await editor.execCommand('insertParagraphBreak');
75+
expect(vDocument.root.children.length).to.equal(4);
76+
77+
const t1 = [];
78+
const t2 = [];
79+
for (let k = 1; k < 25; k++) {
80+
let d = Date.now();
81+
memory
82+
.switchTo('0')
83+
.create(k.toString())
84+
.switchTo(k.toString());
85+
t1.push(Date.now() - d);
86+
87+
d = Date.now();
88+
await editor.execCommand('insertParagraphBreak');
89+
t2.push(Date.now() - d);
90+
}
91+
92+
// We remove the first load because it does not represent time in
93+
// use. In fact, time is much longer because the functions and
94+
// object are not yet loaded. The loading test is done separately.
95+
t1.shift();
96+
t2.shift();
97+
98+
const averageInsert = Math.round(t2.reduce((a, b) => a + b) / t2.length);
99+
expect(averageInsert).to.lessThan(30, 'Time to compute the insert paragraph');
100+
101+
const averageSwitch = Math.round(t1.reduce((a, b) => a + b) / t1.length);
102+
expect(averageSwitch).to.lessThan(1, 'Time to switch the memory');
103+
});
104+
});
105+
});
106+
});

packages/plugin-char/src/CharNode.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { MarkerNode } from '../../core/src/VNodes/MarkerNode';
22
import { InlineNode } from '../../plugin-inline/src/InlineNode';
33
import { VNode } from '../../core/src/VNodes/VNode';
44
import { Formats } from '../../plugin-inline/src/Formats';
5+
import { makeVersionable } from '../../core/src/Memory/Versionable';
56

67
export class CharNode extends InlineNode {
78
static readonly atomic = true;
@@ -33,7 +34,7 @@ export class CharNode extends InlineNode {
3334
*/
3435
clone(): this {
3536
const clone = new this.constructor<typeof CharNode>(this.char, this.formats.clone());
36-
clone.attributes = { ...this.attributes };
37+
clone.attributes = makeVersionable({ ...this.attributes });
3738
return clone;
3839
}
3940

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ParsingEngine } from '../../plugin-parser/src/ParsingEngine';
22
import { DefaultDomParser } from './DefaultDomParser';
3+
import { makeVersionable } from '../../core/src/Memory/Versionable';
34

45
export class DomParsingEngine extends ParsingEngine<Node> {
56
static readonly id = 'dom';
@@ -10,9 +11,11 @@ export class DomParsingEngine extends ParsingEngine<Node> {
1011
* @param node
1112
*/
1213
parseAttributes(node: Element): Record<string, string> {
13-
return Array.from(node.attributes || []).reduce((attributes, attribute) => {
14-
attributes[attribute.name] = attribute.value;
15-
return attributes;
16-
}, {});
14+
return makeVersionable(
15+
Array.from(node.attributes || []).reduce((attributes, attribute) => {
16+
attributes[attribute.name] = attribute.value;
17+
return attributes;
18+
}, {}),
19+
);
1720
}
1821
}

packages/plugin-heading/src/HeadingNode.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { VElement } from '../../core/src/VNodes/VElement';
2+
import { makeVersionable } from '../../core/src/Memory/Versionable';
23

34
export class HeadingNode extends VElement {
45
level: number;
@@ -11,7 +12,7 @@ export class HeadingNode extends VElement {
1112
}
1213
clone(): this {
1314
const clone = new this.constructor<typeof HeadingNode>(this.level);
14-
clone.attributes = { ...this.attributes };
15+
clone.attributes = makeVersionable({ ...this.attributes });
1516
return clone;
1617
}
1718
}

packages/plugin-inline/src/Format.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { deepEqualObjects, Constructor } from '../../utils/src/utils';
22
import { InlineNode } from './InlineNode';
3+
import { VersionableObject } from '../../core/src/Memory/VersionableObject';
4+
import { makeVersionable } from '../../core/src/Memory/Versionable';
35

46
interface FormatConstructor {
57
new <T extends Constructor<Format>>(...args: ConstructorParameters<T>): this;
68
}
79
export interface Format {
810
constructor: FormatConstructor & this;
911
}
10-
export class Format {
12+
export class Format extends VersionableObject {
1113
htmlTag: string; // TODO: remove this reference to DOM.
12-
attributes: Record<string, string> = {};
14+
attributes: Record<string, string> = makeVersionable({});
1315
constructor(htmlTag?: string) {
16+
super();
1417
this.htmlTag = htmlTag;
1518
}
1619
get name(): string {
@@ -41,7 +44,7 @@ export class Format {
4144
clone(): this {
4245
const clone = new this.constructor();
4346
clone.htmlTag = this.htmlTag;
44-
clone.attributes = { ...this.attributes };
47+
clone.attributes = makeVersionable({ ...this.attributes });
4548
return clone;
4649
}
4750
isSameAs(otherFormat: Format): boolean {

packages/plugin-inline/src/Formats.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { Format } from './Format';
22
import { Constructor } from '../../utils/src/utils';
3+
import { VersionableArray } from '../../core/src/Memory/VersionableArray';
34

4-
export class Formats extends Array<Format> {
5+
export class Formats extends VersionableArray<Format> {
56
constructor(...formats: Array<Format | Constructor<Format>>) {
67
// Native Array constructor takes the length as argument.
78
const length = formats[0];
89
if (typeof length === 'number') {
910
super(length);
1011
} else {
11-
super(0);
12+
super();
1213
this.append(...formats);
1314
}
1415
}

0 commit comments

Comments
 (0)