From 37ca144bf553e36cc27af51291bd43ce30f06693 Mon Sep 17 00:00:00 2001 From: Kilu Date: Mon, 25 Nov 2024 14:35:34 +0800 Subject: [PATCH] fix: colors --- .../application/slate-yjs/command/index.ts | 13 +-- .../slate-yjs/utils/slateUtils.tsx | 25 ----- .../slate-yjs/utils/yjsOperations.ts | 7 +- .../_shared/image-upload/Unsplash.tsx | 2 +- .../src/components/_shared/outline/utils.ts | 22 +++++ .../src/components/app/ViewModal.tsx | 7 +- .../src/components/app/header/MoreActions.tsx | 3 + .../app/header/MoreActionsContent.tsx | 13 ++- .../src/components/app/outline/ViewItem.tsx | 2 +- .../app/view-actions/AddPageActions.tsx | 16 +++- .../app/view-actions/DeletePageConfirm.tsx | 22 ++++- .../app/view-actions/ViewActions.tsx | 7 +- .../src/components/editor/Editable.tsx | 21 ++++- .../ImageBlockPopoverContent.tsx | 2 +- .../components/blocks/text/Placeholder.tsx | 7 +- .../editor/components/blocks/text/Text.tsx | 5 +- .../panels/slash-panel/SlashPanel.tsx | 8 ++ .../toolbar/block-controls/utils.ts | 7 +- .../SelectionToolbar.hooks.ts | 83 ++++++++++++++++- .../selection-toolbar/actions/Color.tsx | 68 ++++++++------ .../src/components/editor/editor.scss | 26 +----- .../src/components/editor/shortcut.hooks.ts | 91 ++++++------------- .../components/view-meta/ViewMetaPreview.tsx | 7 +- .../appflowy_web_app/src/pages/TrashPage.tsx | 16 +++- 24 files changed, 299 insertions(+), 181 deletions(-) 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 afb82f32b0c0d..6f3f2b705684b 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 db491ad56440b..5bbd992b6357c 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 790fce46f377e..b8037945f36f0 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 3db40cc6df425..e0c47c84e8401 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 fe56ea6fd2a79..7346a7b7f392a 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 549a9b5cfeee6..926f7acfd54fc 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 e813d4235a41c..d67f5b3cb6544 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 bffbfc9040aaf..a0c5ef8f27622 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 1fc2426bd6dbe..ec0bfe63f4743 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 2fc41c698c7c9..bbb53ed79c6c6 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 dea3703d228b1..d597b1ea65ab6 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 7fe1cb83ce728..87966df4c8068 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 f99ca5699186b..1705a02867590 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 d910c9e01a913..a646212a20474 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/SelectionToolbar.hooks.ts b/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts index 636f1871b78a9..8260680b562f7 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts +++ b/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts @@ -1,7 +1,10 @@ import { YjsEditor } from '@/application/slate-yjs'; +import { CustomEditor } from '@/application/slate-yjs/command'; +import { EditorMarkFormat } from '@/application/slate-yjs/types'; import { getOffsetPointFromSlateRange } from '@/application/slate-yjs/utils/yjsOperations'; import { getRangeRect, getSelectionPosition } from '@/components/editor/components/toolbar/selection-toolbar/utils'; import { useEditorContext } from '@/components/editor/EditorContext'; +import { createHotkey, HOT_KEY_NAME } from '@/utils/hotkeys'; import { PopoverPosition } from '@mui/material'; import { debounce } from 'lodash-es'; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; @@ -19,12 +22,14 @@ export function useVisible () { const isExpanded = selection ? Range.isExpanded(selection) : false; + const selectedText = selection ? editor.string(selection) : ''; + const visible = useMemo(() => { if (forceShow) return true; if (!focus) return false; - return isExpanded && !isDragging; - }, [focus, forceShow, isExpanded, isDragging]); + return selectedText && isExpanded && !isDragging; + }, [focus, selectedText, forceShow, isExpanded, isDragging]); useEffect(() => { if (!visible) { @@ -73,6 +78,80 @@ export function useVisible () { } }, [addDecorate, editor.selection]); + useEffect(() => { + if (!visible) return; + const handleKeyDown = (event: KeyboardEvent) => { + + switch (true) { + /** + * Bold: Mod + B + */ + case createHotkey(HOT_KEY_NAME.BOLD)(event): + event.preventDefault(); + CustomEditor.toggleMark(editor, { + key: EditorMarkFormat.Bold, + value: true, + }); + break; + /** + * Italic: Mod + I + */ + case createHotkey(HOT_KEY_NAME.ITALIC)(event): + event.preventDefault(); + CustomEditor.toggleMark(editor, { + key: EditorMarkFormat.Italic, + value: true, + }); + break; + /** + * Underline: Mod + U + */ + case createHotkey(HOT_KEY_NAME.UNDERLINE)(event): + event.preventDefault(); + CustomEditor.toggleMark(editor, { + key: EditorMarkFormat.Underline, + value: true, + }); + break; + /** + * Strikethrough: Mod + Shift + S / Mod + Shift + X + */ + case createHotkey(HOT_KEY_NAME.STRIKETHROUGH)(event): + event.preventDefault(); + CustomEditor.toggleMark(editor, { + key: EditorMarkFormat.StrikeThrough, + value: true, + }); + break; + /** + * Code: Mod + E + */ + case createHotkey(HOT_KEY_NAME.CODE)(event): + event.preventDefault(); + CustomEditor.toggleMark(editor, { + key: EditorMarkFormat.Code, + value: true, + }); + break; + /** + * Highlight: Mod + Shift + H + */ + case createHotkey(HOT_KEY_NAME.HIGH_LIGHT)(event): + event.preventDefault(); + CustomEditor.highlight(editor); + break; + } + }; + + const slateEditorDom = ReactEditor.toDOMNode(editor, editor); + + slateEditorDom.addEventListener('keydown', handleKeyDown); + + return () => { + slateEditorDom.removeEventListener('keydown', handleKeyDown); + }; + }, [editor, visible]); + return { visible, forceShow: handleForceShow, 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 8ad413b97289a..320469295e893 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 0bc81fb93ca15..f224b58c691fc 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 f93b5476689b1..ab6c43870b25f 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); } @@ -253,56 +273,7 @@ export function useShortcuts (editor: ReactEditor) { } break; - /** - * Bold: Mod + B - */ - case createHotkey(HOT_KEY_NAME.BOLD)(e): - event.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Bold, - value: true, - }); - break; - /** - * Italic: Mod + I - */ - case createHotkey(HOT_KEY_NAME.ITALIC)(e): - event.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Italic, - value: true, - }); - break; - /** - * Underline: Mod + U - */ - case createHotkey(HOT_KEY_NAME.UNDERLINE)(e): - event.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Underline, - value: true, - }); - break; - /** - * Strikethrough: Mod + Shift + S / Mod + Shift + X - */ - case createHotkey(HOT_KEY_NAME.STRIKETHROUGH)(e): - event.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.StrikeThrough, - value: true, - }); - break; - /** - * Code: Mod + E - */ - case createHotkey(HOT_KEY_NAME.CODE)(e): - event.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Code, - value: true, - }); - break; + /** * Open link: Opt + SHIFT + Enter */ @@ -382,14 +353,6 @@ export function useShortcuts (editor: ReactEditor) { CustomEditor.pastedText(yjsEditor, text); }); break; - /** - * Highlight: Mod + Shift + H - */ - case createHotkey(HOT_KEY_NAME.HIGH_LIGHT)(e): - event.preventDefault(); - CustomEditor.highlight(editor); - break; - /** * Scroll to top: Home */ 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 64f4a0342fda4..a8a1c3fa05f09 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 85b525a2bbdb7..23477b372f426 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);