diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/FormatButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatButton.tsx
similarity index 87%
rename from frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/FormatButton.tsx
rename to frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatButton.tsx
index 903603480e..174af2e1ef 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/FormatButton.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatButton.tsx
@@ -1,8 +1,8 @@
-import { toggleFormat, isFormatActive } from '$app/utils/slate/format';
+import { toggleFormat, isFormatActive } from '$app/utils/document/slate/format';
 import IconButton from '@mui/material/IconButton';
 import Tooltip from '@mui/material/Tooltip';
 
-import { command } from '$app/constants/toolbar';
+import { command } from '$app/constants/document/toolbar';
 import FormatIcon from './FormatIcon';
 import { BaseEditor } from 'slate';
 
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/FormatIcon.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatIcon.tsx
similarity index 91%
rename from frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/FormatIcon.tsx
rename to frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatIcon.tsx
index 371ec6585c..39aeafebac 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/FormatIcon.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/FormatIcon.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import { FormatBold, FormatUnderlined, FormatItalic, CodeOutlined, StrikethroughSOutlined } from '@mui/icons-material';
-import { iconSize } from '$app/constants/toolbar';
+import { iconSize } from '$app/constants/document/toolbar';
 
 export default function FormatIcon({ icon }: { icon: string }) {
   switch (icon) {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/index.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.hooks.ts
similarity index 91%
rename from frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/index.hooks.ts
rename to frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.hooks.ts
index 0d3764522a..3347d398f6 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/index.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.hooks.ts
@@ -1,6 +1,6 @@
 import { useEffect, useRef } from 'react';
 import { useFocused, useSlate } from 'slate-react';
-import { calcToolbarPosition } from '$app/utils/slate/toolbar';
+import { calcToolbarPosition } from '$app/utils/document/slate/toolbar';
 export function useHoveringToolbar(id: string) {
   const editor = useSlate();
   const inFocus = useFocused();
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.tsx
similarity index 85%
rename from frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/index.tsx
rename to frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.tsx
index c64e387d70..1ad8dc025e 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/HoveringToolbar/index.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockHorizontalToolbar/index.tsx
@@ -1,8 +1,8 @@
 import FormatButton from './FormatButton';
-import Portal from '../../BlockPortal';
+import Portal from '../BlockPortal';
 import { useHoveringToolbar } from './index.hooks';
 
-const HoveringToolbar = ({ id }: { id: string }) => {
+const BlockHorizontalToolbar = ({ id }: { id: string }) => {
   const { inFocus, ref, editor } = useHoveringToolbar(id);
   if (!inFocus) return null;
 
@@ -27,4 +27,4 @@ const HoveringToolbar = ({ id }: { id: string }) => {
   );
 };
 
-export default HoveringToolbar;
+export default BlockHorizontalToolbar;
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockMenu/MenuItem.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockMenu/MenuItem.hooks.ts
index 359e767da7..f944c07f91 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockMenu/MenuItem.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockMenu/MenuItem.hooks.ts
@@ -1,7 +1,7 @@
 import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
 import { useAppDispatch } from '@/appflowy_app/stores/store';
 import { useCallback, useContext } from 'react';
-import { insertAfterNodeThunk, deleteNodeThunk } from '@/appflowy_app/stores/reducers/document/async_actions';
+import { insertAfterNodeThunk, deleteNodeThunk } from '$app/stores/reducers/document/async-actions';
 
 export enum ActionType {
   InsertAfter = 'insertAfter',
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideTools/BlockSideTools.hooks.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockSideToolbar.hooks.tsx
similarity index 97%
rename from frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideTools/BlockSideTools.hooks.tsx
rename to frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockSideToolbar.hooks.tsx
index 1187b53180..e4c3b68089 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideTools/BlockSideTools.hooks.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/BlockSideToolbar.hooks.tsx
@@ -3,7 +3,7 @@ import { useAppSelector } from '@/appflowy_app/stores/store';
 import { debounce } from '@/appflowy_app/utils/tool';
 import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
 
-export function useBlockSideTools({ container }: { container: HTMLDivElement }) {
+export function useBlockSideToolbar({ container }: { container: HTMLDivElement }) {
   const [nodeId, setHoverNodeId] = useState<string>('');
   const [menuOpen, setMenuOpen] = useState(false);
   const ref = useRef<HTMLDivElement | null>(null);
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideTools/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx
similarity index 88%
rename from frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideTools/index.tsx
rename to frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx
index d3e361f43f..e319e484fd 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideTools/index.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/BlockSideToolbar/index.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { useBlockSideTools } from './BlockSideTools.hooks';
+import { useBlockSideToolbar } from './BlockSideToolbar.hooks';
 import ExpandCircleDownSharpIcon from '@mui/icons-material/ExpandCircleDownSharp';
 import DragIndicatorRoundedIcon from '@mui/icons-material/DragIndicatorRounded';
 import Portal from '../BlockPortal';
@@ -8,8 +8,8 @@ import BlockMenu from '../BlockMenu';
 
 const sx = { height: 24, width: 24 };
 
-export default function BlockSideTools(props: { container: HTMLDivElement }) {
-  const { nodeId, ref, menuOpen, handleToggleMenu } = useBlockSideTools(props);
+export default function BlockSideToolbar(props: { container: HTMLDivElement }) {
+  const { nodeId, ref, menuOpen, handleToggleMenu } = useBlockSideToolbar(props);
 
   if (!nodeId) return null;
   return (
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/HeadingBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/HeadingBlock/index.tsx
index 252b72b7b9..30af6c677d 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/HeadingBlock/index.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/HeadingBlock/index.tsx
@@ -1,22 +1,16 @@
 import TextBlock from '../TextBlock';
-import { HeadingBlockData, Node } from '@/appflowy_app/interfaces/document';
+import { BlockType, NestedBlock } from '@/appflowy_app/interfaces/document';
 
 const fontSize: Record<string, string> = {
-  1: 'mt-8 text-3xl',
-  2: 'mt-6 text-2xl',
-  3: 'mt-4 text-xl',
+  1: 'mt-5 text-3xl',
+  2: 'mt-4 text-2xl',
+  3: 'text-xl',
 };
 
-export default function HeadingBlock({
-  node,
-}: {
-  node: Node & {
-    data: HeadingBlockData;
-  };
-}) {
+export default function HeadingBlock({ node }: { node: NestedBlock<BlockType.HeadingBlock> }) {
   return (
     <div className={`${fontSize[node.data.level]} font-semibold	`}>
-      {/*<TextBlock node={node} childIds={[]} delta={delta} />*/}
+      <TextBlock node={node} childIds={[]} />
     </div>
   );
 }
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx
index 8338fd8087..cf13e853c3 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx
@@ -4,18 +4,22 @@ import { withErrorBoundary } from 'react-error-boundary';
 import { ErrorBoundaryFallbackComponent } from '../_shared/ErrorBoundaryFallbackComponent';
 import TextBlock from '../TextBlock';
 import { NodeContext } from '../_shared/SubscribeNode.hooks';
-import { Node } from '$app/interfaces/document';
+import { BlockType } from '$app/interfaces/document';
+import HeadingBlock from '$app/components/document/HeadingBlock';
 
 function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<HTMLDivElement>) {
   const { node, childIds, isSelected, ref } = useNode(id);
 
   const renderBlock = useCallback(() => {
     switch (node.type) {
-      case 'text': {
+      case BlockType.TextBlock: {
         return <TextBlock node={node} childIds={childIds} />;
       }
+      case BlockType.HeadingBlock: {
+        return <HeadingBlock node={node} />;
+      }
       default:
-        break;
+        return null;
     }
   }, [node, childIds]);
 
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/Overlay/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/Overlay/index.tsx
index 62d15de804..304c09027b 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/Overlay/index.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/Overlay/index.tsx
@@ -1,12 +1,12 @@
 import React, { useState } from 'react';
-import BlockSideTools from '../BlockSideTools';
+import BlockSideToolbar from '../BlockSideToolbar';
 import BlockSelection from '../BlockSelection';
 
 export default function Overlay({ container }: { container: HTMLDivElement }) {
   const [isDragging, setDragging] = useState(false);
   return (
     <>
-      {isDragging ? null : <BlockSideTools container={container} />}
+      {isDragging ? null : <BlockSideToolbar container={container} />}
       <BlockSelection onDragging={setDragging} container={container} />
     </>
   );
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx
index 4ce3884400..8069b0e1a4 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx
@@ -18,9 +18,17 @@ function Root({ documentData }: { documentData: DocumentData }) {
     return <Skeleton />;
   }
 
-
   return (
-    <div id='appflowy-block-doc' className='h-[100%] overflow-hidden'>
+    <div
+      id='appflowy-block-doc'
+      className='h-[100%] overflow-hidden'
+      onKeyDown={(e) => {
+        // prevent backspace from going back
+        if (e.key === 'Backspace') {
+          e.stopPropagation();
+        }
+      }}
+    >
       <VirtualizedList node={node} childIds={childIds} renderNode={renderNode} />
     </div>
   );
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/TextBlock.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/TextBlock.hooks.ts
index 63cbc58ce0..9fe898069e 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/TextBlock.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/TextBlock.hooks.ts
@@ -1,6 +1,6 @@
 import { useCallback, useContext, useMemo } from 'react';
 import { Editor } from 'slate';
-import { TextDelta, TextSelection } from '$app/interfaces/document';
+import { TextBlockKeyEventHandlerParams, TextDelta, TextSelection } from '$app/interfaces/document';
 import { useTextInput } from '../_shared/TextInput.hooks';
 import { useAppDispatch } from '@/appflowy_app/stores/store';
 import { DocumentControllerContext } from '@/appflowy_app/stores/effects/document/document_controller';
@@ -8,22 +8,24 @@ import {
   backspaceNodeThunk,
   indentNodeThunk,
   splitNodeThunk,
-} from '@/appflowy_app/stores/reducers/document/async_actions';
+  setCursorNextLineThunk,
+  setCursorPreLineThunk,
+} from '@/appflowy_app/stores/reducers/document/async-actions';
 import { documentActions } from '@/appflowy_app/stores/reducers/document/slice';
 import {
-  triggerHotkey,
-  canHandleEnterKey,
   canHandleBackspaceKey,
-  canHandleTabKey,
-  onHandleEnterKey,
-  keyBoardEventKeyMap,
-  canHandleUpKey,
   canHandleDownKey,
+  canHandleEnterKey,
   canHandleLeftKey,
   canHandleRightKey,
-} from '@/appflowy_app/utils/slate/hotkey';
-import { updateNodeDeltaThunk } from '$app/stores/reducers/document/async_actions/update';
-import { setCursorPreLineThunk, setCursorNextLineThunk } from '$app/stores/reducers/document/async_actions/set_cursor';
+  canHandleTabKey,
+  canHandleUpKey,
+  onHandleEnterKey,
+  triggerHotkey,
+} from '$app/utils/document/slate/hotkey';
+import { updateNodeDeltaThunk } from '$app_reducers/document/async-actions/blocks/text/update';
+import { useMarkDown } from './useMarkDown.hooks';
+import { keyBoardEventKeyMap } from '$app/constants/document/text_block';
 
 export function useTextBlock(id: string) {
   const { editor, onChange, value } = useTextInput(id);
@@ -54,25 +56,15 @@ export function useTextBlock(id: string) {
   };
 }
 
-type TextBlockKeyEventHandlerParams = [React.KeyboardEvent<HTMLDivElement>, Editor];
-
 function useTextBlockKeyEvent(id: string, editor: Editor) {
   const { indentAction, backSpaceAction, splitAction, wrapAction, focusPreLineAction, focusNextLineAction } =
     useActions(id);
 
-  const dispatch = useAppDispatch();
-  const keepSelection = useCallback(() => {
-    // This is a hack to make sure the selection is updated after next render
-    // It will save the selection to the store, and the selection will be restored
-    if (!editor.selection || !editor.selection.anchor || !editor.selection.focus) return;
-    const { anchor, focus } = editor.selection;
-    const selection = { anchor, focus } as TextSelection;
-    dispatch(documentActions.setTextSelection({ blockId: id, selection }));
-  }, [editor]);
+  const { markdownEvents } = useMarkDown(id);
 
   const enterEvent = useMemo(() => {
     return {
-      key: keyBoardEventKeyMap.Enter,
+      triggerEventKey: keyBoardEventKeyMap.Enter,
       canHandle: canHandleEnterKey,
       handler: (...args: TextBlockKeyEventHandlerParams) => {
         onHandleEnterKey(...args, {
@@ -85,29 +77,27 @@ function useTextBlockKeyEvent(id: string, editor: Editor) {
 
   const tabEvent = useMemo(() => {
     return {
-      key: keyBoardEventKeyMap.Tab,
+      triggerEventKey: keyBoardEventKeyMap.Tab,
       canHandle: canHandleTabKey,
       handler: (..._args: TextBlockKeyEventHandlerParams) => {
-        keepSelection();
         void indentAction();
       },
     };
-  }, [keepSelection, indentAction]);
+  }, [indentAction]);
 
   const backSpaceEvent = useMemo(() => {
     return {
-      key: keyBoardEventKeyMap.Backspace,
+      triggerEventKey: keyBoardEventKeyMap.Backspace,
       canHandle: canHandleBackspaceKey,
       handler: (..._args: TextBlockKeyEventHandlerParams) => {
-        keepSelection();
         void backSpaceAction();
       },
     };
-  }, [keepSelection, backSpaceAction]);
+  }, [backSpaceAction]);
 
   const upEvent = useMemo(() => {
     return {
-      key: keyBoardEventKeyMap.Up,
+      triggerEventKey: keyBoardEventKeyMap.Up,
       canHandle: canHandleUpKey,
       handler: (...args: TextBlockKeyEventHandlerParams) => {
         void focusPreLineAction({
@@ -119,7 +109,7 @@ function useTextBlockKeyEvent(id: string, editor: Editor) {
 
   const downEvent = useMemo(() => {
     return {
-      key: keyBoardEventKeyMap.Down,
+      triggerEventKey: keyBoardEventKeyMap.Down,
       canHandle: canHandleDownKey,
       handler: (...args: TextBlockKeyEventHandlerParams) => {
         void focusNextLineAction({
@@ -131,7 +121,7 @@ function useTextBlockKeyEvent(id: string, editor: Editor) {
 
   const leftEvent = useMemo(() => {
     return {
-      key: keyBoardEventKeyMap.Left,
+      triggerEventKey: keyBoardEventKeyMap.Left,
       canHandle: canHandleLeftKey,
       handler: (...args: TextBlockKeyEventHandlerParams) => {
         void focusPreLineAction({
@@ -144,7 +134,7 @@ function useTextBlockKeyEvent(id: string, editor: Editor) {
 
   const rightEvent = useMemo(() => {
     return {
-      key: keyBoardEventKeyMap.Right,
+      triggerEventKey: keyBoardEventKeyMap.Right,
       canHandle: canHandleRightKey,
       handler: (...args: TextBlockKeyEventHandlerParams) => {
         void focusNextLineAction({
@@ -159,6 +149,8 @@ function useTextBlockKeyEvent(id: string, editor: Editor) {
     (event: React.KeyboardEvent<HTMLDivElement>) => {
       // This is list of key events that can be handled by TextBlock
       const keyEvents = [enterEvent, backSpaceEvent, tabEvent, upEvent, downEvent, leftEvent, rightEvent];
+
+      keyEvents.push(...markdownEvents);
       const matchKey = keyEvents.find((keyEvent) => keyEvent.canHandle(event, editor));
       if (!matchKey) {
         triggerHotkey(event, editor);
@@ -169,7 +161,7 @@ function useTextBlockKeyEvent(id: string, editor: Editor) {
       event.preventDefault();
       matchKey.handler(event, editor);
     },
-    [editor, enterEvent, backSpaceEvent, tabEvent, upEvent, downEvent, leftEvent, rightEvent]
+    [editor, enterEvent, backSpaceEvent, tabEvent, upEvent, downEvent, leftEvent, rightEvent, markdownEvents]
   );
 
   return {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx
index 03a2cc1880..d88b73577d 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx
@@ -2,9 +2,9 @@ import { Slate, Editable } from 'slate-react';
 import Leaf from './Leaf';
 import { useTextBlock } from './TextBlock.hooks';
 import NodeComponent from '../Node';
-import HoveringToolbar from '../_shared/HoveringToolbar';
-import React, { useEffect } from 'react';
-import { Node } from '$app/interfaces/document';
+import BlockHorizontalToolbar from '../BlockHorizontalToolbar';
+import React from 'react';
+import { BlockType, NestedBlock } from '$app/interfaces/document';
 
 function TextBlock({
   node,
@@ -12,7 +12,7 @@ function TextBlock({
   placeholder,
   ...props
 }: {
-  node: Node;
+  node: NestedBlock;
   childIds?: string[];
   placeholder?: string;
 } & React.HTMLAttributes<HTMLDivElement>) {
@@ -21,7 +21,7 @@ function TextBlock({
     <>
       <div {...props} className={`py-[2px] ${props.className}`}>
         <Slate editor={editor} onChange={onChange} value={value}>
-          <HoveringToolbar id={node.id} />
+          <BlockHorizontalToolbar id={node.id} />
           <Editable
             onKeyDownCapture={onKeyDownCapture}
             onDOMBeforeInput={onDOMBeforeInput}
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
new file mode 100644
index 0000000000..c90875c70f
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/useMarkDown.hooks.ts
@@ -0,0 +1,41 @@
+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 { useAppDispatch } from '$app/stores/store';
+import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
+import { turnToHeadingBlockThunk } from '$app_reducers/document/async-actions/blocks/heading';
+
+export function useMarkDown(id: string) {
+  const { toHeadingBlockAction } = useActions(id);
+  const toHeadingBlockEvent = useMemo(() => {
+    return {
+      triggerEventKey: keyBoardEventKeyMap.Space,
+      canHandle: canHandleToHeadingBlock,
+      handler: toHeadingBlockAction,
+    };
+  }, [toHeadingBlockAction]);
+
+  const markdownEvents = useMemo(() => [toHeadingBlockEvent], [toHeadingBlockEvent]);
+
+  return {
+    markdownEvents,
+  };
+}
+
+function useActions(id: string) {
+  const controller = useContext(DocumentControllerContext);
+  const dispatch = useAppDispatch();
+  const toHeadingBlockAction = useCallback(
+    (...args: TextBlockKeyEventHandlerParams) => {
+      if (!controller) return;
+      const [_event, editor] = args;
+      dispatch(turnToHeadingBlockThunk({ id, editor, controller }));
+    },
+    [controller, dispatch, id]
+  );
+
+  return {
+    toHeadingBlockAction,
+  };
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextInput.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextInput.hooks.ts
index fba074b561..3c45bbeba5 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextInput.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextInput.hooks.ts
@@ -1,17 +1,16 @@
+import { createEditor, Descendant, Transforms } from 'slate';
+import { withReact, ReactEditor } from 'slate-react';
+import * as Y from 'yjs';
+import { withYjs, YjsEditor, slateNodesToInsertDelta } from '@slate-yjs/core';
 import { useCallback, useContext, useMemo, useRef, useEffect, useState } from 'react';
+
 import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
 import { TextDelta, TextSelection } from '$app/interfaces/document';
 import { NodeContext } from './SubscribeNode.hooks';
 import { useAppDispatch, useAppSelector } from '@/appflowy_app/stores/store';
-
-import { createEditor, Descendant, Transforms } from 'slate';
-import { withReact, ReactEditor } from 'slate-react';
-
-import * as Y from 'yjs';
-import { withYjs, YjsEditor, slateNodesToInsertDelta } from '@slate-yjs/core';
-import { updateNodeDeltaThunk } from '@/appflowy_app/stores/reducers/document/async_actions/update';
+import { updateNodeDeltaThunk } from '$app_reducers/document/async-actions/blocks/text/update';
+import { deltaToSlateValue, getDeltaFromSlateNodes } from '$app/utils/document/blocks/common';
 import { documentActions } from '@/appflowy_app/stores/reducers/document/slice';
-import { deltaToSlateValue, getDeltaFromSlateNodes } from '@/appflowy_app/utils/block';
 
 export function useTextInput(id: string) {
   const dispatch = useAppDispatch();
@@ -34,16 +33,35 @@ export function useTextInput(id: string) {
 
   const [value, setValue] = useState<Descendant[]>([]);
 
-  const onChange = useCallback((e: Descendant[]) => {
-    setValue(e);
-  }, []);
+  const storeSelection = useCallback(() => {
+    // This is a hack to make sure the selection is updated after next render
+    // It will save the selection to the store, and the selection will be restored
+    if (!ReactEditor.isFocused(editor) || !editor.selection || !editor.selection.anchor || !editor.selection.focus)
+      return;
+    const { anchor, focus } = editor.selection;
+    const selection = { anchor, focus } as TextSelection;
+    dispatch(documentActions.setTextSelection({ blockId: id, selection }));
+  }, [editor]);
 
   const currentSelection = useAppSelector((state) => state.document.textSelections[id]);
-
-  useEffect(() => {
+  const restoreSelection = useCallback(() => {
+    if (editor.selection && JSON.stringify(currentSelection) === JSON.stringify(editor.selection)) return;
     setSelection(editor, currentSelection);
   }, [editor, currentSelection]);
 
+  const onChange = useCallback(
+    (e: Descendant[]) => {
+      setValue(e);
+      storeSelection();
+    },
+
+    [storeSelection]
+  );
+
+  useEffect(() => {
+    restoreSelection();
+  }, [restoreSelection]);
+
   if (editor.selection && ReactEditor.isFocused(editor)) {
     const domSelection = window.getSelection();
     // this is a hack to fix the issue where the selection is not in the dom
@@ -98,7 +116,6 @@ function useBindYjs(id: string, delta: TextDelta[]) {
     };
 
     yText.observe(textEventHandler);
-
     return () => {
       yText.unobserve(textEventHandler);
     };
@@ -148,8 +165,10 @@ function useController(id: string) {
 function setSelection(editor: ReactEditor, currentSelection: TextSelection) {
   // If the current selection is empty, blur the editor and deselect the selection
   if (!currentSelection || !currentSelection.anchor || !currentSelection.focus) {
-    ReactEditor.blur(editor);
-    ReactEditor.deselect(editor);
+    if (ReactEditor.isFocused(editor)) {
+      ReactEditor.blur(editor);
+      ReactEditor.deselect(editor);
+    }
     return;
   }
 
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx
index fc47388757..309c26f8db 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx
@@ -105,14 +105,20 @@ export const NavigationPanel = ({
         <div className={'flex flex-col'}>
           <AppLogo iconToShow={'hide'} onHideMenuClick={onHideMenuClick}></AppLogo>
           <WorkspaceUser></WorkspaceUser>
-          <div className={'relative flex flex-col'} style={{ height: 'calc(100vh - 300px)' }}>
-            <div className={'flex flex-col overflow-auto px-2'} ref={el}>
+          <div className={'relative flex flex-1 flex-col'}>
+            <div
+              className={'flex flex-col overflow-auto px-2'}
+              style={{
+                maxHeight: 'calc(100vh - 350px)',
+              }}
+              ref={el}
+            >
               <WorkspaceApps folders={folders} pages={pages} onPageClick={onPageClick} />
             </div>
           </div>
         </div>
 
-        <div className={'flex flex-col'}>
+        <div className={'flex max-h-[215px] flex-col'}>
           <div className={'border-b border-shade-6 px-2 pb-4'}>
             {/*<PluginsButton></PluginsButton>*/}
 
diff --git a/frontend/appflowy_tauri/src/appflowy_app/constants/block.ts b/frontend/appflowy_tauri/src/appflowy_app/constants/document/block.ts
similarity index 100%
rename from frontend/appflowy_tauri/src/appflowy_app/constants/block.ts
rename to frontend/appflowy_tauri/src/appflowy_app/constants/document/block.ts
diff --git a/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts b/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts
new file mode 100644
index 0000000000..071e6492a5
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/constants/document/config.ts
@@ -0,0 +1,6 @@
+import { BlockType } from '$app/interfaces/document';
+
+/**
+ * Block types that are allowed to have children
+ */
+export const allowedChildrenBlockTypes = [BlockType.TextBlock, BlockType.PageBlock];
diff --git a/frontend/appflowy_tauri/src/appflowy_app/constants/document/text_block.ts b/frontend/appflowy_tauri/src/appflowy_app/constants/document/text_block.ts
new file mode 100644
index 0000000000..fbe973b69c
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/constants/document/text_block.ts
@@ -0,0 +1,10 @@
+export const keyBoardEventKeyMap = {
+  Enter: 'Enter',
+  Backspace: 'Backspace',
+  Tab: 'Tab',
+  Up: 'ArrowUp',
+  Down: 'ArrowDown',
+  Left: 'ArrowLeft',
+  Right: 'ArrowRight',
+  Space: ' ',
+};
diff --git a/frontend/appflowy_tauri/src/appflowy_app/constants/toolbar.ts b/frontend/appflowy_tauri/src/appflowy_app/constants/document/toolbar.ts
similarity index 100%
rename from frontend/appflowy_tauri/src/appflowy_app/constants/toolbar.ts
rename to frontend/appflowy_tauri/src/appflowy_app/constants/document/toolbar.ts
diff --git a/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts b/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts
index 6a81dbb022..077ec411e2 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts
@@ -1,3 +1,5 @@
+import { Editor } from 'slate';
+
 export enum BlockType {
   PageBlock = 'page',
   HeadingBlock = 'heading',
@@ -12,9 +14,8 @@ export enum BlockType {
   ColumnBlock = 'column',
 }
 
-export interface HeadingBlockData {
+export interface HeadingBlockData extends TextBlockData {
   level: number;
-  delta: TextDelta[];
 }
 
 export interface TextBlockData {
@@ -23,12 +24,16 @@ export interface TextBlockData {
 
 export type PageBlockData = TextBlockData;
 
-export type BlockData = TextBlockData | HeadingBlockData | PageBlockData;
+export type BlockData<Type> = Type extends BlockType.HeadingBlock
+  ? HeadingBlockData
+  : Type extends BlockType.PageBlock
+  ? PageBlockData
+  : TextBlockData;
 
-export interface NestedBlock {
+export interface NestedBlock<Type = any> {
   id: string;
   type: BlockType;
-  data: BlockData | Record<string, any>;
+  data: BlockData<Type>;
   parent: string | null;
   children: string;
 }
@@ -98,3 +103,5 @@ export interface BlockPBValue {
   children: string;
   data: string;
 }
+
+export type TextBlockKeyEventHandlerParams = [React.KeyboardEvent<HTMLDivElement>, Editor];
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_controller.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_controller.ts
index 23afdaa555..f55e26f871 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_controller.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_controller.ts
@@ -12,9 +12,9 @@ import {
 } from '@/services/backend';
 import { DocumentObserver } from './document_observer';
 import * as Y from 'yjs';
-import { blockPB2Node } from '@/appflowy_app/utils/block';
-import { BLOCK_MAP_NAME, CHILDREN_MAP_NAME, META_NAME } from '@/appflowy_app/constants/block';
+import { BLOCK_MAP_NAME, CHILDREN_MAP_NAME, META_NAME } from '$app/constants/document/block';
 import { get } from '@/appflowy_app/utils/tool';
+import { blockPB2Node } from '$app/utils/document/blocks/common';
 
 export const DocumentControllerContext = createContext<DocumentController | null>(null);
 
@@ -46,7 +46,9 @@ export class DocumentController {
     if (document.ok) {
       const nodes: DocumentData['nodes'] = {};
       get<Map<string, BlockPB>>(document.val, [BLOCK_MAP_NAME]).forEach((block) => {
-        nodes[block.id] = blockPB2Node(block);
+        Object.assign(nodes, {
+          [block.id]: blockPB2Node(block),
+        });
       });
       const children: Record<string, string[]> = {};
       get<Map<string, ChildrenPB>>(document.val, [META_NAME, CHILDREN_MAP_NAME]).forEach((child, key) => {
@@ -97,6 +99,12 @@ export class DocumentController {
     };
   };
 
+  getMoveChildrenAction = (children: Node[], parentId: string, prevId: string | null) => {
+    return children.reverse().map((child) => {
+      return this.getMoveAction(child, parentId, prevId);
+    });
+  };
+
   getDeleteAction = (node: Node) => {
     return {
       action: BlockActionTypePB.Delete,
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/activePageId/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/active-page-id/slice.ts
similarity index 100%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/activePageId/slice.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/active-page-id/slice.ts
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
new file mode 100644
index 0000000000..8fec913504
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/heading.ts
@@ -0,0 +1,42 @@
+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';
+
+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 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 }));
+  }
+);
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/backspace.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts
similarity index 86%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/backspace.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts
index b9ee8403e5..fbea40b6d3 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/backspace.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/backspace.ts
@@ -1,10 +1,11 @@
-import { BlockType, DocumentState } from '@/appflowy_app/interfaces/document';
+import { BlockType, DocumentState } from '$app/interfaces/document';
 import { DocumentController } from '$app/stores/effects/document/document_controller';
 import { createAsyncThunk } from '@reduxjs/toolkit';
-import { documentActions } from '../slice';
+import { documentActions } from '$app_reducers/document/slice';
 import { outdentNodeThunk } from './outdent';
-import { setCursorAfterThunk } from './set_cursor';
-import { getPrevLineId } from '$app/utils/block';
+import { setCursorAfterThunk } from '../../cursor';
+import { turnToTextBlockThunk } from '$app_reducers/document/async-actions/blocks/text/index';
+import { getPrevLineId } from '$app/utils/document/blocks/common';
 
 const composeNodeThunk = createAsyncThunk(
   'document/composeNode',
@@ -32,11 +33,8 @@ const composeNodeThunk = createAsyncThunk(
     const updateAction = controller.getUpdateAction(newNode);
 
     // move children
-    const children = state.children[node.children];
-    // the reverse can ensure that every child will be inserted in first place and don't need to update prevId
-    const moveActions = children.reverse().map((childId) => {
-      return controller.getMoveAction(state.nodes[childId], newNode.id, '');
-    });
+    const children = state.children[node.children].map((id) => state.nodes[id]);
+    const moveActions = controller.getMoveChildrenAction(children, newNode.id, '');
 
     // delete node
     const deleteAction = controller.getDeleteAction(node);
@@ -88,7 +86,8 @@ export const backspaceNodeThunk = createAsyncThunk(
     const nextNodeId = children[index + 1];
     // transform to text block
     if (node.type !== BlockType.TextBlock) {
-      // todo: transform to text block
+      await dispatch(turnToTextBlockThunk({ id, controller }));
+      return;
     }
     // compose to previous line when it has next sibling or no ancestor
     if (nextNodeId || !ancestorId) {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/delete.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/delete.ts
similarity index 93%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/delete.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/delete.ts
index 7bcf90f25d..a81401eadf 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/delete.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/delete.ts
@@ -7,7 +7,7 @@ export const deleteNodeThunk = createAsyncThunk(
   'document/deleteNode',
   async (payload: { id: string; controller: DocumentController }, thunkAPI) => {
     const { id, controller } = payload;
-    const { dispatch, getState } = thunkAPI;
+    const { getState } = thunkAPI;
     const state = getState() as { document: DocumentState };
     const node = state.document.nodes[id];
     if (!node) return;
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/indent.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/indent.ts
similarity index 84%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/indent.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/indent.ts
index 9a03823d7c..b7be58b237 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/indent.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/indent.ts
@@ -1,6 +1,7 @@
-import { BlockType, DocumentState } from '@/appflowy_app/interfaces/document';
+import { BlockType, DocumentState } from '$app/interfaces/document';
 import { DocumentController } from '$app/stores/effects/document/document_controller';
 import { createAsyncThunk } from '@reduxjs/toolkit';
+import { allowedChildrenBlockTypes } from '$app/constants/document/config';
 
 export const indentNodeThunk = createAsyncThunk(
   'document/indentNode',
@@ -19,7 +20,7 @@ export const indentNodeThunk = createAsyncThunk(
     const newParentId = children[index - 1];
     const prevNode = state.nodes[newParentId];
     // check if prev node is allowed to have children
-    if (prevNode.type !== BlockType.TextBlock) return;
+    if (!allowedChildrenBlockTypes.includes(prevNode.type)) return;
     // check if prev node has children and get last child for new prev node
     const prevNodeChildren = state.children[prevNode.children];
     const newPrevId = prevNodeChildren[prevNodeChildren.length - 1];
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
new file mode 100644
index 0000000000..fb38ecf363
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/index.ts
@@ -0,0 +1,39 @@
+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';
+
+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, {
+      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 }));
+  }
+);
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/insert.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/insert.ts
similarity index 66%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/insert.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/insert.ts
index e8d57ae501..398046b467 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/insert.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/insert.ts
@@ -1,7 +1,7 @@
-import { BlockType, DocumentState, NestedBlock } from '@/appflowy_app/interfaces/document';
+import { DocumentState } from '$app/interfaces/document';
 import { DocumentController } from '$app/stores/effects/document/document_controller';
 import { createAsyncThunk } from '@reduxjs/toolkit';
-import { generateId } from '@/appflowy_app/utils/block';
+import { newTextBlock } from '$app/utils/document/blocks/text';
 
 export const insertAfterNodeThunk = createAsyncThunk(
   'document/insertAfterNode',
@@ -14,15 +14,9 @@ export const insertAfterNodeThunk = createAsyncThunk(
     const parentId = node.parent;
     if (!parentId) return;
     // create new node
-    const newNode: NestedBlock = {
-      id: generateId(),
-      parent: parentId,
-      type: BlockType.TextBlock,
-      data: {
-        delta: [],
-      },
-      children: generateId(),
-    };
+    const newNode = newTextBlock(parentId, {
+      delta: [],
+    });
     await controller.applyActions([controller.getInsertAction(newNode, node.id)]);
   }
 );
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/outdent.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/outdent.ts
similarity index 100%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/outdent.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/outdent.ts
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/split.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts
similarity index 68%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/split.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts
index e78ad4f5ca..b500b874ae 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/split.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/split.ts
@@ -1,9 +1,9 @@
-import { BlockType, DocumentState, TextDelta } from '@/appflowy_app/interfaces/document';
-import { DocumentController } from '@/appflowy_app/stores/effects/document/document_controller';
+import { BlockType, DocumentState, TextDelta } from '$app/interfaces/document';
+import { DocumentController } from '$app/stores/effects/document/document_controller';
 import { createAsyncThunk } from '@reduxjs/toolkit';
-import { generateId } from '@/appflowy_app/utils/block';
-import { documentActions } from '../slice';
-import { setCursorBeforeThunk } from './set_cursor';
+import { documentActions } from '$app_reducers/document/slice';
+import { setCursorBeforeThunk } from '../../cursor';
+import { newTextBlock } from '$app/utils/document/blocks/text';
 
 export const splitNodeThunk = createAsyncThunk(
   'document/splitNode',
@@ -19,15 +19,10 @@ export const splitNodeThunk = createAsyncThunk(
     const children = state.children[node.children];
     const prevId = children.length > 0 ? null : node.id;
     const parent = children.length > 0 ? node : state.nodes[node.parent];
-    const newNode = {
-      id: generateId(),
-      parent: parent.id,
-      type: BlockType.TextBlock,
-      data: {
-        delta: insert,
-      },
-      children: generateId(),
-    };
+
+    const newNode = newTextBlock(parent.id, {
+      delta: insert,
+    });
     const retainNode = {
       ...node,
       data: {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/update.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/update.ts
similarity index 82%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/update.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/update.ts
index 99c8c4863c..6e9f0580ff 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/update.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/blocks/text/update.ts
@@ -1,7 +1,7 @@
-import { TextDelta, NestedBlock, DocumentState } from '@/appflowy_app/interfaces/document';
-import { DocumentController } from '@/appflowy_app/stores/effects/document/document_controller';
+import { TextDelta, NestedBlock, DocumentState } from '$app/interfaces/document';
+import { DocumentController } from '$app/stores/effects/document/document_controller';
 import { createAsyncThunk } from '@reduxjs/toolkit';
-import { documentActions } from '../slice';
+import { documentActions } from '$app_reducers/document/slice';
 import { debounce } from '$app/utils/tool';
 export const updateNodeDeltaThunk = createAsyncThunk(
   'document/updateNodeDelta',
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/set_cursor.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/cursor.ts
similarity index 96%
rename from frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/set_cursor.ts
rename to frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/cursor.ts
index 98e4b293d3..3cd15bc32a 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/set_cursor.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/cursor.ts
@@ -1,7 +1,6 @@
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import { documentActions } from '../slice';
 import { DocumentState, TextSelection } from '$app/interfaces/document';
-import { getNextLineId, getPrevLineId } from '$app/utils/block';
 import { Editor } from 'slate';
 import {
   getBeforeRangeAt,
@@ -10,7 +9,8 @@ import {
   getNodeBeginSelection,
   getNodeEndSelection,
   getStartLineSelectionByOffset,
-} from '$app/utils/slate/text';
+} from '$app/utils/document/slate/text';
+import { getNextLineId, getPrevLineId } from '$app/utils/document/blocks/common';
 
 export const setCursorBeforeThunk = createAsyncThunk(
   'document/setCursorBefore',
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/index.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/index.ts
new file mode 100644
index 0000000000..be0989f7a9
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async-actions/index.ts
@@ -0,0 +1,7 @@
+export * from './blocks/text/delete';
+export * from './blocks/text/indent';
+export * from './blocks/text/insert';
+export * from './blocks/text/backspace';
+export * from './blocks/text/outdent';
+export * from './blocks/text/split';
+export * from './cursor';
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/index.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/index.ts
deleted file mode 100644
index caeb5e7be6..0000000000
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/async_actions/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export * from './delete';
-export * from './indent';
-export * from './insert';
-export * from './backspace';
-export * from './outdent';
-export * from './split';
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/slice.ts
index 738177a6f9..71648bc85d 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/slice.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/document/slice.ts
@@ -2,7 +2,7 @@ import { DocumentState, Node, TextSelection } from '@/appflowy_app/interfaces/do
 import { BlockEventPayloadPB } from '@/services/backend';
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import { RegionGrid } from '@/appflowy_app/utils/region_grid';
-import { parseValue, matchChange } from '@/appflowy_app/utils/block_change';
+import { parseValue, matchChange } from '$app/utils/document/subscribe';
 
 const regionGrid = new RegionGrid(50);
 
@@ -92,6 +92,8 @@ export const documentSlice = createSlice({
     ) => {
       const { blockId, selection } = action.payload;
       const node = state.nodes[blockId];
+      const oldSelection = state.textSelections[blockId];
+      if (JSON.stringify(oldSelection) === JSON.stringify(selection)) return;
       if (!node || !selection) {
         delete state.textSelections[blockId];
       } else {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts
index 9bd9c15909..be47237dd1 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts
@@ -17,7 +17,7 @@ import { databaseSlice } from './reducers/database/slice';
 import { documentSlice } from './reducers/document/slice';
 import { boardSlice } from './reducers/board/slice';
 import { errorSlice } from './reducers/error/slice';
-import { activePageIdSlice } from './reducers/activePageId/slice';
+import { activePageIdSlice } from '$app_reducers/active-page-id/slice';
 
 const listenerMiddlewareInstance = createListenerMiddleware({
   onError: () => console.error,
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/block.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts
similarity index 86%
rename from frontend/appflowy_tauri/src/appflowy_app/utils/block.ts
rename to frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts
index 557959d974..c3af7c1c37 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/utils/block.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/common.ts
@@ -1,11 +1,8 @@
-import { BlockPB } from '@/services/backend/models/flowy-document2';
-import { nanoid } from 'nanoid';
+import { BlockData, BlockType, DocumentState, NestedBlock, TextDelta } from '$app/interfaces/document';
 import { Descendant, Element, Text } from 'slate';
-import { BlockType, DocumentState, NestedBlock, TextDelta } from '../interfaces/document';
-import { Log } from './log';
-export function generateId() {
-  return nanoid(10);
-}
+import { BlockPB } from '@/services/backend';
+import { Log } from '$app/utils/log';
+import { nanoid } from 'nanoid';
 
 export function deltaToSlateValue(delta: TextDelta[]) {
   const slateNode = {
@@ -53,6 +50,10 @@ export function blockPB2Node(block: BlockPB) {
   return node;
 }
 
+export function generateId() {
+  return nanoid(10);
+}
+
 export function getPrevLineId(state: DocumentState, id: string) {
   const node = state.nodes[id];
   if (!node.parent) return;
@@ -99,3 +100,13 @@ export function getNextNodeId(state: DocumentState, id: string) {
   const nextNodeId = children[index + 1];
   return nextNodeId;
 }
+
+export function newBlock<Type>(type: BlockType, parentId: string, data: BlockData<Type>): NestedBlock<Type> {
+  return {
+    id: generateId(),
+    type,
+    parent: parentId,
+    children: generateId(),
+    data,
+  };
+}
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
new file mode 100644
index 0000000000..948e55f515
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/heading.ts
@@ -0,0 +1,22 @@
+import { Editor } from 'slate';
+import { getAfterRangeAt, getBeforeRangeAt } from '$app/utils/document/slate/text';
+import { BlockType, HeadingBlockData, NestedBlock } from '$app/interfaces/document';
+import { getDeltaFromSlateNodes, newBlock } from '$app/utils/document/blocks/common';
+
+export function newHeadingBlock(parentId: string, data: HeadingBlockData): NestedBlock {
+  return newBlock<BlockType.HeadingBlock>(BlockType.HeadingBlock, parentId, data);
+}
+
+export function getHeadingDataFromEditor(editor: Editor): HeadingBlockData | undefined {
+  const selection = editor.selection;
+  if (!selection) return;
+  const hashTags = Editor.string(editor, getBeforeRangeAt(editor, selection));
+  const level = hashTags.match(/#/g)?.length;
+  if (!level) return;
+  const slateNodes = Editor.fragment(editor, getAfterRangeAt(editor, selection));
+  const delta = getDeltaFromSlateNodes(slateNodes);
+  return {
+    level,
+    delta,
+  };
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text.ts
new file mode 100644
index 0000000000..8cdf73e41b
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/blocks/text.ts
@@ -0,0 +1,6 @@
+import { BlockType, NestedBlock, TextBlockData } from '$app/interfaces/document';
+import { newBlock } from '$app/utils/document/blocks/common';
+
+export function newTextBlock(parentId: string, data: TextBlockData): NestedBlock {
+  return newBlock<BlockType.TextBlock>(BlockType.TextBlock, parentId, data);
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/slate/format.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/format.ts
similarity index 100%
rename from frontend/appflowy_tauri/src/appflowy_app/utils/slate/format.ts
rename to frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/format.ts
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/slate/hotkey.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/hotkey.ts
similarity index 94%
rename from frontend/appflowy_tauri/src/appflowy_app/utils/slate/hotkey.ts
rename to frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/hotkey.ts
index bf96418c0b..70f5b764f8 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/utils/slate/hotkey.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/hotkey.ts
@@ -1,8 +1,9 @@
 import isHotkey from 'is-hotkey';
 import { toggleFormat } from './format';
 import { Editor, Range } from 'slate';
-import { getBeforeRangeAt, getDelta, getAfterRangeAt, pointInEnd, pointInBegin, clonePoint } from './text';
+import { clonePoint, getAfterRangeAt, getBeforeRangeAt, getDelta, pointInBegin, pointInEnd } from './text';
 import { SelectionPoint, TextDelta, TextSelection } from '$app/interfaces/document';
+import { keyBoardEventKeyMap } from '$app/constants/document/text_block';
 
 const HOTKEYS: Record<string, string> = {
   'mod+b': 'bold',
@@ -13,16 +14,6 @@ const HOTKEYS: Record<string, string> = {
   'mod+shift+S': 'strikethrough',
 };
 
-export const keyBoardEventKeyMap = {
-  Enter: 'Enter',
-  Backspace: 'Backspace',
-  Tab: 'Tab',
-  Up: 'ArrowUp',
-  Down: 'ArrowDown',
-  Left: 'ArrowLeft',
-  Right: 'ArrowRight',
-};
-
 export function triggerHotkey(event: React.KeyboardEvent<HTMLDivElement>, editor: Editor) {
   for (const hotkey in HOTKEYS) {
     if (isHotkey(hotkey, event)) {
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
new file mode 100644
index 0000000000..721699ab5a
--- /dev/null
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/markdown.ts
@@ -0,0 +1,18 @@
+import { keyBoardEventKeyMap } from '$app/constants/document/text_block';
+import { getBeforeRangeAt } from '$app/utils/document/slate/text';
+import { Editor } from 'slate';
+
+export function canHandleToHeadingBlock(event: React.KeyboardEvent<HTMLDivElement>, editor: Editor): boolean {
+  const isSpaceKey = event.key === keyBoardEventKeyMap.Space;
+  const selection = editor.selection;
+
+  if (!isSpaceKey || !selection) {
+    return false;
+  }
+
+  const beforeSpaceContent = Editor.string(editor, getBeforeRangeAt(editor, selection));
+
+  const isHeadingMarkdown = /^(#{1,3})$/.test(beforeSpaceContent.trim());
+
+  return isHeadingMarkdown;
+}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/slate/text.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/text.ts
similarity index 100%
rename from frontend/appflowy_tauri/src/appflowy_app/utils/slate/text.ts
rename to frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/text.ts
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/slate/toolbar.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/toolbar.ts
similarity index 100%
rename from frontend/appflowy_tauri/src/appflowy_app/utils/slate/toolbar.ts
rename to frontend/appflowy_tauri/src/appflowy_app/utils/document/slate/toolbar.ts
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/block_change.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/document/subscribe.ts
similarity index 97%
rename from frontend/appflowy_tauri/src/appflowy_app/utils/block_change.ts
rename to frontend/appflowy_tauri/src/appflowy_app/utils/document/subscribe.ts
index df3e348e90..aabc8eacbe 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/utils/block_change.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/document/subscribe.ts
@@ -1,7 +1,7 @@
 import { DeltaTypePB } from '@/services/backend/models/flowy-document2';
-import { BlockType, NestedBlock, DocumentState, ChangeType, BlockPBValue } from '../interfaces/document';
-import { Log } from './log';
-import { BLOCK_MAP_NAME, CHILDREN_MAP_NAME, META_NAME } from '../constants/block';
+import { BlockType, NestedBlock, DocumentState, ChangeType, BlockPBValue } from '../../interfaces/document';
+import { Log } from '../log';
+import { BLOCK_MAP_NAME, CHILDREN_MAP_NAME, META_NAME } from '../../constants/document/block';
 
 // This is a list of all the possible changes that can happen to document data
 const matchCases = [
@@ -153,12 +153,14 @@ function onMatchChildrenDelete(state: DocumentState, id: string, _children: stri
  * @param value
  */
 export function blockChangeValue2Node(value: BlockPBValue): NestedBlock {
-  const block = {
+  const block: NestedBlock = {
     id: value.id,
     type: value.ty as BlockType,
     parent: value.parent,
     children: value.children,
-    data: {},
+    data: {
+      delta: [],
+    },
   };
   if ('data' in value && typeof value.data === 'string') {
     try {
diff --git a/frontend/appflowy_tauri/tsconfig.json b/frontend/appflowy_tauri/tsconfig.json
index 7e82aa0265..f6b44e7521 100644
--- a/frontend/appflowy_tauri/tsconfig.json
+++ b/frontend/appflowy_tauri/tsconfig.json
@@ -19,7 +19,8 @@
     "baseUrl": "./",
     "paths": {
       "@/*": ["src/*"],
-      "$app/*": ["src/appflowy_app/*"]
+      "$app/*": ["src/appflowy_app/*"],
+      "$app_reducers/*": ["src/appflowy_app/stores/reducers/*"],
     },
   },
   "include": ["src", "vite.config.ts", "../app_flowy/assets/translations"],
diff --git a/frontend/appflowy_tauri/vite.config.ts b/frontend/appflowy_tauri/vite.config.ts
index 871e54f6d2..014e017c23 100644
--- a/frontend/appflowy_tauri/vite.config.ts
+++ b/frontend/appflowy_tauri/vite.config.ts
@@ -27,7 +27,8 @@ export default defineConfig({
   resolve: {
     alias: [
       { find: '@/', replacement: `${__dirname}/src/` },
-      { find: '$app/', replacement: `${__dirname}/src/appflowy_app/` }
+      { find: '$app/', replacement: `${__dirname}/src/appflowy_app/` },
+      { find: '$app_reducers/', replacement: `${__dirname}/src/appflowy_app/stores/reducers/` },
     ],
   },
 });