diff --git a/packages/engine/src/change/event.ts b/packages/engine/src/change/event.ts index d4f59e4d..e7ddb423 100644 --- a/packages/engine/src/change/event.ts +++ b/packages/engine/src/change/event.ts @@ -10,7 +10,7 @@ import { CARD_ELEMENT_KEY } from '../constants/card'; import { ClipboardData } from '../types/clipboard'; import { DATA_ELEMENT, UI } from '../constants'; import { $ } from '../node'; -import { isMobile, isSafari } from '../utils'; +import { isAndroid, isMobile, isSafari } from '../utils'; type GlobalEventType = 'root' | 'window' | 'container' | 'document'; class ChangeEvent implements ChangeEventInterface { @@ -61,6 +61,7 @@ class ChangeEvent implements ChangeEventInterface { onInput(callback: EventListener) { const { bindInput } = this.options; if (bindInput && !bindInput()) return; + let androidCustomeListComposingNode: NodeInterface | null = null; // 处理中文输入法状态 // https://developer.mozilla.org/en-US-US/docs/Web/Events/compositionstart this.onContainer('compositionstart', (event) => { @@ -101,8 +102,9 @@ class ChangeEvent implements ChangeEventInterface { //https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes this.onContainer('beforeinput', (event: InputEvent) => { if (this.engine.readonly) return; - // safari 组合输入法会直接输入字符 - if (isSafari && event.data === '@') { + // safari 组合输入法会直接插入@字符,这里统一全部拦截输入@字符的时候再去触发@事件 + if (event.data === '@') { + // 如果没有要对 @ 字符处理的就不拦截 const result = this.engine.trigger('keydown:at', event); if (result === false) { event.preventDefault(); @@ -112,9 +114,9 @@ class ChangeEvent implements ChangeEventInterface { const { change, card, node, block, list } = this.engine; if (!change.rangePathBeforeCommand) change.cacheRangeBeforeCommand(); + const sourceRange = change.range.get(); // 单独选中卡片或者selection处于卡片边缘,手动删除卡片 - const range = change.range - .get() + const range = sourceRange .cloneRange() .shrinkToTextNode() .enlargeToElementNode(); @@ -128,6 +130,36 @@ class ChangeEvent implements ChangeEventInterface { if (startNode.first()?.name !== 'br') startNode.prepend('
'); } + // 安卓在自定义列表前组合输入的时候会出现字符错乱 + // 解决:在列表下的自定义卡片后面插入一个零宽字符,等组合输入法完成后再删除 + if ( + isAndroid && + startNode.name === 'li' && + range.startOffset === 1 && + node.isCustomize(startNode) && + this.isComposing + ) { + const first = startNode.first(); + const next = first?.next(); + const addTemp = () => { + const zeroText = $('\u200B', null); + first?.after(zeroText); + range.setOffset(zeroText, 1, 1); + change.range.select(range); + androidCustomeListComposingNode = startNode; + }; + if (next?.isText()) { + const text = next.text(); + if (!/^\u200b/.test(text)) { + addTemp(); + } + } else { + if (next?.name === 'br') { + next.remove(); + } + addTemp(); + } + } if (!range.collapsed) { if ( @@ -188,8 +220,41 @@ class ChangeEvent implements ChangeEventInterface { this.engine.hidePlaceholder(); } if (inputTimeout) clearTimeout(inputTimeout); + inputTimeout = setTimeout(() => { if (!this.isComposing) { + // 清理输入前插入到自定义列表的卡片后的零宽字符 + if (isAndroid && androidCustomeListComposingNode) { + const first = androidCustomeListComposingNode.first(); + const next = first?.next(); + if (next?.isText()) { + const text = next.text(); + if (/^\u200b/.test(text)) { + const textNode = next.get(); + textNode?.splitText(1); + textNode?.remove(); + } + } + const range = this.engine.change.range.get(); + const { startNode, startOffset } = range; + if (range.collapsed && startNode?.isText()) { + const text = startNode.text(); + const sufix = text.substring(startOffset); + if (/^\u200b/.test(sufix)) { + startNode.text( + text.substring(0, startOffset) + + sufix.substring(1), + ); + range.setOffset( + startNode, + startOffset, + startOffset, + ); + this.engine.change.range.select(range); + } + } + androidCustomeListComposingNode = null; + } callback(e); // 组合输入法结束后提交协同 this.engine.ot.submitMutationCache(); diff --git a/packages/engine/src/typing/keydown/at.ts b/packages/engine/src/typing/keydown/at.ts index a7add081..f61ba1d4 100644 --- a/packages/engine/src/typing/keydown/at.ts +++ b/packages/engine/src/typing/keydown/at.ts @@ -1,10 +1,8 @@ -import { isMobile } from '../../utils'; import Default from './default'; class At extends Default { hotkey = (event: KeyboardEvent) => event.key === '@' || - (event.shiftKey && event.keyCode === 229 && event.code === 'Digit2') || - (isMobile && event.keyCode === 229); + (event.shiftKey && event.keyCode === 229 && event.code === 'Digit2'); } export default At; diff --git a/packages/engine/src/typing/keydown/index.ts b/packages/engine/src/typing/keydown/index.ts index b908538c..a45a46af 100644 --- a/packages/engine/src/typing/keydown/index.ts +++ b/packages/engine/src/typing/keydown/index.ts @@ -57,11 +57,11 @@ const defaultHandles: Array<{ handle: ShiftEnter, triggerName: 'keydown:shift-enter', }, - { - name: 'at', - handle: At, - triggerName: 'keydown:at', - }, + // { + // name: 'at', + // handle: At, + // triggerName: 'keydown:at', + // }, { name: 'space', handle: Space,