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

Feat/loro-2 #99

Merged
merged 15 commits into from
Dec 6, 2023
1 change: 1 addition & 0 deletions packages/blocky-core/src/data/change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface ChangesetApplyOptions {
ignoreCursor: boolean;
record: ChangesetRecordOption;
refreshCursor: boolean;
source?: string;
}

const defaultApplyOptions: ChangesetApplyOptions = {
Expand Down
3 changes: 3 additions & 0 deletions packages/blocky-core/src/data/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@ export interface ElementSetAttributeEvent {
key: string;
value: any;
oldValue?: any;
source?: string;
}

export interface ElementRemoveChildEvent {
type: "element-remove-child";
parent: DataBaseNode;
child: DataBaseNode;
index: number;
source?: string;
}

export interface ElementInsertChildEvent {
type: "element-insert-child";
parent: DataBaseNode;
child: DataBaseNode;
index: number;
source?: string;
}

export type ElementChangedEvent =
Expand Down
41 changes: 29 additions & 12 deletions packages/blocky-core/src/data/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,23 +135,24 @@ export class State implements ChangesetStateLogger {
return false;
}
this.beforeChangesetApply.next(changeset);
const options = changeset.options;

for (const operation of changeset.operations) {
switch (operation.op) {
case "insert-nodes": {
this.#applyInsertOperation(operation);
this.#applyInsertOperation(operation, options);
break;
}
case "update-attributes": {
this.#applyUpdateOperation(operation);
this.#applyUpdateOperation(operation, options);
break;
}
case "remove-nodes": {
this.#applyRemoveOperation(operation);
this.#applyRemoveOperation(operation, options);
break;
}
case "text-edit": {
this.#applyTextEditOperation(operation);
this.#applyTextEditOperation(operation, options);
break;
}
}
Expand Down Expand Up @@ -214,7 +215,10 @@ export class State implements ChangesetStateLogger {
return rebasedChange.finalize(options);
}

#applyInsertOperation(insertOperation: InsertNodeOperation) {
#applyInsertOperation(
insertOperation: InsertNodeOperation,
options: ChangesetApplyOptions
) {
const { location, children } = insertOperation;
const parentLoc = location.slice(0, location.length - 1);
let index = location.last;
Expand All @@ -223,7 +227,11 @@ export class State implements ChangesetStateLogger {
// TODO: optimize insert
for (const child of children) {
if (parent instanceof DataElement) {
parent.__insertChildAt(index++, blockyNodeFromJsonNode(child));
parent.__insertChildAt(
index++,
blockyNodeFromJsonNode(child),
options.source
);
}
}
return;
Expand All @@ -232,31 +240,40 @@ export class State implements ChangesetStateLogger {
throw new Error(`can not insert node at: ${location.toString()}`);
}

#applyUpdateOperation(updateOperation: UpdateNodeOperation) {
#applyUpdateOperation(
updateOperation: UpdateNodeOperation,
options: ChangesetApplyOptions
) {
const { location, attributes } = updateOperation;
const node = this.findNodeByLocation(location) as DataBaseElement;
for (const key in attributes) {
const value = attributes[key];
node.__setAttribute(key, value);
node.__setAttribute(key, value, options.source);
}
}

#applyRemoveOperation(removeOperation: RemoveNodeOperation) {
#applyRemoveOperation(
removeOperation: RemoveNodeOperation,
options: ChangesetApplyOptions
) {
const { location, children } = removeOperation;
const parentLoc = location.slice(0, location.length - 1);
const index = location.last;
if (isNumber(index)) {
const parent = this.findNodeByLocation(parentLoc) as DataBaseElement;
if (parent instanceof DataElement) {
parent.__deleteChildrenAt(index, children.length);
parent.__deleteChildrenAt(index, children.length, options.source);
}
return;
}

throw new Error(`can not remove node at: ${location.toString()}`);
}

#applyTextEditOperation(textEditOperation: TextEditOperation) {
#applyTextEditOperation(
textEditOperation: TextEditOperation,
options: ChangesetApplyOptions
) {
const { location, delta } = textEditOperation;
const node = this.findNodeByLocation(location) as DataBaseElement;
const textNode = node.getAttribute(textEditOperation.key) as
Expand All @@ -269,7 +286,7 @@ export class State implements ChangesetStateLogger {
}>, by location: ${location.toString()}`
);
}
textNode.__applyDelta(delta);
textNode.__applyDelta(delta, options.source);
}

findNodeByLocation(location: NodeLocation): DataBaseNode {
Expand Down
32 changes: 21 additions & 11 deletions packages/blocky-core/src/data/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface DeltaChangedEvent {
oldDelta: Delta;
newDelta?: Delta;
apply: Delta;
source?: string;
}

export interface AttributesObject {
Expand Down Expand Up @@ -96,7 +97,7 @@ export class BlockyTextModel {
* If you want to modify the state of the document and
* notify the editor to update, apply a changeset.
*/
__applyDelta(v: Delta) {
__applyDelta(v: Delta, source?: string) {
const oldDelta = this.#delta;
const newDelta = oldDelta.compose(v);
this.#delta = newDelta;
Expand All @@ -107,6 +108,7 @@ export class BlockyTextModel {
oldDelta,
newDelta,
apply: v,
source,
});
}

Expand Down Expand Up @@ -209,7 +211,7 @@ export class DataBaseElement implements DataBaseNode {
* If you want to modify the state of the document and
* notify the editor to update, apply a changeset.
*/
__setAttribute(name: string, value: any) {
__setAttribute(name: string, value: any, source?: string) {
if (bannedAttributesName.has(name)) {
throw new Error(`'${name}' is preserved`);
}
Expand All @@ -230,6 +232,7 @@ export class DataBaseElement implements DataBaseNode {
key: name,
value,
oldValue,
source,
});
}

Expand Down Expand Up @@ -391,7 +394,7 @@ export class DataElement extends DataBaseElement implements DataNode {
this.childrenLength++;
}

#appendChild(node: DataBaseNode) {
#appendChild(node: DataBaseNode, source?: string) {
this.#validateChild(node);
const insertIndex = this.childrenLength;

Expand All @@ -404,10 +407,15 @@ export class DataElement extends DataBaseElement implements DataNode {
parent: this,
child: node,
index: insertIndex,
source,
});
}

protected __symInsertAfter(node: DataBaseNode, after?: DataBaseNode) {
protected __symInsertAfter(
node: DataBaseNode,
after?: DataBaseNode,
source?: string
) {
if (after && after.parent !== this) {
throw new TypeError("after node is a child of this node");
}
Expand Down Expand Up @@ -458,6 +466,7 @@ export class DataElement extends DataBaseElement implements DataNode {
parent: this,
child: node,
index: cnt,
source,
});
}

Expand All @@ -466,14 +475,14 @@ export class DataElement extends DataBaseElement implements DataNode {
* If you want to modify the state of the document and
* notify the editor to update, apply a changeset.
*/
__insertChildAt(index: number, node: DataBaseNode) {
__insertChildAt(index: number, node: DataBaseNode, source?: string) {
if (index === this.childrenLength) {
this.#appendChild(node);
this.#appendChild(node, source);
return;
}

if (index === 0) {
this.__symInsertAfter(node);
this.__symInsertAfter(node, undefined, source);
return;
}

Expand All @@ -484,15 +493,15 @@ export class DataElement extends DataBaseElement implements DataNode {
ptr = ptr.nextSibling;
}

this.__symInsertAfter(node, ptr ?? undefined);
this.__symInsertAfter(node, ptr ?? undefined, source);
}

/**
* Used internally.
* If you want to modify the state of the document and
* notify the editor to update, apply a changeset.
*/
__deleteChildrenAt(index: number, count: number) {
__deleteChildrenAt(index: number, count: number, source?: string) {
let ptr = this.#firstChild;

while (index > 0) {
Expand All @@ -502,14 +511,14 @@ export class DataElement extends DataBaseElement implements DataNode {

while (ptr && count > 0) {
const next = ptr.nextSibling;
this.#removeChild(ptr);
this.#removeChild(ptr, source);

ptr = next;
count--;
}
}

#removeChild(node: DataBaseNode) {
#removeChild(node: DataBaseNode, source?: string) {
const { parent } = node;
if (parent !== this) {
throw new TypeError("node is not the child of this element");
Expand Down Expand Up @@ -549,6 +558,7 @@ export class DataElement extends DataBaseElement implements DataNode {
parent: this,
child: node,
index: ptr,
source,
});
}

Expand Down
5 changes: 3 additions & 2 deletions packages/blocky-core/src/helper/idHelper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

const a = "a".charCodeAt(0);
const z = "z".charCodeAt(0);

Expand Down Expand Up @@ -75,6 +74,7 @@ export interface IdGenerator {
isBlockId: (id: string) => boolean;
mkSpanId: () => string;
isSpanId: (id: string) => boolean;
mkUserId: () => string;
}

export function makeDefaultIdGenerator(): IdGenerator {
Expand All @@ -85,5 +85,6 @@ export function makeDefaultIdGenerator(): IdGenerator {
isBlockId,
mkSpanId,
isSpanId,
}
mkUserId,
};
}
19 changes: 16 additions & 3 deletions packages/blocky-core/src/view/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,14 +410,25 @@ export class EditorController {
if (!blockNode) {
return;
}
const { nextSibling, prevSibling } = blockNode;

if (!isUpperCase(blockNode.t)) {
return;
}

new Changeset(this.state).removeNode(blockNode).apply({
refreshCursor: true,
});
let nextCusorState: CursorState | null = null;
if (nextSibling instanceof BlockDataElement) {
nextCusorState = CursorState.collapse(nextSibling.id, 0);
} else if (prevSibling instanceof BlockDataElement) {
nextCusorState = CursorState.collapse(prevSibling.id, 0);
}

new Changeset(this.state)
.removeNode(blockNode)
.setCursorState(nextCusorState)
.apply({
refreshCursor: true,
});
}

/**
Expand Down Expand Up @@ -680,6 +691,8 @@ export class EditorController {
});
return pasteHandler.call(blockDef, evt);
}

return new BlockDataElement(dataType, this.idGenerator.mkBlockId());
};

/**
Expand Down
Loading
Loading