+
- {childIds && childIds.length > 0 ? (
-
- {childIds.map((item) => (
-
- ))}
-
- ) : null}
+
>
);
}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useMarkDown.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useMarkDown.hooks.ts
index c90875c70f..941f300bba 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useMarkDown.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useMarkDown.hooks.ts
@@ -1,13 +1,14 @@
import { useCallback, useContext, useMemo } from 'react';
import { TextBlockKeyEventHandlerParams } from '$app/interfaces/document';
import { keyBoardEventKeyMap } from '$app/constants/document/text_block';
-import { canHandleToHeadingBlock } from '$app/utils/document/slate/markdown';
+import { canHandleToHeadingBlock, canHandleToCheckboxBlock } from '$app/utils/document/slate/markdown';
import { useAppDispatch } from '$app/stores/store';
import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
import { turnToHeadingBlockThunk } from '$app_reducers/document/async-actions/blocks/heading';
+import { turnToTodoListBlockThunk } from '$app_reducers/document/async-actions/blocks/todo_list';
export function useMarkDown(id: string) {
- const { toHeadingBlockAction } = useActions(id);
+ const { toHeadingBlockAction, toCheckboxBlockAction } = useActions(id);
const toHeadingBlockEvent = useMemo(() => {
return {
triggerEventKey: keyBoardEventKeyMap.Space,
@@ -16,7 +17,18 @@ export function useMarkDown(id: string) {
};
}, [toHeadingBlockAction]);
- const markdownEvents = useMemo(() => [toHeadingBlockEvent], [toHeadingBlockEvent]);
+ const toCheckboxBlockEvent = useMemo(() => {
+ return {
+ triggerEventKey: keyBoardEventKeyMap.Space,
+ canHandle: canHandleToCheckboxBlock,
+ handler: toCheckboxBlockAction,
+ };
+ }, [toCheckboxBlockAction]);
+
+ const markdownEvents = useMemo(
+ () => [toHeadingBlockEvent, toCheckboxBlockEvent],
+ [toHeadingBlockEvent, toCheckboxBlockEvent]
+ );
return {
markdownEvents,
@@ -35,7 +47,17 @@ function useActions(id: string) {
[controller, dispatch, id]
);
+ const toCheckboxBlockAction = useCallback(
+ (...args: TextBlockKeyEventHandlerParams) => {
+ if (!controller) return;
+ const [_event, editor] = args;
+ dispatch(turnToTodoListBlockThunk({ id, controller, editor }));
+ },
+ [controller, dispatch, id]
+ );
+
return {
toHeadingBlockAction,
+ toCheckboxBlockAction,
};
}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/TodoListBlock.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/TodoListBlock.hooks.ts
new file mode 100644
index 0000000000..f6b6a3bd3e
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/TodoListBlock.hooks.ts
@@ -0,0 +1,38 @@
+import { useAppDispatch } from '$app/stores/store';
+import { useCallback, useContext } from 'react';
+import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
+import { updateNodeDataThunk } from '$app_reducers/document/async-actions/blocks/text/update';
+import { BlockData, BlockType } from '$app/interfaces/document';
+import isHotkey from 'is-hotkey';
+
+export function useTodoListBlock(id: string, data: BlockData
) {
+ const dispatch = useAppDispatch();
+ const controller = useContext(DocumentControllerContext);
+ const toggleCheckbox = useCallback(() => {
+ if (!controller) return;
+ void dispatch(
+ updateNodeDataThunk({
+ id,
+ controller,
+ data: {
+ checked: !data.checked,
+ },
+ })
+ );
+ }, [controller, dispatch, id, data.checked]);
+
+ const handleShortcut = useCallback(
+ (event: React.KeyboardEvent) => {
+ // Accepts mod for the classic "cmd on Mac, ctrl on Windows" use case.
+ if (isHotkey('mod+enter', event)) {
+ toggleCheckbox();
+ }
+ },
+ [toggleCheckbox]
+ );
+
+ return {
+ toggleCheckbox,
+ handleShortcut,
+ };
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/index.tsx
new file mode 100644
index 0000000000..1ae9a33c00
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TodoListBlock/index.tsx
@@ -0,0 +1,42 @@
+import { BlockType, NestedBlock } from '$app/interfaces/document';
+import TextBlock from '$app/components/document/TextBlock';
+import { useTodoListBlock } from '$app/components/document/TodoListBlock/TodoListBlock.hooks';
+import { EditorCheckSvg } from '$app/components/_shared/svg/EditorCheckSvg';
+import { EditorUncheckSvg } from '$app/components/_shared/svg/EditorUncheckSvg';
+import React from 'react';
+import NodeChildren from '$app/components/document/Node/NodeChildren';
+
+export default function TodoListBlock({
+ node,
+ childIds,
+}: {
+ node: NestedBlock;
+ childIds?: string[];
+}) {
+ const { id, data } = node;
+ const { toggleCheckbox, handleShortcut } = useTodoListBlock(id, node.data);
+
+ const checked = !!data.checked;
+
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts b/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts
index 071e6492a5..3bfae1ddca 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts
@@ -3,4 +3,9 @@ import { BlockType } from '$app/interfaces/document';
/**
* Block types that are allowed to have children
*/
-export const allowedChildrenBlockTypes = [BlockType.TextBlock, BlockType.PageBlock];
+export const allowedChildrenBlockTypes = [BlockType.TextBlock, BlockType.PageBlock, BlockType.TodoListBlock];
+
+/**
+ * Block types that split node can extend to the next line
+ */
+export const splitableBlockTypes = [BlockType.TextBlock, BlockType.TodoListBlock];
diff --git a/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts b/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts
index 077ec411e2..7545482c08 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts
@@ -3,8 +3,8 @@ import { Editor } from 'slate';
export enum BlockType {
PageBlock = 'page',
HeadingBlock = 'heading',
- ListBlock = 'list',
TextBlock = 'text',
+ TodoListBlock = 'todo_list',
CodeBlock = 'code',
EmbedBlock = 'embed',
QuoteBlock = 'quote',
@@ -18,6 +18,10 @@ export interface HeadingBlockData extends TextBlockData {
level: number;
}
+export interface TodoListBlockData extends TextBlockData {
+ checked: boolean;
+}
+
export interface TextBlockData {
delta: TextDelta[];
}
@@ -28,6 +32,8 @@ export type BlockData = Type extends BlockType.HeadingBlock
? HeadingBlockData
: Type extends BlockType.PageBlock
? PageBlockData
+ : Type extends BlockType.TodoListBlock
+ ? TodoListBlockData
: TextBlockData;
export interface NestedBlock {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/heading.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/heading.ts
index 8fec913504..b9bb6c3883 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/heading.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/heading.ts
@@ -1,42 +1,31 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { Editor } from 'slate';
import { DocumentController } from '$app/stores/effects/document/document_controller';
-import { DocumentState } from '$app/interfaces/document';
-import { getHeadingDataFromEditor, newHeadingBlock } from '$app/utils/document/blocks/heading';
-import { setCursorBeforeThunk } from '$app_reducers/document/async-actions/cursor';
+import { BlockType } from '$app/interfaces/document';
+import { turnToBlockThunk } from '$app_reducers/document/async-actions/turn_to';
+import { getHeadingDataFromEditor } from '$app/utils/document/blocks/heading';
+/**
+ * transform to heading block
+ * 1. insert heading block after current block
+ * 2. move all children to parent after heading block, because heading block can't have children
+ * 3. delete current block
+ */
export const turnToHeadingBlockThunk = createAsyncThunk(
'document/turnToHeadingBlock',
async (payload: { id: string; editor: Editor; controller: DocumentController }, thunkAPI) => {
const { id, editor, controller } = payload;
- const { dispatch, getState } = thunkAPI;
- const state = (getState() as { document: DocumentState }).document;
-
- const node = state.nodes[id];
- if (!node.parent) return;
-
- const parent = state.nodes[node.parent];
- const children = state.children[node.children].map((id) => state.nodes[id]);
-
- /**
- * transform to heading block
- * 1. insert heading block after current block
- * 2. move all children to parent after heading block, because heading block can't have children
- * 3. delete current block
- */
+ const { dispatch } = thunkAPI;
const data = getHeadingDataFromEditor(editor);
if (!data) return;
- const headingBlock = newHeadingBlock(parent.id, data);
- const insertHeadingAction = controller.getInsertAction(headingBlock, node.id);
-
- const moveChildrenActions = controller.getMoveChildrenAction(children, parent.id, headingBlock.id);
-
- const deleteAction = controller.getDeleteAction(node);
-
- // submit actions
- await controller.applyActions([insertHeadingAction, ...moveChildrenActions, deleteAction]);
- // set cursor
- await dispatch(setCursorBeforeThunk({ id: headingBlock.id }));
+ await dispatch(
+ turnToBlockThunk({
+ id,
+ controller,
+ type: BlockType.HeadingBlock,
+ data,
+ })
+ );
}
);
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts
index fbea40b6d3..da8053a923 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts
@@ -84,7 +84,7 @@ export const backspaceNodeThunk = createAsyncThunk(
const index = children.indexOf(id);
const prevNodeId = children[index - 1];
const nextNodeId = children[index + 1];
- // transform to text block
+ // turn to text block
if (node.type !== BlockType.TextBlock) {
await dispatch(turnToTextBlockThunk({ id, controller }));
return;
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/index.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/index.ts
index fb38ecf363..040e9545ec 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/index.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/index.ts
@@ -1,39 +1,32 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { DocumentController } from '$app/stores/effects/document/document_controller';
-import { DocumentState } from '$app/interfaces/document';
-import { newTextBlock } from '$app/utils/document/blocks/text';
-import { setCursorBeforeThunk } from '$app_reducers/document/async-actions/cursor';
+import { BlockType, DocumentState } from '$app/interfaces/document';
+import { turnToBlockThunk } from '$app_reducers/document/async-actions/turn_to';
+/**
+ * transform to text block
+ * 1. insert text block after current block
+ * 2. move children to text block
+ * 3. delete current block
+ */
export const turnToTextBlockThunk = createAsyncThunk(
'document/turnToTextBlock',
async (payload: { id: string; controller: DocumentController }, thunkAPI) => {
const { id, controller } = payload;
const { dispatch, getState } = thunkAPI;
const state = (getState() as { document: DocumentState }).document;
-
const node = state.nodes[id];
- if (!node.parent) return;
-
- const parent = state.nodes[node.parent];
- const children = state.children[node.children].map((id) => state.nodes[id]);
-
- /**
- * transform to text block
- * 1. insert text block after current block
- * 2. move children to text block
- * 3. delete current block
- */
-
- const textBlock = newTextBlock(parent.id, {
+ const data = {
delta: node.data.delta,
- });
- const insertTextAction = controller.getInsertAction(textBlock, node.id);
- const moveChildrenActions = controller.getMoveChildrenAction(children, textBlock.id, '');
- const deleteAction = controller.getDeleteAction(node);
+ };
- // submit actions
- await controller.applyActions([insertTextAction, ...moveChildrenActions, deleteAction]);
- // set cursor
- await dispatch(setCursorBeforeThunk({ id: textBlock.id }));
+ await dispatch(
+ turnToBlockThunk({
+ id,
+ controller,
+ type: BlockType.TextBlock,
+ data,
+ })
+ );
}
);
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts
index b500b874ae..dd20d11c8b 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts
@@ -3,7 +3,8 @@ import { DocumentController } from '$app/stores/effects/document/document_contro
import { createAsyncThunk } from '@reduxjs/toolkit';
import { documentActions } from '$app_reducers/document/slice';
import { setCursorBeforeThunk } from '../../cursor';
-import { newTextBlock } from '$app/utils/document/blocks/text';
+import { getDefaultBlockData, newBlock } from '$app/utils/document/blocks/common';
+import { splitableBlockTypes } from '$app/constants/document/config';
export const splitNodeThunk = createAsyncThunk(
'document/splitNode',
@@ -20,7 +21,10 @@ export const splitNodeThunk = createAsyncThunk(
const prevId = children.length > 0 ? null : node.id;
const parent = children.length > 0 ? node : state.nodes[node.parent];
- const newNode = newTextBlock(parent.id, {
+ const newNodeType = splitableBlockTypes.includes(node.type) ? node.type : BlockType.TextBlock;
+ const defaultData = getDefaultBlockData(newNodeType);
+ const newNode = newBlock(newNodeType, parent.id, {
+ ...defaultData,
delta: insert,
});
const retainNode = {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/update.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/update.ts
index 6e9f0580ff..7c8d5f2910 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/update.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/update.ts
@@ -1,4 +1,4 @@
-import { TextDelta, NestedBlock, DocumentState } from '$app/interfaces/document';
+import { TextDelta, NestedBlock, DocumentState, BlockData } from '$app/interfaces/document';
import { DocumentController } from '$app/stores/effects/document/document_controller';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { documentActions } from '$app_reducers/document/slice';
@@ -35,3 +35,29 @@ const debounceApplyUpdate = debounce((controller: DocumentController, updateNode
}),
]);
}, 200);
+
+export const updateNodeDataThunk = createAsyncThunk<
+ void,
+ {
+ id: string;
+ data: Partial>;
+ controller: DocumentController;
+ }
+>('document/updateNodeDataExceptDelta', async (payload, thunkAPI) => {
+ const { id, data, controller } = payload;
+ const { dispatch, getState } = thunkAPI;
+ const state = (getState() as { document: DocumentState }).document;
+
+ dispatch(documentActions.updateNodeData({ id, data: { ...data } }));
+
+ const node = state.nodes[id];
+ await controller.applyActions([
+ controller.getUpdateAction({
+ ...node,
+ data: {
+ ...node.data,
+ ...data,
+ },
+ }),
+ ]);
+});
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/todo_list.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/todo_list.ts
new file mode 100644
index 0000000000..6c6d53084b
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/todo_list.ts
@@ -0,0 +1,31 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { DocumentController } from '$app/stores/effects/document/document_controller';
+import { BlockType } from '$app/interfaces/document';
+import { turnToBlockThunk } from '$app_reducers/document/async-actions/turn_to';
+import { Editor } from 'slate';
+import { getTodoListDataFromEditor } from '$app/utils/document/blocks/todo_list';
+
+/**
+ * transform to todolist block
+ * 1. insert todolist block after current block
+ * 2. move children to todolist block
+ * 3. delete current block
+ */
+export const turnToTodoListBlockThunk = createAsyncThunk(
+ 'document/turnToTodoListBlock',
+ async (payload: { id: string; editor: Editor; controller: DocumentController }, thunkAPI) => {
+ const { id, controller, editor } = payload;
+ const { dispatch } = thunkAPI;
+ const data = getTodoListDataFromEditor(editor);
+ if (!data) return;
+
+ await dispatch(
+ turnToBlockThunk({
+ id,
+ controller,
+ type: BlockType.TodoListBlock,
+ data,
+ })
+ );
+ }
+);
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/turn_to.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/turn_to.ts
new file mode 100644
index 0000000000..7120d767cc
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/turn_to.ts
@@ -0,0 +1,46 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { DocumentController } from '$app/stores/effects/document/document_controller';
+import { BlockData, BlockType, DocumentState, NestedBlock } from '$app/interfaces/document';
+import { setCursorBeforeThunk } from '$app_reducers/document/async-actions/cursor';
+import { allowedChildrenBlockTypes } from '$app/constants/document/config';
+import { newBlock } from '$app/utils/document/blocks/common';
+
+export const turnToBlockThunk = createAsyncThunk(
+ 'document/turnToBlock',
+ async (payload: { id: string; controller: DocumentController; type: BlockType; data: BlockData }, thunkAPI) => {
+ const { id, controller, type, data } = payload;
+ const { dispatch, getState } = thunkAPI;
+ const state = (getState() as { document: DocumentState }).document;
+
+ const node = state.nodes[id];
+ if (!node.parent) return;
+
+ const parent = state.nodes[node.parent];
+ const children = state.children[node.children].map((id) => state.nodes[id]);
+
+ /**
+ * transform to block
+ * 1. insert block after current block
+ * 2. move all children
+ * 3. delete current block
+ */
+
+ const block = newBlock(type, parent.id, data);
+ // insert new block after current block
+ const insertHeadingAction = controller.getInsertAction(block, node.id);
+
+ // if new block is not allowed to have children, move children to parent
+ const newParent = allowedChildrenBlockTypes.includes(block.type) ? block : parent;
+ // if move children to parent, set prev to current block, otherwise the prev is empty
+ const newPrev = newParent.id === parent.id ? block.id : '';
+ const moveChildrenActions = controller.getMoveChildrenAction(children, newParent.id, newPrev);
+
+ // delete current block
+ const deleteAction = controller.getDeleteAction(node);
+
+ // submit actions
+ await controller.applyActions([insertHeadingAction, ...moveChildrenActions, deleteAction]);
+ // set cursor in new block
+ await dispatch(setCursorBeforeThunk({ id: block.id }));
+ }
+);
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts
index c3af7c1c37..3765525cb6 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts
@@ -110,3 +110,17 @@ export function newBlock(type: BlockType, parentId: string, data: BlockDat
data,
};
}
+
+export function getDefaultBlockData(type: BlockType) {
+ switch (type) {
+ case BlockType.TodoListBlock:
+ return {
+ checked: false,
+ delta: [],
+ };
+ default:
+ return {
+ delta: [],
+ };
+ }
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/heading.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/heading.ts
index 948e55f515..6677a1bb64 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/heading.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/heading.ts
@@ -7,6 +7,10 @@ export function newHeadingBlock(parentId: string, data: HeadingBlockData): Neste
return newBlock(BlockType.HeadingBlock, parentId, data);
}
+/**
+ * get heading data from editor, only support markdown
+ * @param editor
+ */
export function getHeadingDataFromEditor(editor: Editor): HeadingBlockData | undefined {
const selection = editor.selection;
if (!selection) return;
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/todo_list.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/todo_list.ts
new file mode 100644
index 0000000000..3d48f8529e
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/todo_list.ts
@@ -0,0 +1,21 @@
+import { Editor } from 'slate';
+import { TodoListBlockData } from '$app/interfaces/document';
+import { getAfterRangeAt, getBeforeRangeAt } from '$app/utils/document/slate/text';
+import { getDeltaFromSlateNodes } from '$app/utils/document/blocks/common';
+
+/**
+ * get todo_list data from editor, only support markdown
+ * @param editor
+ */
+export function getTodoListDataFromEditor(editor: Editor): TodoListBlockData | undefined {
+ const selection = editor.selection;
+ 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);
+ return {
+ delta,
+ checked: !!checked,
+ };
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/markdown.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/markdown.ts
index 721699ab5a..8cca17aca0 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/markdown.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/markdown.ts
@@ -3,16 +3,28 @@ import { getBeforeRangeAt } from '$app/utils/document/slate/text';
import { Editor } from 'slate';
export function canHandleToHeadingBlock(event: React.KeyboardEvent, editor: Editor): boolean {
+ const flag = getMarkdownFlag(event, editor);
+ if (!flag) return false;
+ const isHeadingMarkdown = /^(#{1,3})$/.test(flag);
+
+ return isHeadingMarkdown;
+}
+
+export function canHandleToCheckboxBlock(event: React.KeyboardEvent, editor: Editor) {
+ const flag = getMarkdownFlag(event, editor);
+ if (!flag) return false;
+
+ const isCheckboxMarkdown = /^((-)?\[(x|\s)?\])$/.test(flag);
+ return isCheckboxMarkdown;
+}
+
+function getMarkdownFlag(event: React.KeyboardEvent, editor: Editor) {
const isSpaceKey = event.key === keyBoardEventKeyMap.Space;
const selection = editor.selection;
if (!isSpaceKey || !selection) {
- return false;
+ return null;
}
- const beforeSpaceContent = Editor.string(editor, getBeforeRangeAt(editor, selection));
-
- const isHeadingMarkdown = /^(#{1,3})$/.test(beforeSpaceContent.trim());
-
- return isHeadingMarkdown;
+ return Editor.string(editor, getBeforeRangeAt(editor, selection)).trim();
}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts
index 4b18191292..6bba20b6fe 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { DocumentData } from '../interfaces/document';
import { DocumentController } from '$app/stores/effects/document/document_controller';
@@ -14,9 +14,9 @@ export const useDocument = () => {
const [controller, setController] = useState(null);
const dispatch = useAppDispatch();
- const onDocumentChange = (props: { isRemote: boolean; data: BlockEventPayloadPB }) => {
+ const onDocumentChange = useCallback((props: { isRemote: boolean; data: BlockEventPayloadPB }) => {
dispatch(documentActions.onDataChange(props));
- };
+ }, []);
useEffect(() => {
let documentController: DocumentController | null = null;
@@ -34,14 +34,17 @@ export const useDocument = () => {
Log.error(e);
}
})();
- return () => {
- void (async () => {
- if (documentController) {
- await documentController.dispose();
- }
- Log.debug('close document', params.id);
- })();
+
+ const closeDocument = () => {
+ if (documentController) {
+ void documentController.dispose();
+ }
+ Log.debug('close document', params.id);
};
+ // dispose controller before unload
+ window.addEventListener('beforeunload', closeDocument);
+ return closeDocument;
}, [params.id]);
+
return { documentId, documentData, controller };
};
diff --git a/frontend/appflowy_tauri/tailwind.config.cjs b/frontend/appflowy_tauri/tailwind.config.cjs
index b2ba17c424..cc80504561 100644
--- a/frontend/appflowy_tauri/tailwind.config.cjs
+++ b/frontend/appflowy_tauri/tailwind.config.cjs
@@ -38,6 +38,7 @@ module.exports = {
4: '#BDBDBD',
5: '#E0E0E0',
6: '#F2F2F2',
+ 7: '#FFFFFF',
},
surface: {
1: '#F7F8FC',
diff --git a/frontend/rust-lib/flowy-document2/src/manager.rs b/frontend/rust-lib/flowy-document2/src/manager.rs
index 461e68e502..0b8fdc9df7 100644
--- a/frontend/rust-lib/flowy-document2/src/manager.rs
+++ b/frontend/rust-lib/flowy-document2/src/manager.rs
@@ -57,6 +57,7 @@ impl DocumentManager {
document
.lock()
.open(move |events, is_remote| {
+ tracing::debug!("data_change: {:?}, from remote: {}", &events, is_remote);
send_notification(&clone_doc_id, DocumentNotification::DidReceiveUpdate)
.payload::((events, is_remote).into())
.send();