From e468a172b9c0b8eadc3c0f817965efdc6c5d8ffc Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sun, 7 Apr 2019 14:20:33 +0200 Subject: [PATCH 1/7] Add textable in Sorter --- src/utils/Sorter.js | 156 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 29 deletions(-) diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index 12362a946..db445f2e5 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -1,6 +1,6 @@ import Backbone from 'backbone'; import { isString, isFunction, isArray, result } from 'underscore'; -import { on, off, matches, getElement } from 'utils/mixins'; +import { on, off, matches, getElement, getPointerEvent } from 'utils/mixins'; const $ = Backbone.$; module.exports = Backbone.View.extend({ @@ -51,6 +51,7 @@ module.exports = Backbone.View.extend({ this.canvasRelative = o.canvasRelative || 0; this.selectOnEnd = !o.avoidSelectOnEnd; this.scale = o.scale; + this.activeTextModel = null; if (this.em && this.em.on) { this.em.on('change:canvasOffset', this.udpateOffset); @@ -96,6 +97,39 @@ module.exports = Backbone.View.extend({ this.dropContent = content; }, + updateTextViewCursorPosition(e) { + const { em } = this; + const Canvas = em.get('Canvas'); + const targetDoc = Canvas.getDocument(); + const poiner = getPointerEvent(e); + let range = null; + + if (targetDoc.caretRangeFromPoint) { + // Chrome + range = targetDoc.caretRangeFromPoint(poiner.clientX, poiner.clientY); + } else if (e.rangeParent) { + // Firefox + range = targetDoc.createRange(); + range.setStart(e.rangeParent, e.rangeOffset); + } + + const sel = Canvas.getWindow().getSelection(); + Canvas.getFrameEl().focus(); + sel.removeAllRanges(); + sel.addRange(range); + }, + + disableEditingOn(model) { + model && model.getView().disableEditing(); + }, + + setContentEditable(model, mode) { + if (model) { + const el = model.getEl(); + if (el.contentEditable != mode) el.contentEditable = mode; + } + }, + /** * Toggle cursor while sorting * @param {Boolean} active @@ -359,7 +393,8 @@ module.exports = Backbone.View.extend({ * @param {Event} e * */ onMove(e) { - const em = this.em; + const ev = e; + const { em, onMoveClb, activeTextModel } = this; this.moved = 1; // Turn placeholder visibile @@ -385,6 +420,7 @@ module.exports = Backbone.View.extend({ this.eventMove = e; //var targetNew = this.getTargetFromEl(e.target); + const sourceModel = this.getSourceModel(); const dims = this.dimsFromTarget(e.target, rX, rY); const target = this.target; const targetModel = this.getTargetModel(target); @@ -392,26 +428,39 @@ module.exports = Backbone.View.extend({ if (!targetModel) plh.style.display = 'none'; this.lastDims = dims; - var pos = this.findPosition(dims, rX, rY); - // If there is a significant changes with the pointer - if ( - !this.lastPos || - (this.lastPos.index != pos.index || this.lastPos.method != pos.method) - ) { - this.movePlaceholder(this.plh, dims, pos, this.prevTargetDim); - if (!this.$plh) this.$plh = $(this.plh); - - // With canvasRelative the offset is calculated automatically for - // each element - if (!this.canvasRelative) { - if (this.offTop) this.$plh.css('top', '+=' + this.offTop + 'px'); - if (this.offLeft) this.$plh.css('left', '+=' + this.offLeft + 'px'); - } + const pos = this.findPosition(dims, rX, rY); + + if (this.isTextableActive(sourceModel, targetModel)) { + this.activeTextModel = targetModel; + this.setContentEditable(targetModel, true); + plh.style.display = 'none'; this.lastPos = pos; + this.updateTextViewCursorPosition(ev); + } else { + this.disableTextable(); + this.activeTextModel = null; + + // If there is a significant changes with the pointer + if ( + !this.lastPos || + (this.lastPos.index != pos.index || this.lastPos.method != pos.method) + ) { + this.movePlaceholder(this.plh, dims, pos, this.prevTargetDim); + if (!this.$plh) this.$plh = $(this.plh); + + // With canvasRelative the offset is calculated automatically for + // each element + if (!this.canvasRelative) { + if (this.offTop) this.$plh.css('top', '+=' + this.offTop + 'px'); + if (this.offLeft) this.$plh.css('left', '+=' + this.offLeft + 'px'); + } + + this.lastPos = pos; + } } - if (typeof this.onMoveClb === 'function') this.onMoveClb(e); + isFunction(onMoveClb) && onMoveClb(e); em && em.trigger('sorter:drag', { @@ -424,6 +473,15 @@ module.exports = Backbone.View.extend({ }); }, + isTextableActive(src, trg) { + return src && src.get('textable') && trg && trg.is('text'); + }, + + disableTextable() { + const { activeTextModel } = this; + activeTextModel && this.setContentEditable(activeTextModel, false); + }, + /** * Returns true if the elements is in flow, so is not in flow where * for example the component is with float:left @@ -512,20 +570,35 @@ module.exports = Backbone.View.extend({ return result; } + // check if the source is draggable in target + let draggable = srcModel.get('draggable'); + draggable = draggable instanceof Array ? draggable.join(', ') : draggable; + result.dragInfo = draggable; + draggable = isString(draggable) ? this.matches(trg, draggable) : draggable; + result.draggable = draggable; + // Check if the target could accept the source let droppable = trgModel.get('droppable'); droppable = droppable instanceof Backbone.Collection ? 1 : droppable; droppable = droppable instanceof Array ? droppable.join(', ') : droppable; result.dropInfo = droppable; droppable = isString(droppable) ? this.matches(src, droppable) : droppable; + droppable = + trgModel.is('text') && draggable && srcModel.get('textable') + ? 1 + : droppable; result.droppable = droppable; - // check if the source is draggable in target - let draggable = srcModel.get('draggable'); - draggable = draggable instanceof Array ? draggable.join(', ') : draggable; - result.dragInfo = draggable; - draggable = isString(draggable) ? this.matches(trg, draggable) : draggable; - result.draggable = draggable; + console.log( + `trgModel.is('text')`, + trgModel.is('text'), + `srcModel.get('textable')`, + srcModel.get('textable'), + 'droppable', + droppable, + 'draggable', + draggable + ); if (!droppable || !draggable) { result.valid = false; @@ -953,6 +1026,7 @@ module.exports = Backbone.View.extend({ this.dragHelper = null; } + this.disableTextable(); this.selectTargetModel(); this.toggleSortCursor(); @@ -967,7 +1041,7 @@ module.exports = Backbone.View.extend({ * @param {Object} pos Object with position coordinates * */ move(dst, src, pos) { - var em = this.em; + const { em, activeTextModel } = this; const srcEl = getElement(src); em && em.trigger('component:dragEnd:before', dst, srcEl, pos); // @depricated var warns = []; @@ -981,8 +1055,21 @@ module.exports = Backbone.View.extend({ var dropInfo = validResult.dropInfo; var dragInfo = validResult.dragInfo; var dropContent = this.dropContent; - droppable = - validResult.trgModel instanceof Backbone.Collection ? 1 : droppable; + const { trgModel } = validResult; + droppable = trgModel instanceof Backbone.Collection ? 1 : droppable; + const modelIsTextable = model.get('textable'); + const targetIsTextView = trgModel && trgModel.is('text'); + + console.log( + 'droppable', + droppable, + 'draggable', + draggable, + 'modelIsTextable', + modelIsTextable, + 'targetIsTextView', + targetIsTextView + ); if (targetCollection && droppable && draggable) { index = pos.method === 'after' ? index + 1 : index; @@ -993,7 +1080,7 @@ module.exports = Backbone.View.extend({ opts.temporary = 1; modelTemp = targetCollection.add({}, { ...opts }); - if (model) { + if (model.collection) { modelToDrop = model.collection.remove(model, { temporary: 1 }); } } else { @@ -1002,7 +1089,14 @@ module.exports = Backbone.View.extend({ opts.avoidUpdateStyle = 1; } - created = targetCollection.add(modelToDrop, opts); + if (targetIsTextView && modelIsTextable) { + const viewActive = activeTextModel.getView(); + activeTextModel.trigger('active'); + const { activeRte } = viewActive; + activeRte.insertHTML && activeRte.insertHTML(model.toHTML()); + } else { + created = targetCollection.add(modelToDrop, opts); + } if (!dropContent) { targetCollection.remove(modelTemp); @@ -1010,6 +1104,10 @@ module.exports = Backbone.View.extend({ this.dropContent = null; } + if (targetIsTextView && modelIsTextable) { + this.disableEditingOn(activeTextModel); // TODO disableEditingOn is used only here + } + // This will cause to recalculate children dimensions this.prevTarget = null; } else { From ad81a8b04b791d66432bc0bbe09f6a2193d6a14f Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sun, 7 Apr 2019 14:26:00 +0200 Subject: [PATCH 2/7] Clean Sorter --- src/utils/Sorter.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index db445f2e5..c5711bb6a 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -589,17 +589,6 @@ module.exports = Backbone.View.extend({ : droppable; result.droppable = droppable; - console.log( - `trgModel.is('text')`, - trgModel.is('text'), - `srcModel.get('textable')`, - srcModel.get('textable'), - 'droppable', - droppable, - 'draggable', - draggable - ); - if (!droppable || !draggable) { result.valid = false; } @@ -987,7 +976,6 @@ module.exports = Backbone.View.extend({ * @return void * */ endMove(e) { - var created; const moved = [null]; const docs = this.getDocuments(); const container = this.getContainerEl(); @@ -996,10 +984,7 @@ module.exports = Backbone.View.extend({ off(container, 'mousemove dragover', this.onMove); off(docs, 'mouseup dragend touchend', this.endMove); off(docs, 'keydown', this.rollback); - //this.$document.off('mouseup', this.endMove); - //this.$document.off('keydown', this.rollback); this.plh.style.display = 'none'; - var clsReg = new RegExp('(?:^|\\s)' + this.freezeClass + '(?!\\S)', 'gi'); let src = this.eV; if (src && this.selectOnEnd) { @@ -1060,17 +1045,6 @@ module.exports = Backbone.View.extend({ const modelIsTextable = model.get('textable'); const targetIsTextView = trgModel && trgModel.is('text'); - console.log( - 'droppable', - droppable, - 'draggable', - draggable, - 'modelIsTextable', - modelIsTextable, - 'targetIsTextView', - targetIsTextView - ); - if (targetCollection && droppable && draggable) { index = pos.method === 'after' ? index + 1 : index; var opts = { at: index, noIncrement: 1 }; From f3935285a509d5db4bec71c0ac5db6066da6f93f Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sun, 7 Apr 2019 14:30:33 +0200 Subject: [PATCH 3/7] Update validTarget --- src/utils/Sorter.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index c5711bb6a..e70638500 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -584,9 +584,7 @@ module.exports = Backbone.View.extend({ result.dropInfo = droppable; droppable = isString(droppable) ? this.matches(src, droppable) : droppable; droppable = - trgModel.is('text') && draggable && srcModel.get('textable') - ? 1 - : droppable; + draggable && this.isTextableActive(srcModel, trgModel) ? 1 : droppable; result.droppable = droppable; if (!droppable || !draggable) { From 356952a8967ed0c04eb4bd4d6e3c26424a955ad1 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Mon, 8 Apr 2019 19:13:49 +0200 Subject: [PATCH 4/7] Update getSourceModel for textable components in Sorter --- src/utils/Sorter.js | 51 ++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index e70638500..9b153c05f 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -342,30 +342,40 @@ module.exports = Backbone.View.extend({ * Get the model of the current source element (element to drag) * @return {Model} */ - getSourceModel(source) { - var src = source || this.eV; - let dropContent = this.dropContent; - let dropModel = this.dropModel; - const em = this.em; + getSourceModel(source, { target, avoidChildren = 1 } = {}) { + const { em, eV } = this; + const src = source || eV; + let { dropModel, dropContent } = this; + const isTextable = src => + src && + target && + src.opt.avoidChildren && + this.isTextableActive(src, target); if (dropContent && em) { + if (isTextable(dropModel)) { + dropModel = null; + } + if (!dropModel) { - let comps = em.get('DomComponents').getComponents(); + const comps = em.get('DomComponents').getComponents(); const opts = { + avoidChildren, avoidStore: 1, - avoidChildren: 1, avoidUpdateStyle: 1 }; - let tempModel = comps.add(dropContent, { ...opts, temporary: 1 }); + const tempModel = comps.add(dropContent, { ...opts, temporary: 1 }); dropModel = comps.remove(tempModel, opts); this.dropModel = dropModel instanceof Array ? dropModel[0] : dropModel; + + if (isTextable(dropModel)) { + return this.getSourceModel(src, { target, avoidChildren: 0 }); + } } return dropModel; } - if (src) { - return $(src).data('model'); - } + return src && $(src).data('model'); }, /** @@ -553,9 +563,9 @@ module.exports = Backbone.View.extend({ * @return {Boolean} */ validTarget(trg, src) { - let srcModel = this.getSourceModel(src); + const trgModel = this.getTargetModel(trg); + const srcModel = this.getSourceModel(src, { target: trgModel }); src = srcModel && srcModel.view && srcModel.view.el; - let trgModel = this.getTargetModel(trg); trg = trgModel && trgModel.view && trgModel.view.el; let result = { valid: true, @@ -1040,8 +1050,7 @@ module.exports = Backbone.View.extend({ var dropContent = this.dropContent; const { trgModel } = validResult; droppable = trgModel instanceof Backbone.Collection ? 1 : droppable; - const modelIsTextable = model.get('textable'); - const targetIsTextView = trgModel && trgModel.is('text'); + const isTextableActive = this.isTextableActive(model, trgModel); if (targetCollection && droppable && draggable) { index = pos.method === 'after' ? index + 1 : index; @@ -1061,10 +1070,18 @@ module.exports = Backbone.View.extend({ opts.avoidUpdateStyle = 1; } - if (targetIsTextView && modelIsTextable) { + if (isTextableActive) { const viewActive = activeTextModel.getView(); activeTextModel.trigger('active'); const { activeRte } = viewActive; + console.log( + 'model.toHTML()', + model.toHTML(), + 'el', + model.getEl().outerHTML, + 'model', + model + ); activeRte.insertHTML && activeRte.insertHTML(model.toHTML()); } else { created = targetCollection.add(modelToDrop, opts); @@ -1076,7 +1093,7 @@ module.exports = Backbone.View.extend({ this.dropContent = null; } - if (targetIsTextView && modelIsTextable) { + if (isTextableActive) { this.disableEditingOn(activeTextModel); // TODO disableEditingOn is used only here } From fbf144131c97b1a05f7aaf80fcbecba7ce1edd57 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Tue, 9 Apr 2019 08:28:45 +0200 Subject: [PATCH 5/7] Render the textable element --- src/utils/Sorter.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index 9b153c05f..6b138fc54 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -119,10 +119,6 @@ module.exports = Backbone.View.extend({ sel.addRange(range); }, - disableEditingOn(model) { - model && model.getView().disableEditing(); - }, - setContentEditable(model, mode) { if (model) { const el = model.getEl(); @@ -404,11 +400,10 @@ module.exports = Backbone.View.extend({ * */ onMove(e) { const ev = e; - const { em, onMoveClb, activeTextModel } = this; + const { em, onMoveClb, plh } = this; this.moved = 1; // Turn placeholder visibile - var plh = this.plh; var dsp = plh.style.display; if (!dsp || dsp === 'none') plh.style.display = 'block'; @@ -489,7 +484,7 @@ module.exports = Backbone.View.extend({ disableTextable() { const { activeTextModel } = this; - activeTextModel && this.setContentEditable(activeTextModel, false); + activeTextModel && activeTextModel.getView().disableEditing(); }, /** @@ -1074,15 +1069,19 @@ module.exports = Backbone.View.extend({ const viewActive = activeTextModel.getView(); activeTextModel.trigger('active'); const { activeRte } = viewActive; + const modelEl = model.getEl(); + model.getView().render(); + modelEl.setAttribute('data-gjs-textable', 'true'); + const { outerHTML } = modelEl; console.log( 'model.toHTML()', model.toHTML(), - 'el', - model.getEl().outerHTML, 'model', - model + model, + 'outerHTML', + outerHTML ); - activeRte.insertHTML && activeRte.insertHTML(model.toHTML()); + activeRte.insertHTML && activeRte.insertHTML(outerHTML); } else { created = targetCollection.add(modelToDrop, opts); } @@ -1093,10 +1092,6 @@ module.exports = Backbone.View.extend({ this.dropContent = null; } - if (isTextableActive) { - this.disableEditingOn(activeTextModel); // TODO disableEditingOn is used only here - } - // This will cause to recalculate children dimensions this.prevTarget = null; } else { From 78c65716a714278ea76dc1d5f3e881f6c44efd88 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Tue, 9 Apr 2019 08:49:38 +0200 Subject: [PATCH 6/7] Make the text component understand textable nodes --- src/dom_components/view/ComponentTextView.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dom_components/view/ComponentTextView.js b/src/dom_components/view/ComponentTextView.js index 7be5fe659..ea1ca5567 100644 --- a/src/dom_components/view/ComponentTextView.js +++ b/src/dom_components/view/ComponentTextView.js @@ -88,19 +88,19 @@ module.exports = ComponentView.extend({ model.set('content', content, contentOpt); } else { const clean = model => { - const selectable = !['text', 'default', ''].some(type => - model.is(type) - ); + const textable = !!model.get('textable'); + const selectable = + !['text', 'default', ''].some(type => model.is(type)) || textable; model.set( { editable: selectable && model.get('editable'), selectable: selectable, hoverable: selectable, + removable: textable, + draggable: textable, highlightable: 0, - removable: 0, - draggable: 0, - copyable: 0, - toolbar: '' + copyable: textable, + ...(!textable && { toolbar: '' }) }, opts ); From 29da7d096499000366db7878c8ad44cb99d69925 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Tue, 9 Apr 2019 23:38:52 +0200 Subject: [PATCH 7/7] Update the render of textable components --- src/dom_components/view/ComponentView.js | 13 ++++++++----- src/utils/Sorter.js | 15 +++------------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/dom_components/view/ComponentView.js b/src/dom_components/view/ComponentView.js index 74f19e932..3670ed263 100644 --- a/src/dom_components/view/ComponentView.js +++ b/src/dom_components/view/ComponentView.js @@ -236,11 +236,14 @@ module.exports = Backbone.View.extend({ updateAttributes() { const attrs = []; const { model, $el, el } = this; - const defaultAttr = { 'data-gjs-type': model.get('type') || 'default' }; - - if (model.get('highlightable')) { - defaultAttr['data-highlightable'] = 1; - } + const defaultAttr = { + 'data-gjs-type': model.get('type') || 'default', + ...(model.get('highlightable') && { 'data-highlightable': 1 }), + ...(model.get('textable') && { + contenteditable: 'false', + 'data-gjs-textable': 'true' + }) + }; // Remove all current attributes each(el.attributes, attr => attrs.push(attr.nodeName)); diff --git a/src/utils/Sorter.js b/src/utils/Sorter.js index 6b138fc54..bfda067e9 100644 --- a/src/utils/Sorter.js +++ b/src/utils/Sorter.js @@ -98,14 +98,13 @@ module.exports = Backbone.View.extend({ }, updateTextViewCursorPosition(e) { - const { em } = this; - const Canvas = em.get('Canvas'); + const Canvas = this.em.get('Canvas'); const targetDoc = Canvas.getDocument(); - const poiner = getPointerEvent(e); let range = null; if (targetDoc.caretRangeFromPoint) { // Chrome + const poiner = getPointerEvent(e); range = targetDoc.caretRangeFromPoint(poiner.clientX, poiner.clientY); } else if (e.rangeParent) { // Firefox @@ -116,7 +115,7 @@ module.exports = Backbone.View.extend({ const sel = Canvas.getWindow().getSelection(); Canvas.getFrameEl().focus(); sel.removeAllRanges(); - sel.addRange(range); + range && sel.addRange(range); }, setContentEditable(model, mode) { @@ -1073,14 +1072,6 @@ module.exports = Backbone.View.extend({ model.getView().render(); modelEl.setAttribute('data-gjs-textable', 'true'); const { outerHTML } = modelEl; - console.log( - 'model.toHTML()', - model.toHTML(), - 'model', - model, - 'outerHTML', - outerHTML - ); activeRte.insertHTML && activeRte.insertHTML(outerHTML); } else { created = targetCollection.add(modelToDrop, opts);