diff --git a/internal_packages/composer/stylesheets/composer.less b/internal_packages/composer/stylesheets/composer.less index 73f674e9b..fd21103b1 100644 --- a/internal_packages/composer/stylesheets/composer.less +++ b/internal_packages/composer/stylesheets/composer.less @@ -460,7 +460,6 @@ body.platform-win32 { z-index: 10; } img.n1-overlaid-component-anchor-container { - display: block; border: 0; } .toggle-serialized { diff --git a/src/components/contenteditable/contenteditable.cjsx b/src/components/contenteditable/contenteditable.cjsx index 29f73f18d..6b851af6f 100644 --- a/src/components/contenteditable/contenteditable.cjsx +++ b/src/components/contenteditable/contenteditable.cjsx @@ -42,6 +42,8 @@ render: function() { class Contenteditable extends React.Component @displayName: "Contenteditable" + @IgnoreMutationClassName: 'ignore-mutations' + @propTypes: # The current html state, as a string, of the contenteditable. value: React.PropTypes.string @@ -298,16 +300,27 @@ class Contenteditable extends React.Component ########################### Event Handlers ########################### ###################################################################### - # Every time the contents of the contenteditable DOM node change, the - # `_onDOMMutated` event gets fired. + # Every time the contents of the contenteditable DOM node change due to a user + # action, the `_onDOMMutated` event gets fired. # # If we are in the middle of an `atomic` change transaction, we ignore # those changes. # + # If any target element of the mutations contains `IgnoreMutationClassName` in + # its className, we will also ignore the mutations. This is order to support + # extensions that might imperatively want to mutate the contenteditable + # wihtout it being due to a direct user action, i.e. without wanting to + # trigger this callback. For example, `OverlaidComponents` will mutate the + # dimensions of its anchor elements without the user explicitly doing so. + # # At all other times we take the change, apply various filters to the # new content, then notify our parent that the content has been updated. + # _onDOMMutated: (mutations) => return unless mutations and mutations.length > 0 + for mutation in mutations + if mutation.target?.className?.includes(Contenteditable.IgnoreMutationClassName) + return @_mutationObserver.disconnect() @setInnerState dragging: false if @innerState.dragging diff --git a/src/components/contenteditable/editor-api.coffee b/src/components/contenteditable/editor-api.coffee index 451c1067d..ff3f1cec6 100644 --- a/src/components/contenteditable/editor-api.coffee +++ b/src/components/contenteditable/editor-api.coffee @@ -2,7 +2,7 @@ _ = require 'underscore' {DOMUtils} = require 'nylas-exports' React = require 'react' ExtendedSelection = require './extended-selection' -OverlaidComponents = require('../overlaid-components/overlaid-components').default +OverlaidComponents = null # An extended interface of execCommand # @@ -73,8 +73,15 @@ class EditorAPI normalize: -> @rootNode.normalize(); @ insertCustomComponent: (componentKey, props = {}) -> - anchorTag = OverlaidComponents.buildAnchorTag(componentKey, props) + OverlaidComponents ?= require('../overlaid-components/overlaid-components').default + {anchorId, anchorTag} = OverlaidComponents.buildAnchorTag(componentKey, props) @insertHTML(anchorTag) + return anchorId + + removeCustomComponentByAnchorId: (anchorId) -> + return unless anchorId + node = @rootNode.querySelector("img[data-overlay-id=\"#{anchorId}\"]") + node?.parentNode.removeChild(node) ######################################################################## ####################### execCommand Delegation ######################### diff --git a/src/components/overlaid-components/overlaid-components.jsx b/src/components/overlaid-components/overlaid-components.jsx index d29379018..cec9ceb89 100644 --- a/src/components/overlaid-components/overlaid-components.jsx +++ b/src/components/overlaid-components/overlaid-components.jsx @@ -3,6 +3,7 @@ import React from 'react' import ReactDOM from 'react-dom' import Utils from '../../flux/models/utils' import CustomContenteditableComponents from './custom-contenteditable-components' +import {IgnoreMutationClassName} from '../contenteditable/contenteditable' import {ANCHOR_CLASS, IMG_SRC} from './anchor-constants' const MUTATION_CONFIG = { @@ -19,6 +20,7 @@ export default class OverlaidComponents extends React.Component { static propTypes = { children: React.PropTypes.node, + className: React.PropTypes.string, exposedProps: React.PropTypes.object, } @@ -34,11 +36,17 @@ export default class OverlaidComponents extends React.Component { } static buildAnchorTag(componentKey, props = {}, existingId = null, style = "") { - const id = existingId || Utils.generateTempId() - let className = ANCHOR_CLASS - if (props.className) { className = `${className} ${props.className}` } + const overlayId = existingId || Utils.generateTempId() + let className = `${IgnoreMutationClassName} ${ANCHOR_CLASS}` + if (props.className) { + className = `${className} ${props.className}` + } const propsStr = OverlaidComponents.propsToDOMAttr(props); - return `` + return { + anchorId: overlayId, + anchorTag: + ``, + } } constructor(props) { @@ -168,7 +176,7 @@ export default class OverlaidComponents extends React.Component { const data = this._anchorData[id]; if (!data) { throw new Error("No mounted rect for #{id}") } - const style = {left: data.left, top: data.top, position: "relative"} + const style = {left: data.left, top: data.top, position: 'absolute'} const componentData = CustomContenteditableComponents.get(data.componentKey); if (!componentData) { @@ -189,13 +197,14 @@ export default class OverlaidComponents extends React.Component { const el = React.createElement(component, props) const wrap = ( -
{el} -
+ ) els.push(wrap) @@ -210,8 +219,9 @@ export default class OverlaidComponents extends React.Component { } render() { + const {className} = this.props return ( -
+
{this.props.children}
diff --git a/src/components/overlaid-components/overlaid-composer-extension.es6 b/src/components/overlaid-components/overlaid-composer-extension.es6 index 3d099992a..677a14d85 100644 --- a/src/components/overlaid-components/overlaid-composer-extension.es6 +++ b/src/components/overlaid-components/overlaid-composer-extension.es6 @@ -75,7 +75,7 @@ export default class OverlaidComposerExtension extends ComposerExtension { const matcher = self.overlayMatches(self._serializedExtractRe(), outDraft.body); for (const match of matcher) { - const anchorTag = OverlaidComponents.buildAnchorTag(match.dataComponentKey, match.dataComponentProps, match.dataOverlayId, match.dataStyle); + const {anchorTag} = OverlaidComponents.buildAnchorTag(match.dataComponentKey, match.dataComponentProps, match.dataOverlayId, match.dataStyle); outBody = outBody.replace(OverlaidComposerExtension._serializedReplacerRe(match.dataOverlayId), anchorTag) } diff --git a/src/pro b/src/pro index 8fcfe1357..b0d3088fd 160000 --- a/src/pro +++ b/src/pro @@ -1 +1 @@ -Subproject commit 8fcfe13577d014ad4583b5f23b639047f1cb5789 +Subproject commit b0d3088fd8d9f853d82872ddd5aae14cc02447cf