From 234d23887bfcb19c3591deb391688ec9d4e49a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Ku=CC=88hn?= Date: Tue, 27 Apr 2021 21:07:12 +0200 Subject: [PATCH] fix: prevent a bug for node views when pressing enter on iOS, fix #1214 --- packages/core/src/NodeView.ts | 45 +++++++++++++++++++++++----- packages/core/src/utilities/isiOS.ts | 12 ++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 packages/core/src/utilities/isiOS.ts diff --git a/packages/core/src/NodeView.ts b/packages/core/src/NodeView.ts index f4bfe96f93..3f72bb2a34 100644 --- a/packages/core/src/NodeView.ts +++ b/packages/core/src/NodeView.ts @@ -3,6 +3,7 @@ import { NodeSelection } from 'prosemirror-state' import { Node as ProseMirrorNode } from 'prosemirror-model' import { Editor as CoreEditor } from './Editor' import { Node } from './Node' +import isiOS from './utilities/isiOS' import { NodeViewRendererProps } from './types' interface NodeViewRendererOptions { @@ -176,22 +177,50 @@ export class NodeView impleme } ignoreMutation(mutation: MutationRecord | { type: 'selection', target: Element }) { - if (mutation.type === 'selection') { - if (this.node.isLeaf) { - return true - } + if (!this.dom || !this.contentDOM) { + return true + } + + // a leaf/atom node is like a black box for ProseMirror + // and should be fully handled by the node view + if (this.node.isLeaf) { + return true + } + // ProseMirror should handle any selections + if (mutation.type === 'selection') { return false } - if (!this.contentDOM) { + // try to prevent a bug on iOS that will break node views on enter + // this is because ProseMirror can’t preventDispatch on enter + // this will lead to a re-render of the node view on enter + // see: https://github.com/ueberdosis/tiptap/issues/1214 + if (this.dom.contains(mutation.target) && mutation.type === 'childList' && isiOS()) { + const changedNodes = [ + ...Array.from(mutation.addedNodes), + ...Array.from(mutation.removedNodes), + ] as HTMLElement[] + + // we’ll check if every changed node is contentEditable + // to make sure it’s probably mutated by ProseMirror + if (changedNodes.every(node => node.isContentEditable)) { + return false + } + } + + // we will allow mutation contentDOM with attributes + // so we can for example adding classes within our node view + if (this.contentDOM === mutation.target && mutation.type === 'attributes') { return true } - const contentDOMHasChanged = !this.contentDOM.contains(mutation.target) - || (this.contentDOM === mutation.target && mutation.type === 'attributes') + // ProseMirror should handle any changes within contentDOM + if (this.contentDOM.contains(mutation.target)) { + return false + } - return contentDOMHasChanged + return true } updateAttributes(attributes: {}) { diff --git a/packages/core/src/utilities/isiOS.ts b/packages/core/src/utilities/isiOS.ts new file mode 100644 index 0000000000..7e611e1bf4 --- /dev/null +++ b/packages/core/src/utilities/isiOS.ts @@ -0,0 +1,12 @@ +export default function isiOS(): boolean { + return [ + 'iPad Simulator', + 'iPhone Simulator', + 'iPod Simulator', + 'iPad', + 'iPhone', + 'iPod', + ].includes(navigator.platform) + // iPad on iOS 13 detection + || (navigator.userAgent.includes('Mac') && 'ontouchend' in document) +}