mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-21 03:56:23 +08:00
Merge branch 'develop' into feat/while-bord-order
This commit is contained in:
@@ -17,7 +17,7 @@ interface AffineEditorProps {
|
||||
*/
|
||||
scrollBlank?: boolean;
|
||||
|
||||
isWhiteboard?: boolean;
|
||||
isEdgeless?: boolean;
|
||||
|
||||
scrollContainer?: HTMLElement;
|
||||
scrollController?: {
|
||||
@@ -29,12 +29,12 @@ interface AffineEditorProps {
|
||||
function _useConstantWithDispose(
|
||||
workspace: string,
|
||||
rootBlockId: string,
|
||||
isWhiteboard: boolean
|
||||
isEdgeless: boolean
|
||||
) {
|
||||
const ref = useRef<{ data: BlockEditor; onInit: boolean }>(null);
|
||||
const { setCurrentEditors } = useCurrentEditors();
|
||||
ref.current ??= {
|
||||
data: createEditor(workspace, rootBlockId, isWhiteboard),
|
||||
data: createEditor(workspace, rootBlockId, isEdgeless),
|
||||
onInit: true,
|
||||
};
|
||||
|
||||
@@ -42,18 +42,14 @@ function _useConstantWithDispose(
|
||||
if (ref.current.onInit) {
|
||||
ref.current.onInit = false;
|
||||
} else {
|
||||
ref.current.data = createEditor(
|
||||
workspace,
|
||||
rootBlockId,
|
||||
isWhiteboard
|
||||
);
|
||||
ref.current.data = createEditor(workspace, rootBlockId, isEdgeless);
|
||||
}
|
||||
setCurrentEditors(prev => ({
|
||||
...prev,
|
||||
[rootBlockId]: ref.current.data,
|
||||
}));
|
||||
return () => ref.current.data.dispose();
|
||||
}, [workspace, rootBlockId, isWhiteboard, setCurrentEditors]);
|
||||
}, [workspace, rootBlockId, isEdgeless, setCurrentEditors]);
|
||||
|
||||
return ref.current.data;
|
||||
}
|
||||
@@ -64,7 +60,7 @@ export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
||||
workspace,
|
||||
rootBlockId,
|
||||
scrollBlank = true,
|
||||
isWhiteboard,
|
||||
isEdgeless,
|
||||
scrollController,
|
||||
scrollContainer,
|
||||
},
|
||||
@@ -73,7 +69,7 @@ export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
||||
const editor = _useConstantWithDispose(
|
||||
workspace,
|
||||
rootBlockId,
|
||||
isWhiteboard
|
||||
isEdgeless
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -30,7 +30,7 @@ import { BlockEditor } from '@toeverything/framework/virgo';
|
||||
export const createEditor = (
|
||||
workspace: string,
|
||||
rootBlockId: string,
|
||||
isWhiteboard?: boolean
|
||||
isEdgeless?: boolean
|
||||
) => {
|
||||
const blockEditor = new BlockEditor({
|
||||
workspace,
|
||||
@@ -61,7 +61,7 @@ export const createEditor = (
|
||||
[Protocol.Block.Type.groupDivider]: new GroupDividerBlock(),
|
||||
},
|
||||
plugins,
|
||||
isWhiteboard,
|
||||
isEdgeless,
|
||||
});
|
||||
|
||||
return blockEditor;
|
||||
|
||||
@@ -148,7 +148,7 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
||||
workspace={workspace}
|
||||
rootBlockId={rootBlockId}
|
||||
scrollBlank={false}
|
||||
isWhiteboard
|
||||
isEdgeless
|
||||
/>
|
||||
{editingText ? null : <Mask />}
|
||||
</Container>
|
||||
|
||||
@@ -3840,12 +3840,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
};
|
||||
|
||||
private get_viewbox_from_svg = (svgStr: string | ArrayBuffer | null) => {
|
||||
const viewBoxRegex =
|
||||
/.*?viewBox=["'](-?[\d.]+[, ]+-?[\d.]+[, ][\d.]+[, ][\d.]+)["']/;
|
||||
|
||||
if (typeof svgStr === 'string') {
|
||||
const matches = svgStr.match(viewBoxRegex);
|
||||
return matches && matches.length >= 2 ? matches[1] : null;
|
||||
let viewBox = new DOMParser().parseFromString(svgStr, 'text/xml');
|
||||
return viewBox.children[0].getAttribute('viewBox');
|
||||
}
|
||||
|
||||
console.warn('could not get viewbox from svg string');
|
||||
|
||||
@@ -2,28 +2,28 @@
|
||||
import {
|
||||
Descendant,
|
||||
Editor,
|
||||
Location,
|
||||
Node as SlateNode,
|
||||
Path,
|
||||
Point,
|
||||
Transforms,
|
||||
Range,
|
||||
Text,
|
||||
Location,
|
||||
Path,
|
||||
Node as SlateNode,
|
||||
Transforms,
|
||||
} from 'slate';
|
||||
import { ReactEditor } from 'slate-react';
|
||||
|
||||
import {
|
||||
getCommentsIdsOnTextNode,
|
||||
getEditorMarkForCommentId,
|
||||
MARKDOWN_STYLE_MAP,
|
||||
MatchRes,
|
||||
} from './utils';
|
||||
import {
|
||||
fontBgColorPalette,
|
||||
fontColorPalette,
|
||||
type TextAlignOptions,
|
||||
type TextStyleMark,
|
||||
} from './constants';
|
||||
import {
|
||||
getCommentsIdsOnTextNode,
|
||||
getEditorMarkForCommentId,
|
||||
MARKDOWN_STYLE_MAP,
|
||||
MatchRes,
|
||||
} from './utils';
|
||||
|
||||
function isInlineAndVoid(editor: Editor, el: any) {
|
||||
return editor.isInline(el) && editor.isVoid(el);
|
||||
@@ -567,8 +567,51 @@ class SlateUtils {
|
||||
anchor: point1,
|
||||
focus: point2,
|
||||
});
|
||||
// @ts-ignore
|
||||
return fragment[0].children;
|
||||
if (!fragment.length) {
|
||||
console.error('Debug information:', point1, point2, fragment);
|
||||
throw new Error('Failed to get content between!');
|
||||
}
|
||||
|
||||
if (fragment.length > 1) {
|
||||
console.warn(
|
||||
'Fragment length is greater than one, ' +
|
||||
'please be careful if there is missing content!\n' +
|
||||
'Debug information:',
|
||||
point1,
|
||||
point2,
|
||||
fragment
|
||||
);
|
||||
}
|
||||
|
||||
const firstFragment = fragment[0];
|
||||
if (!('type' in firstFragment)) {
|
||||
console.error('Debug information:', point1, point2, fragment);
|
||||
throw new Error(
|
||||
'Failed to get content between! type of firstFragment not found!'
|
||||
);
|
||||
}
|
||||
|
||||
const fragmentChildren = firstFragment.children;
|
||||
|
||||
const textChildren: Text[] = [];
|
||||
for (const child of fragmentChildren) {
|
||||
if (!('text' in child)) {
|
||||
console.error('Debug information:', point1, point2, fragment);
|
||||
throw new Error('Fragment exists nested!');
|
||||
}
|
||||
// Filter empty string
|
||||
if (child.text === '') {
|
||||
continue;
|
||||
}
|
||||
textChildren.push(child);
|
||||
}
|
||||
// If nothing, should preserve empty string
|
||||
// Fix Slate Cannot get the start point in the node at path [0] because it has no start text node.
|
||||
if (!textChildren.length) {
|
||||
textChildren.push({ text: '' });
|
||||
}
|
||||
|
||||
return textChildren;
|
||||
}
|
||||
|
||||
public getSplitContentsBySelection() {
|
||||
|
||||
@@ -173,7 +173,7 @@ export const BulletView = ({ block, editor }: CreateView) => {
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return tabBlock(block, isShiftKey);
|
||||
return tabBlock(editor, block, isShiftKey);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
SelectBlock,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { Protocol, services } from '@toeverything/datasource/db-service';
|
||||
import { FigmaView } from './FigmaView';
|
||||
|
||||
export class FigmaBlock extends BaseView {
|
||||
@@ -19,7 +19,10 @@ export class FigmaBlock extends BaseView {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
||||
const href = el.getAttribute('href');
|
||||
if (href.indexOf('.figma.com') !== -1) {
|
||||
const allowedHosts = ['www.figma.com'];
|
||||
const host = new URL(href).host;
|
||||
|
||||
if (allowedHosts.includes(host)) {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
|
||||
@@ -103,7 +103,7 @@ export const GroupView = (props: CreateView) => {
|
||||
<View {...props} />
|
||||
</GroupContainer>
|
||||
|
||||
{editor.isWhiteboard ? null : (
|
||||
{editor.isEdgeless ? null : (
|
||||
<GroupAction onAddGroup={addGroup} />
|
||||
)}
|
||||
</GroupBox>
|
||||
|
||||
@@ -49,7 +49,7 @@ const weakSqlCreator = (weak_sql_express = ''): Promise<Constraint[]> => {
|
||||
constraints.push({
|
||||
field: field.trim(),
|
||||
relation: relation.trim() as Relation,
|
||||
value: pickValue(value.replace(/&&|&|;/, '').trim()),
|
||||
value: pickValue(value.replace(/&&|&|;/g, '').trim()),
|
||||
});
|
||||
|
||||
/* meaningless return value */
|
||||
|
||||
@@ -167,7 +167,7 @@ export const NumberedView = ({ block, editor }: CreateView) => {
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
tabBlock(block, isShiftKey);
|
||||
tabBlock(editor, block, isShiftKey);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,13 +2,11 @@ import { useState } from 'react';
|
||||
|
||||
import { CustomText, TextProps } from '@toeverything/components/common';
|
||||
import {
|
||||
mergeGroup,
|
||||
BlockPendantProvider,
|
||||
RenderBlockChildren,
|
||||
splitGroup,
|
||||
supportChildren,
|
||||
unwrapGroup,
|
||||
useOnSelect,
|
||||
BlockPendantProvider,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
@@ -69,24 +67,36 @@ export const TextView = ({
|
||||
const { contentBeforeSelection, contentAfterSelection } = splitContents;
|
||||
const before = [...contentBeforeSelection.content];
|
||||
const after = [...contentAfterSelection.content];
|
||||
const _nextBlockChildren = await block.children();
|
||||
const _nextBlock = await editor.createBlock('text');
|
||||
await _nextBlock.setProperty('text', {
|
||||
const nextBlockChildren = await block.children();
|
||||
const nextBlock = await editor.createBlock('text');
|
||||
if (!nextBlock) {
|
||||
throw new Error('Failed to create text block');
|
||||
}
|
||||
await nextBlock.setProperty('text', {
|
||||
value: after as CustomText[],
|
||||
});
|
||||
_nextBlock.append(..._nextBlockChildren);
|
||||
block.removeChildren();
|
||||
await block.setProperty('text', {
|
||||
value: before as CustomText[],
|
||||
});
|
||||
await block.after(_nextBlock);
|
||||
|
||||
editor.selectionManager.activeNodeByNodeId(_nextBlock.id);
|
||||
if (editor.getRootBlockId() === block.id) {
|
||||
// If the block is the root block,
|
||||
// new block can not append as next sibling,
|
||||
// all new blocks should be append as children.
|
||||
await block.insert(0, [nextBlock]);
|
||||
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
|
||||
return true;
|
||||
}
|
||||
await nextBlock.append(...nextBlockChildren);
|
||||
await block.removeChildren();
|
||||
await block.after(nextBlock);
|
||||
|
||||
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
|
||||
return true;
|
||||
};
|
||||
|
||||
const onBackspace: TextProps['handleBackSpace'] = async props => {
|
||||
return await editor.withSuspend(async () => {
|
||||
const onBackspace: TextProps['handleBackSpace'] = editor.withBatch(
|
||||
async props => {
|
||||
const { isCollAndStart } = props;
|
||||
if (!isCollAndStart) {
|
||||
return false;
|
||||
@@ -95,20 +105,27 @@ export const TextView = ({
|
||||
await block.setType('text');
|
||||
return true;
|
||||
}
|
||||
if (editor.getRootBlockId() === block.id) {
|
||||
// Can not delete
|
||||
return false;
|
||||
}
|
||||
const parentBlock = await block.parent();
|
||||
|
||||
if (!parentBlock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const preParent = await parentBlock.previousSibling();
|
||||
if (Protocol.Block.Type.group === parentBlock.type) {
|
||||
if (
|
||||
Protocol.Block.Type.group === parentBlock.type ||
|
||||
editor.getRootBlockId() === parentBlock.id
|
||||
) {
|
||||
const children = await block.children();
|
||||
const preNode = await block.physicallyPerviousSibling();
|
||||
// FIXME support children do not means has textBlock
|
||||
// TODO: abstract this part of code
|
||||
if (preNode) {
|
||||
if (supportChildren(preNode)) {
|
||||
editor.suspend(true);
|
||||
await editor.selectionManager.activePreviousNode(
|
||||
block.id,
|
||||
'end'
|
||||
@@ -127,7 +144,6 @@ export const TextView = ({
|
||||
}
|
||||
await preNode.append(...children);
|
||||
await block.remove();
|
||||
editor.suspend(false);
|
||||
} else {
|
||||
// TODO: point does not clear
|
||||
await editor.selectionManager.activePreviousNode(
|
||||
@@ -194,8 +210,9 @@ export const TextView = ({
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const handleConvert = async (
|
||||
toType: string,
|
||||
options?: Record<string, unknown>
|
||||
@@ -211,7 +228,7 @@ export const TextView = ({
|
||||
block.firstCreateFlag = true;
|
||||
};
|
||||
const onTab: TextProps['handleTab'] = async ({ isShiftKey }) => {
|
||||
await tabBlock(block, isShiftKey);
|
||||
await tabBlock(editor, block, isShiftKey);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ export const TodoView = ({ block, editor }: CreateView) => {
|
||||
};
|
||||
|
||||
const on_tab: TextProps['handleTab'] = async ({ isShiftKey }) => {
|
||||
await tabBlock(block, isShiftKey);
|
||||
await tabBlock(editor, block, isShiftKey);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
SelectBlock,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { YoutubeView } from './YoutubeView';
|
||||
|
||||
export class YoutubeBlock extends BaseView {
|
||||
@@ -19,7 +19,10 @@ export class YoutubeBlock extends BaseView {
|
||||
const tag_name = el.tagName;
|
||||
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
|
||||
const href = el.getAttribute('href');
|
||||
if (href.indexOf('.youtube.com') !== -1) {
|
||||
const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
|
||||
const host = new URL(href).host;
|
||||
|
||||
if (allowedHosts.includes(host)) {
|
||||
return [
|
||||
{
|
||||
type: this.type,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const _regex =
|
||||
/^(https?:\/\/(localhost:4200|(nightly|app)\.affine\.pro|.*?\.ligo-virgo\.pages\.dev)\/\w{28}\/)?(affine\w{16})(\/whiteboard)?$/;
|
||||
/^(https?:\/\/(localhost:4200|(nightly|app|livedemo)\.affine\.pro|.*?\.ligo-virgo\.pages\.dev)\/(\w{28}|AFFiNE)\/)?(affine[\w\-_]{16})(\/edgeless)?$/;
|
||||
|
||||
export const isAffineUrl = (url?: string) => {
|
||||
if (!url) return false;
|
||||
@@ -7,5 +7,5 @@ export const isAffineUrl = (url?: string) => {
|
||||
};
|
||||
|
||||
export const toAffineEmbedUrl = (url: string) => {
|
||||
return _regex.exec(url)?.[4];
|
||||
return _regex.exec(url)?.[5];
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const isYoutubeUrl = (url?: string): boolean => {
|
||||
return url.includes('youtu.be') || url.includes('youtube.com');
|
||||
const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
|
||||
const host = new URL(url).host;
|
||||
return allowedHosts.includes(host);
|
||||
};
|
||||
|
||||
const _regexp = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#&?]*).*/;
|
||||
|
||||
@@ -4,10 +4,7 @@ import {
|
||||
type SlateUtils,
|
||||
type TextProps,
|
||||
} from '@toeverything/components/common';
|
||||
import {
|
||||
useOnSelectActive,
|
||||
useOnSelectSetSelection,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { useOnSelectActive } from '@toeverything/components/editor-core';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import { ContentColumnValue } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
@@ -119,15 +116,6 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
|
||||
const properties = block.getProperties();
|
||||
|
||||
const onTextViewSetSelection = (selection: Range | Point) => {
|
||||
if (selection instanceof Point) {
|
||||
//do some thing
|
||||
} else {
|
||||
textRef.current.setSelection(selection);
|
||||
}
|
||||
};
|
||||
|
||||
// block = await editor.commands.blockCommands.createNextBlock(block.id,)
|
||||
const onTextViewActive = useCallback(
|
||||
(point: CursorTypes) => {
|
||||
// TODO code to be optimized
|
||||
@@ -209,7 +197,6 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
);
|
||||
|
||||
useOnSelectActive(block.id, onTextViewActive);
|
||||
useOnSelectSetSelection<'Range'>(block.id, onTextViewSetSelection);
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef.current) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { supportChildren } from '@toeverything/components/editor-core';
|
||||
import {
|
||||
type BlockEditor,
|
||||
supportChildren,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { AsyncBlock } from '@toeverything/framework/virgo';
|
||||
import type { TodoAsyncBlock } from '../blocks/todo/types';
|
||||
@@ -6,14 +9,19 @@ import type { TodoAsyncBlock } from '../blocks/todo/types';
|
||||
/**
|
||||
* Is the block in top level
|
||||
*/
|
||||
export const isTopLevelBlock = (parentBlock: AsyncBlock): boolean => {
|
||||
export const isTopLevelBlock = (
|
||||
editor: BlockEditor,
|
||||
block: AsyncBlock
|
||||
): boolean => {
|
||||
return (
|
||||
parentBlock.type === Protocol.Block.Type.group ||
|
||||
parentBlock.type === Protocol.Block.Type.page
|
||||
editor.getRootBlockId() === block.id ||
|
||||
block.type === Protocol.Block.Type.group ||
|
||||
block.type === Protocol.Block.Type.page
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move down
|
||||
* @returns true if indent is success
|
||||
* @example
|
||||
* ```
|
||||
@@ -31,7 +39,6 @@ export const isTopLevelBlock = (parentBlock: AsyncBlock): boolean => {
|
||||
* ```
|
||||
*/
|
||||
const indentBlock = async (block: TodoAsyncBlock) => {
|
||||
// Move down
|
||||
const previousBlock = await block.previousSibling();
|
||||
|
||||
if (!previousBlock || !supportChildren(previousBlock)) {
|
||||
@@ -57,6 +64,7 @@ const indentBlock = async (block: TodoAsyncBlock) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Move up
|
||||
* @returns true if dedent is success
|
||||
* @example
|
||||
* ```
|
||||
@@ -73,13 +81,15 @@ const indentBlock = async (block: TodoAsyncBlock) => {
|
||||
* └─ [ ]
|
||||
* ```
|
||||
*/
|
||||
const dedentBlock = async (block: AsyncBlock) => {
|
||||
// Move up
|
||||
const dedentBlock = async (editor: BlockEditor, block: AsyncBlock) => {
|
||||
if (editor.getRootBlockId() === block.id) {
|
||||
return false;
|
||||
}
|
||||
let parentBlock = await block.parent();
|
||||
if (!parentBlock) {
|
||||
throw new Error('Failed to dedent block! Parent block not found!');
|
||||
}
|
||||
if (isTopLevelBlock(parentBlock)) {
|
||||
if (isTopLevelBlock(editor, parentBlock)) {
|
||||
// Top, do nothing
|
||||
return false;
|
||||
}
|
||||
@@ -111,9 +121,13 @@ const dedentBlock = async (block: AsyncBlock) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export const tabBlock = async (block: AsyncBlock, isShiftKey: boolean) => {
|
||||
export const tabBlock = async (
|
||||
editor: BlockEditor,
|
||||
block: AsyncBlock,
|
||||
isShiftKey: boolean
|
||||
) => {
|
||||
if (isShiftKey) {
|
||||
return await dedentBlock(block);
|
||||
return await dedentBlock(editor, block);
|
||||
} else {
|
||||
return await indentBlock(block);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -31,7 +31,7 @@ export const BlockPendantProvider = ({
|
||||
<StyledPendantContainer ref={triggerRef}>
|
||||
<PendantPopover
|
||||
block={block}
|
||||
container={triggerRef.current}
|
||||
// container={triggerRef.current}
|
||||
>
|
||||
<StyledTriggerLine />
|
||||
</PendantPopover>
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ export const PendantHistoryPanel = ({
|
||||
/>
|
||||
}
|
||||
trigger="click"
|
||||
container={historyPanelRef.current}
|
||||
// container={historyPanelRef.current}
|
||||
>
|
||||
<PendantTag
|
||||
style={{
|
||||
|
||||
@@ -59,7 +59,7 @@ export const PendantRender = ({ block }: { block: AsyncBlock }) => {
|
||||
popperHandlerRef={ref => {
|
||||
popoverHandlerRef.current[id] = ref;
|
||||
}}
|
||||
container={blockRenderContainerRef.current}
|
||||
// container={blockRenderContainerRef.current}
|
||||
key={id}
|
||||
trigger="click"
|
||||
placement="bottom-start"
|
||||
|
||||
@@ -2,47 +2,47 @@
|
||||
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 {
|
||||
@@ -75,7 +75,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 +85,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 +148,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: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ export enum RecastScene {
|
||||
Page = 'page',
|
||||
Kanban = 'kanban',
|
||||
Table = 'table',
|
||||
// Whiteboard = 'whiteboard',
|
||||
}
|
||||
|
||||
export type RecastViewId = string & {
|
||||
|
||||
@@ -14,7 +14,7 @@ export class GroupMenuPlugin extends BasePlugin {
|
||||
}
|
||||
|
||||
protected override _onRender(): void {
|
||||
if (this.editor.isWhiteboard) return;
|
||||
if (this.editor.isEdgeless) return;
|
||||
this.root = new PluginRenderRoot({
|
||||
name: PLUGIN_NAME,
|
||||
render: this.editor.reactRenderRoot.render,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { StatusText } from './StatusText';
|
||||
import { StatusTrack } from './StatusTrack';
|
||||
import { DocMode } from './type';
|
||||
|
||||
const isBoard = (pathname: string): boolean => pathname.endsWith('/whiteboard');
|
||||
const isBoard = (pathname: string): boolean => pathname.endsWith('/edgeless');
|
||||
|
||||
export const Switcher = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -20,11 +20,11 @@ export const Switcher = () => {
|
||||
/**
|
||||
* There are two possible modes:
|
||||
* Page mode: /{workspaceId}/{pageId}
|
||||
* Board mode: /{workspaceId}/{pageId}/whiteboard
|
||||
* Board mode: /{workspaceId}/{pageId}/edgeless
|
||||
*/
|
||||
const pageId = params['*'].split('/')[0];
|
||||
const targetUrl = `/${workspaceId}/${pageId}${
|
||||
targetViewMode === DocMode.board ? '/whiteboard' : ''
|
||||
targetViewMode === DocMode.board ? '/edgeless' : ''
|
||||
}`;
|
||||
navigate(targetUrl);
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ function hideAffineHeader(pathname: string): boolean {
|
||||
}
|
||||
|
||||
type HeaderIconProps = {
|
||||
isWhiteboardView?: boolean;
|
||||
isEdgelessView?: boolean;
|
||||
};
|
||||
|
||||
export const AffineHeader = () => {
|
||||
@@ -38,7 +38,7 @@ export const AffineHeader = () => {
|
||||
const { toggleSettingsSidebar: toggleInfoSidebar } =
|
||||
useShowSettingsSidebar();
|
||||
const theme = useTheme();
|
||||
const isWhiteboardView = pathname.endsWith('/whiteboard');
|
||||
const isEdgelessView = pathname.endsWith('/edgeless');
|
||||
const pageHistoryPortalFlag = useFlag('BooleanPageHistoryPortal', false);
|
||||
const pageSettingPortalFlag = useFlag('PageSettingPortal', false);
|
||||
const BooleanPageSharePortal = useFlag('BooleanPageSharePortal', false);
|
||||
@@ -77,9 +77,9 @@ export const AffineHeader = () => {
|
||||
<Tooltip content="Doc">
|
||||
<HeaderIcon
|
||||
style={{ width: '80px' }}
|
||||
isWhiteboardView={!isWhiteboardView}
|
||||
isEdgelessView={!isEdgelessView}
|
||||
onClick={() =>
|
||||
isWhiteboardView
|
||||
isEdgelessView
|
||||
? navigate(
|
||||
`/${
|
||||
params['workspace_id'] ||
|
||||
@@ -100,17 +100,17 @@ export const AffineHeader = () => {
|
||||
</span>
|
||||
</HeaderIcon>
|
||||
</Tooltip>
|
||||
<Tooltip content="Whiteboard">
|
||||
<Tooltip content="Edgeless">
|
||||
<HeaderIcon
|
||||
isWhiteboardView={isWhiteboardView}
|
||||
isEdgelessView={isEdgelessView}
|
||||
onClick={() =>
|
||||
isWhiteboardView
|
||||
isEdgelessView
|
||||
? null
|
||||
: navigate(
|
||||
`/${
|
||||
params['workspace_id'] ||
|
||||
'space'
|
||||
}/${params['*']}` + '/whiteboard'
|
||||
}/${params['*']}` + '/edgeless'
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -166,14 +166,14 @@ const StyledHeaderRight = styled('div')`
|
||||
`;
|
||||
|
||||
const HeaderIcon = styled(IconButton, {
|
||||
shouldForwardProp: (prop: string) => prop !== 'isWhiteboardView',
|
||||
})<HeaderIconProps>(({ isWhiteboardView = false }) => ({
|
||||
shouldForwardProp: (prop: string) => prop !== 'isEdgelessView',
|
||||
})<HeaderIconProps>(({ isEdgelessView = false }) => ({
|
||||
color: '#98ACBD',
|
||||
minWidth: 48,
|
||||
width: 48,
|
||||
height: 36,
|
||||
borderRadius: '8px',
|
||||
...(isWhiteboardView && {
|
||||
...(isEdgelessView && {
|
||||
color: '#fff',
|
||||
backgroundColor: '#3E6FDB',
|
||||
'&:hover': {
|
||||
|
||||
Reference in New Issue
Block a user