Browse Source

Merge pull request #1189 from artf/dev

Merge dev
pull/1362/head
Artur Arseniev 8 years ago
committed by GitHub
parent
commit
bc163524b3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1817
      dist/grapes.js
  2. 6
      dist/grapes.min.js
  3. 2
      package-lock.json
  4. 2
      package.json
  5. 16
      src/canvas/index.js
  6. 87
      src/commands/index.js
  7. 14
      src/commands/view/ComponentEnter.js
  8. 13
      src/commands/view/ComponentExit.js
  9. 15
      src/commands/view/ComponentNext.js
  10. 15
      src/commands/view/ComponentPrev.js
  11. 10
      src/commands/view/CopyComponent.js
  12. 22
      src/commands/view/MoveComponent.js
  13. 6
      src/commands/view/OpenStyleManager.js
  14. 4
      src/commands/view/OpenTraitManager.js
  15. 30
      src/commands/view/PasteComponent.js
  16. 75
      src/commands/view/SelectComponent.js
  17. 44
      src/dom_components/index.js
  18. 11
      src/dom_components/model/Component.js
  19. 2
      src/dom_components/model/ComponentVideo.js
  20. 1
      src/dom_components/view/ComponentTextView.js
  21. 7
      src/dom_components/view/ComponentView.js
  22. 9
      src/dom_components/view/ComponentsView.js
  23. 3
      src/editor/config/config.js
  24. 54
      src/editor/index.js
  25. 183
      src/editor/model/Editor.js
  26. 16
      src/keymaps/index.js
  27. 4
      src/navigator/config/config.js
  28. 45
      src/navigator/index.js
  29. 36
      src/navigator/view/ItemView.js
  30. 7
      src/navigator/view/ItemsView.js
  31. 2
      src/selector_manager/view/ClassTagView.js
  32. 8
      src/selector_manager/view/ClassTagsView.js
  33. 1
      src/style_manager/model/Properties.js
  34. 17
      src/style_manager/model/Property.js
  35. 2
      src/style_manager/view/PropertyView.js
  36. 2
      src/style_manager/view/SectorsView.js
  37. 2
      src/trait_manager/view/TraitsView.js
  38. 2
      src/undo_manager/index.js
  39. 28
      src/utils/Sorter.js
  40. 14
      src/utils/mixins.js
  41. 91
      test/specs/grapesjs/index.js
  42. 6
      test/specs/selector_manager/e2e/ClassManager.js
  43. 9
      test/specs/selector_manager/view/ClassTagView.js
  44. 4
      test/specs/selector_manager/view/ClassTagsView.js
  45. 12
      test/specs/style_manager/model/Models.js

1817
dist/grapes.js

File diff suppressed because it is too large

6
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
package-lock.json

