Merge branch 'develop' into fix/experience

This commit is contained in:
QiShaoXuan
2022-08-18 16:47:28 +08:00
116 changed files with 1355 additions and 1399 deletions
+11 -17
View File
@@ -1,16 +1,16 @@
import type { BlockEditor } from './editor';
import { styled, usePatchNodes } from '@toeverything/components/ui';
import type { PropsWithChildren } from 'react';
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { EditorProvider } from './Contexts';
import { SelectionRect, SelectionRef } from './Selection';
import {
Protocol,
services,
type ReturnUnobserve,
} from '@toeverything/datasource/db-service';
import { addNewGroup, appendNewGroup } from './recast-block';
import type { PropsWithChildren } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { EditorProvider } from './Contexts';
import type { BlockEditor } from './editor';
import { useIsOnDrag } from './hooks';
import { addNewGroup, appendNewGroup } from './recast-block';
import { SelectionRect, SelectionRef } from './Selection';
interface RenderRootProps {
editor: BlockEditor;
@@ -160,7 +160,7 @@ export const RenderRoot = ({
return (
<EditorProvider value={{ editor, editorElement }}>
<Container
isWhiteboard={editor.isWhiteboard}
isEdgeless={editor.isEdgeless}
ref={ref => {
if (ref != null && ref !== editor.container) {
editor.container = ref;
@@ -188,7 +188,7 @@ export const RenderRoot = ({
</Content>
{/** TODO: remove selectionManager insert */}
{editor && <SelectionRect ref={selectionRef} editor={editor} />}
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
{editor.isEdgeless ? null : <ScrollBlank editor={editor} />}
{patchedNodes}
</Container>
</EditorProvider>
@@ -262,16 +262,10 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
const PADDING_X = 150;
const Container = styled('div')(
({
isWhiteboard,
isOnDrag,
}: {
isWhiteboard: boolean;
isOnDrag: boolean;
}) => ({
({ isEdgeless, isOnDrag }: { isEdgeless: boolean; isOnDrag: boolean }) => ({
width: '100%',
padding: isWhiteboard ? 0 : `72px ${PADDING_X}px 0 ${PADDING_X}px`,
minWidth: isWhiteboard ? 'unset' : '940px',
padding: isEdgeless ? 0 : `72px ${PADDING_X}px 0 ${PADDING_X}px`,
minWidth: isEdgeless ? 'unset' : '940px',
position: 'relative',
...(isOnDrag && {
cursor: 'grabbing',
@@ -1,4 +1,5 @@
/* eslint-disable max-lines */
import { Protocol } from '@toeverything/datasource/db-service';
import { domToRect, Point } from '@toeverything/utils';
import { AsyncBlock } from '../..';
import { GridDropType } from '../commands/types';
@@ -6,7 +7,6 @@ import { Editor } from '../editor';
import { BlockDropPlacement, GroupDirection } from '../types';
// TODO: Evaluate implementing custom events with Rxjs
import EventEmitter from 'eventemitter3';
import { Protocol } from '@toeverything/datasource/db-service';
enum DragType {
dragBlock = 'dragBlock',
@@ -281,6 +281,10 @@ export class DragDropManager {
this._editor.getRootBlockId()
);
let direction = BlockDropPlacement.none;
if (!rootBlock || !rootBlock.dom) {
console.warn('Can not find dom bind with block', rootBlock);
return;
}
const rootBlockRect = domToRect(rootBlock.dom);
let targetBlock: AsyncBlock | undefined;
let typesInfo = {
@@ -303,6 +307,10 @@ export class DragDropManager {
if (direction !== BlockDropPlacement.none) {
const blockList = await this._editor.getBlockListByLevelOrder();
targetBlock = blockList.find(block => {
if (!block.dom) {
console.warn('Can not find dom bind with block', block);
return false;
}
const domRect = domToRect(block.dom);
const pointChecker =
direction === BlockDropPlacement.outerLeft
@@ -1,55 +1,51 @@
/* eslint-disable max-lines */
import HotKeys from 'hotkeys-js';
import LRUCache from 'lru-cache';
import { services } from '@toeverything/datasource/db-service';
import type { PatchNode } from '@toeverything/components/ui';
import type {
BlockFlavors,
ReturnEditorBlock,
UpdateEditorBlock,
} from '@toeverything/datasource/db-service';
import type { PatchNode } from '@toeverything/components/ui';
import { services } from '@toeverything/datasource/db-service';
import { AsyncBlock } from './block';
import type { WorkspaceAndBlockId } from './block';
import type { BaseView } from './views/base-view';
import { SelectionManager } from './selection';
import { Hooks, PluginManager } from './plugin';
import { EditorCommands } from './commands';
import {
Virgo,
HooksRunner,
PluginHooks,
PluginCreator,
StorageManager,
VirgoSelection,
PluginManagerInterface,
} from './types';
import { KeyboardManager } from './keyboard';
import { MouseManager } from './mouse';
import { ScrollManager } from './scroll';
import assert from 'assert';
import { domToRect, last, Point, sleep } from '@toeverything/utils';
import { Commands } from '@toeverything/datasource/commands';
import { domToRect, last, Point, sleep } from '@toeverything/utils';
import assert from 'assert';
import type { WorkspaceAndBlockId } from './block';
import { AsyncBlock } from './block';
import { BlockHelper } from './block/block-helper';
import { BrowserClipboard } from './clipboard/browser-clipboard';
import { ClipboardPopulator } from './clipboard/clipboard-populator';
import { BlockHelper } from './block/block-helper';
import { DragDropManager } from './drag-drop';
import { EditorCommands } from './commands';
import { EditorConfig } from './config';
import { DragDropManager } from './drag-drop';
import { KeyboardManager } from './keyboard';
import { MouseManager } from './mouse';
import { Hooks, PluginManager } from './plugin';
import { ScrollManager } from './scroll';
import { SelectionManager } from './selection';
import {
HooksRunner,
PluginCreator,
PluginHooks,
PluginManagerInterface,
StorageManager,
Virgo,
VirgoSelection,
} from './types';
import type { BaseView } from './views/base-view';
export interface EditorCtorProps {
workspace: string;
views: Partial<Record<keyof BlockFlavors, BaseView>>;
plugins: PluginCreator[];
rootBlockId: string;
isWhiteboard?: boolean;
isEdgeless?: boolean;
}
export class Editor implements Virgo {
private _cacheManager = new LRUCache<string, Promise<AsyncBlock | null>>({
max: 8192,
ttl: 1000 * 60 * 30,
});
private _cacheManager = new Map<string, Promise<AsyncBlock | null>>();
public mouseManager = new MouseManager(this);
public selectionManager = new SelectionManager(this);
public keyboardManager = new KeyboardManager(this);
@@ -75,7 +71,7 @@ export class Editor implements Virgo {
render: PatchNode;
has: (key: string) => boolean;
};
public isWhiteboard = false;
public isEdgeless = false;
private _isDisposed = false;
constructor(props: EditorCtorProps) {
@@ -85,8 +81,8 @@ export class Editor implements Virgo {
this.hooks = new Hooks();
this.plugin_manager = new PluginManager(this, this.hooks);
this.plugin_manager.registerAll(props.plugins);
if (props.isWhiteboard) {
this.isWhiteboard = true;
if (props.isEdgeless) {
this.isEdgeless = true;
}
for (const [name, block] of Object.entries(props.views)) {
services.api.editorBlock.registerContentExporter(
@@ -148,18 +144,41 @@ export class Editor implements Virgo {
public get container() {
return this.ui_container;
}
// preference to use withSuspend
/**
* Use it discreetly.
* Preference to use {@link withBatch}
*/
public suspend(flag: boolean) {
services.api.editorBlock.suspend(this.workspace, flag);
}
public async withSuspend<T extends (...args: any[]) => any>(
// TODO support suspend recursion
private _isSuspend = false;
public withBatch<T extends (...args: any[]) => Promise<any>>(fn: T): T {
return (async (...args) => {
if (this._isSuspend) {
console.warn(
'The editor currently has suspend! Please do not call batch method repeatedly!'
);
}
this._isSuspend = true;
services.api.editorBlock.suspend(this.workspace, true);
const result = await fn(...args);
services.api.editorBlock.suspend(this.workspace, false);
this._isSuspend = false;
return result;
}) as T;
}
/**
* Use it discreetly.
* Preference to use {@link withBatch}
*/
public async batch<T extends (...args: any[]) => any>(
fn: T
): Promise<Awaited<ReturnType<T>>> {
services.api.editorBlock.suspend(this.workspace, true);
const result = await fn();
services.api.editorBlock.suspend(this.workspace, false);
return result;
return this.withBatch(fn)();
}
public setReactRenderRoot(props: {
@@ -211,7 +230,12 @@ export class Editor implements Virgo {
return await services.api.editorBlock.update(patches);
},
remove: async ({ workspace, id }: WorkspaceAndBlockId) => {
return await services.api.editorBlock.delete({ workspace, id });
const ret = await services.api.editorBlock.delete({
workspace,
id,
});
this._cacheManager.delete(id);
return ret;
},
observe: async (
{ workspace, id }: WorkspaceAndBlockId,
@@ -1,15 +1,16 @@
/* eslint-disable max-lines */
import {
debounce,
domToRect,
getBlockIdByDom,
last,
Point,
Rect,
last,
without,
debounce,
getBlockIdByDom,
} from '@toeverything/utils';
import EventEmitter from 'eventemitter3';
import { Protocol } from '@toeverything/datasource/db-service';
import { BlockEditor } from '../..';
import { AsyncBlock } from '../block';
import { VirgoSelection } from '../types';
@@ -18,19 +19,17 @@ import {
changeEventName,
CursorTypes,
IdList,
SelectBlock,
selectEndEventName,
SelectEventCallbackTypes,
SelectEventTypes,
SelectInfo,
SelectionSettings,
SelectionSettingsMap,
SelectionTypes,
SelectPosition,
SelectBlock,
SelectInfo,
} from './types';
import { isLikeBlockListIds } from './utils';
import { Protocol } from '@toeverything/datasource/db-service';
import { Editor } from 'slate';
// IMP: maybe merge active and select into single function
export type SelectionInfo = InstanceType<
@@ -336,12 +335,12 @@ export class SelectionManager implements VirgoSelection {
});
for await (const childBlock of selectableChildren) {
const { dom } = childBlock;
if (dom && selectionRect.isIntersect(domToRect(dom))) {
selectedNodes.push(childBlock);
}
if (!dom) {
console.warn('can not find dom bind with block');
}
if (dom && selectionRect.isIntersect(domToRect(dom))) {
selectedNodes.push(childBlock);
}
}
// if just only has one selected maybe select the children
if (selectedNodes.length === 1) {
@@ -1063,10 +1062,10 @@ export class SelectionManager implements VirgoSelection {
index: number,
blockId: string
): Promise<void> {
let preRang = document.createRange();
const preRang = document.createRange();
preRang.setStart(nowRange.startContainer, index);
preRang.setEnd(nowRange.endContainer, index);
let prePosition = preRang.getClientRects().item(0);
const prePosition = preRang.getClientRects().item(0);
this.activeNodeByNodeId(
blockId,
new Point(prePosition.left, prePosition.bottom)
@@ -107,7 +107,7 @@ export interface Virgo {
getBlockDomById: (id: string) => Promise<HTMLElement>;
getBlockByPoint: (point: Point) => Promise<AsyncBlock>;
getGroupBlockByPoint: (point: Point) => Promise<AsyncBlock>;
isWhiteboard: boolean;
isEdgeless: boolean;
mouseManager: MouseManager;
}
@@ -9,10 +9,10 @@ import {
BlockDecoration,
MapOperation,
} from '@toeverything/datasource/jwt';
import { cloneDeep } from '@toeverything/utils';
import type { EventData } from '../block';
import { AsyncBlock } from '../block';
import type { Editor } from '../editor';
import { cloneDeep } from '@toeverything/utils';
import { SelectBlock } from '../selection';
export interface CreateView {
@@ -114,7 +114,21 @@ export abstract class BaseView {
// Whether the component is empty
isEmpty(block: AsyncBlock): boolean {
const text = block.getProperty('text');
return !text?.value?.[0]?.text;
const result = !text?.value?.[0]?.text;
// Assert that the text is really empty
if (
result &&
block.getProperty('text')?.value.some(content => content.text)
) {
console.warn(
'Assertion isEmpty error! The block has an empty start fragment, but it is not empty',
block
);
}
// Assert end
return result;
}
getSelProperties(block: AsyncBlock, selectInfo: any): DefaultColumnsValue {
@@ -7,7 +7,6 @@ export enum RecastScene {
Page = 'page',
Kanban = 'kanban',
Table = 'table',
// Whiteboard = 'whiteboard',
}
export type RecastViewId = string & {