diff --git a/src/ed.css b/src/ed.css index 6c4f353..88f70b1 100644 --- a/src/ed.css +++ b/src/ed.css @@ -72,6 +72,7 @@ img { } .ProseMirror-content > blockquote:before { font-size: 250%; + line-height: 100%; content: '“'; font-family: Georgia; diff --git a/src/ed.js b/src/ed.js index ae2d172..dad41f9 100644 --- a/src/ed.js +++ b/src/ed.js @@ -129,52 +129,23 @@ export default class Ed { // MUTATION this._content.splice(index, 1, block) this.onChange() - - // Trigger remeasure - this.pm.signal('draw') - } - updatePlaceholderHeights (changes) { - // Do this in a batch, with one widget remeasure/move - for (let i = 0, len = changes.length; i < len; i++) { - const change = changes[i] - // TODO do this with standard pm.tr interface, not direct DOM - const placeholder = document.querySelector(`.EdSchemaMedia[grid-id="${change.id}"]`) - placeholder.style.height = change.height + 'px' - } - this.pm.signal('draw') } replaceBlock (index, block) { let content = this.getContent() // MUTATION content.splice(index, 1, block) - this._content = content // Render - this.setContent(content, false) + this._setMergedContent(content) } insertBlocks (index, blocks) { const content = this.getContent() // MUTATION const newContent = arrayInsertAll(content, index, blocks) - this._content = newContent // Render - this.setContent(newContent, false) + this._setMergedContent(newContent) // Signal this.onChange() } - setContent (content, needsMerge = true) { - if (needsMerge) { - this._content = mergeContent(this.getContent(), content) - } else { - this._content = content - } - let doc = GridToDoc(this._content) - // Cache selection to restore after DOM update - let selection = fixSelection(this.pm.selection, doc) - // Populate ProseMirror - this.pm.setDoc(doc, selection) - // Let widgets know to update - this.pm.signal('ed.content.changed') - } insertPlaceholders (index, count) { let toInsert = [] let ids = [] @@ -203,6 +174,20 @@ export default class Ed { let doc = this.pm.getContent() return DocToGrid(dom, doc, this._content) } + setContent (content) { + const merged = mergeContent(this.getContent(), content) + this._setMergedContent(merged) + } + _setMergedContent (content) { + this._content = content + let doc = GridToDoc(this._content) + // Cache selection to restore after DOM update + let selection = fixSelection(this.pm.selection, doc) + // Populate ProseMirror + this.pm.setDoc(doc, selection) + // Let widgets know to update + this.pm.signal('ed.content.changed') + } } // Util diff --git a/src/plugins/widget-base.js b/src/plugins/widget-base.js index aeca814..4f8df2e 100644 --- a/src/plugins/widget-base.js +++ b/src/plugins/widget-base.js @@ -1,3 +1,7 @@ +function stopPropagation (event) { + event.stopPropagation() +} + export default class WidgetBase { static type () { return 'base -- extend me' } constructor (options) { @@ -15,29 +19,23 @@ export default class WidgetBase { this.el = document.createElement('div') this.el.setAttribute('grid-id', options.id) this.initialBlock = options.initialBlock - this.el.style.position = 'absolute' - this.move(options.initialRectangle) - options.widgetContainer.appendChild(this.el) + this.container = options.widgetContainer + this.container.appendChild(this.el) + // Don't let contenteditable flow get events + this.el.addEventListener('mousedown', stopPropagation) + this.el.addEventListener('mouseup', stopPropagation) + this.el.addEventListener('click', stopPropagation) + this.el.addEventListener('keydown', stopPropagation) + this.el.addEventListener('keyup', stopPropagation) + this.el.addEventListener('keypress', stopPropagation) } teardown () { this.el.parentNode.removeChild(this.el) } - move (rectangle) { - if (this.top !== rectangle.top) { - this.el.style.top = rectangle.top + 'px' - this.top = rectangle.top - } - if (this.left !== rectangle.left) { - this.el.style.left = rectangle.left + 'px' - this.left = rectangle.left - } - if (this.width !== rectangle.width) { - this.el.style.width = rectangle.width + 'px' - this.width = rectangle.width - } - if (this.height !== rectangle.height) { - this.el.style.height = rectangle.height + 'px' - this.height = rectangle.height + checkMounted (container) { + if (this.el && this.container !== container) { + this.container = container + this.container.appendChild(this.el) } } show () { diff --git a/src/plugins/widget-iframe.js b/src/plugins/widget-iframe.js index 29f5879..3cd9982 100644 --- a/src/plugins/widget-iframe.js +++ b/src/plugins/widget-iframe.js @@ -19,6 +19,7 @@ export default class WidgetIframe extends WidgetBase { super(options) this.postInitialBlock = postInitialBlock.bind(this) this.postMessage = postMessage.bind(this) + this.height = 1 this.frame = document.createElement('iframe') this.frame.setAttribute('grid-id', options.id) @@ -27,9 +28,10 @@ export default class WidgetIframe extends WidgetBase { this.frame.addEventListener('load', this.postInitialBlock) } this.frame.src = this.src() + this.frame.style.border = 'none' + this.frame.style.width = '100%' + this.setHeight(240) this.el.appendChild(this.frame) - - this.height = 100 } teardown () { if (this.initialBlock) { @@ -41,4 +43,10 @@ export default class WidgetIframe extends WidgetBase { // Don't measure from outside: iframes report own height return this.height } + setHeight (height) { + if (this.height !== height) { + this.height = height + this.frame.style.height = height + 'px' + } + } } diff --git a/src/plugins/widget.js b/src/plugins/widget.js index 5586def..849e0c6 100644 --- a/src/plugins/widget.js +++ b/src/plugins/widget.js @@ -32,14 +32,7 @@ function onDOMChanged () { throw new Error('Bad placeholder!') } inDoc.push(id) - const rect = el.getBoundingClientRect() - const rectangle = { - top: rect.top + window.scrollY, - left: rect.left + window.scrollX, - width: rect.width, - height: rect.height - } - this.checkWidget(id, type, rectangle) + this.checkWidget(id, type, el) } // Hide or show widgets @@ -54,25 +47,6 @@ function onDOMChanged () { } } - // Measure inner heights of widgets - let heightChanges = [] - for (let i = 0, len = inDOM.length; i < len; i++) { - const id = inDOM[i] - const widget = this.widgets[id] - if (!widget.shown) continue - const innerHeight = widget.getHeight() - if (innerHeight !== widget.height) { - heightChanges.push({ - id: id, - height: innerHeight - }) - } - } - if (heightChanges.length) { - // Will trigger a redraw / this onDOMChanged again - this.ed.updatePlaceholderHeights(heightChanges) - } - // Signal widgets initialized if first if (!this.initialized) { this.initialized = true @@ -80,7 +54,7 @@ function onDOMChanged () { } } -function checkWidget (id, type, rectangle) { +function checkWidget (id, type, el) { let widget = this.widgets[id] if (widget && widget.type !== type) { // Remove it @@ -89,15 +63,16 @@ function checkWidget (id, type, rectangle) { widget = null } if (widget) { - // Move it - widget.move(rectangle) + // Check that widget container is still parent + // For cut and paste, delete and undo + widget.checkMounted(el) } else { // Make it - this.initializeWidget(id, type, rectangle) + this.initializeWidget(id, type, el) } } -function initializeWidget (id, type, rectangle) { +function initializeWidget (id, type, el) { let Widget = WidgetTypes[type] || WidgetTypes.react let initialBlock = this.ed.getBlock(id) @@ -106,8 +81,7 @@ function initializeWidget (id, type, rectangle) { ed: this.ed, id, type, - widgetContainer: this.el, - initialRectangle: rectangle, + widgetContainer: el, initialBlock: initialBlock }) @@ -128,11 +102,9 @@ function onIframeMessage (message) { this.ed.updateMediaBlock(block) break case 'height': - if (isNaN(message.data.payload)) throw new Error('Iframe height message with non-numeric payload') - this.ed.updatePlaceholderHeights([{ - id: message.data.id, - height: message.data.payload - }]) + const {id, payload} = message.data + if (isNaN(payload)) throw new Error('Iframe height message with non-numeric payload') + this.widgets[id].setHeight(payload) break case 'cursor': default: diff --git a/src/schema/media.css b/src/schema/media.css index f974819..566f2f4 100644 --- a/src/schema/media.css +++ b/src/schema/media.css @@ -2,4 +2,7 @@ margin: 1rem 0; display: block; width: 100%; + border: 1px #CCC solid; + box-sizing: border-box; + overflow: hidden; } diff --git a/src/schema/media.js b/src/schema/media.js index d73fad6..7640694 100644 --- a/src/schema/media.js +++ b/src/schema/media.js @@ -13,8 +13,7 @@ export class Media extends Block { get attrs () { return { id: new Attribute(), - type: new Attribute(), - height: new Attribute({default: 50}) + type: new Attribute() } } } @@ -34,7 +33,7 @@ Media.register('parseDOM', 'div', { } }) Media.prototype.serializeDOM = (node, s) => { - const {id, type, height} = node.attrs + const {id, type} = node.attrs if (!id) { throw new Error('Can not serialize Media div without id') } @@ -46,7 +45,6 @@ Media.prototype.serializeDOM = (node, s) => { class: 'EdSchemaMedia', 'grid-id': id, 'grid-type': type, - style: `height: ${height};`, contenteditable: 'false' } )