From 6d770dbfa67b4a0a81a1554ad1edf7c28b527b45 Mon Sep 17 00:00:00 2001 From: xiaodong zuo Date: Fri, 26 Aug 2022 09:13:19 +0800 Subject: [PATCH] feat: Double-link: In-line cursor handling, e.g., up, down, left,right, backspace keys --- .../common/src/lib/text/EditableText.tsx | 19 +++++- .../common/src/lib/text/slate-utils.ts | 13 +++- .../src/components/text-manage/TextManage.tsx | 59 ++++++++++++++++++- 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/libs/components/common/src/lib/text/EditableText.tsx b/libs/components/common/src/lib/text/EditableText.tsx index 8637246d7d..3722b90c3b 100644 --- a/libs/components/common/src/lib/text/EditableText.tsx +++ b/libs/components/common/src/lib/text/EditableText.tsx @@ -41,7 +41,12 @@ import { InlineDate, withDate } from './plugins/date'; import { DoubleLinkComponent } from './plugins/DoubleLink'; import { LinkComponent, LinkModal, withLinks, wrapLink } from './plugins/link'; import { InlineRefLink } from './plugins/reflink'; -import { Contents, isSelectAll, SlateUtils } from './slate-utils'; +import { + Contents, + isSelectAll, + SelectionStartAndEnd, + SlateUtils, +} from './slate-utils'; import { getCommentsIdsOnTextNode, getExtraPropertiesFromEditorOutmostNode, @@ -88,8 +93,12 @@ export interface TextProps { /** Backspace event */ handleBackSpace?: ({ isCollAndStart, + splitContents, + selection, }: { isCollAndStart: boolean; + splitContents: Contents; + selection: SelectionStartAndEnd; }) => boolean | undefined | Promise; /** Whether markdown is supported */ supportMarkdown?: boolean; @@ -464,7 +473,13 @@ export const Text = forwardRef((props, ref) => { if (!isCool) { hideInlineMenu && hideInlineMenu(); } - preventBindIfNeeded(handleBackSpace)(e, { isCollAndStart }); + const selection = utils.current.getSelectionStartAndEnd(); + const splitContents = utils.current.getSplitContentsBySelection(); + preventBindIfNeeded(handleBackSpace)(e, { + isCollAndStart, + selection, + splitContents, + }); }; const onTab = (e: KeyboardEvent) => { diff --git a/libs/components/common/src/lib/text/slate-utils.ts b/libs/components/common/src/lib/text/slate-utils.ts index 29639c3e05..cbe9a24ed3 100644 --- a/libs/components/common/src/lib/text/slate-utils.ts +++ b/libs/components/common/src/lib/text/slate-utils.ts @@ -503,7 +503,7 @@ Editor.after = function ( return target; }; -type SelectionStartAndEnd = { +export type SelectionStartAndEnd = { selectionStart: Point; selectionEnd: Point; }; @@ -517,6 +517,10 @@ export type Contents = { content: Descendant[]; isEmpty: boolean; }; + contentSelection: { + content: Descendant[]; + isEmpty: boolean; + }; }; class SlateUtils { @@ -572,6 +576,7 @@ class SlateUtils { anchor: point1, focus: point2, }); + if (!fragment.length) { console.error('Debug information:', point1, point2, fragment); throw new Error('Failed to get content between!'); @@ -602,7 +607,7 @@ class SlateUtils { for (let i = 0; i < fragmentChildren.length; i++) { const child = fragmentChildren[i]; if ('type' in child && child.type === 'link') { - i !== fragmentChildren.length - 1 && textChildren.push(child); + textChildren.push(child); continue; } if (!('text' in child)) { @@ -638,6 +643,10 @@ class SlateUtils { content: this.getContentBetween(selectionEnd, end), isEmpty: Point.equals(end, selectionEnd), }, + contentSelection: { + content: this.getContentBetween(selectionStart, selectionEnd), + isEmpty: false, + }, } as Contents; } diff --git a/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx b/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx index e42a85c5ed..fa15639172 100644 --- a/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx +++ b/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ import { + CustomText, Text, type SlateUtils, type TextProps, @@ -108,7 +109,7 @@ const findLowestCommonAncestor = async ( export const TextManage = forwardRef( (props, ref) => { - const { block, editor, ...otherOptions } = props; + const { block, editor, handleBackSpace, ...otherOptions } = props; const defaultRef = useRef(null); // Maybe there is a better way const textRef = @@ -446,6 +447,61 @@ export const TextManage = forwardRef( } } }; + + const onBackspace: TextProps['handleBackSpace'] = async props => { + const { isCollAndStart, splitContents, selection } = props; + if (!isCollAndStart) { + const { + contentBeforeSelection, + contentAfterSelection, + contentSelection, + } = splitContents; + const { selectionStart, selectionEnd } = selection; + let hasDeleteDoubleLink = false; + + const beforeContent = [...contentBeforeSelection.content]; + const lastItem = beforeContent[beforeContent.length - 1]; + if ( + 'linkType' in lastItem && + lastItem.linkType === 'doubleLink' + ) { + if ( + selectionStart === selectionEnd || + selectionStart.offset + ) { + beforeContent.splice(beforeContent.length - 1, 1); + hasDeleteDoubleLink = true; + } + } + + const afterContent = [...contentAfterSelection.content]; + const beginItem = afterContent[0]; + const lastSel = contentSelection.content[0]; + if ( + selectionEnd.offset && + 'linkType' in beginItem && + beginItem.linkType === 'doubleLink' && + 'linkType' in lastSel && + lastSel.linkType === 'doubleLink' + ) { + afterContent.splice(0, 1); + hasDeleteDoubleLink = true; + } + + if (hasDeleteDoubleLink) { + await block.setProperty('text', { + value: [ + ...beforeContent, + ...afterContent, + ] as CustomText[], + }); + return true; + } + } + + return (handleBackSpace && handleBackSpace(props)) || false; + }; + if (!properties || !properties.text) { return <>; } @@ -467,6 +523,7 @@ export const TextManage = forwardRef( handleUndo={onUndo} handleRedo={onRedo} handleEsc={onKeyboardEsc} + handleBackSpace={onBackspace} {...otherOptions} /> );