fix(mail-merge): Switch to using new overlaid components api

Summary:
- Fixes several selection and focus issues along the way
- Can now preview what tokens will look like when not editing
- Adds decorator to listen to mail merge session changes and removes a bunch of duplicated code
- Gets rid of all imperative code (and specs) that had to imperatively
reach into the dom to update the tokens

Test Plan: - Unit tests

Reviewers: bengotow, evan

Reviewed By: evan

Differential Revision: https://phab.nylas.com/D2989
This commit is contained in:
Juan Tejada 2016-05-27 11:02:27 -07:00
parent 6ba667daa2
commit 3398640e86
6 changed files with 44 additions and 15 deletions

View file

@ -460,7 +460,6 @@ body.platform-win32 {
z-index: 10;
}
img.n1-overlaid-component-anchor-container {
display: block;
border: 0;
}
.toggle-serialized {

View file

@ -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

View file

@ -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 #########################

View file

@ -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 `<img class="${className}" src="${IMG_SRC}" data-overlay-id="${id}" data-component-props="${propsStr}" data-component-key="${componentKey}" style="${style}">`
return {
anchorId: overlayId,
anchorTag:
`<img class="${className}" src="${IMG_SRC}" data-overlay-id="${overlayId}" data-component-props="${propsStr}" data-component-key="${componentKey}" style="${style}">`,
}
}
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 = (
<div
<span
key={id}
className={OverlaidComponents.WRAP_CLASS}
style={style}
data-overlay-id={id}
>
{el}
</div>
</span>
)
els.push(wrap)
@ -210,8 +219,9 @@ export default class OverlaidComponents extends React.Component {
}
render() {
const {className} = this.props
return (
<div className="overlaid-components-wrap" style={{position: "relative"}}>
<div className={`overlaid-components-wrap ${className}`} style={{position: "relative"}}>
<div className="anchor-container" ref="anchorContainer">
{this.props.children}
</div>

View file

@ -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)
}

@ -1 +1 @@
Subproject commit 8fcfe13577d014ad4583b5f23b639047f1cb5789
Subproject commit b0d3088fd8d9f853d82872ddd5aae14cc02447cf