chore: add "Clear formatting" option to text format menu and make selected options clearer by showing check if selected [skip e2e]
This commit is contained in:
@@ -111,6 +111,8 @@
|
|||||||
"@ariakit/react": "^0.3.9",
|
"@ariakit/react": "^0.3.9",
|
||||||
"@lexical/headless": "0.13.1",
|
"@lexical/headless": "0.13.1",
|
||||||
"@lexical/list": "0.13.1",
|
"@lexical/list": "0.13.1",
|
||||||
|
"@lexical/rich-text": "0.13.1",
|
||||||
|
"@lexical/utils": "0.13.1",
|
||||||
"@radix-ui/react-slot": "^1.0.1",
|
"@radix-ui/react-slot": "^1.0.1",
|
||||||
"@react-pdf/renderer": "^3.3.2",
|
"@react-pdf/renderer": "^3.3.2",
|
||||||
"comlink": "^4.4.1",
|
"comlink": "^4.4.1",
|
||||||
|
|||||||
@@ -20,11 +20,18 @@ import {
|
|||||||
ElementFormatType,
|
ElementFormatType,
|
||||||
$isElementNode,
|
$isElementNode,
|
||||||
COMMAND_PRIORITY_LOW,
|
COMMAND_PRIORITY_LOW,
|
||||||
|
$createParagraphNode,
|
||||||
|
$isTextNode,
|
||||||
} from 'lexical'
|
} from 'lexical'
|
||||||
import { mergeRegister, $findMatchingParent, $getNearestNodeOfType } from '@lexical/utils'
|
import {
|
||||||
|
mergeRegister,
|
||||||
|
$findMatchingParent,
|
||||||
|
$getNearestNodeOfType,
|
||||||
|
$getNearestBlockElementAncestorOrThrow,
|
||||||
|
} from '@lexical/utils'
|
||||||
import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||||
import { $isListNode, ListNode } from '@lexical/list'
|
import { $isListNode, ListNode } from '@lexical/list'
|
||||||
import { $isHeadingNode } from '@lexical/rich-text'
|
import { $isHeadingNode, $isQuoteNode } from '@lexical/rich-text'
|
||||||
import {
|
import {
|
||||||
ComponentPropsWithoutRef,
|
ComponentPropsWithoutRef,
|
||||||
ForwardedRef,
|
ForwardedRef,
|
||||||
@@ -66,6 +73,7 @@ import { getDOMRangeRect } from '../../Lexical/Utils/getDOMRangeRect'
|
|||||||
import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPopoverStyles'
|
import { getPositionedPopoverStyles } from '@/Components/Popover/GetPositionedPopoverStyles'
|
||||||
import usePreference from '@/Hooks/usePreference'
|
import usePreference from '@/Hooks/usePreference'
|
||||||
import { ElementIds } from '@/Constants/ElementIDs'
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
|
import { $isDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode'
|
||||||
|
|
||||||
const TOGGLE_LINK_AND_EDIT_COMMAND = createCommand<string | null>('TOGGLE_LINK_AND_EDIT_COMMAND')
|
const TOGGLE_LINK_AND_EDIT_COMMAND = createCommand<string | null>('TOGGLE_LINK_AND_EDIT_COMMAND')
|
||||||
|
|
||||||
@@ -181,6 +189,7 @@ const ToolbarMenuItem = ({ name, iconName, active, onClick, ...props }: ToolbarM
|
|||||||
>
|
>
|
||||||
<Icon type={iconName} className="-mt-px mr-2.5 flex-shrink-0" />
|
<Icon type={iconName} className="-mt-px mr-2.5 flex-shrink-0" />
|
||||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{name}</span>
|
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{name}</span>
|
||||||
|
{active && <Icon type="check" className="ml-auto" />}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -378,6 +387,49 @@ const ToolbarPlugin = () => {
|
|||||||
containerElement.style.removeProperty('opacity')
|
containerElement.style.removeProperty('opacity')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const clearFormatting = useCallback(() => {
|
||||||
|
activeEditor.update(() => {
|
||||||
|
const selection = $getSelection()
|
||||||
|
if ($isRangeSelection(selection)) {
|
||||||
|
const anchor = selection.anchor
|
||||||
|
const focus = selection.focus
|
||||||
|
const nodes = selection.getNodes()
|
||||||
|
|
||||||
|
if (anchor.key === focus.key && anchor.offset === focus.offset) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.forEach((node, idx) => {
|
||||||
|
// We split the first and last node by the selection
|
||||||
|
// So that we don't format unselected text inside those nodes
|
||||||
|
if ($isTextNode(node)) {
|
||||||
|
// Use a separate variable to ensure TS does not lose the refinement
|
||||||
|
let textNode = node
|
||||||
|
if (idx === 0 && anchor.offset !== 0) {
|
||||||
|
textNode = textNode.splitText(anchor.offset)[1] || textNode
|
||||||
|
}
|
||||||
|
if (idx === nodes.length - 1) {
|
||||||
|
textNode = textNode.splitText(focus.offset)[0] || textNode
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textNode.__style !== '') {
|
||||||
|
textNode.setStyle('')
|
||||||
|
}
|
||||||
|
if (textNode.__format !== 0) {
|
||||||
|
textNode.setFormat(0)
|
||||||
|
$getNearestBlockElementAncestorOrThrow(textNode).setFormat('')
|
||||||
|
}
|
||||||
|
node = textNode
|
||||||
|
} else if ($isHeadingNode(node) || $isQuoteNode(node)) {
|
||||||
|
node.replace($createParagraphNode(), true)
|
||||||
|
} else if ($isDecoratorBlockNode(node)) {
|
||||||
|
node.setFormat('')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [activeEditor])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return
|
return
|
||||||
@@ -848,10 +900,11 @@ const ToolbarPlugin = () => {
|
|||||||
active={isSuperscript}
|
active={isSuperscript}
|
||||||
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript')}
|
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript')}
|
||||||
/>
|
/>
|
||||||
|
<ToolbarMenuItem name="Clear formatting" iconName="trash" onClick={clearFormatting} />
|
||||||
</Menu>
|
</Menu>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Popover
|
<Popover
|
||||||
title="Text style"
|
title="Block style"
|
||||||
anchorElement={textStyleAnchorRef}
|
anchorElement={textStyleAnchorRef}
|
||||||
open={isTextStyleMenuOpen}
|
open={isTextStyleMenuOpen}
|
||||||
togglePopover={() => setIsTextStyleMenuOpen(!isTextStyleMenuOpen)}
|
togglePopover={() => setIsTextStyleMenuOpen(!isTextStyleMenuOpen)}
|
||||||
@@ -864,7 +917,7 @@ const ToolbarPlugin = () => {
|
|||||||
portal={false}
|
portal={false}
|
||||||
documentElement={popoverDocumentElement}
|
documentElement={popoverDocumentElement}
|
||||||
>
|
>
|
||||||
<Menu a11yLabel="Text style" className="!px-0" onClick={() => setIsTextStyleMenuOpen(false)}>
|
<Menu a11yLabel="Block style" className="!px-0" onClick={() => setIsTextStyleMenuOpen(false)}>
|
||||||
<ToolbarMenuItem
|
<ToolbarMenuItem
|
||||||
name="Normal"
|
name="Normal"
|
||||||
iconName="paragraph"
|
iconName="paragraph"
|
||||||
|
|||||||
@@ -7684,6 +7684,8 @@ __metadata:
|
|||||||
"@lexical/headless": 0.13.1
|
"@lexical/headless": 0.13.1
|
||||||
"@lexical/list": 0.13.1
|
"@lexical/list": 0.13.1
|
||||||
"@lexical/react": 0.13.1
|
"@lexical/react": 0.13.1
|
||||||
|
"@lexical/rich-text": 0.13.1
|
||||||
|
"@lexical/utils": 0.13.1
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.10
|
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.10
|
||||||
"@radix-ui/react-slot": ^1.0.1
|
"@radix-ui/react-slot": ^1.0.1
|
||||||
"@react-pdf/renderer": ^3.3.2
|
"@react-pdf/renderer": ^3.3.2
|
||||||
|
|||||||
Reference in New Issue
Block a user