diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts b/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts index afb82f32b0c0..6f3f2b705684 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts @@ -2,8 +2,6 @@ import { YjsEditor } from '@/application/slate-yjs/plugins/withYjs'; import { EditorMarkFormat } from '@/application/slate-yjs/types'; import { beforePasted, - findIndentPath, - findLiftPath, findSlateEntryByBlockId, } from '@/application/slate-yjs/utils/slateUtils'; @@ -166,12 +164,12 @@ export const CustomEditor = { const block = getBlock(node.blockId as string, sharedRoot); const blockType = block.get(YjsEditorKey.block_type) as BlockType; - if (blockType !== BlockType.Paragraph) { - handleNonParagraphBlockBackspaceAndEnterWithTxn(editor, sharedRoot, block, point); + if (path.length > 1 && handleLiftBlockOnBackspaceAndEnterWithTxn(editor, sharedRoot, block, point)) { return; } - if (path.length > 1 && handleLiftBlockOnBackspaceAndEnterWithTxn(editor, sharedRoot, block, point)) { + if (blockType !== BlockType.Paragraph) { + handleNonParagraphBlockBackspaceAndEnterWithTxn(editor, sharedRoot, block, point); return; } @@ -232,6 +230,7 @@ export const CustomEditor = { const endBlockPath = endNode[1]; const startAtPath = point.path.slice(startBlockPath.length); const startAtOffset = point.offset; + const endAtPath = endPoint.path.slice(endBlockPath.length); const endAtOffset = endPoint.offset; let newStartBlockPath: Path = []; @@ -239,6 +238,7 @@ export const CustomEditor = { const isSameBlock = node[0].blockId === endNode[0].blockId; + editor.deselect(); if (isSameBlock) { const block = getBlock(node[0].blockId as string, sharedRoot); let newBlockId: string | undefined; @@ -272,9 +272,10 @@ export const CustomEditor = { }); if (newBlockIds.length === 0) return; const newStartBlockEntry = findSlateEntryByBlockId(editor, newBlockIds[0]); + const newEndBlockEntry = findSlateEntryByBlockId(editor, newBlockIds[newBlockIds.length - 1]); newStartBlockPath = newStartBlockEntry[1]; - newEndBlockPath = type === 'tabForward' ? findIndentPath(startBlockPath, endBlockPath, newStartBlockPath) : findLiftPath(startBlockPath, endBlockPath, newStartBlockPath); + newEndBlockPath = newEndBlockEntry[1]; } const newStartPath = [...newStartBlockPath, ...startAtPath]; diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/utils/slateUtils.tsx b/frontend/appflowy_web_app/src/application/slate-yjs/utils/slateUtils.tsx index db491ad56440..5bbd992b6357 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/utils/slateUtils.tsx +++ b/frontend/appflowy_web_app/src/application/slate-yjs/utils/slateUtils.tsx @@ -1,31 +1,6 @@ import { Editor, Element, NodeEntry, Path, Range, Transforms, Node, Point, BasePoint } from 'slate'; import { ReactEditor } from 'slate-react'; -export function findIndentPath (originalStart: Path, originalEnd: Path, newStart: Path): Path { - // Find the common ancestor path - const commonPath = Path.common(originalStart, originalEnd); - - // Calculate end's path relative to common ancestor - const endRelativePath = originalEnd.slice(commonPath.length); - - // Calculate new common ancestor path by maintaining the same level difference - const startToCommonLevels = originalStart.length - commonPath.length; - const newCommonAncestor = newStart.slice(0, newStart.length - startToCommonLevels); - - // Append the relative path to new common ancestor - return [...newCommonAncestor, ...endRelativePath]; -} - -export function findLiftPath (originalStart: Path, originalEnd: Path, newStart: Path): Path { - // Same logic as findIndentPath - const commonPath = Path.common(originalStart, originalEnd); - const endRelativePath = originalEnd.slice(commonPath.length); - const startToCommonLevels = originalStart.length - commonPath.length; - const newCommonAncestor = newStart.slice(0, newStart.length - startToCommonLevels); - - return [...newCommonAncestor, ...endRelativePath]; -} - export function findSlateEntryByBlockId (editor: Editor, blockId: string) { const [node] = Editor.nodes(editor, { match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId === blockId, diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/utils/yjsOperations.ts b/frontend/appflowy_web_app/src/application/slate-yjs/utils/yjsOperations.ts index 790fce46f377..b8037945f36f 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/utils/yjsOperations.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/utils/yjsOperations.ts @@ -276,14 +276,15 @@ export function handleCollapsedBreakWithTxn (editor: YjsEditor, sharedRoot: YSha if (yText.length === 0) { const point = Editor.start(editor, at); - if (blockType !== BlockType.Paragraph) { - handleNonParagraphBlockBackspaceAndEnterWithTxn(editor, sharedRoot, block, point); + if (path.length > 1 && handleLiftBlockOnBackspaceAndEnterWithTxn(editor, sharedRoot, block, point)) { return; } - if (path.length > 1 && handleLiftBlockOnBackspaceAndEnterWithTxn(editor, sharedRoot, block, point)) { + if (blockType !== BlockType.Paragraph) { + handleNonParagraphBlockBackspaceAndEnterWithTxn(editor, sharedRoot, block, point); return; } + } const { operations, select } = getSplitBlockOperations(sharedRoot, block, startOffset); diff --git a/frontend/appflowy_web_app/src/components/_shared/image-upload/Unsplash.tsx b/frontend/appflowy_web_app/src/components/_shared/image-upload/Unsplash.tsx index 3db40cc6df42..e0c47c84e840 100644 --- a/frontend/appflowy_web_app/src/components/_shared/image-upload/Unsplash.tsx +++ b/frontend/appflowy_web_app/src/components/_shared/image-upload/Unsplash.tsx @@ -89,7 +89,7 @@ export function Unsplash ({ onDone, onEscape }: { onDone?: (value: string) => vo
boolean): View[] { + const filter = (views: View[]): View[] => { + let result: View[] = []; + + for (const view of views) { + if (condition(view)) { + result.push(view); + } + + if (view.children) { + const filteredChildren = filter(view.children); + + result = result.concat(filteredChildren); + } + } + + return result; + }; + + return filter(views); +} + export function filterOutByCondition (views: View[], condition: (view: View) => { remove: boolean; }): View[] { diff --git a/frontend/appflowy_web_app/src/components/app/ViewModal.tsx b/frontend/appflowy_web_app/src/components/app/ViewModal.tsx index fe56ea6fd2a7..7346a7b7f392 100644 --- a/frontend/appflowy_web_app/src/components/app/ViewModal.tsx +++ b/frontend/appflowy_web_app/src/components/app/ViewModal.tsx @@ -133,7 +133,12 @@ function ViewModal ({
- + { + onClose(); + }} + viewId={viewId} + /> void; }) { const [anchorEl, setAnchorEl] = React.useState(null); @@ -56,6 +58,7 @@ function MoreActions ({ itemClicked={() => { handleClose(); }} + onDeleted={onDeleted} viewId={viewId} movePopoverOrigins={{ transformOrigin: { diff --git a/frontend/appflowy_web_app/src/components/app/header/MoreActionsContent.tsx b/frontend/appflowy_web_app/src/components/app/header/MoreActionsContent.tsx index 549a9b5cfeee..926f7acfd54f 100644 --- a/frontend/appflowy_web_app/src/components/app/header/MoreActionsContent.tsx +++ b/frontend/appflowy_web_app/src/components/app/header/MoreActionsContent.tsx @@ -8,8 +8,9 @@ import { ReactComponent as DeleteIcon } from '@/assets/trash.svg'; import { ReactComponent as DuplicateIcon } from '@/assets/duplicate.svg'; import { ReactComponent as MoveToIcon } from '@/assets/move_to.svg'; -function MoreActionsContent ({ itemClicked, viewId, movePopoverOrigins }: { +function MoreActionsContent ({ itemClicked, viewId, movePopoverOrigins, onDeleted }: { itemClicked?: () => void; + onDeleted?: () => void; viewId: string; movePopoverOrigins: Origins }) { @@ -51,9 +52,15 @@ function MoreActionsContent ({ itemClicked, viewId, movePopoverOrigins }: { >{t('button.delete')} setDeleteModalOpen(false)} + onClose={() => { + setDeleteModalOpen(false); + itemClicked?.(); + }} viewId={viewId} - onDeleted={itemClicked} + onDeleted={() => { + onDeleted?.(); + itemClicked?.(); + }} /> setHovered(true)} onMouseLeave={() => setHovered(false)} diff --git a/frontend/appflowy_web_app/src/components/app/view-actions/AddPageActions.tsx b/frontend/appflowy_web_app/src/components/app/view-actions/AddPageActions.tsx index e813d4235a41..d67f5b3cb654 100644 --- a/frontend/appflowy_web_app/src/components/app/view-actions/AddPageActions.tsx +++ b/frontend/appflowy_web_app/src/components/app/view-actions/AddPageActions.tsx @@ -7,8 +7,9 @@ import CircularProgress from '@mui/material/CircularProgress'; import React, { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -function AddPageActions ({ view }: { - view: View +function AddPageActions ({ view, onClose }: { + view: View; + onClose: () => void; }) { const { t } = useTranslation(); const { @@ -36,7 +37,11 @@ function AddPageActions ({ view }: { } }, [addPage, openPageModal, t, view.view_id]); - const actions = useMemo(() => [ + const actions: { + label: string; + icon: React.ReactNode; + onClick: (e: React.MouseEvent) => void; + }[] = useMemo(() => [ { label: t('document.menuName'), icon: { + action.onClick(e); + onClose(); + }} className={'px-3 py-1 justify-start'} color={'inherit'} startIcon={action.icon} diff --git a/frontend/appflowy_web_app/src/components/app/view-actions/DeletePageConfirm.tsx b/frontend/appflowy_web_app/src/components/app/view-actions/DeletePageConfirm.tsx index bffbfc9040aa..a0c5ef8f2762 100644 --- a/frontend/appflowy_web_app/src/components/app/view-actions/DeletePageConfirm.tsx +++ b/frontend/appflowy_web_app/src/components/app/view-actions/DeletePageConfirm.tsx @@ -1,7 +1,8 @@ import { NormalModal } from '@/components/_shared/modal'; import { notify } from '@/components/_shared/notify'; +import { filterViewsByCondition } from '@/components/_shared/outline/utils'; import { useAppHandlers, useAppView } from '@/components/app/app.hooks'; -import React from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; function DeletePageConfirm ({ open, onClose, viewId, onDeleted }: { @@ -17,7 +18,7 @@ function DeletePageConfirm ({ open, onClose, viewId, onDeleted }: { } = useAppHandlers(); const { t } = useTranslation(); - const handleOk = async () => { + const handleOk = useCallback(async () => { if (!view) return; setLoading(true); try { @@ -30,12 +31,27 @@ function DeletePageConfirm ({ open, onClose, viewId, onDeleted }: { } finally { setLoading(false); } - }; + }, [deletePage, onClose, onDeleted, view, viewId]); + + const hasPublished = useMemo(() => { + const publishedView = filterViewsByCondition(view?.children || [], v => v.is_published); + + return view?.is_published || !!publishedView.length; + }, [view]); + + useEffect(() => { + if (!hasPublished && open) { + void handleOk(); + } + }, [handleOk, hasPublished, open]); + + if (!hasPublished) return null; return ( ; + return { + handleClosePopover(); + }} + view={view} + />; } if (popoverType.category === 'space') { diff --git a/frontend/appflowy_web_app/src/components/editor/Editable.tsx b/frontend/appflowy_web_app/src/components/editor/Editable.tsx index 1fc2426bd6db..ec0bfe63f474 100644 --- a/frontend/appflowy_web_app/src/components/editor/Editable.tsx +++ b/frontend/appflowy_web_app/src/components/editor/Editable.tsx @@ -1,18 +1,20 @@ import { YjsEditor } from '@/application/slate-yjs'; +import { CustomEditor } from '@/application/slate-yjs/command'; import { ensureBlockText } from '@/application/slate-yjs/utils/yjsOperations'; +import { BlockType } from '@/application/types'; import { BlockPopoverProvider } from '@/components/editor/components/block-popover/BlockPopoverContext'; import { useDecorate } from '@/components/editor/components/blocks/code/useDecorate'; import { Leaf } from '@/components/editor/components/leaf'; +import { PanelProvider } from '@/components/editor/components/panels/PanelsContext'; import { useEditorContext } from '@/components/editor/EditorContext'; import { useShortcuts } from '@/components/editor/shortcut.hooks'; import { getTextCount } from '@/utils/word'; +import { Skeleton } from '@mui/material'; import { debounce } from 'lodash-es'; import React, { lazy, Suspense, useCallback, useEffect, useMemo } from 'react'; -import { BaseRange, Editor, NodeEntry, Range } from 'slate'; +import { BaseRange, Editor, NodeEntry, Range, Element as SlateElement } from 'slate'; import { Editable, RenderElementProps, useSlate } from 'slate-react'; import { Element } from './components/element'; -import { Skeleton } from '@mui/material'; -import { PanelProvider } from '@/components/editor/components/panels/PanelsContext'; const EditorOverlay = lazy(() => import('@/components/editor/EditorOverlay')); @@ -107,7 +109,7 @@ const EditorEditable = () => { return [...codeDecoration, ...decoration]; }} - className={'outline-none scroll-mb-[100px] scroll-mt-[300px] mb-36 min-w-0 max-w-full w-[988px] max-sm:px-6 px-24 focus:outline-none'} + className={'outline-none scroll-mb-[100px] scroll-mt-[300px] pb-36 min-w-0 max-w-full w-[988px] max-sm:px-6 px-24 focus:outline-none'} renderLeaf={Leaf} renderElement={renderElement} readOnly={readOnly} @@ -116,6 +118,17 @@ const EditorEditable = () => { autoComplete={'off'} onCompositionStart={onCompositionStart} onKeyDown={onKeyDown} + onClick={e => { + const currentTarget = e.currentTarget as HTMLElement; + const bottomArea = currentTarget.getBoundingClientRect().bottom - 36 * 4; + + if (e.clientY > bottomArea) { + const lastBlockId = (editor.children[editor.children.length - 1] as SlateElement).blockId as string; + + if (!lastBlockId) return; + CustomEditor.addBelowBlock(editor as YjsEditor, lastBlockId, BlockType.Paragraph, {}); + } + }} /> {!readOnly && diff --git a/frontend/appflowy_web_app/src/components/editor/components/block-popover/ImageBlockPopoverContent.tsx b/frontend/appflowy_web_app/src/components/editor/components/block-popover/ImageBlockPopoverContent.tsx index 2fc41c698c7c..bbb53ed79c6c 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/block-popover/ImageBlockPopoverContent.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/block-popover/ImageBlockPopoverContent.tsx @@ -93,7 +93,7 @@ function ImageBlockPopoverContent ({ />; })} -
+
{tabOptions.map((tab, index) => { const { key, panel } = tab; diff --git a/frontend/appflowy_web_app/src/components/editor/components/blocks/text/Placeholder.tsx b/frontend/appflowy_web_app/src/components/editor/components/blocks/text/Placeholder.tsx index dea3703d228b..d597b1ea65ab 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/blocks/text/Placeholder.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/blocks/text/Placeholder.tsx @@ -28,7 +28,11 @@ function Placeholder ({ node, ...attributes }: { node: Element; className?: stri }, [editor, node]); const className = useMemo(() => { - return `text-placeholder select-none ${attributes.className ?? ''}`; + const classList = attributes.className?.split(' ') ?? []; + + classList.push('text-placeholder select-none'); + + return classList.join(' '); }, [attributes.className]); const unSelectedPlaceholder = useMemo(() => { @@ -79,7 +83,6 @@ function Placeholder ({ node, ...attributes }: { node: Element; className?: stri } } - case BlockType.CalloutBlock: case BlockType.CodeBlock: return t('editor.typeSomething'); default: diff --git a/frontend/appflowy_web_app/src/components/editor/components/blocks/text/Text.tsx b/frontend/appflowy_web_app/src/components/editor/components/blocks/text/Text.tsx index 7fe1cb83ce72..87966df4c806 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/blocks/text/Text.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/blocks/text/Text.tsx @@ -24,8 +24,9 @@ export const Text = forwardRef>( const content = useMemo(() => { return <> - {placeholder} - {children} + + + {placeholder}{children} ; }, [placeholder, isEmpty, children]); diff --git a/frontend/appflowy_web_app/src/components/editor/components/panels/slash-panel/SlashPanel.tsx b/frontend/appflowy_web_app/src/components/editor/components/panels/slash-panel/SlashPanel.tsx index f99ca5699186..1705a0286759 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/panels/slash-panel/SlashPanel.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/panels/slash-panel/SlashPanel.tsx @@ -1,5 +1,6 @@ import { YjsEditor } from '@/application/slate-yjs'; import { CustomEditor } from '@/application/slate-yjs/command'; +import { isEmbedBlockTypes } from '@/application/slate-yjs/command/const'; import { findSlateEntryByBlockId } from '@/application/slate-yjs/utils/slateUtils'; import { getBlockEntry } from '@/application/slate-yjs/utils/yjsOperations'; import { @@ -91,6 +92,12 @@ export function SlashPanel ({ newBlockId = CustomEditor.addBelowBlock(editor, blockId, type, data); } + if (newBlockId && isEmbedBlockTypes(type)) { + const [, path] = findSlateEntryByBlockId(editor, newBlockId); + + editor.select(editor.start(path)); + } + if ([BlockType.FileBlock, BlockType.ImageBlock, BlockType.EquationBlock].includes(type)) { setTimeout(() => { if (!newBlockId) return; @@ -422,6 +429,7 @@ export function SlashPanel ({ break; case 'ArrowUp': case 'ArrowDown': { + e.stopPropagation(); e.preventDefault(); const index = options.findIndex((option) => option.key === selectedOptionRef.current); const nextIndex = key === 'ArrowDown' ? (index + 1) % options.length : (index - 1 + options.length) % options.length; diff --git a/frontend/appflowy_web_app/src/components/editor/components/toolbar/block-controls/utils.ts b/frontend/appflowy_web_app/src/components/editor/components/toolbar/block-controls/utils.ts index d910c9e01a91..a646212a2047 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/toolbar/block-controls/utils.ts +++ b/frontend/appflowy_web_app/src/components/editor/components/toolbar/block-controls/utils.ts @@ -8,9 +8,14 @@ export function getBlockActionsPosition (editor: ReactEditor, blockElement: HTML const editorDom = ReactEditor.toDOMNode(editor, editor); const editorDomRect = editorDom.getBoundingClientRect(); const blockDomRect = blockElement.getBoundingClientRect(); + const parentBlockDom = blockElement.parentElement?.closest('[data-block-type]'); const relativeTop = blockDomRect.top - editorDomRect.top; - const relativeLeft = blockDomRect.left - editorDomRect.left; + let relativeLeft = blockDomRect.left - editorDomRect.left; + + if (parentBlockDom?.getAttribute('data-block-type') === BlockType.QuoteBlock) { + relativeLeft -= 16; + } return { top: relativeTop, diff --git a/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/actions/Color.tsx b/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/actions/Color.tsx index 8ad413b97289..320469295e89 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/actions/Color.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/actions/Color.tsx @@ -6,6 +6,7 @@ import { useSelectionToolbarContext, } from '@/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks'; import { ColorEnum, renderColor } from '@/utils/color'; +import { Tooltip } from '@mui/material'; import React, { useCallback, useEffect, useMemo } from 'react'; import ActionButton from './ActionButton'; import { useTranslation } from 'react-i18next'; @@ -124,23 +125,29 @@ function Color () {
{t('editor.textColor')}
{editorTextColors.map((color, index) => { - return
handlePickedColor(EditorMarkFormat.FontColor, color.color)} - style={{ - color: color.color || 'var(--text-title)', - }} + title={color.label} + placement={'top'} >
handlePickedColor(EditorMarkFormat.FontColor, color.color)} style={{ - borderColor: color.color || 'var(--text-title)', - opacity: color.color ? undefined : 1, + color: color.color || 'var(--text-title)', }} - /> - -
; + > +
+ +
+ ; })}
@@ -148,24 +155,31 @@ function Color () {
{t('editor.backgroundColor')}
{editorBgColors.map((color, index) => { - return
handlePickedColor(EditorMarkFormat.BgColor, color.color)} + title={color.label} + placement={'top'} >
-
-
; + key={index} + className={'h-6 relative w-6 overflow-hidden flex items-center rounded-[6px] cursor-pointer justify-center'} + onClick={() => handlePickedColor(EditorMarkFormat.BgColor, color.color)} + > +
+
+
+ ; })}
diff --git a/frontend/appflowy_web_app/src/components/editor/editor.scss b/frontend/appflowy_web_app/src/components/editor/editor.scss index 0bc81fb93ca1..f224b58c691f 100644 --- a/frontend/appflowy_web_app/src/components/editor/editor.scss +++ b/frontend/appflowy_web_app/src/components/editor/editor.scss @@ -55,9 +55,10 @@ .block-element[data-block-type="quote"] { - .block-element { + .border-l-4 > .block-element { margin-left: 0 !important; } + } .block-element[data-block-type="callout"] { @@ -198,7 +199,7 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) { } .text-placeholder { - @apply absolute left-[5px] transform -translate-y-1/2 pointer-events-none select-none whitespace-nowrap; + @apply absolute left-0 transform -translate-y-1/2 pointer-events-none select-none whitespace-nowrap; &:after { @apply text-text-placeholder absolute top-0; content: (attr(data-placeholder)); @@ -211,11 +212,6 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) { } } -.has-start-icon .text-placeholder { - &:after { - @apply left-[24px]; - } -} .block-align-center { .text-placeholder { @@ -225,13 +221,6 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) { } } - .has-start-icon .text-placeholder { - @apply left-[calc(50%+13px)]; - &:after { - @apply left-0; - } - } - } @@ -239,9 +228,9 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) { .text-placeholder { - @apply relative w-fit h-0 order-2; + @apply hidden; &:after { - @apply relative w-fit top-1/2 left-[-6px]; + @apply relative w-fit; } } @@ -249,11 +238,6 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) { @apply order-1; } - .has-start-icon .text-placeholder { - &:after { - @apply left-[-6px]; - } - } } diff --git a/frontend/appflowy_web_app/src/components/editor/shortcut.hooks.ts b/frontend/appflowy_web_app/src/components/editor/shortcut.hooks.ts index f93b5476689b..f66575b44e9a 100644 --- a/frontend/appflowy_web_app/src/components/editor/shortcut.hooks.ts +++ b/frontend/appflowy_web_app/src/components/editor/shortcut.hooks.ts @@ -20,7 +20,7 @@ export function useShortcuts (editor: ReactEditor) { const title = document.getElementById('editor-title'); if (!title) return; - + const selection = window.getSelection(); const range = document.createRange(); @@ -51,15 +51,16 @@ export function useShortcuts (editor: ReactEditor) { if (selection && Range.isCollapsed(selection)) { switch (true) { case createHotkey(HOT_KEY_NAME.UP)(e): { - const before = Editor.before(editor, selection, { unit: 'offset' }); - const beforeText = findInlineTextNode(editor, before); const path = editor.start(selection).path; - if (!Path.hasPrevious(path)) { + if (Path.isAncestor([0, 0], path)) { focusedFocusableElement(false); break; } + const before = Editor.before(editor, selection, { unit: 'offset' }); + const beforeText = findInlineTextNode(editor, before); + if (before && beforeText) { e.preventDefault(); Transforms.move(editor, { unit: 'line', reverse: true, distance: 2 }); @@ -69,11 +70,30 @@ export function useShortcuts (editor: ReactEditor) { break; } - case createHotkey(HOT_KEY_NAME.BACKSPACE)(e): + case createHotkey(HOT_KEY_NAME.BACKSPACE)(e): { + const [node] = getBlockEntry(yjsEditor); + const type = node.type as BlockType; + + if (type !== BlockType.Paragraph) { + break; + } + + const path = editor.start(selection).path; + + const before = Editor.before(editor, selection, { unit: 'offset' }); + + if (!before && Path.isAncestor([0, 0], path)) { + focusedFocusableElement(true); + } + + break; + } + case createHotkey(HOT_KEY_NAME.LEFT)(e): { + const path = editor.start(selection).path; const before = Editor.before(editor, selection, { unit: 'offset' }); - if (!before) { + if (!before && Path.isAncestor([0, 0], path)) { focusedFocusableElement(true); } diff --git a/frontend/appflowy_web_app/src/components/view-meta/ViewMetaPreview.tsx b/frontend/appflowy_web_app/src/components/view-meta/ViewMetaPreview.tsx index 64f4a0342fda..a8a1c3fa05f0 100644 --- a/frontend/appflowy_web_app/src/components/view-meta/ViewMetaPreview.tsx +++ b/frontend/appflowy_web_app/src/components/view-meta/ViewMetaPreview.tsx @@ -135,7 +135,7 @@ export function ViewMetaPreview ({
setIsHover(true)} onMouseLeave={() => setIsHover(false)} - className={'flex mt-2 flex-col relative'} + className={'flex mt-2 flex-col relative w-full overflow-hidden'} >
{isHover && !readOnly && }

{icon?.value ? diff --git a/frontend/appflowy_web_app/src/pages/TrashPage.tsx b/frontend/appflowy_web_app/src/pages/TrashPage.tsx index 85b525a2bbdb..23477b372f42 100644 --- a/frontend/appflowy_web_app/src/pages/TrashPage.tsx +++ b/frontend/appflowy_web_app/src/pages/TrashPage.tsx @@ -105,16 +105,20 @@ function TrashPage () {

; } else if (column.id === 'created_at' || column.id === 'last_edited_time') { - content = dayjs(value).format('MMM D, YYYY h:mm A'); + content =
{dayjs(value).format('MMM D, YYYY h:mm A')}
; } else { - content = value || t('menuAppHeader.defaultNewPageName'); + content =
+ + {value} + +
|| t('menuAppHeader.defaultNewPageName'); } return ( {content} @@ -133,7 +137,7 @@ function TrashPage () { className={'flex items-center justify-between px-4'} > {t('trash.text')} -
+ {trashList?.length &&
-
+
} +
{!trashList ? {columns.map((column) => { return renderCell(column, row);