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 TableActionMenuPlugin from './Plugins/TableCellActionMenuPlugin'
|
||||||
import ToolbarPlugin from './Plugins/ToolbarPlugin/ToolbarPlugin'
|
import ToolbarPlugin from './Plugins/ToolbarPlugin/ToolbarPlugin'
|
||||||
import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||||
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin'
|
|
||||||
import RemoteImagePlugin from './Plugins/RemoteImagePlugin/RemoteImagePlugin'
|
import RemoteImagePlugin from './Plugins/RemoteImagePlugin/RemoteImagePlugin'
|
||||||
import CodeOptionsPlugin from './Plugins/CodeOptionsPlugin/CodeOptions'
|
import CodeOptionsPlugin from './Plugins/CodeOptionsPlugin/CodeOptions'
|
||||||
import { SuperSearchContextProvider } from './Plugins/SearchPlugin/Context'
|
import { SuperSearchContextProvider } from './Plugins/SearchPlugin/Context'
|
||||||
@@ -35,6 +34,7 @@ import { SearchPlugin } from './Plugins/SearchPlugin/SearchPlugin'
|
|||||||
import AutoLinkPlugin from './Plugins/AutoLinkPlugin/AutoLinkPlugin'
|
import AutoLinkPlugin from './Plugins/AutoLinkPlugin/AutoLinkPlugin'
|
||||||
import DatetimePlugin from './Plugins/DateTimePlugin/DateTimePlugin'
|
import DatetimePlugin from './Plugins/DateTimePlugin/DateTimePlugin'
|
||||||
import PasswordPlugin from './Plugins/PasswordPlugin/PasswordPlugin'
|
import PasswordPlugin from './Plugins/PasswordPlugin/PasswordPlugin'
|
||||||
|
import { CheckListPlugin } from './Plugins/CheckListPlugin'
|
||||||
|
|
||||||
type BlocksEditorProps = {
|
type BlocksEditorProps = {
|
||||||
onChange?: (value: string, preview: string) => void
|
onChange?: (value: string, preview: string) => void
|
||||||
|
|||||||
@@ -57,6 +57,12 @@
|
|||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.Lexical__listItemChecked {
|
.Lexical__listItemChecked {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
@@ -80,11 +86,6 @@
|
|||||||
left: auto;
|
left: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
.Lexical__listItemUnchecked:focus:before,
|
|
||||||
.Lexical__listItemChecked:focus:before {
|
|
||||||
box-shadow: 0 0 0 2px #a6cdfe;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
.Lexical__listItemUnchecked:before {
|
.Lexical__listItemUnchecked:before {
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
border-radius: 2px;
|
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