mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-16 09:36:17 +08:00
Merge branch 'develop' into fix/experience
This commit is contained in:
@@ -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 & {
|
||||
|
||||
Reference in New Issue
Block a user