-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
79151f7
commit ee45e22
Showing
21 changed files
with
908 additions
and
128 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { Model } from '../model/model'; | ||
import { Range } from '../model/types'; | ||
import { Operation } from '../model/operations'; | ||
|
||
export type Command = { | ||
ops: Array<Operation>; | ||
}; | ||
|
||
export function insertText(range: Range, text: string): Command { | ||
return { | ||
ops: [ | ||
{ | ||
type: 'edit', | ||
range, | ||
value: [{ type: 'text', text }], | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
export function splitBlock(range: Range): Command { | ||
// TODO(hackerwins): Implement this according to the schema. | ||
return { | ||
ops: [ | ||
{ | ||
type: 'edit', | ||
range, | ||
value: [{ type: 'p' }], | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
/** | ||
* `execute` executes the command on the model and returns inverse command. | ||
*/ | ||
export function execute(model: Model, command: Command): Command { | ||
const ops = []; | ||
|
||
for (const op of command.ops) { | ||
const inverse = model.apply(op); | ||
ops.push(inverse); | ||
} | ||
|
||
return { | ||
ops: ops, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,109 @@ | ||
import { Observable } from '../utils/observable'; | ||
import { Node, Text, Element, Range } from './types'; | ||
import { Schema, SchemaSpec } from './schema'; | ||
import { Operation } from './operations'; | ||
import { | ||
insertAfter, | ||
nodesBetween as nodesBetween, | ||
pathOf, | ||
removeNode, | ||
toNodePos, | ||
toXML, | ||
} from './nodes'; | ||
import { firstOf, lastOf } from '../utils/array'; | ||
|
||
// TODO(hackerwins): Build a tree-based model with schema validation. | ||
export class Model extends Observable<string> { | ||
private value: string; | ||
export class Model extends Observable<Node> { | ||
private schema: Schema; | ||
private root: Element; | ||
|
||
static create(initialValue: string): Model { | ||
return new Model(initialValue); | ||
static create(spec: SchemaSpec, initialValue: string): Model { | ||
const schema = new Schema(spec); | ||
const value = schema.fromXML(initialValue); | ||
return new Model(schema, value); | ||
} | ||
|
||
constructor(value: string) { | ||
constructor(schema: Schema, value: Node) { | ||
super(); | ||
this.value = value; | ||
this.schema = schema; | ||
this.root = value; | ||
} | ||
|
||
setValue(value: string): void { | ||
this.value = value; | ||
this.notify(this.value); | ||
createText(value: string): Text { | ||
return this.schema.create('text', value) as Text; | ||
} | ||
|
||
getValue(from?: number, to?: number): string { | ||
if (from !== undefined && to !== undefined) { | ||
return this.value.slice(from, to); | ||
toXML(): string { | ||
return toXML(this.root); | ||
} | ||
|
||
apply(op: Operation): Operation { | ||
switch (op.type) { | ||
case 'edit': | ||
return this.edit(op.range, op.value); | ||
case 'move': | ||
return this.move(/*op.source, op.target*/); | ||
default: | ||
throw new Error(`invalid operation type: ${op}`); | ||
} | ||
} | ||
|
||
edit(range: Range, values: Array<Node>): Operation { | ||
const start = toNodePos(range.s, this.root); | ||
const end = toNodePos(range.e, this.root); | ||
|
||
const nodesToRemove: Array<Node> = []; | ||
nodesBetween(this.root, start, end, (node) => { | ||
nodesToRemove.push(node); | ||
}); | ||
|
||
for (const node of nodesToRemove) { | ||
removeNode(node); | ||
} | ||
|
||
insertAfter(start.node, ...values); | ||
|
||
const firstNode = firstOf(values)!; | ||
const lastNode = lastOf(values)!; | ||
|
||
return { | ||
type: 'edit', | ||
range: { | ||
s: pathOf(firstNode, this.root), | ||
e: pathOf(lastNode, this.root) | ||
}, | ||
value: nodesToRemove | ||
}; | ||
} | ||
|
||
/** | ||
* `getContentEndRange` returns the range of the end of the content in the | ||
* model. | ||
*/ | ||
getContentEndRange(): Range { | ||
const path = []; | ||
|
||
let node = this.root; | ||
while (node) { | ||
if (node.type === 'text') { | ||
const text = node as Text; | ||
path.push(text.text.length); | ||
break; | ||
} | ||
|
||
const elem = node as Element; | ||
const children = elem.children || []; | ||
if (children.length === 0) { | ||
break; | ||
} | ||
|
||
path.push(children.length - 1); | ||
node = lastOf(children)!; | ||
} | ||
|
||
return this.value; | ||
return { s: path, e: path }; | ||
} | ||
|
||
edit(start: number, end: number, text: string): void { | ||
this.value = this.value.slice(0, start) + text + this.value.slice(end); | ||
this.notify(this.value); | ||
move(/*source: Range, target: Range*/): Operation { | ||
throw new Error('Method not implemented.'); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.