mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Feat/number list block (#2453)
* feat: support bulleted list block * feat: support number list block
This commit is contained in:
parent
ad99998d33
commit
e2ced6524f
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { BlockType, NestedBlock } from '$app/interfaces/document';
|
||||
import { Circle } from '@mui/icons-material';
|
||||
import TextBlock from '$app/components/document/TextBlock';
|
||||
import NodeChildren from '$app/components/document/Node/NodeChildren';
|
||||
|
||||
function BulletedListBlock({ node, childIds }: { node: NestedBlock<BlockType.BulletedListBlock>; childIds?: string[] }) {
|
||||
return (
|
||||
<>
|
||||
<div className={'flex'}>
|
||||
<div className={`relative flex h-[calc(1.5em_+_2px)] min-w-[24px] select-none items-center`}>
|
||||
<Circle sx={{ width: 8, height: 8 }} />
|
||||
</div>
|
||||
<div className={'flex-1'}>
|
||||
<TextBlock node={node} />
|
||||
</div>
|
||||
</div>
|
||||
<NodeChildren className='pl-[1.5em]' childIds={childIds} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default BulletedListBlock;
|
@ -1,7 +1,7 @@
|
||||
import NodeComponent from '$app/components/document/Node';
|
||||
import React from 'react';
|
||||
import NodeComponent from '../Node';
|
||||
|
||||
export default function ColumnBlock({ id, index, width }: { id: string; index: number; width: string }) {
|
||||
export function ColumnBlock({ id, index, width }: { id: string; index: number; width: string }) {
|
||||
const renderResizer = () => {
|
||||
return (
|
||||
<div className={`relative w-[46px] flex-shrink-0 flex-grow-0 transition-opacity`} style={{ opacity: 0 }}></div>
|
@ -1,7 +1,6 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import ColumnBlock from '../ColumnBlock';
|
||||
|
||||
import { Node } from '$app/interfaces/document';
|
||||
import { ColumnBlock } from './Column';
|
||||
|
||||
export default function ColumnListBlock({
|
||||
node,
|
@ -1,30 +0,0 @@
|
||||
import { Circle } from '@mui/icons-material';
|
||||
import NodeComponent from '../Node';
|
||||
import { Node } from '$app/interfaces/document';
|
||||
|
||||
export default function BulletedListBlock({
|
||||
title,
|
||||
node,
|
||||
childIds,
|
||||
}: {
|
||||
title: JSX.Element;
|
||||
node: Node;
|
||||
childIds?: string[];
|
||||
}) {
|
||||
return (
|
||||
<div className='bulleted-list-block relative'>
|
||||
<div className='relative flex'>
|
||||
<div className={`relative flex h-[calc(1.5em_+_3px_+_3px)] min-w-[24px] select-none items-center`}>
|
||||
<Circle sx={{ width: 8, height: 8 }} />
|
||||
</div>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div className='pl-[24px]'>
|
||||
{childIds?.map((item) => (
|
||||
<NodeComponent key={item} id={item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import NodeComponent from '../Node';
|
||||
import { Node } from '$app/interfaces/document';
|
||||
|
||||
export default function NumberedListBlock({
|
||||
title,
|
||||
node,
|
||||
childIds,
|
||||
}: {
|
||||
title: JSX.Element;
|
||||
node: Node;
|
||||
childIds?: string[];
|
||||
}) {
|
||||
const index = 1;
|
||||
return (
|
||||
<div className='numbered-list-block'>
|
||||
<div className='relative flex'>
|
||||
<div
|
||||
className={`relative flex h-[calc(1.5em_+_3px_+_3px)] min-w-[24px] max-w-[24px] select-none items-center`}
|
||||
>{`${index} .`}</div>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div className='pl-[24px]'>
|
||||
{childIds?.map((item) => (
|
||||
<NodeComponent key={item} id={item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import TextBlock from '../TextBlock';
|
||||
import NumberedListBlock from './NumberedListBlock';
|
||||
import BulletedListBlock from './BulletedListBlock';
|
||||
import ColumnListBlock from './ColumnListBlock';
|
||||
import { Node, TextDelta } from '@/appflowy_app/interfaces/document';
|
||||
|
||||
export default function ListBlock({ node }: { node: Node }) {
|
||||
const title = useMemo(() => {
|
||||
// if (node.data.style?.type === 'column') return <></>;
|
||||
return <div className='flex-1'>{/*<TextBlock delta={delta} node={node} childIds={[]} />*/}</div>;
|
||||
}, [node]);
|
||||
|
||||
// if (node.data.type === 'numbered') {
|
||||
// return <NumberedListBlock title={title} node={node} />;
|
||||
// }
|
||||
//
|
||||
// if (node.data.type === 'bulleted') {
|
||||
// return <BulletedListBlock title={title} node={node} />;
|
||||
// }
|
||||
//
|
||||
// if (node.data.type === 'column') {
|
||||
// return <ColumnListBlock node={node} />;
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
@ -8,6 +8,9 @@ import { BlockType } from '$app/interfaces/document';
|
||||
import HeadingBlock from '$app/components/document/HeadingBlock';
|
||||
import TodoListBlock from '$app/components/document/TodoListBlock';
|
||||
import QuoteBlock from '$app/components/document/QuoteBlock';
|
||||
import BulletedListBlock from '$app/components/document/BulletedListBlock';
|
||||
import NumberedListBlock from '$app/components/document/NumberedListBlock';
|
||||
import { Alert } from '@mui/material';
|
||||
|
||||
function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { node, childIds, isSelected, ref } = useNode(id);
|
||||
@ -26,8 +29,18 @@ function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<H
|
||||
case BlockType.QuoteBlock: {
|
||||
return <QuoteBlock node={node} childIds={childIds} />;
|
||||
}
|
||||
case BlockType.BulletedListBlock: {
|
||||
return <BulletedListBlock node={node} childIds={childIds} />;
|
||||
}
|
||||
case BlockType.NumberedListBlock: {
|
||||
return <NumberedListBlock node={node} childIds={childIds} />;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
return (
|
||||
<Alert severity='info' className='mb-2'>
|
||||
<p>The current version does not support this Block.</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
}, [node, childIds]);
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { useAppSelector } from '$app/stores/store';
|
||||
import { BlockType, NestedBlock } from '$app/interfaces/document';
|
||||
|
||||
export function useNumberedListBlock(node: NestedBlock<BlockType.NumberedListBlock>) {
|
||||
// Find the last index of the previous blocks
|
||||
const prevNumberedIndex = useAppSelector((state) => {
|
||||
const nodes = state['document'].nodes;
|
||||
const children = state['document'].children;
|
||||
// The parent must be existed
|
||||
const parent = nodes[node.parent!];
|
||||
const siblings = children[parent.children];
|
||||
const index = siblings.indexOf(node.id);
|
||||
if (index === 0) return 0;
|
||||
const prevNodeIds = siblings.slice(0, index);
|
||||
// The index is distance from last block to the last non-numbered-list block
|
||||
const lastIndex = prevNodeIds.reverse().findIndex((id) => {
|
||||
return nodes[id].type !== BlockType.NumberedListBlock;
|
||||
});
|
||||
if (lastIndex === -1) return prevNodeIds.length;
|
||||
return lastIndex;
|
||||
});
|
||||
|
||||
return {
|
||||
index: prevNumberedIndex + 1,
|
||||
};
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { BlockType, NestedBlock } from '$app/interfaces/document';
|
||||
import TextBlock from '$app/components/document/TextBlock';
|
||||
import NodeChildren from '$app/components/document/Node/NodeChildren';
|
||||
import { useNumberedListBlock } from '$app/components/document/NumberedListBlock/NumberedListBlock.hooks';
|
||||
|
||||
function NumberedListBlock({ node, childIds }: { node: NestedBlock<BlockType.NumberedListBlock>; childIds?: string[] }) {
|
||||
const { index } = useNumberedListBlock(node);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'flex'}>
|
||||
<div
|
||||
className={`relative flex h-[calc(1.5em_+_4px)] min-w-[24px] select-none items-center whitespace-nowrap text-center`}
|
||||
>
|
||||
{index}.
|
||||
</div>
|
||||
<div className={'flex-1'}>
|
||||
<TextBlock node={node} />
|
||||
</div>
|
||||
</div>
|
||||
<NodeChildren className='pl-[1.5em]' childIds={childIds} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default NumberedListBlock;
|
@ -7,12 +7,20 @@ import { turnToBlockThunk } from '$app_reducers/document/async-actions';
|
||||
import { blockConfig } from '$app/constants/document/config';
|
||||
import { Editor } from 'slate';
|
||||
import { getBeforeRangeAt } from '$app/utils/document/slate/text';
|
||||
import { getHeadingDataFromEditor, getQuoteDataFromEditor, getTodoListDataFromEditor } from '$app/utils/document/blocks';
|
||||
import {
|
||||
getHeadingDataFromEditor,
|
||||
getQuoteDataFromEditor,
|
||||
getTodoListDataFromEditor,
|
||||
getBulletedDataFromEditor,
|
||||
getNumberedListDataFromEditor,
|
||||
} from '$app/utils/document/blocks';
|
||||
|
||||
const blockDataFactoryMap: Record<string, (editor: Editor) => BlockData<any> | undefined> = {
|
||||
[BlockType.HeadingBlock]: getHeadingDataFromEditor,
|
||||
[BlockType.TodoListBlock]: getTodoListDataFromEditor,
|
||||
[BlockType.QuoteBlock]: getQuoteDataFromEditor,
|
||||
[BlockType.BulletedListBlock]: getBulletedDataFromEditor,
|
||||
[BlockType.NumberedListBlock]: getNumberedListDataFromEditor
|
||||
};
|
||||
|
||||
export function useTurnIntoBlock(id: string) {
|
||||
|
@ -53,8 +53,9 @@ export const blockConfig: Record<
|
||||
splitType: BlockType.NumberedListBlock,
|
||||
/**
|
||||
* 1. or 2. or 3.
|
||||
* a. or b. or c.
|
||||
*/
|
||||
markdownRegexps: [/^(\s*\d+\.)$/],
|
||||
markdownRegexps: [/^(\s*[\d|a-zA-Z]+\.)$/],
|
||||
},
|
||||
[BlockType.QuoteBlock]: {
|
||||
canAddChild: true,
|
||||
|
@ -25,6 +25,14 @@ export interface TodoListBlockData extends TextBlockData {
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
export interface BulletListBlockData extends TextBlockData {
|
||||
format: 'default' | 'circle' | 'square' | 'disc';
|
||||
}
|
||||
|
||||
export interface NumberedListBlockData extends TextBlockData {
|
||||
format: 'default' | 'numbers' | 'letters' | 'roman_numerals';
|
||||
}
|
||||
|
||||
export interface QuoteBlockData extends TextBlockData {
|
||||
size: 'default' | 'large';
|
||||
}
|
||||
@ -43,6 +51,10 @@ export type BlockData<Type> = Type extends BlockType.HeadingBlock
|
||||
? TodoListBlockData
|
||||
: Type extends BlockType.QuoteBlock
|
||||
? QuoteBlockData
|
||||
: Type extends BlockType.BulletedListBlock
|
||||
? BulletListBlockData
|
||||
: Type extends BlockType.NumberedListBlock
|
||||
? NumberedListBlockData
|
||||
: TextBlockData;
|
||||
|
||||
export interface NestedBlock<Type = any> {
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { Editor } from 'slate';
|
||||
import { HeadingBlockData, TodoListBlockData } from '$app/interfaces/document';
|
||||
import { getAfterRangeAt, getBeforeRangeAt } from '$app/utils/document/slate/text';
|
||||
import { getDeltaAfterSelection, getDeltaFromSlateNodes } from '$app/utils/document/blocks/common';
|
||||
import {
|
||||
BulletListBlockData,
|
||||
HeadingBlockData,
|
||||
NumberedListBlockData,
|
||||
TodoListBlockData,
|
||||
} from '$app/interfaces/document';
|
||||
import { getBeforeRangeAt } from '$app/utils/document/slate/text';
|
||||
import { getDeltaAfterSelection } from '$app/utils/document/blocks/common';
|
||||
|
||||
/**
|
||||
* get heading data from editor, only support markdown
|
||||
@ -43,10 +48,36 @@ export function getTodoListDataFromEditor(editor: Editor): TodoListBlockData | u
|
||||
if (!selection) return;
|
||||
const hashTags = Editor.string(editor, getBeforeRangeAt(editor, selection));
|
||||
const checked = hashTags.match(/x/g)?.length;
|
||||
const slateNodes = Editor.fragment(editor, getAfterRangeAt(editor, selection));
|
||||
const delta = getDeltaFromSlateNodes(slateNodes);
|
||||
const delta = getDeltaAfterSelection(editor);
|
||||
if (!delta) return;
|
||||
return {
|
||||
delta,
|
||||
checked: !!checked,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get bulleted_list data from editor, only support markdown
|
||||
* @param editor
|
||||
*/
|
||||
export function getBulletedDataFromEditor(editor: Editor): BulletListBlockData | undefined {
|
||||
const delta = getDeltaAfterSelection(editor);
|
||||
if (!delta) return;
|
||||
return {
|
||||
delta,
|
||||
format: 'default',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get numbered_list data from editor, only support markdown
|
||||
* @param editor
|
||||
*/
|
||||
export function getNumberedListDataFromEditor(editor: Editor): NumberedListBlockData | undefined {
|
||||
const delta = getDeltaAfterSelection(editor);
|
||||
if (!delta) return;
|
||||
return {
|
||||
delta,
|
||||
format: 'default',
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user