feat: Added keyboard shortcut to toggle checklist item and fixed issue with pressing Space inside checklist toggling it
This commit is contained in:
@@ -27,7 +27,6 @@ import { RemoveBrokenTablesPlugin } from './Plugins/TablePlugin'
|
||||
import TableActionMenuPlugin from './Plugins/TableCellActionMenuPlugin'
|
||||
import ToolbarPlugin from './Plugins/ToolbarPlugin/ToolbarPlugin'
|
||||
import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin'
|
||||
import RemoteImagePlugin from './Plugins/RemoteImagePlugin/RemoteImagePlugin'
|
||||
import CodeOptionsPlugin from './Plugins/CodeOptionsPlugin/CodeOptions'
|
||||
import { SuperSearchContextProvider } from './Plugins/SearchPlugin/Context'
|
||||
@@ -35,6 +34,7 @@ import { SearchPlugin } from './Plugins/SearchPlugin/SearchPlugin'
|
||||
import AutoLinkPlugin from './Plugins/AutoLinkPlugin/AutoLinkPlugin'
|
||||
import DatetimePlugin from './Plugins/DateTimePlugin/DateTimePlugin'
|
||||
import PasswordPlugin from './Plugins/PasswordPlugin/PasswordPlugin'
|
||||
import { CheckListPlugin } from './Plugins/CheckListPlugin'
|
||||
|
||||
type BlocksEditorProps = {
|
||||
onChange?: (value: string, preview: string) => void
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
list-style-type: none;
|
||||
outline: none;
|
||||
vertical-align: middle;
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.Lexical__listItemChecked {
|
||||
text-decoration: line-through;
|
||||
@@ -80,11 +86,6 @@
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.Lexical__listItemUnchecked:focus:before,
|
||||
.Lexical__listItemChecked:focus:before {
|
||||
box-shadow: 0 0 0 2px #a6cdfe;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.Lexical__listItemUnchecked:before {
|
||||
border: 1px solid #999;
|
||||
border-radius: 2px;
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { $isListItemNode, $isListNode, INSERT_CHECK_LIST_COMMAND, insertList, ListNode } from '@lexical/list'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { calculateZoomLevel, isHTMLElement, mergeRegister } from '@lexical/utils'
|
||||
import {
|
||||
$getNearestNodeFromDOMNode,
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
KEY_ENTER_COMMAND,
|
||||
} from 'lexical'
|
||||
import { useEffect } from 'react'
|
||||
import { useApplication } from '../../ApplicationProvider'
|
||||
import { getPrimaryModifier } from '@standardnotes/ui-services'
|
||||
|
||||
export function CheckListPlugin(): null {
|
||||
const application = useApplication()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
const primaryModifier = getPrimaryModifier(application.platform)
|
||||
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_CHECK_LIST_COMMAND,
|
||||
() => {
|
||||
insertList(editor, 'check')
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerRootListener((rootElement, prevElement) => {
|
||||
function handleCheckItemEvent(event: PointerEvent, callback: () => void) {
|
||||
const target = event.target
|
||||
|
||||
if (target === null || !isHTMLElement(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore clicks on LI that have nested lists
|
||||
const firstChild = target.firstChild
|
||||
|
||||
if (
|
||||
firstChild != null &&
|
||||
isHTMLElement(firstChild) &&
|
||||
(firstChild.tagName === 'UL' || firstChild.tagName === 'OL')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
editor.update(() => {
|
||||
const targetNode = $getNearestNodeFromDOMNode(target)
|
||||
|
||||
const parentNode = targetNode?.getParent()
|
||||
|
||||
if (!$isListNode(parentNode) || parentNode.getListType() !== 'check') {
|
||||
return
|
||||
}
|
||||
|
||||
const rect = target.getBoundingClientRect()
|
||||
|
||||
const listItemElementStyles = getComputedStyle(target)
|
||||
const paddingLeft = parseFloat(listItemElementStyles.paddingLeft) || 0
|
||||
const paddingRight = parseFloat(listItemElementStyles.paddingRight) || 0
|
||||
const lineHeight = parseFloat(listItemElementStyles.lineHeight) || 0
|
||||
|
||||
const checkStyles = getComputedStyle(target, ':before')
|
||||
const checkWidth = parseFloat(checkStyles.width) || 0
|
||||
|
||||
const pageX = event.pageX / calculateZoomLevel(target)
|
||||
|
||||
const isWithinHorizontalThreshold =
|
||||
target.dir === 'rtl'
|
||||
? pageX < rect.right && pageX > rect.right - paddingRight
|
||||
: pageX > rect.left && pageX < rect.left + (checkWidth || paddingLeft)
|
||||
|
||||
const isWithinVerticalThreshold = event.clientY > rect.top && event.clientY < rect.top + lineHeight
|
||||
|
||||
if (isWithinHorizontalThreshold && isWithinVerticalThreshold) {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleClick(event: Event) {
|
||||
handleCheckItemEvent(event as PointerEvent, () => {
|
||||
if (!editor.isEditable()) {
|
||||
return
|
||||
}
|
||||
|
||||
editor.update(() => {
|
||||
const domNode = event.target as HTMLElement
|
||||
|
||||
if (!event.target) {
|
||||
return
|
||||
}
|
||||
|
||||
const node = $getNearestNodeFromDOMNode(domNode)
|
||||
|
||||
if (!$isListItemNode(node)) {
|
||||
return
|
||||
}
|
||||
|
||||
domNode.focus()
|
||||
node.toggleChecked()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handlePointerDown(event: PointerEvent) {
|
||||
handleCheckItemEvent(event, () => {
|
||||
// Prevents caret moving when clicking on check mark
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
|
||||
if (rootElement !== null) {
|
||||
rootElement.addEventListener('click', handleClick)
|
||||
rootElement.addEventListener('pointerdown', handlePointerDown)
|
||||
}
|
||||
|
||||
if (prevElement !== null) {
|
||||
prevElement.removeEventListener('click', handleClick)
|
||||
prevElement.removeEventListener('pointerdown', handlePointerDown)
|
||||
}
|
||||
}),
|
||||
editor.registerCommand(
|
||||
KEY_ENTER_COMMAND,
|
||||
() => {
|
||||
if (!application.keyboardService.activeModifiers.has(primaryModifier)) {
|
||||
return false
|
||||
}
|
||||
const selection = $getSelection()
|
||||
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
|
||||
return false
|
||||
}
|
||||
const focusNode = selection.focus.getNode()
|
||||
const parent = focusNode.getParent()
|
||||
const node = $isListItemNode(parent) ? parent : focusNode
|
||||
if (!$isListItemNode(node) || node.getParent<ListNode>()?.getListType() !== 'check') {
|
||||
return false
|
||||
}
|
||||
node.toggleChecked()
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
application.keyboardService.registerExternalKeyboardShortcutHelpItem({
|
||||
platform: application.platform,
|
||||
modifiers: [primaryModifier],
|
||||
key: 'Enter',
|
||||
category: 'Super notes',
|
||||
description: 'Toggle checklist item',
|
||||
}),
|
||||
)
|
||||
}, [application.keyboardService, application.platform, editor])
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user