mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-23 00:41:50 +03:00
Merge pull request #318 from toeverything/feat/block-render
Feat/block render
This commit is contained in:
commit
15055bc7c6
@ -1,7 +1,6 @@
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
||||
|
||||
import {
|
||||
RenderBlock,
|
||||
RenderRoot,
|
||||
type BlockEditor,
|
||||
} from '@toeverything/components/editor-core';
|
||||
@ -88,9 +87,7 @@ export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
|
||||
editor={editor}
|
||||
editorElement={AffineEditor as any}
|
||||
scrollBlank={scrollBlank}
|
||||
>
|
||||
<RenderBlock blockId={editor.getRootBlockId()} />
|
||||
</RenderRoot>
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -1,29 +1,28 @@
|
||||
import type { TextProps } from '@toeverything/components/common';
|
||||
import {
|
||||
ContentColumnValue,
|
||||
services,
|
||||
Protocol,
|
||||
services,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { type CreateView } from '@toeverything/framework/virgo';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
BlockPendantProvider,
|
||||
RenderBlockChildren,
|
||||
supportChildren,
|
||||
useOnSelect,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import { BlockContainer } from '../../components/BlockContainer';
|
||||
import { List } from '../../components/style-container';
|
||||
import {
|
||||
TextManage,
|
||||
type ExtendedTextUtils,
|
||||
} from '../../components/text-manage';
|
||||
import { tabBlock } from '../../utils/indent';
|
||||
import { BulletIcon, getChildrenType, NumberType } from './data';
|
||||
import { BulletBlock, BulletProperties } from './types';
|
||||
import {
|
||||
supportChildren,
|
||||
RenderBlockChildren,
|
||||
useOnSelect,
|
||||
BlockPendantProvider,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { List } from '../../components/style-container';
|
||||
import { getChildrenType, BulletIcon, NumberType } from './data';
|
||||
import { IndentWrapper } from '../../components/IndentWrapper';
|
||||
import { BlockContainer } from '../../components/BlockContainer';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
|
||||
export const defaultBulletProps: BulletProperties = {
|
||||
text: { value: [{ text: '' }] },
|
||||
@ -208,9 +207,7 @@ export const BulletView = ({ block, editor }: CreateView) => {
|
||||
</div>
|
||||
</List>
|
||||
</BlockPendantProvider>
|
||||
<IndentWrapper>
|
||||
<RenderBlockChildren block={block} />
|
||||
</IndentWrapper>
|
||||
<RenderBlockChildren block={block} />
|
||||
</BlockContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,18 +1,16 @@
|
||||
import {
|
||||
DefaultColumnsValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
CreateView,
|
||||
getTextHtml,
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
getTextHtml,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import {
|
||||
Protocol,
|
||||
DefaultColumnsValue,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
// import { withTreeViewChildren } from '../../utils/with-tree-view-children';
|
||||
import { defaultBulletProps, BulletView } from './BulletView';
|
||||
import { IndentWrapper } from '../../components/IndentWrapper';
|
||||
import { BulletView, defaultBulletProps } from './BulletView';
|
||||
|
||||
export class BulletBlock extends BaseView {
|
||||
public type = Protocol.Block.Type.bullet;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { RenderBlock } from '@toeverything/components/editor-core';
|
||||
import { useBlockRender } from '@toeverything/components/editor-core';
|
||||
import { ChildrenView, CreateView } from '@toeverything/framework/virgo';
|
||||
|
||||
export const GridItemRender = function (
|
||||
@ -6,10 +6,11 @@ export const GridItemRender = function (
|
||||
) {
|
||||
const GridItem = function (props: CreateView) {
|
||||
const { block } = props;
|
||||
const { BlockRender } = useBlockRender();
|
||||
const children = (
|
||||
<>
|
||||
{block.childrenIds.map(id => {
|
||||
return <RenderBlock key={id} blockId={id} />;
|
||||
return <BlockRender key={id} blockId={id} />;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { RenderBlock } from '@toeverything/components/editor-core';
|
||||
import { CreateView } from '@toeverything/framework/virgo';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { GridHandle } from './GirdHandle';
|
||||
import { useBlockRender } from '@toeverything/components/editor-core';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { CreateView } from '@toeverything/framework/virgo';
|
||||
import { debounce, domToRect, Point } from '@toeverything/utils';
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {
|
||||
GRID_ITEM_CLASS_NAME,
|
||||
GRID_ITEM_CONTENT_CLASS_NAME,
|
||||
} from '../grid-item/GridItem';
|
||||
import { debounce, domToRect, Point } from '@toeverything/utils';
|
||||
import clsx from 'clsx';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { GridHandle } from './GirdHandle';
|
||||
|
||||
const DB_UPDATE_DELAY = 50;
|
||||
const GRID_ON_DRAG_CLASS = 'grid-layout-on-drag';
|
||||
@ -31,6 +31,7 @@ export const Grid = function (props: CreateView) {
|
||||
const originalLeftWidth = useRef<number>(gridItemMinWidth);
|
||||
const originalRightWidth = useRef<number>(gridItemMinWidth);
|
||||
const [alertHandleId, setAlertHandleId] = useState<string>(null);
|
||||
const { BlockRender } = useBlockRender();
|
||||
|
||||
const getLeftRightGridItemDomByIndex = (index: number) => {
|
||||
const gridItems = Array.from(gridContainerRef.current?.children).filter(
|
||||
@ -226,7 +227,7 @@ export const Grid = function (props: CreateView) {
|
||||
key={id}
|
||||
className={GRID_ITEM_CLASS_NAME}
|
||||
>
|
||||
<RenderBlock hasContainer={false} blockId={id} />
|
||||
<BlockRender hasContainer={false} blockId={id} />
|
||||
<GridHandle
|
||||
onDrag={event => handleDragGrid(event, i)}
|
||||
editor={editor}
|
||||
|
@ -2,5 +2,5 @@ import { RenderBlockChildren } from '@toeverything/components/editor-core';
|
||||
import type { CreateView } from '@toeverything/framework/virgo';
|
||||
|
||||
export const ScenePage = ({ block }: CreateView) => {
|
||||
return <RenderBlockChildren block={block} />;
|
||||
return <RenderBlockChildren block={block} indent={false} />;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {
|
||||
KanbanCard,
|
||||
RenderBlock,
|
||||
useBlockRender,
|
||||
useEditor,
|
||||
useKanban,
|
||||
} from '@toeverything/components/editor-core';
|
||||
@ -94,6 +94,7 @@ export const CardItem = ({
|
||||
const [editable, setEditable] = useState(false);
|
||||
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
|
||||
const { editor } = useEditor();
|
||||
const { BlockRender } = useBlockRender();
|
||||
|
||||
const onAddItem = async () => {
|
||||
setEditable(true);
|
||||
@ -114,7 +115,7 @@ export const CardItem = ({
|
||||
<MuiClickAwayListener onClickAway={() => setEditable(false)}>
|
||||
<CardContainer>
|
||||
<CardContent>
|
||||
<RenderBlock blockId={id} />
|
||||
<BlockRender blockId={id} />
|
||||
</CardContent>
|
||||
{showKanbanRefPageFlag && !editable && (
|
||||
<Overlay onClick={onClickCard}>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { TextProps } from '@toeverything/components/common';
|
||||
import {
|
||||
ContentColumnValue,
|
||||
services,
|
||||
Protocol,
|
||||
services,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { type CreateView } from '@toeverything/framework/virgo';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
@ -11,18 +11,17 @@ import {
|
||||
type ExtendedTextUtils,
|
||||
} from '../../components/text-manage';
|
||||
import { tabBlock } from '../../utils/indent';
|
||||
import { IndentWrapper } from '../../components/IndentWrapper';
|
||||
import type { Numbered, NumberedAsyncBlock } from './types';
|
||||
|
||||
import { getChildrenType, getNumber } from './data';
|
||||
import {
|
||||
supportChildren,
|
||||
RenderBlockChildren,
|
||||
useOnSelect,
|
||||
BlockPendantProvider,
|
||||
RenderBlockChildren,
|
||||
supportChildren,
|
||||
useOnSelect,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { List } from '../../components/style-container';
|
||||
import { BlockContainer } from '../../components/BlockContainer';
|
||||
import { List } from '../../components/style-container';
|
||||
import { getChildrenType, getNumber } from './data';
|
||||
|
||||
export const defaultTodoProps: Numbered = {
|
||||
text: { value: [{ text: '' }] },
|
||||
@ -204,9 +203,7 @@ export const NumberedView = ({ block, editor }: CreateView) => {
|
||||
</List>
|
||||
</BlockPendantProvider>
|
||||
|
||||
<IndentWrapper>
|
||||
<RenderBlockChildren block={block} />
|
||||
</IndentWrapper>
|
||||
<RenderBlockChildren block={block} />
|
||||
</BlockContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,17 +1,16 @@
|
||||
import {
|
||||
DefaultColumnsValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BaseView,
|
||||
getTextHtml,
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
getTextHtml,
|
||||
} from '@toeverything/framework/virgo';
|
||||
import {
|
||||
Protocol,
|
||||
DefaultColumnsValue,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
// import { withTreeViewChildren } from '../../utils/with-tree-view-children';
|
||||
import { defaultTodoProps, NumberedView } from './NumberedView';
|
||||
import { IndentWrapper } from '../../components/IndentWrapper';
|
||||
|
||||
export class NumberedBlock extends BaseView {
|
||||
public type = Protocol.Block.Type.numbered;
|
||||
|
@ -12,7 +12,6 @@ import { styled } from '@toeverything/components/ui';
|
||||
import { Protocol } from '@toeverything/datasource/db-service';
|
||||
import { CreateView } from '@toeverything/framework/virgo';
|
||||
import { BlockContainer } from '../../components/BlockContainer';
|
||||
import { IndentWrapper } from '../../components/IndentWrapper';
|
||||
import { TextManage } from '../../components/text-manage';
|
||||
import { dedentBlock, tabBlock } from '../../utils/indent';
|
||||
interface CreateTextView extends CreateView {
|
||||
@ -255,9 +254,7 @@ export const TextView = ({
|
||||
handleTab={onTab}
|
||||
/>
|
||||
</BlockPendantProvider>
|
||||
<IndentWrapper>
|
||||
<RenderBlockChildren block={block} />
|
||||
</IndentWrapper>
|
||||
<RenderBlockChildren block={block} />
|
||||
</BlockContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,11 +1,17 @@
|
||||
import { TextProps } from '@toeverything/components/common';
|
||||
import {
|
||||
AsyncBlock,
|
||||
BlockPendantProvider,
|
||||
CreateView,
|
||||
useOnSelect,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import {
|
||||
ContentColumnValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
import { AsyncBlock, type CreateView } from '@toeverything/framework/virgo';
|
||||
import { useRef } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { BlockContainer } from '../../components/BlockContainer';
|
||||
import {
|
||||
TextManage,
|
||||
type ExtendedTextUtils,
|
||||
@ -36,6 +42,10 @@ const todoIsEmpty = (contentValue: ContentColumnValue): boolean => {
|
||||
export const TodoView = ({ block, editor }: CreateView) => {
|
||||
const properties = { ...defaultTodoProps, ...block.getProperties() };
|
||||
const text_ref = useRef<ExtendedTextUtils>(null);
|
||||
const [isSelect, setIsSelect] = useState<boolean>(false);
|
||||
useOnSelect(block.id, (isSelect: boolean) => {
|
||||
setIsSelect(isSelect);
|
||||
});
|
||||
|
||||
const turn_into_text_block = async () => {
|
||||
// Convert to text block
|
||||
@ -121,28 +131,34 @@ export const TodoView = ({ block, editor }: CreateView) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<TodoBlock>
|
||||
<div className={'checkBoxContainer'}>
|
||||
<CheckBox
|
||||
checked={properties.checked?.value}
|
||||
onChange={on_checked_change}
|
||||
/>
|
||||
</div>
|
||||
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
||||
<BlockPendantProvider block={block}>
|
||||
<TodoBlock>
|
||||
<div className={'checkBoxContainer'}>
|
||||
<CheckBox
|
||||
checked={properties.checked?.value}
|
||||
onChange={on_checked_change}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={'textContainer'}>
|
||||
<TextManage
|
||||
className={properties.checked?.value ? 'checked' : ''}
|
||||
ref={text_ref}
|
||||
editor={editor}
|
||||
block={block}
|
||||
supportMarkdown
|
||||
placeholder="To-do"
|
||||
handleEnter={on_text_enter}
|
||||
handleBackSpace={on_backspace}
|
||||
handleTab={on_tab}
|
||||
/>
|
||||
</div>
|
||||
</TodoBlock>
|
||||
<div className={'textContainer'}>
|
||||
<TextManage
|
||||
className={
|
||||
properties.checked?.value ? 'checked' : ''
|
||||
}
|
||||
ref={text_ref}
|
||||
editor={editor}
|
||||
block={block}
|
||||
supportMarkdown
|
||||
placeholder="To-do"
|
||||
handleEnter={on_text_enter}
|
||||
handleBackSpace={on_backspace}
|
||||
handleTab={on_tab}
|
||||
/>
|
||||
</div>
|
||||
</TodoBlock>
|
||||
</BlockPendantProvider>
|
||||
</BlockContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,16 @@
|
||||
import {
|
||||
BaseView,
|
||||
getTextProperties,
|
||||
AsyncBlock,
|
||||
SelectBlock,
|
||||
BaseView,
|
||||
getTextHtml,
|
||||
} from '@toeverything/framework/virgo';
|
||||
// import type { CreateView } from '@toeverything/framework/virgo';
|
||||
getTextProperties,
|
||||
SelectBlock,
|
||||
withTreeViewChildren,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import {
|
||||
Protocol,
|
||||
DefaultColumnsValue,
|
||||
Protocol,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
// import { withTreeViewChildren } from '../../utils/with-tree-view-children';
|
||||
import { withTreeViewChildren } from '../../utils/WithTreeViewChildren';
|
||||
import { TodoView, defaultTodoProps } from './TodoView';
|
||||
import { defaultTodoProps, TodoView } from './TodoView';
|
||||
import type { TodoAsyncBlock } from './types';
|
||||
|
||||
export class TodoBlock extends BaseView {
|
||||
|
@ -23,7 +23,7 @@ export const BlockContainer = function ({
|
||||
);
|
||||
};
|
||||
|
||||
export const Container = styled('div')<{ selected: boolean }>(
|
||||
export const Container = styled('div')<{ selected?: boolean }>(
|
||||
({ selected, theme }) => ({
|
||||
backgroundColor: selected ? theme.affine.palette.textSelected : '',
|
||||
marginBottom: '2px',
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { ChildrenView } from '@toeverything/framework/virgo';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
|
||||
/**
|
||||
* Indent rendering child nodes
|
||||
*/
|
||||
export const IndentWrapper = (props: PropsWithChildren) => {
|
||||
return <StyledIdentWrapper>{props.children}</StyledIdentWrapper>;
|
||||
};
|
||||
|
||||
const StyledIdentWrapper = styled('div')({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// TODO: marginLeft should use theme provided by styled
|
||||
marginLeft: '30px',
|
||||
});
|
@ -1 +0,0 @@
|
||||
export * from './IndentWrapper';
|
@ -1,4 +1,5 @@
|
||||
export const isYoutubeUrl = (url?: string): boolean => {
|
||||
if (!url) return false;
|
||||
const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
|
||||
const host = new URL(url).host;
|
||||
return allowedHosts.includes(host);
|
||||
|
@ -1,22 +1,34 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import type { BlockEditor, AsyncBlock } from './editor';
|
||||
import { genErrorObj } from '@toeverything/utils';
|
||||
import { createContext, PropsWithChildren, useContext } from 'react';
|
||||
import type { AsyncBlock, BlockEditor } from './editor';
|
||||
|
||||
const RootContext = createContext<{
|
||||
type EditorProps = {
|
||||
editor: BlockEditor;
|
||||
// TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top
|
||||
editorElement: () => JSX.Element;
|
||||
}>(
|
||||
};
|
||||
|
||||
const EditorContext = createContext<EditorProps>(
|
||||
genErrorObj(
|
||||
'Failed to get context! The context only can use under the "render-root"'
|
||||
'Failed to get EditorContext! The context only can use under the "render-root"'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
);
|
||||
|
||||
export const EditorProvider = RootContext.Provider;
|
||||
|
||||
export const useEditor = () => {
|
||||
return useContext(RootContext);
|
||||
return useContext(EditorContext);
|
||||
};
|
||||
|
||||
export const EditorProvider = ({
|
||||
editor,
|
||||
editorElement,
|
||||
children,
|
||||
}: PropsWithChildren<EditorProps>) => {
|
||||
return (
|
||||
<EditorContext.Provider value={{ editor, editorElement }}>
|
||||
{children}
|
||||
</EditorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -4,12 +4,12 @@ import {
|
||||
services,
|
||||
type ReturnUnobserve,
|
||||
} from '@toeverything/datasource/db-service';
|
||||
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 { BlockRenderProvider, RenderBlock } from './render-block';
|
||||
import { SelectionRect, SelectionRef } from './Selection';
|
||||
|
||||
interface RenderRootProps {
|
||||
@ -24,11 +24,7 @@ interface RenderRootProps {
|
||||
const MAX_PAGE_WIDTH = 5000;
|
||||
export const MIN_PAGE_WIDTH = 1480;
|
||||
|
||||
export const RenderRoot = ({
|
||||
editor,
|
||||
editorElement,
|
||||
children,
|
||||
}: PropsWithChildren<RenderRootProps>) => {
|
||||
export const RenderRoot = ({ editor, editorElement }: RenderRootProps) => {
|
||||
const selectionRef = useRef<SelectionRef>(null);
|
||||
const triggeredBySelect = useRef(false);
|
||||
const [pageWidth, setPageWidth] = useState<number>(MIN_PAGE_WIDTH);
|
||||
@ -158,39 +154,43 @@ export const RenderRoot = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorProvider value={{ editor, editorElement }}>
|
||||
<Container
|
||||
isEdgeless={editor.isEdgeless}
|
||||
ref={ref => {
|
||||
if (ref != null && ref !== editor.container) {
|
||||
editor.container = ref;
|
||||
editor.getHooks().render();
|
||||
}
|
||||
}}
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseOut={onMouseOut}
|
||||
onContextMenu={onContextmenu}
|
||||
onKeyDown={onKeyDown}
|
||||
onKeyDownCapture={onKeyDownCapture}
|
||||
onKeyUp={onKeyUp}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
onDragOverCapture={onDragOverCapture}
|
||||
onDragEnd={onDragEnd}
|
||||
onDrop={onDrop}
|
||||
isOnDrag={isOnDrag}
|
||||
>
|
||||
<Content style={{ maxWidth: pageWidth + 'px' }}>
|
||||
{children}
|
||||
</Content>
|
||||
{/** TODO: remove selectionManager insert */}
|
||||
{editor && <SelectionRect ref={selectionRef} editor={editor} />}
|
||||
{editor.isEdgeless ? null : <ScrollBlank editor={editor} />}
|
||||
{patchedNodes}
|
||||
</Container>
|
||||
<EditorProvider editor={editor} editorElement={editorElement}>
|
||||
<BlockRenderProvider blockRender={RenderBlock}>
|
||||
<Container
|
||||
isEdgeless={editor.isEdgeless}
|
||||
ref={ref => {
|
||||
if (ref != null && ref !== editor.container) {
|
||||
editor.container = ref;
|
||||
editor.getHooks().render();
|
||||
}
|
||||
}}
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseOut={onMouseOut}
|
||||
onContextMenu={onContextmenu}
|
||||
onKeyDown={onKeyDown}
|
||||
onKeyDownCapture={onKeyDownCapture}
|
||||
onKeyUp={onKeyUp}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
onDragOverCapture={onDragOverCapture}
|
||||
onDragEnd={onDragEnd}
|
||||
onDrop={onDrop}
|
||||
isOnDrag={isOnDrag}
|
||||
>
|
||||
<Content style={{ maxWidth: pageWidth + 'px' }}>
|
||||
<RenderBlock blockId={editor.getRootBlockId()} />
|
||||
</Content>
|
||||
{/** TODO: remove selectionManager insert */}
|
||||
{editor && (
|
||||
<SelectionRect ref={selectionRef} editor={editor} />
|
||||
)}
|
||||
{editor.isEdgeless ? null : <ScrollBlank editor={editor} />}
|
||||
{patchedNodes}
|
||||
</Container>
|
||||
</BlockRenderProvider>
|
||||
</EditorProvider>
|
||||
);
|
||||
};
|
||||
@ -251,7 +251,7 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<ScrollBlankContainter
|
||||
<ScrollBlankContainer
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseMove={onMouseMove}
|
||||
onClick={onClick}
|
||||
@ -283,7 +283,7 @@ const Content = styled('div')({
|
||||
transitionTimingFunction: 'ease-in',
|
||||
});
|
||||
|
||||
const ScrollBlankContainter = styled('div')({
|
||||
const ScrollBlankContainer = styled('div')({
|
||||
paddingBottom: '30vh',
|
||||
margin: `0 -${PADDING_X}px`,
|
||||
});
|
||||
|
@ -1,19 +1,16 @@
|
||||
export { RenderRoot, MIN_PAGE_WIDTH } from './RenderRoot';
|
||||
export * from './render-block';
|
||||
export * from './hooks';
|
||||
|
||||
export { RenderBlock } from './render-block';
|
||||
|
||||
export * from './recast-block';
|
||||
export * from './recast-block/types';
|
||||
|
||||
export * from './block-pendant';
|
||||
|
||||
export { useEditor } from './Contexts';
|
||||
export * from './editor';
|
||||
export * from './hooks';
|
||||
export * from './kanban';
|
||||
export * from './kanban/types';
|
||||
|
||||
export * from './recast-block';
|
||||
export * from './recast-block/types';
|
||||
export {
|
||||
BlockRenderProvider,
|
||||
RenderBlockChildren,
|
||||
useBlockRender,
|
||||
withTreeViewChildren,
|
||||
} from './render-block';
|
||||
export { MIN_PAGE_WIDTH, RenderRoot } from './RenderRoot';
|
||||
export * from './utils';
|
||||
|
||||
export * from './editor';
|
||||
|
||||
export { useEditor } from './Contexts';
|
||||
|
@ -10,6 +10,8 @@ import {
|
||||
RecastMetaProperty,
|
||||
RecastPropertyId,
|
||||
} from '../recast-block/types';
|
||||
import { BlockRenderProvider } from '../render-block';
|
||||
import { KanbanBlockRender } from '../render-block/RenderKanbanBlock';
|
||||
import { useInitKanbanEffect, useRecastKanban } from './kanban';
|
||||
import { KanbanGroup } from './types';
|
||||
|
||||
@ -54,9 +56,11 @@ export const KanbanProvider = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<KanbanContext.Provider value={value}>
|
||||
{children}
|
||||
</KanbanContext.Provider>
|
||||
<BlockRenderProvider blockRender={KanbanBlockRender}>
|
||||
<KanbanContext.Provider value={value}>
|
||||
{children}
|
||||
</KanbanContext.Provider>
|
||||
</BlockRenderProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
35
libs/components/editor-core/src/render-block/Context.tsx
Normal file
35
libs/components/editor-core/src/render-block/Context.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { genErrorObj } from '@toeverything/utils';
|
||||
import { createContext, PropsWithChildren, useContext } from 'react';
|
||||
import { RenderBlockProps } from './RenderBlock';
|
||||
|
||||
type BlockRenderProps = {
|
||||
blockRender: (args: RenderBlockProps) => JSX.Element | null;
|
||||
};
|
||||
|
||||
export const BlockRenderContext = createContext<BlockRenderProps>(
|
||||
genErrorObj(
|
||||
'Failed to get BlockChildrenContext! The context only can use under the "render-root"'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) as any
|
||||
);
|
||||
|
||||
/**
|
||||
* CAUTION! DO NOT PROVIDE A DYNAMIC BLOCK RENDER!
|
||||
*/
|
||||
export const BlockRenderProvider = ({
|
||||
blockRender,
|
||||
children,
|
||||
}: PropsWithChildren<BlockRenderProps>) => {
|
||||
return (
|
||||
<BlockRenderContext.Provider value={{ blockRender }}>
|
||||
{children}
|
||||
</BlockRenderContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useBlockRender = () => {
|
||||
const { blockRender } = useContext(BlockRenderContext);
|
||||
return {
|
||||
BlockRender: blockRender,
|
||||
};
|
||||
};
|
@ -4,7 +4,12 @@ import { useCallback, useMemo } from 'react';
|
||||
import { useEditor } from '../Contexts';
|
||||
import { useBlock } from '../hooks';
|
||||
|
||||
interface RenderBlockProps {
|
||||
/**
|
||||
* Render nothing
|
||||
*/
|
||||
export const NullBlockRender = (): null => null;
|
||||
|
||||
export interface RenderBlockProps {
|
||||
blockId: string;
|
||||
hasContainer?: boolean;
|
||||
}
|
||||
@ -29,7 +34,7 @@ export function RenderBlock({
|
||||
if (block?.type) {
|
||||
return editor.getView(block.type).View;
|
||||
}
|
||||
return () => null;
|
||||
return (): null => null;
|
||||
}, [editor, block?.type]);
|
||||
|
||||
if (!block) {
|
||||
@ -64,4 +69,5 @@ export function RenderBlock({
|
||||
|
||||
const BlockContainer = styled('div')(({ theme }) => ({
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
flex: 1,
|
||||
}));
|
||||
|
@ -1,16 +1,39 @@
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import type { AsyncBlock } from '../editor';
|
||||
import { RenderBlock } from './RenderBlock';
|
||||
import { useBlockRender } from './Context';
|
||||
import { NullBlockRender } from './RenderBlock';
|
||||
|
||||
interface RenderChildrenProps {
|
||||
export interface RenderChildrenProps {
|
||||
block: AsyncBlock;
|
||||
indent?: boolean;
|
||||
}
|
||||
|
||||
export const RenderBlockChildren = ({ block }: RenderChildrenProps) => {
|
||||
export const RenderBlockChildren = ({
|
||||
block,
|
||||
indent = true,
|
||||
}: RenderChildrenProps) => {
|
||||
const { BlockRender } = useBlockRender();
|
||||
if (BlockRender === NullBlockRender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return block.childrenIds.length ? (
|
||||
<>
|
||||
<StyledIdentWrapper indent={indent}>
|
||||
{block.childrenIds.map(childId => {
|
||||
return <RenderBlock key={childId} blockId={childId} />;
|
||||
return <BlockRender key={childId} blockId={childId} />;
|
||||
})}
|
||||
</>
|
||||
</StyledIdentWrapper>
|
||||
) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Indent rendering child nodes
|
||||
*/
|
||||
const StyledIdentWrapper = styled('div')<{ indent?: boolean }>(
|
||||
({ indent }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// TODO: marginLeft should use theme provided by styled
|
||||
...(indent && { marginLeft: '30px' }),
|
||||
})
|
||||
);
|
||||
|
@ -0,0 +1,56 @@
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import { useBlock } from '../hooks';
|
||||
import { BlockRenderProvider } from './Context';
|
||||
import { NullBlockRender, RenderBlock, RenderBlockProps } from './RenderBlock';
|
||||
|
||||
/**
|
||||
* Render block without children.
|
||||
*/
|
||||
const BlockWithoutChildrenRender = ({ blockId }: RenderBlockProps) => {
|
||||
return (
|
||||
<BlockRenderProvider blockRender={NullBlockRender}>
|
||||
<RenderBlock blockId={blockId} />
|
||||
</BlockRenderProvider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render a block, but only one level of children.
|
||||
*/
|
||||
const OneLevelBlockRender = ({ blockId }: RenderBlockProps) => {
|
||||
return (
|
||||
<BlockRenderProvider blockRender={BlockWithoutChildrenRender}>
|
||||
<RenderBlock blockId={blockId} />
|
||||
</BlockRenderProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const KanbanBlockRender = ({ blockId }: RenderBlockProps) => {
|
||||
const { block } = useBlock(blockId);
|
||||
|
||||
if (!block) {
|
||||
return (
|
||||
<BlockRenderProvider blockRender={NullBlockRender}>
|
||||
<RenderBlock blockId={blockId} />
|
||||
</BlockRenderProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BlockRenderProvider blockRender={NullBlockRender}>
|
||||
<RenderBlock blockId={blockId} />
|
||||
{block?.childrenIds.map(childId => (
|
||||
<StyledBorder key={childId}>
|
||||
<RenderBlock blockId={childId} />
|
||||
</StyledBorder>
|
||||
))}
|
||||
</BlockRenderProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledBorder = styled('div')({
|
||||
border: '1px solid #E0E6EB',
|
||||
borderRadius: '5px',
|
||||
margin: '4px',
|
||||
padding: '0 4px',
|
||||
});
|
@ -1,10 +1,3 @@
|
||||
import {
|
||||
BlockPendantProvider,
|
||||
CreateView,
|
||||
RenderBlock,
|
||||
useCurrentView,
|
||||
useOnSelect,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import type {
|
||||
ComponentPropsWithoutRef,
|
||||
@ -12,9 +5,10 @@ import type {
|
||||
CSSProperties,
|
||||
ReactElement,
|
||||
} from 'react';
|
||||
import { forwardRef, useState } from 'react';
|
||||
import { SCENE_CONFIG } from '../blocks/group/config';
|
||||
import { BlockContainer } from '../components/BlockContainer';
|
||||
import { forwardRef } from 'react';
|
||||
import { CreateView } from '../editor';
|
||||
import { useBlockRender } from './Context';
|
||||
import { NullBlockRender } from './RenderBlock';
|
||||
|
||||
type WithChildrenConfig = {
|
||||
indent: CSSProperties['marginLeft'];
|
||||
@ -41,45 +35,6 @@ const TreeView = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
interface ChildrenViewProp {
|
||||
childrenIds: string[];
|
||||
handleCollapse: () => void;
|
||||
indent?: string | number;
|
||||
}
|
||||
|
||||
const ChildrenView = ({
|
||||
childrenIds,
|
||||
handleCollapse,
|
||||
indent,
|
||||
}: ChildrenViewProp) => {
|
||||
const [currentView] = useCurrentView();
|
||||
const isKanbanScene = currentView.type === SCENE_CONFIG.KANBAN;
|
||||
|
||||
return (
|
||||
<Children style={{ ...(!isKanbanScene && { marginLeft: indent }) }}>
|
||||
{childrenIds.map((childId, idx) => {
|
||||
if (isKanbanScene) {
|
||||
return (
|
||||
<StyledBorder key={childId}>
|
||||
<RenderBlock blockId={childId} />
|
||||
</StyledBorder>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TreeView
|
||||
key={childId}
|
||||
lastItem={idx === childrenIds.length - 1}
|
||||
onClick={handleCollapse}
|
||||
>
|
||||
<RenderBlock key={childId} blockId={childId} />
|
||||
</TreeView>
|
||||
);
|
||||
})}
|
||||
</Children>
|
||||
);
|
||||
};
|
||||
|
||||
const CollapsedNode = forwardRef<
|
||||
HTMLDivElement,
|
||||
ComponentPropsWithoutRef<'div'>
|
||||
@ -104,15 +59,15 @@ export const withTreeViewChildren = (
|
||||
};
|
||||
|
||||
return (props: CreateView) => {
|
||||
const { block, editor } = props;
|
||||
const { block } = props;
|
||||
const { BlockRender } = useBlockRender();
|
||||
const collapsed = block.getProperty('collapsed')?.value;
|
||||
const childrenIds = block.childrenIds;
|
||||
const showChildren = !collapsed && childrenIds.length > 0;
|
||||
const showChildren =
|
||||
!collapsed &&
|
||||
childrenIds.length > 0 &&
|
||||
BlockRender !== NullBlockRender;
|
||||
|
||||
const [isSelect, setIsSelect] = useState<boolean>(false);
|
||||
useOnSelect(block.id, (isSelect: boolean) => {
|
||||
setIsSelect(isSelect);
|
||||
});
|
||||
const handleCollapse = () => {
|
||||
block.setProperty('collapsed', { value: true });
|
||||
};
|
||||
@ -122,15 +77,8 @@ export const withTreeViewChildren = (
|
||||
};
|
||||
|
||||
return (
|
||||
<BlockContainer
|
||||
editor={props.editor}
|
||||
block={block}
|
||||
selected={isSelect}
|
||||
className={Wrapper.toString()}
|
||||
>
|
||||
<BlockPendantProvider block={block}>
|
||||
<div>{creator(props)}</div>
|
||||
</BlockPendantProvider>
|
||||
<>
|
||||
{creator(props)}
|
||||
|
||||
{collapsed && (
|
||||
<CollapsedNode
|
||||
@ -138,22 +86,24 @@ export const withTreeViewChildren = (
|
||||
style={{ marginLeft: config.indent }}
|
||||
/>
|
||||
)}
|
||||
{showChildren && (
|
||||
<ChildrenView
|
||||
childrenIds={childrenIds}
|
||||
handleCollapse={handleCollapse}
|
||||
indent={config.indent}
|
||||
/>
|
||||
)}
|
||||
</BlockContainer>
|
||||
{showChildren &&
|
||||
childrenIds.map((childId, idx) => {
|
||||
return (
|
||||
<TreeView
|
||||
key={childId}
|
||||
lastItem={idx === childrenIds.length - 1}
|
||||
onClick={handleCollapse}
|
||||
style={{ marginLeft: config.indent }}
|
||||
>
|
||||
<BlockRender key={childId} blockId={childId} />
|
||||
</TreeView>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Wrapper = styled('div')({ display: 'flex', flexDirection: 'column' });
|
||||
|
||||
const Children = Wrapper;
|
||||
|
||||
const TREE_COLOR = '#D5DFE6';
|
||||
// adjust left and right margins of the the tree line
|
||||
const TREE_LINE_LEFT_OFFSET = '-16px';
|
||||
@ -163,6 +113,7 @@ const TREE_LINE_WIDTH = '12px';
|
||||
|
||||
const TreeWrapper = styled('div')({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
const StyledTreeView = styled('div')({
|
||||
@ -228,9 +179,3 @@ const LastItemRadius = styled('div')({
|
||||
borderRadius: '0 0 0 3px',
|
||||
pointerEvents: 'none',
|
||||
});
|
||||
|
||||
const StyledBorder = styled('div')({
|
||||
border: '1px solid #E0E6EB',
|
||||
borderRadius: '5px',
|
||||
margin: '4px',
|
||||
});
|
@ -1,2 +1,4 @@
|
||||
export { BlockRenderProvider, useBlockRender } from './Context';
|
||||
export * from './RenderBlock';
|
||||
export * from './RenderBlockChildren';
|
||||
export { withTreeViewChildren } from './WithTreeViewChildren';
|
||||
|
Loading…
Reference in New Issue
Block a user