Ver Fonte

Feat/number list block (#2453)

* feat: support bulleted list block

* feat: support number list block
Kilu.He há 2 anos atrás
pai
commit
e2ced6524f

+ 23 - 0
frontend/appflowy_tauri/src/appflowy_app/components/document/BulletedListBlock/index.tsx

@@ -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;

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/components/document/ColumnBlock/index.tsx → frontend/appflowy_tauri/src/appflowy_app/components/document/ColumnListBlock/Column.tsx

@@ -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 - 2
frontend/appflowy_tauri/src/appflowy_app/components/document/ListBlock/ColumnListBlock.tsx → frontend/appflowy_tauri/src/appflowy_app/components/document/ColumnListBlock/index.tsx

@@ -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,

+ 0 - 30
frontend/appflowy_tauri/src/appflowy_app/components/document/ListBlock/BulletedListBlock.tsx

@@ -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>
-  );
-}

+ 0 - 30
frontend/appflowy_tauri/src/appflowy_app/components/document/ListBlock/NumberedListBlock.tsx

@@ -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>
-  );
-}

+ 0 - 27
frontend/appflowy_tauri/src/appflowy_app/components/document/ListBlock/index.tsx

@@ -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;
-}

+ 14 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx

@@ -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]);
 

+ 26 - 0
frontend/appflowy_tauri/src/appflowy_app/components/document/NumberedListBlock/NumberedListBlock.hooks.ts

@@ -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,
+  };
+}

+ 27 - 0
frontend/appflowy_tauri/src/appflowy_app/components/document/NumberedListBlock/index.tsx

@@ -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;

+ 9 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/events/TurnIntoEvents.hooks.ts

@@ -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) {

+ 2 - 1
frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts

@@ -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,

+ 12 - 0
frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts

@@ -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> {

+ 36 - 5
frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/index.ts

@@ -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',
+  };
+}