@ -1,6 +1,6 @@
{
"name": "grapesjs",
"version": "0.14.17",
"version": "0.14.20",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

2
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.14.17",
"version": "0.14.20",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",

16
src/canvas/index.js

@ -94,6 +94,14 @@ module.exports = () => {
return this.getFrameEl().contentDocument;
},
/**
* Returns the frame's window
* @return {HTMLElement}
*/
getWindow() {
return this.getFrameEl().contentWindow;
},
/**
* Returns body element of the frame
* @return {HTMLElement}
@ -341,6 +349,14 @@ module.exports = () => {
};
},
/**
* Check if the canvas is focused
* @return {Boolean}
*/
hasFocus() {
return this.getDocument().hasFocus();
},
/**
* Detects if some input is focused (input elements, text components, etc.)
* Used internally, for example, to avoid undo/redo in text editing mode

87
src/commands/index.js

@ -38,7 +38,7 @@
* },
* ...
*/
import { isFunction } from 'underscore';
import { isFunction, isArray } from 'underscore';
module.exports = () => {
let em;
@ -118,18 +118,8 @@ module.exports = () => {
defaultCommands['tlb-clone'] = {
run(ed) {
var sel = ed.getSelected();
if (!sel || !sel.get('copyable')) {
console.warn('The element is not clonable');
return;
}
var collection = sel.collection;
var index = collection.indexOf(sel);
const added = collection.add(sel.clone(), { at: index + 1 });
sel.emitUpdate();
ed.trigger('component:clone', added);
ed.runCommand('core:copy');
ed.runCommand('core:paste');
}
};
@ -139,12 +129,14 @@ module.exports = () => {
const em = ed.getModel();
const event = opts && opts.event;
const sel = ed.getSelected();
const selAll = [...ed.getSelectedAll()];
const toolbarStyle = ed.Canvas.getToolbarEl().style;
const nativeDrag = event && event.type == 'dragstart';
const defComOptions = { preserveSelected: 1 };
const hideTlb = () => {
toolbarStyle.display = 'none';
em.stopDefault();
em.stopDefault(defComOptions);
};
if (!sel || !sel.get('draggable')) {
@ -164,8 +156,9 @@ module.exports = () => {
};
const onEnd = (e, opts) => {
em.runDefault();
em.setSelected(sel);
em.runDefault(defComOptions);
selAll.forEach(sel => sel.set('status', 'selected'));
ed.select(selAll);
sel.emitUpdate();
dragger && dragger.blur();
};
@ -194,50 +187,54 @@ module.exports = () => {
const cmdMove = ed.Commands.get('move-comp');
cmdMove.onEndMoveFromModel = onEnd;
cmdMove.initSorterFromModel(sel);
cmdMove.initSorterFromModels(selAll);
}
sel.set('status', 'freezed-selected');
selAll.forEach(sel => sel.set('status', 'freezed-selected'));
}
};
// Core commands
defaultCommands['core:undo'] = e => e.UndoManager.undo();
defaultCommands['core:redo'] = e => e.UndoManager.redo();
defaultCommands['core:copy'] = require('./view/CopyComponent').run;
defaultCommands['core:paste'] = require('./view/PasteComponent').run;
defaultCommands[
'core:component-next'
] = require('./view/ComponentNext').run;
defaultCommands[
'core:component-prev'
] = require('./view/ComponentPrev').run;
defaultCommands[
'core:component-enter'
] = require('./view/ComponentEnter').run;
defaultCommands[
'core:component-exit'
] = require('./view/ComponentExit').run;
defaultCommands['core:canvas-clear'] = e => {
e.DomComponents.clear();
e.CssComposer.clear();
};
defaultCommands['core:copy'] = ed => {
const em = ed.getModel();
const model = ed.getSelected();
if (model && model.get('copyable') && !ed.Canvas.isInputFocused()) {
em.set('clipboard', model);
}
};
defaultCommands['core:paste'] = ed => {
const em = ed.getModel();
const clp = em.get('clipboard');
const model = ed.getSelected();
const coll = model && model.collection;
if (coll && clp && !ed.Canvas.isInputFocused()) {
const at = coll.indexOf(model) + 1;
coll.add(clp.clone(), { at });
}
};
defaultCommands['core:component-delete'] = (ed, sender, opts = {}) => {
let component = opts.component || ed.getSelected();
if (!component || !component.get('removable')) {
console.warn('The element is not removable');
return;
}
let components = opts.component || ed.getSelectedAll();
components = isArray(components) ? [...components] : [components];
// It's important to deselect components first otherwise,
// with undo, the component will be set with the wrong `collection`
ed.select(null);
component.destroy();
return component;
components.forEach(component => {
if (!component || !component.get('removable')) {
console.warn('The element is not removable', component);
return;
}
if (component) {
const coll = component.collection;
coll && coll.remove(component);
}
});
return components;
};
if (c.em) c.model = c.em.get('Canvas');

14
src/commands/view/ComponentEnter.js

@ -0,0 +1,14 @@
module.exports = {
run(ed) {
if (!ed.Canvas.hasFocus() || ed.getModel().isEditing()) return;
const toSelect = [];
ed.getSelectedAll().forEach(component => {
const coll = component.components();
const next = coll && coll.at(0);
next && toSelect.push(next);
});
toSelect.length && ed.select(toSelect);
}
};

13
src/commands/view/ComponentExit.js

@ -0,0 +1,13 @@
module.exports = {
run(ed) {
if (!ed.Canvas.hasFocus() || ed.getModel().isEditing()) return;
const toSelect = [];
ed.getSelectedAll().forEach(component => {
const next = component.parent();
next && toSelect.push(next);
});
toSelect.length && ed.select(toSelect);
}
};

15
src/commands/view/ComponentNext.js

@ -0,0 +1,15 @@
module.exports = {
run(ed) {
if (!ed.Canvas.hasFocus() || ed.getModel().isEditing()) return;
const toSelect = [];
ed.getSelectedAll().forEach(component => {
const coll = component.collection;
const at = coll.indexOf(component);
const next = coll.at(at + 1);
toSelect.push(next || component);
});
toSelect.length && ed.select(toSelect);
}
};

15
src/commands/view/ComponentPrev.js

@ -0,0 +1,15 @@
module.exports = {
run(ed) {
if (!ed.Canvas.hasFocus() || ed.getModel().isEditing()) return;
const toSelect = [];
ed.getSelectedAll().forEach(component => {
const coll = component.collection;
const at = coll.indexOf(component);
const next = coll.at(at - 1);
toSelect.push(next && at - 1 >= 0 ? next : component);
});
toSelect.length && ed.select(toSelect);
}
};

10
src/commands/view/CopyComponent.js

@ -0,0 +1,10 @@
module.exports = {
run(ed) {
const em = ed.getModel();
const models = [...ed.getSelectedAll()];
if (models.length && !ed.Canvas.isInputFocused()) {
em.set('clipboard', models);
}
}
};

22
src/commands/view/MoveComponent.js

@ -81,6 +81,25 @@ module.exports = _.extend({}, SelectPosition, SelectComponent, {
on(this.getContentWindow(), 'keydown', this.rollback);
},
/**
* Init sorter from models
* @param {Object} model
* @private
*/
initSorterFromModels(models) {
// TODO: if one only check for `draggable`
// Avoid badge showing on move
this.cacheEl = null;
const lastModel = models[models.length - 1];
const doc = this.frameEl.contentDocument;
this.startSelectPosition(lastModel.view.el, doc);
this.sorter.draggable = lastModel.get('draggable');
this.sorter.toMove = models;
this.sorter.onEndMove = this.onEndMoveFromModel.bind(this);
this.stopSelectComponent();
on(this.getContentWindow(), 'keydown', this.rollback);
},
onEndMoveFromModel() {
off(this.getContentWindow(), 'keydown', this.rollback);
},
@ -142,8 +161,7 @@ module.exports = _.extend({}, SelectPosition, SelectComponent, {
this.getBadgeEl().removeClass(this.badgeClass);
this.getHighlighterEl().removeClass(this.hoverClass);
var wp = this.$wrapper;
wp
.css('cursor', '')
wp.css('cursor', '')
.unbind()
.removeClass(this.noSelClass);
}

6
src/commands/view/OpenStyleManager.js

@ -45,7 +45,7 @@ module.exports = {
this.panel.set('appendContent', this.$cn).trigger('change:appendContent');
this.target = em.editor;
this.listenTo(this.target, 'change:selectedComponent', this.toggleSm);
this.listenTo(this.target, 'component:toggled', this.toggleSm);
}
this.toggleSm();
},
@ -55,10 +55,10 @@ module.exports = {
* @private
*/
toggleSm() {
const sender = this.sender;
const { target, sender } = this;
if (sender && sender.get && !sender.get('active')) return;
if (this.target.get('selectedComponent')) {
if (target.getSelectedAll().length === 1) {
this.$cn2.show();
this.$header.hide();
} else {

4
src/commands/view/OpenTraitManager.js

@ -34,7 +34,7 @@ module.exports = {
.trigger('change:appendContent');
this.target = editor.getModel();
this.listenTo(this.target, 'change:selectedComponent', this.toggleTm);
this.listenTo(this.target, 'component:toggled', this.toggleTm);
}
this.toggleTm();
@ -48,7 +48,7 @@ module.exports = {
const sender = this.sender;
if (sender && sender.get && !sender.get('active')) return;
if (this.target.get('selectedComponent')) {
if (this.target.getSelectedAll().length === 1) {
this.$cn2.show();
this.$header.hide();
} else {

30
src/commands/view/PasteComponent.js

@ -0,0 +1,30 @@
import { isArray, contains } from 'underscore';
module.exports = {
run(ed) {
const em = ed.getModel();
const clp = em.get('clipboard');
const selected = ed.getSelected();
if (clp && selected && !ed.Canvas.isInputFocused()) {
ed.getSelectedAll().forEach(comp => {
if (!comp) return;
const coll = comp.collection;
const at = coll.indexOf(comp) + 1;
const copyable = clp.filter(cop => cop.get('copyable'));
let added;
if (contains(clp, comp) && comp.get('copyable')) {
added = coll.add(comp.clone(), { at });
} else {
added = coll.add(copyable.map(cop => cop.clone()), { at });
}
added = isArray(added) ? added : [added];
added.forEach(add => ed.trigger('component:clone', add));
});
selected.emitUpdate();
}
}
};

75
src/commands/view/SelectComponent.js

@ -1,4 +1,4 @@
import { bindAll, isElement } from 'underscore';
import { bindAll, isElement, isUndefined } from 'underscore';
import { on, off, getUnitFromValue } from 'utils/mixins';
const ToolbarView = require('dom_components/view/ToolbarView');
@ -54,7 +54,7 @@ module.exports = {
methods[method](body, 'click', this.onClick);
methods[method](win, 'scroll resize', this.onFrameScroll);
methods[method](win, 'keydown', this.onKeyPress);
em[method]('change:selectedComponent', this.onSelect, this);
em[method]('component:toggled', this.onSelect, this);
em[method]('change:componentHovered', this.onHovered, this);
},
@ -64,7 +64,7 @@ module.exports = {
* */
onKeyPress(e) {
var key = e.which || e.keyCode;
var comp = this.editorModel.get('selectedComponent');
var comp = this.editorModel.getSelected();
var focused = this.frameEl.contentDocument.activeElement.tagName !== 'BODY';
// On CANC (46) or Backspace (8)
@ -202,16 +202,74 @@ module.exports = {
if (model) {
if (model.get('selectable')) {
editor.select(model);
this.initResize(model);
this.select(model, e);
} else {
let parent = model.parent();
while (parent && !parent.get('selectable')) parent = parent.parent();
parent && editor.select(parent);
this.select(parent, e);
}
}
},
/**
* Select component
* @param {Component} model
* @param {Event} event
*/
select(model, event = {}) {
if (!model) return;
const ctrlKey = event.ctrlKey || event.metaKey;
const shiftKey = event.shiftKey;
const { editor } = this;
const multiple = editor.getConfig('multipleSelection');
const em = this.em;
if (ctrlKey && multiple) {
editor.selectToggle(model);
} else if (shiftKey && multiple) {
em.clearSelection(editor.Canvas.getWindow());
const coll = model.collection;
const index = coll.indexOf(model);
const selAll = editor.getSelectedAll();
let min, max;
// Fin min and max siblings
editor.getSelectedAll().forEach(sel => {
const selColl = sel.collection;
const selIndex = selColl.indexOf(sel);
if (selColl === coll) {
if (selIndex < index) {
// First model BEFORE the selected one
min = isUndefined(min) ? selIndex : Math.max(min, selIndex);
} else if (selIndex > index) {
// First model AFTER the selected one
max = isUndefined(max) ? selIndex : Math.min(max, selIndex);
}
}
});
if (!isUndefined(min)) {
while (min !== index) {
editor.selectAdd(coll.at(min));
min++;
}
}
if (!isUndefined(max)) {
while (max !== index) {
editor.selectAdd(coll.at(max));
max--;
}
}
editor.selectAdd(model);
} else {
editor.select(model);
}
this.initResize(model);
},
/**
* Update badge for the component
* @param {Object} Component
@ -411,7 +469,7 @@ module.exports = {
*/
updateToolbar(mod) {
var em = this.config.em;
var model = mod == em ? em.get('selectedComponent') : mod;
var model = mod == em ? em.getSelected() : mod;
var toolbarEl = this.canvas.getToolbarEl();
var toolbarStyle = toolbarEl.style;
@ -505,7 +563,7 @@ module.exports = {
if (el) {
var elPos = this.getElementPos(el);
this.updateBadge(el, elPos);
var model = this.em.get('selectedComponent');
var model = this.em.getSelected();
if (model) {
this.updateToolbarPos(model.view.el);
@ -583,6 +641,5 @@ module.exports = {
em.off('component:update', this.updateAttached, this);
em.off('change:canvasOffset', this.updateAttached, this);
em.off('change:selectedComponent', this.updateToolbar, this);
}
};

44
src/dom_components/index.js

@ -174,6 +174,7 @@ module.exports = () => {
init(config) {
c = config || {};
em = c.em;
this.em = em;
if (em) {
c.components = em.config.components || c.components;
@ -191,8 +192,15 @@ module.exports = () => {
c.modal = em.get('Modal') || '';
c.am = em.get('AssetManager') || '';
em.get('Parser').compTypes = componentTypes;
em.on('change:selectedComponent', this.componentChanged, this);
em.on('change:componentHovered', this.componentHovered, this);
const selected = em.get('selected');
em.listenTo(selected, 'add', (sel, c, opts) =>
this.selectAdd(sel, opts)
);
em.listenTo(selected, 'remove', (sel, c, opts) =>
this.selectRemove(sel, opts)
);
}
// Build wrapper
@ -462,8 +470,7 @@ module.exports = () => {
* @return {this}
*/
clear() {
var c = this.getComponents();
for (var i = 0, len = c.length; i < len; i++) c.pop();
this.getComponents().reset();
return this;
},
@ -511,23 +518,28 @@ module.exports = () => {
return;
},
/**
* Triggered when the selected component is changed
* @private
*/
componentChanged() {
const em = c.em;
const model = em.get('selectedComponent');
const previousModel = em.previous('selectedComponent');
selectAdd(component, opts = {}) {
if (component) {
component.set({
status: 'selected'
});
['component:selected', 'component:toggled'].forEach(event =>
this.em.trigger(event, component, opts)
);
}
},
// Deselect the previous component
previousModel &&
previousModel.set({
selectRemove(component, opts = {}) {
if (component) {
const { em } = this;
component.set({
status: '',
state: ''
});
model && model.set('status', 'selected');
['component:deselected', 'component:toggled'].forEach(event =>
this.em.trigger(event, component, opts)
);
}
},
/**

11
src/dom_components/model/Component.js

@ -460,10 +460,10 @@ const Component = Backbone.Model.extend(Styleable).extend(
* @param {Object} [opts={}] Options, same as in `model.add()`(from backbone)
* @return {Array} Array of appended components
* @example
* someModel.get('components').lenght // -> 0
* someModel.get('components').length // -> 0
* const videoComponent = someModel.append('<video></video><div></div>')[0];
* // This will add 2 components (`video` and `div`) to your `someModel`
* someModel.get('components').lenght // -> 2
* someModel.get('components').length // -> 2
* // You can pass components directly
* otherModel.append(otherModel2);
* otherModel.append([otherModel3, otherModel4]);
@ -652,7 +652,10 @@ const Component = Backbone.Model.extend(Styleable).extend(
attr.style = style;
}
return new this.constructor(attr, opts);
return new this.constructor(
attr,
opts
);
},
/**
@ -741,7 +744,7 @@ const Component = Backbone.Model.extend(Styleable).extend(
const defaults = this.defaults;
forEach(defaults, (value, key) => {
if (key !== 'type' && obj[key] === value) {
if (['type', 'content'].indexOf(key) === -1 && obj[key] === value) {
delete obj[key];
}
});

2
src/dom_components/model/ComponentVideo.js

@ -125,7 +125,7 @@ module.exports = Component.extend(
this.set('tagName', 'video');
}
this.loadTraits(traits);
this.em.trigger('change:selectedComponent');
this.em.trigger('component:toggled');
},
// Listen provider change and switch traits, in TraitView listen traits change

1
src/dom_components/view/ComponentTextView.js

@ -120,6 +120,7 @@ module.exports = ComponentView.extend({
toggleEvents(enable) {
var method = enable ? 'on' : 'off';
const mixins = { on, off };
this.em.setEditing(enable);
// The ownerDocument is from the frame
var elDocs = [this.el.ownerDocument, document];

7
src/dom_components/view/ComponentView.js

@ -24,7 +24,6 @@ module.exports = Backbone.View.extend({
this.classe = this.attr.class || [];
const $el = this.$el;
const classes = model.get('classes');
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:style', this.updateStyle);
this.listenTo(model, 'change:attributes', this.updateAttributes);
this.listenTo(model, 'change:highlightable', this.updateHighlight);
@ -40,12 +39,6 @@ module.exports = Backbone.View.extend({
this.init();
},
remove() {
Backbone.View.prototype.remove.apply(this);
const children = this.childrenView;
children && children.stopListening();
},
/**
* Initialize callback
*/

9
src/dom_components/view/ComponentsView.js

@ -8,6 +8,15 @@ module.exports = Backbone.View.extend({
const coll = this.collection;
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.resetChildren);
this.listenTo(coll, 'remove', this.removeChildren);
},
removeChildren(removed) {
const view = removed.view;
if (!view) return;
view.remove.apply(view);
const children = view.childrenView;
children && children.stopListening();
},
/**

3
src/editor/config/config.js

@ -105,6 +105,9 @@ module.exports = {
// Enable native HTML5 drag and drop
nativeDnD: 1,
// Enable multiple selection
multipleSelection: 1,
// Show the wrapper component in the final code, eg. in editor.getHtml()
exportWrapper: 0,

54
src/editor/index.js

@ -18,6 +18,7 @@
* * `component:styleUpdate:{propertyName}` - Listen for a specific style property change, the model is passed as an argument to the callback
* * `component:selected` - New component selected, the selected model is passed as an argument to the callback
* * `component:deselected` - Component deselected, the deselected model is passed as an argument to the callback
* * `component:toggled` - Component selection changed, toggled model is passed as an argument to the callback
* ## Blocks
* * `block:add` - New block added
* * `block:remove` - Block removed
@ -366,13 +367,21 @@ module.exports = config => {
},
/**
* Returns selected component, if there is one
* Returns the last selected component, if there is one
* @return {Model}
*/
getSelected() {
return em.getSelected();
},
/**
* Returns an array of all selected components
* @return {Array}
*/
getSelectedAll() {
return em.getSelectedAll();
},
/**
* Get a stylable entity from the selected component.
* If you select a component without classes the entity is the Component
@ -404,6 +413,42 @@ module.exports = config => {
return this;
},
/**
* Add component to selection
* @param {Component|HTMLElement|Array} el Component to select
* @return {this}
* @example
* editor.selectAdd(model);
*/
selectAdd(el) {
em.addSelected(el);
return this;
},
/**
* Remove component from selection
* @param {Component|HTMLElement|Array} el Component to select
* @return {this}
* @example
* editor.selectRemove(model);
*/
selectRemove(el) {
em.removeSelected(el);
return this;
},
/**
* Toggle component selection
* @param {Component|HTMLElement|Array} el Component to select
* @return {this}
* @example
* editor.selectToggle(model);
*/
selectToggle(el) {
em.toggleSelected(el);
return this;
},
/**
* Set device to the editor. If the device exists it will
* change the canvas to the proper width
@ -573,6 +618,13 @@ module.exports = config => {
return em.trigger.apply(em, arguments);
},
/**
* Destroy the editor
*/
destroy() {
return em.destroyAll();
},
/**
* Returns editor element
* @return {HTMLElement}

183
src/editor/model/Editor.js

@ -1,4 +1,5 @@
import { isUndefined, isElement, defaults } from 'underscore';
import { isUndefined, defaults, isArray, contains } from 'underscore';
import { getModel } from 'utils/mixins';
const deps = [
require('utils'),
@ -24,6 +25,7 @@ const deps = [
];
const Backbone = require('backbone');
const { Collection } = Backbone;
let timedInterval;
require('utils/extender')({
@ -34,18 +36,21 @@ require('utils/extender')({
const $ = Backbone.$;
module.exports = Backbone.Model.extend({
defaults: {
clipboard: null,
designerMode: false,
selectedComponent: null,
componentHovered: null,
previousModel: null,
changesCount: 0,
storables: [],
modules: [],
toLoad: [],
opened: {},
device: ''
defaults() {
return {
editing: 0,
selected: new Collection(),
clipboard: null,
designerMode: false,
componentHovered: null,
previousModel: null,
changesCount: 0,
storables: [],
modules: [],
toLoad: [],
opened: {},
device: ''
};
},
initialize(c = {}) {
@ -56,12 +61,31 @@ module.exports = Backbone.Model.extend({
this.set('storables', []);
if (c.el && c.fromElement) this.config.components = c.el.innerHTML;
this.attrsOrig = c.el
? [...c.el.attributes].reduce((res, next) => {
res[next.nodeName] = next.nodeValue;
return res;
}, {})
: '';
// Load modules
deps.forEach(name => this.loadModule(name));
this.on('change:selectedComponent', this.componentSelected, this);
this.on('change:componentHovered', this.componentHovered, this);
this.on('change:changesCount', this.updateChanges, this);
// Deprecations
[{ from: 'change:selectedComponent', to: 'component:toggled' }].forEach(
event => {
const eventFrom = event.from;
const eventTo = event.to;
this.listenTo(this, eventFrom, (...args) => {
this.trigger(eventTo, ...args);
console.warn(
`The event '${eventFrom}' is deprecated, replace it with '${eventTo}'`
);
});
}
);
},
/**
@ -192,19 +216,6 @@ module.exports = Backbone.Model.extend({
}, 0);
},
/**
* Callback on component selection
* @param {Object} Model
* @param {Mixed} New value
* @param {Object} Options
* @private
* */
componentSelected(editor, selected, options) {
const prev = this.previous('selectedComponent');
prev && this.trigger('component:deselected', prev, options);
selected && this.trigger('component:selected', selected, options);
},
/**
* Callback on component hover
* @param {Object} Model
@ -224,7 +235,16 @@ module.exports = Backbone.Model.extend({
* @private
*/
getSelected() {
return this.get('selectedComponent');
return this.get('selected').last();
},
/**
* Returns an array of all selected components
* @return {Array}
* @private
*/
getSelectedAll() {
return this.get('selected').models;
},
/**
@ -234,11 +254,65 @@ module.exports = Backbone.Model.extend({
* @private
*/
setSelected(el, opts = {}) {
let model = el;
isElement(el) && (model = $(el).data('model'));
if (model && !model.get('selectable')) return;
opts.forceChange && this.set('selectedComponent', '');
this.set('selectedComponent', model, opts);
const multiple = isArray(el);
const els = multiple ? el : [el];
const selected = this.get('selected');
// If an array is passed remove all selected
// expect those yet to be selected
multiple && this.removeSelected(selected.filter(s => !contains(els, s)));
els.forEach(el => {
const model = getModel(el, $);
if (model && !model.get('selectable')) return;
!multiple && this.removeSelected(selected.filter(s => s !== model));
this.addSelected(model, opts);
});
},
/**
* Add component to selection
* @param {Component|HTMLElement} el Component to select
* @param {Object} [opts={}] Options, optional
* @private
*/
addSelected(el, opts = {}) {
const model = getModel(el, $);
const models = isArray(model) ? model : [model];
models.forEach(model => {
if (model && !model.get('selectable')) return;
this.get('selected').push(model, opts);
});
},
/**
* Remove component from selection
* @param {Component|HTMLElement} el Component to select
* @param {Object} [opts={}] Options, optional
* @private
*/
removeSelected(el, opts = {}) {
this.get('selected').remove(getModel(el, $), opts);
},
/**
* Toggle component selection
* @param {Component|HTMLElement} el Component to select
* @param {Object} [opts={}] Options, optional
* @private
*/
toggleSelected(el, opts = {}) {
const model = getModel(el, $);
const models = isArray(model) ? model : [model];
models.forEach(model => {
if (this.get('selected').contains(model)) {
this.removeSelected(model, opts);
} else {
this.addSelected(model, opts);
}
});
},
/**
@ -248,8 +322,7 @@ module.exports = Backbone.Model.extend({
* @private
*/
setHovered(el, opts = {}) {
let model = el;
isElement(el) && (model = $(el).data('model'));
const model = getModel(el, $);
if (model && !model.get('hoverable')) return;
opts.forceChange && this.set('componentHovered', '');
this.set('componentHovered', model, opts);
@ -496,6 +569,14 @@ module.exports = Backbone.Model.extend({
return device && width && !preview ? `(${condition}: ${width})` : '';
},
/**
* Return the component wrapper
* @return {Component}
*/
getWrapper() {
return this.get('DomComponents').getWrapper();
},
/**
* Return the count of changes made to the content and not yet stored.
* This count resets at any `store()`
@ -505,6 +586,38 @@ module.exports = Backbone.Model.extend({
return this.get('changesCount');
},
/**
* Destroy editor
*/
destroyAll() {
const {
DomComponents,
CssComposer,
UndoManager,
Panels,
Canvas
} = this.attributes;
DomComponents.clear();
CssComposer.clear();
UndoManager.clear().removeAll();
Panels.getPanels().reset();
Canvas.getCanvasView().remove();
this.view.remove();
this.stopListening();
$(this.config.el)
.empty()
.attr(this.attrsOrig);
},
setEditing(value) {
this.set('editing', value);
return this;
},
isEditing() {
return !!this.get('editing');
},
/**
* Set/get data from the HTMLElement
* @param {HTMLElement} el

16
src/keymaps/index.js

@ -32,6 +32,22 @@ module.exports = () => {
'core:paste': {
keys: '⌘+v, ctrl+v',
handler: 'core:paste'
},
'core:component-next': {
keys: 's',
handler: 'core:component-next'
},
'core:component-prev': {
keys: 'w',
handler: 'core:component-prev'
},
'core:component-enter': {
keys: 'd',
handler: 'core:component-enter'
},
'core:component-exit': {
keys: 'a',
handler: 'core:component-exit'
}
}
};

4
src/navigator/config/config.js

@ -14,6 +14,10 @@ module.exports = {
// Hide textnodes
hideTextnode: 1,
// Indicate a query string of the element to be selected as the root of layers.
// By default the root is the wrapper
root: '',
// Indicates if the wrapper is visible in layers
showWrapper: 1,

45
src/navigator/index.js

@ -1,13 +1,11 @@
import defaults from './config/config';
import ItemView from './view/ItemView';
import ItemsView from './view/ItemsView';
import { isElement } from 'underscore';
module.exports = () => {
let em;
let layers;
let config = {};
let View = ItemsView;
return {
name: 'LayerManager',
@ -20,30 +18,25 @@ module.exports = () => {
return this;
},
getConfig() {
return config;
},
onLoad() {
const collection = em.get('DomComponents').getComponents();
const parent = collection.parent;
const options = {
layers = new ItemView({
level: 0,
config,
opened: config.opened || {}
};
// Show wrapper if requested
if (config.showWrapper && parent) {
View = ItemView;
options.model = parent;
} else {
options.collection = collection;
}
layers = new View(options);
opened: config.opened || {},
model: em.get('DomComponents').getWrapper()
});
em && em.on('component:selected', this.componentChanged);
this.componentChanged();
},
postRender() {
const elTo = config.appendTo;
const root = config.root;
root && this.setRoot(root);
if (elTo) {
const el = isElement(elTo) ? elTo : document.querySelector(elTo);
@ -51,6 +44,24 @@ module.exports = () => {
}
},
/**
* Set new root for layers
* @param {HTMLElement|Component|String} el Component to be set as the root
* @return {self}
*/
setRoot(el) {
layers.setRoot(el);
return this;
},
/**
* Get the root of layers
* @return {Component}
*/
getRoot() {
return layers.model;
},
/**
* Return the view of layers
* @return {View}

36
src/navigator/view/ItemView.js

@ -1,9 +1,12 @@
import { isUndefined } from 'underscore';
import { isUndefined, isString } from 'underscore';
import { getModel } from 'utils/mixins';
import Backbone from 'backbone';
const ComponentView = require('dom_components/view/ComponentView');
const inputProp = 'contentEditable';
const $ = Backbone.$;
let ItemsView;
module.exports = require('backbone').View.extend({
module.exports = Backbone.View.extend({
events: {
'mousedown [data-toggle-move]': 'startSort',
'click [data-toggle-visible]': 'toggleVisibility',
@ -27,6 +30,7 @@ module.exports = require('backbone').View.extend({
const level = this.level + 1;
const gut = `${30 + level * 10}px`;
const name = model.getName();
return `
${
hidable
@ -35,7 +39,6 @@ module.exports = require('backbone').View.extend({
}" data-toggle-visible></i>`
: ''
}
<div class="${clsTitleC}">
<div class="${clsTitle}" style="padding-left: ${gut}" data-toggle-select>
<div class="${pfx}layer-title-inn">
@ -49,8 +52,7 @@ module.exports = require('backbone').View.extend({
<div class="${this.clsMove}" data-toggle-move>
<i class="fa fa-arrows"></i>
</div>
<div class="${this.clsChildren}"></div>
`;
<div class="${this.clsChildren}"></div>`;
},
initialize(o = {}) {
@ -67,7 +69,6 @@ module.exports = require('backbone').View.extend({
const components = model.get('components');
model.set('open', false);
this.listenTo(components, 'remove add change reset', this.checkChildren);
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:status', this.updateStatus);
this.listenTo(model, 'change:open', this.updateOpening);
this.listenTo(model, 'change:style:display', this.updateVisibility);
@ -315,7 +316,7 @@ module.exports = require('backbone').View.extend({
},
getCaret() {
if (!this.caret) {
if (!this.caret || !this.caret.length) {
const pfx = this.pfx;
this.caret = this.$el
.children(`.${this.clsTitleC}`)
@ -325,13 +326,22 @@ module.exports = require('backbone').View.extend({
return this.caret;
},
setRoot(el) {
el = isString(el) ? this.em.getWrapper().find(el)[0] : el;
const model = getModel(el, $);
if (!model) return;
this.stopListening();
this.model = model;
this.initialize(this.opt);
this.render();
},
render() {
const model = this.model;
var pfx = this.pfx;
var vis = this.isVisible();
const el = this.$el;
const el = this.$el.empty();
const level = this.level + 1;
el.html(this.template(model));
if (isUndefined(ItemsView)) {
ItemsView = require('./ItemsView');
@ -345,7 +355,13 @@ module.exports = require('backbone').View.extend({
parent: model,
level
}).render().$el;
el.find(`.${this.clsChildren}`).append(children);
if (!this.config.showWrapper && level === 1) {
el.append(children);
} else {
el.html(this.template(model));
el.find(`.${this.clsChildren}`).append(children);
}
if (!model.get('draggable') || !this.config.sortable) {
el.children(`.${this.clsMove}`).remove();

7
src/navigator/view/ItemsView.js

@ -16,6 +16,7 @@ module.exports = require('backbone').View.extend({
const coll = this.collection;
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset resetNavigator', this.render);
this.listenTo(coll, 'remove', this.removeChildren);
this.className = `${pfx}layers`;
const em = config.em;
@ -44,6 +45,12 @@ module.exports = require('backbone').View.extend({
parent && this.$el.data('model', parent);
},
removeChildren(removed) {
const view = removed.viewLayer;
if (!view) return;
view.remove.apply(view);
},
/**
* Add to collection
* @param Object Model

2
src/selector_manager/view/ClassTagView.js

@ -94,7 +94,7 @@ module.exports = require('backbone').View.extend({
const model = this.model;
const coll = this.coll;
const el = this.el;
const sel = em && em.get('selectedComponent');
const sel = em && em.getSelected();
sel && sel.get & sel.get('classes').remove(model);
coll && coll.remove(model);
setTimeout(() => this.remove(), 0);

8
src/selector_manager/view/ClassTagsView.js

@ -52,11 +52,7 @@ module.exports = Backbone.View.extend({
this.target = this.config.em;
this.em = this.target;
this.listenTo(
this.target,
'change:selectedComponent',
this.componentChanged
);
this.listenTo(this.target, 'component:toggled', this.componentChanged);
this.listenTo(this.target, 'component:update:classes', this.updateSelector);
this.listenTo(this.collection, 'add', this.addNew);
@ -138,7 +134,7 @@ module.exports = Backbone.View.extend({
* @private
*/
componentChanged(e) {
this.compTarget = this.target.get('selectedComponent');
this.compTarget = this.target.getSelected();
const target = this.compTarget;
let validSelectors = [];

1
src/style_manager/model/Properties.js

@ -119,6 +119,7 @@ module.exports = require('backbone')
const values = value.split(' ');
values.forEach((value, i) => {
const property = this.at(i);
if (!property) return;
properties.push({ ...property.attributes, ...{ value } });
});
return properties;

17
src/style_manager/model/Property.js

@ -1,4 +1,4 @@
import { isUndefined } from 'underscore';
import { isUndefined, isString } from 'underscore';
module.exports = require('backbone').Model.extend({
defaults: {
@ -14,6 +14,9 @@ module.exports = require('backbone').Model.extend({
visible: true,
fixedValues: ['initial', 'inherit'],
// If true to the value will be added '!important'
important: 0,
// If true, will be hidden by default and will show up only for targets
// which require this property (via `stylable-require`)
// Use case:
@ -89,13 +92,19 @@ module.exports = require('backbone').Model.extend({
*/
parseValue(value) {
const result = { value };
const imp = '!important';
if (isString(value) && value.indexOf(imp) !== -1) {
result.value = value.replace(imp, '').trim();
result.important = 1;
}
if (!this.get('functionName')) {
return result;
}
const args = [];
let valueStr = `${value}`;
let valueStr = `${result.value}`;
let start = valueStr.indexOf('(') + 1;
let end = valueStr.lastIndexOf(')');
args.push(start);
@ -134,6 +143,10 @@ module.exports = require('backbone').Model.extend({
value = `${fn}(${value})`;
}
if (this.get('important')) {
value = `${value} !important`;
}
return value || '';
}
});

2
src/style_manager/view/PropertyView.js

@ -413,7 +413,7 @@ module.exports = Backbone.View.extend({
*/
isComponentStylable() {
const em = this.em;
const component = em && em.get('selectedComponent');
const component = em && em.getSelected();
if (!component) {
return true;

2
src/style_manager/view/SectorsView.js

@ -22,7 +22,7 @@ module.exports = Backbone.View.extend({
this.propTarget = target;
const coll = this.collection;
const events =
'change:selectedComponent component:update:classes component:update:state change:device';
'component:toggled component:update:classes component:update:state change:device';
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.render);
this.listenTo(this.target, events, this.targetUpdated);

2
src/trait_manager/view/TraitsView.js

@ -23,7 +23,7 @@ module.exports = DomainViews.extend({
this.pfx = config.stylePrefix || '';
this.ppfx = config.pStylePrefix || '';
this.className = this.pfx + 'traits';
const toListen = 'component:selected';
const toListen = 'component:toggled';
this.listenTo(this.em, toListen, this.updatedCollection);
this.updatedCollection();
},

2
src/undo_manager/index.js

@ -81,7 +81,7 @@ module.exports = () => {
const events = ['style', 'attributes', 'content', 'src'];
events.forEach(ev => um.addUndoType(`change:${ev}`, customUndoType));
um.on('undo redo', () =>
em.trigger('change:selectedComponent change:canvasOffset')
em.trigger('component:toggled change:canvasOffset')
);
['undo', 'redo'].forEach(ev => um.on(ev, () => em.trigger(ev)));

28
src/utils/Sorter.js

@ -1,6 +1,6 @@
import Backbone from 'backbone';
import { isString, isFunction } from 'underscore';
import { on, off, matches } from 'utils/mixins';
import { isString, isFunction, isArray } from 'underscore';
import { on, off, matches, getElement } from 'utils/mixins';
const $ = Backbone.$;
module.exports = Backbone.View.extend({
@ -482,9 +482,9 @@ module.exports = Backbone.View.extend({
* @param {HTMLElement} trg
* @return {Boolean}
*/
validTarget(trg) {
let srcModel = this.getSourceModel();
let src = srcModel && srcModel.view && srcModel.view.el;
validTarget(trg, src) {
let srcModel = this.getSourceModel(src);
src = srcModel && srcModel.view && srcModel.view.el;
let trgModel = this.getTargetModel(trg);
trg = trgModel && trgModel.view && trgModel.view.el;
let result = {
@ -905,8 +905,11 @@ module.exports = Backbone.View.extend({
* */
endMove(e) {
var created;
const moved = [];
const docs = this.getDocuments();
const container = this.getContainerEl();
const onEndMove = this.onEndMove;
const { target, lastPos } = this;
off(container, 'mousemove dragover', this.onMove);
off(docs, 'mouseup dragend', this.endMove);
off(docs, 'keydown', this.rollback);
@ -925,11 +928,14 @@ module.exports = Backbone.View.extend({
}
if (this.moved) {
created = this.move(this.target, src, this.lastPos);
const toMove = this.toMove;
const toMoveArr = isArray(toMove) ? toMove : toMove ? [toMove] : [src];
toMoveArr.forEach(model => {
moved.push(this.move(target, model, lastPos));
});
}
if (this.plh) this.plh.style.display = 'none';
if (isFunction(this.onEndMove)) this.onEndMove(created, this);
var dragHelper = this.dragHelper;
if (dragHelper) {
@ -939,6 +945,9 @@ module.exports = Backbone.View.extend({
this.selectTargetModel();
this.toggleSortCursor();
this.toMove = null;
isFunction(onEndMove) && onEndMove(moved, this);
},
/**
@ -949,11 +958,12 @@ module.exports = Backbone.View.extend({
* */
move(dst, src, pos) {
var em = this.em;
em && em.trigger('component:dragEnd:before', dst, src, pos); // @depricated
const srcEl = getElement(src);
em && em.trigger('component:dragEnd:before', dst, srcEl, pos); // @depricated
var warns = [];
var index = pos.index;
var modelToDrop, modelTemp, created;
var validResult = this.validTarget(dst);
var validResult = this.validTarget(dst, srcEl);
var targetCollection = $(dst).data('collection');
var model = validResult.srcModel;
var droppable = validResult.droppable;

14
src/utils/mixins.js

@ -105,17 +105,29 @@ const hasDnd = em => {
const getElement = el => {
if (isElement(el)) {
return el;
} else if (el.getEl) {
} else if (el && el.getEl) {
return el.getEl();
}
};
/**
* Ensure to fetch the model from the input argument
* @param {HTMLElement|Component} el Component or HTML element
* @return {Component}
*/
const getModel = (el, $) => {
let model = el;
isElement(el) && (model = $(el).data('model'));
return model;
};
export {
on,
off,
hasDnd,
upFirst,
matches,
getModel,
camelCase,
getElement,
shallowDiff,

91
test/specs/grapesjs/index.js

@ -414,5 +414,96 @@ describe('GrapesJS', () => {
expect(editor.getStyle().length).toEqual(2);
expect(css).toEqual(`${protCss}.test2{color:red;}.test3{color:blue;}`);
});
describe('Component selection', () => {
let editor, wrapper, el1, el2, el3;
beforeEach(() => {
config.storageManager = { type: 0 };
config.components = `<div>
<div id="el1"></div>
<div id="el2"></div>
<div id="el3"></div>
</div>`;
editor = obj.init(config);
wrapper = editor.DomComponents.getWrapper();
el1 = wrapper.find('#el1')[0];
el2 = wrapper.find('#el2')[0];
el3 = wrapper.find('#el3')[0];
});
test('Select a single component', () => {
expect(editor.getSelected()).toBeFalsy();
expect(editor.getSelectedAll().length).toBe(0);
// Select via component
editor.select(el1);
expect(editor.getSelected()).toBe(el1);
expect(editor.getSelectedAll().length).toBe(1);
// Select via element
editor.select(el2.getEl());
expect(editor.getSelected()).toBe(el2);
expect(editor.getSelectedAll().length).toBe(1);
// Deselect via empty array
editor.select([]);
expect(editor.getSelected()).toBeFalsy();
expect(editor.getSelectedAll().length).toBe(0);
});
test('Select multiple components', () => {
// Select at first el1 and el3
editor.select([el1, el3.getEl()]);
expect(editor.getSelected()).toBe(el3);
expect(editor.getSelectedAll().length).toBe(2);
// Add el2
editor.selectAdd(el2);
expect(editor.getSelected()).toBe(el2);
expect(editor.getSelectedAll().length).toBe(3);
// Remove el1
editor.selectRemove(el1);
expect(editor.getSelected()).toBe(el2);
expect(editor.getSelectedAll().length).toBe(2);
// Add el1 via toggle
editor.selectToggle(el1);
expect(editor.getSelected()).toBe(el1);
expect(editor.getSelectedAll().length).toBe(3);
// Leave selected only el3
editor.selectRemove([el1, el2]);
expect(editor.getSelected()).toBe(el3);
expect(editor.getSelectedAll().length).toBe(1);
// Toggle all
editor.selectToggle([el1, el2, el3]);
expect(editor.getSelected()).toBe(el2);
expect(editor.getSelectedAll().length).toBe(2);
// Add mutiple
editor.selectAdd([el2, el3]);
expect(editor.getSelected()).toBe(el3);
expect(editor.getSelectedAll().length).toBe(3);
});
test('Selection events', () => {
const toSpy = {
selected() {},
deselected() {},
toggled() {}
};
const selected = jest.spyOn(toSpy, 'selected');
const deselected = jest.spyOn(toSpy, 'deselected');
const toggled = jest.spyOn(toSpy, 'toggled');
editor.on('component:selected', selected);
editor.on('component:deselected', deselected);
editor.on('component:toggled', toggled);
editor.select(el1); // selected=1
editor.selectAdd(el1); // selected=1
editor.selectAdd([el2, el3]); // selected=3
editor.selectToggle([el1, el3]); // deselected=2
editor.selectRemove(el2); // deselected=3
editor.select(el1); // selected=4
expect(selected).toBeCalledTimes(4);
expect(deselected).toBeCalledTimes(3);
expect(toggled).toBeCalledTimes(7);
});
});
});
});

6
test/specs/selector_manager/e2e/ClassManager.js

@ -83,7 +83,7 @@ module.exports = {
test('Can assign only one time the same class on selected component and the class viewer', () => {
var model = components.add({});
gjs.editor.set('selectedComponent', model);
gjs.editor.setSelected(model);
tagEl.addNewTag('test');
tagEl.addNewTag('test');
expect(model.get('classes').length).toEqual(1);
@ -99,7 +99,7 @@ module.exports = {
test('Removing from container removes also from selected component', () => {
var model = components.add({});
gjs.editor.set('selectedComponent', model);
gjs.editor.setSelected(model);
tagEl.addNewTag('test');
tagEl
.getClasses()
@ -111,7 +111,7 @@ module.exports = {
test('Trigger correctly event on target with new class add', () => {
var spy = sinon.spy();
var model = components.add({});
gjs.editor.set('selectedComponent', model);
gjs.editor.setSelected(model);
tagEl.addNewTag('test');
gjs.editor.on('component:update:classes', spy);
tagEl.addNewTag('test');

9
test/specs/selector_manager/view/ClassTagView.js

@ -1,3 +1,4 @@
const EditorModel = require('editor/model/Editor');
const ClassTagView = require('selector_manager/view/ClassTagView');
const Selectors = require('selector_manager/model/Selectors');
@ -17,12 +18,14 @@ module.exports = {
label: testLabel
});
obj = new ClassTagView({
config: {},
config: {
em: new EditorModel()
},
model,
coll
});
obj.target = { get() {} };
_.extend(obj.target, Backbone.Events);
//obj.target = { get() {} };
//_.extend(obj.target, Backbone.Events);
document.body.innerHTML = '<div id="fixtures"></div>';
fixtures = document.body.querySelector('#fixtures');
fixtures.appendChild(obj.render().el);

4
test/specs/selector_manager/view/ClassTagsView.js

@ -114,7 +114,7 @@ module.exports = {
test('Collection changes on update of target', () => {
coll.add({ name: 'test' });
target.trigger('change:selectedComponent');
target.trigger('component:toggled');
expect(coll.length).toEqual(0);
});
@ -131,7 +131,6 @@ module.exports = {
});
test('Accept new tags', () => {
sinon.stub(target, 'get').returns(testContext.targetStub);
view.compTarget = testContext.compTargetStub;
view.addNewTag('test');
view.compTarget = testContext.compTargetStub;
@ -170,7 +169,6 @@ module.exports = {
});
test('Update state visibility on removing of the tag', () => {
sinon.stub(target, 'get').returns(testContext.targetStub);
view.compTarget = testContext.compTargetStub;
view.addNewTag('test');
sinon.stub(view, 'updateStateVis');

12
test/specs/style_manager/model/Models.js

@ -159,6 +159,18 @@ module.exports = {
expect(obj.parseValue('fn(testValue)')).toEqual(result);
expect(obj.parseValue('fn(testValue')).toEqual(result);
});
test('Parse correctly a value with !important', () => {
const result = { value: 'red', important: 1 };
expect(obj.parseValue('red !important ')).toEqual(result);
});
test('getFullValue', () => {
obj = new Property({ functionName: 'fn', value: 'red' });
expect(obj.getFullValue()).toEqual('fn(red)');
obj = new Property({ functionName: 'fn', value: '#123', important: 1 });
expect(obj.getFullValue()).toEqual('fn(#123) !important');
});
});
describe('PropertyInteger', () => {

Loading…
Cancel
Save