Skip to content

Commit

Permalink
Feat/loro-2 (#99)
Browse files Browse the repository at this point in the history
* feat: handle redo/undo events

* subscribe loro events

* feat: submit changeset by loro

* feat: loro map

* feat: remove useless code

* feat: add loro block

* feat: boardcast message

* feat: flush all data to snapshot

* feat: integrate new version loro-wasm

* feat: add idb dao

* feat: introduce dao

* fix: margin of menu

* feat: add toolbar menu items

* refactor: separate bindings

* fix: delete cursor error
  • Loading branch information
vincentdchan authored Dec 6, 2023
1 parent fc49893 commit 10eceaf
Show file tree
Hide file tree
Showing 21 changed files with 1,260 additions and 363 deletions.
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

1 comment on commit 10eceaf

@vercel
Copy link

@vercel vercel bot commented on 10eceaf Dec 6, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.