Skip to content

Commit

Permalink
fix: add toolbar actions
Browse files Browse the repository at this point in the history
  • Loading branch information
qinluhe committed Oct 10, 2024
1 parent c9cd625 commit e00da90
Show file tree
Hide file tree
Showing 62 changed files with 2,580 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export async function updateCollab (workspaceId: string, objectId: string, docSt
code: number;
message: string;
}>(url, {
doc_state: docState,
doc_state: Array.from(docState),
});

if (response?.data.code !== 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class SyncManager {
public initialize () {
if (this.hasUnsyncedChanges) {
// Send an update if there are unsynced changes
void this.sendUpdate();
this.debouncedSendUpdate();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
executeOperations,
getBlock,
getBlockEntry,
getSelectionOrThrow,
getSelectionOrThrow, getSelectionTexts,
getSharedRoot,
handleCollapsedBreakWithTxn,
handleDeleteEntireDocumentWithTxn,
Expand Down Expand Up @@ -33,14 +33,6 @@ import { BasePoint, BaseRange, Editor, Element, Node, NodeEntry, Path, Range, Te
import { ReactEditor } from 'slate-react';

export const CustomEditor = {
findTextNode (editor: ReactEditor, path: number[]): NodeEntry<Element> {
const [node] = editor.nodes({
at: path,
match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.textId !== undefined,
});

return node as NodeEntry<Element>;
},
// Get the text content of a block node, including the text content of its children and formula nodes
getBlockTextContent (node: Node): string {
if (Text.isText(node)) {
Expand Down Expand Up @@ -236,7 +228,7 @@ export const CustomEditor = {
}: {
key: EditorMarkFormat, value: boolean | string
}) {
if (editor.marks && Object.keys(editor.marks).includes(key)) {
if (CustomEditor.isMarkActive(editor, key)) {
editor.removeMark(key);
} else {
editor.addMark(key, value);
Expand All @@ -251,6 +243,10 @@ export const CustomEditor = {
editor.addMark(key, value);
},

removeMark (editor: ReactEditor, key: EditorMarkFormat) {
editor.removeMark(key);
},

turnToBlock<T extends BlockData> (editor: YjsEditor, blockId: string, type: BlockType, data: T) {
const operations: (() => void)[] = [];
const sharedRoot = getSharedRoot(editor);
Expand All @@ -268,4 +264,62 @@ export const CustomEditor = {

executeOperations(sharedRoot, operations, 'turnToBlock');
},

isBlockActive (editor: YjsEditor, type: BlockType) {
try {
const [node] = getBlockEntry(editor);

return node.type === type;
} catch (e) {
return false;
}
},

hasMark (editor: ReactEditor, key: string) {
const selection = editor.selection;

if (!selection) return false;

const isExpanded = Range.isExpanded(selection);

if (isExpanded) {

const texts = getSelectionTexts(editor);

return texts.some((node) => {
const { text, ...attributes } = node;

if (!text) return true;
return Boolean((attributes as Record<string, boolean | string>)[key]);
});
}

const marks = Editor.marks(editor) as Record<string, string | boolean> | null;

return marks ? !!marks[key] : false;
},

isMarkActive (editor: ReactEditor, key: string) {
const selection = editor.selection;

if (!selection) return false;

const isExpanded = Range.isExpanded(selection);

if (isExpanded) {

const texts = getSelectionTexts(editor);

return texts.every((node) => {
const { text, ...attributes } = node;

if (!text) return true;
return Boolean((attributes as Record<string, boolean | string>)[key]);
});
}

const marks = Editor.marks(editor) as Record<string, string | boolean> | null;

return marks ? !!marks[key] : false;
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ function insertText (ydoc: Y.Doc, editor: Editor, { path, offset, text, attribut

if ('formula' in beforeAttributes) {
Object.assign({
formula: undefined,
formula: null,
});
}

if ('mention' in beforeAttributes) {
Object.assign({
mention: undefined,
mention: null,
});
}

Expand Down Expand Up @@ -138,12 +138,11 @@ function applyRemoveText (ydoc: Y.Doc, editor: Editor, op: RemoveTextOperation,
}

function applySetNode (ydoc: Y.Doc, editor: Editor, op: SetNodeOperation, slateContent: Descendant[]) {
const { newProperties, path } = op;
const { newProperties, path, properties } = op;
const leafKeys = Object.values(EditorMarkFormat) as string[];
const properties = Object.keys(newProperties);

const isLeaf = properties.some((prop: string) => leafKeys.includes(prop));
const isData = properties.some((prop: string) => prop === 'data');
const isLeaf = Object.keys(newProperties).some((prop: string) => leafKeys.includes(prop)) || (Object.keys(newProperties).length === 0 && Object.keys(properties).some((prop: string) => leafKeys.includes(prop)));
const isData = Object.keys(newProperties).some((prop: string) => prop === 'data');
const sharedRoot = ydoc.getMap(YjsEditorKey.data_section) as YSharedRoot;

console.log('applySetNode isLeaf', isLeaf, op);
Expand All @@ -154,14 +153,32 @@ function applySetNode (ydoc: Y.Doc, editor: Editor, op: SetNodeOperation, slateC
if (!textId) return;

const yText = getText(textId, sharedRoot);
const [start, end] = Editor.edges(editor, path);
const start = {
path,
offset: 0,
};
const end = {
path,
offset: (getNodeAtPath(slateContent, path) as Text).text.length,
};

const startRelativeOffset = Math.min(calculateOffsetRelativeToParent(node, start), yText.toJSON().length);
const endRelativeOffset = Math.min(calculateOffsetRelativeToParent(node, end), yText.toJSON().length);

const length = endRelativeOffset - startRelativeOffset;

yText.format(startRelativeOffset, length, newProperties);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const formats: Record<string, any> = {
...newProperties,
};

Object.entries(properties).forEach(([key, val]) => {
if (val && !(key in newProperties)) {
formats[key] = null;
}
});

yText.format(startRelativeOffset, length, formats);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ export function slatePointToRelativePosition (
const textId = node.textId as string;
const ytext = getText(textId, sharedRoot);

const offset = Math.min(calculateOffsetRelativeToParent(node, point), ytext.toJSON().length);
if (!ytext) {
throw new Error('YText not found');
}

const offset = Math.min(calculateOffsetRelativeToParent(node, point), ytext.length);

const relPos = Y.createRelativePositionFromTypeIndex(ytext, offset);

Expand Down Expand Up @@ -116,7 +120,7 @@ export function relativePositionToSlatePoint (
return calculatePointFromParentOffset(node, path, absIndex);
}

function calculatePointFromParentOffset (slateNode: Element, path: number[], parentOffset: number): BasePoint {
export function calculatePointFromParentOffset (slateNode: Element, path: number[], parentOffset: number): BasePoint {
let remainingOffset = parentOffset;
let childIndex = 0;

Expand Down Expand Up @@ -151,4 +155,5 @@ function calculatePointFromParentOffset (slateNode: Element, path: number[], par
}

return calculatePointFromParentOffset(childNode, [...path, childIndex], remainingOffset);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,28 @@ import {

import { nanoid } from 'nanoid';
import Delta, { Op } from 'quill-delta';
import { BaseRange, Descendant, Editor, Element, Node, NodeEntry, Path, Point, Range, Transforms } from 'slate';
import {
BaseRange,
Descendant,
Text,
Editor,
Element,
Node,
NodeEntry,
Path,
Point,
Range,
Transforms,
BasePoint,
} from 'slate';
import { ReactEditor } from 'slate-react';
import * as Y from 'yjs';
import { YjsEditor } from '../plugins/withYjs';
import { slatePointToRelativePosition } from './positions';
import {
calculateOffsetRelativeToParent,
calculatePointFromParentOffset,
slatePointToRelativePosition,
} from './positions';

export function createEmptyDocument () {
const doc = new Y.Doc();
Expand Down Expand Up @@ -1214,4 +1232,82 @@ export function getNodeAtPath (children: Descendant[], path: Path): Descendant |
}

return currentNode;
}

export function getSelectionTexts (editor: ReactEditor) {
const selection = editor.selection;

if (!selection) return [];

const texts: Text[] = [];

const isExpanded = Range.isExpanded(selection);

if (isExpanded) {
let anchor = Range.start(selection);
const focus = Range.end(selection);
const isEnd = Editor.isEnd(editor, anchor, anchor.path);

if (isEnd) {
const after = Editor.after(editor, anchor);

if (after) {
anchor = after;
}
}

Array.from(
Editor.nodes(editor, {
at: {
anchor,
focus,
},
}),
).forEach((match) => {
const node = match[0] as Element;

if (Text.isText(node)) {
texts.push(node);
} else if (Editor.isInline(editor, node)) {
texts.push(...(node.children as Text[]));
}
});
}

return texts;
}

export function getOffsetPointFromSlateRange (editor: YjsEditor, point: BasePoint): { offset: number; textId: string } {

const [node] = editor.nodes({
at: point,
match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.textId !== undefined,
});

if (!node) {
throw new Error('Node not found');
}

const [textNode] = node as NodeEntry<Element>;

return {
textId: textNode.textId as string,
offset: calculateOffsetRelativeToParent(textNode, point),
};
}

export function getSlatePointFromOffset (editor: YjsEditor, range: { offset: number; textId: string }): BasePoint {
const [node] = editor.nodes({
match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.textId === range.textId,
});

if (!node) {
throw new Error('Node not found');
}

const [textNode, path] = node as NodeEntry<Element>;

const start = calculatePointFromParentOffset(textNode, path, range.offset);

return start;
}
4 changes: 4 additions & 0 deletions frontend/appflowy_web_app/src/assets/bold.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/appflowy_web_app/src/assets/bulleted_list.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions frontend/appflowy_web_app/src/assets/color_theme.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions frontend/appflowy_web_app/src/assets/h1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions frontend/appflowy_web_app/src/assets/h2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions frontend/appflowy_web_app/src/assets/h3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/appflowy_web_app/src/assets/h4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions frontend/appflowy_web_app/src/assets/h5.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e00da90

Please sign in to comment.