Browse Source

Merge pull request #4105 from artf/dev

Merge dev
pull/4254/head
Artur Arseniev 5 years ago
committed by GitHub
parent
commit
f79ea18430
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      dist/css/grapes.min.css
  2. 4
      dist/grapes.min.js
  3. 2
      dist/grapes.min.js.map
  4. 16
      docs/api/component.md
  5. 1
      docs/api/editor.md
  6. 8
      docs/api/parser.md
  7. 3
      package.json
  8. 22
      src/asset_manager/index.js
  9. 61
      src/canvas/view/FrameView.js
  10. 74
      src/code_manager/model/CssGenerator.js
  11. 28
      src/commands/view/Resize.js
  12. 55
      src/dom_components/model/Component.js
  13. 27
      src/dom_components/view/ComponentImageView.js
  14. 17
      src/domain_abstract/ui/InputNumber.js
  15. 3
      src/editor/config/config.js
  16. 1
      src/editor/index.js
  17. 17
      src/parser/config/config.js
  18. 4
      src/parser/index.js
  19. 66
      src/parser/model/ParserHtml.js
  20. 2
      src/selector_manager/index.js
  21. 10
      src/selector_manager/view/ClassTagsView.js
  22. 6
      src/style_manager/view/PropertyNumberView.js
  23. 3
      src/styles/scss/main.scss
  24. 501
      src/styles/scss/spectrum.scss
  25. 93
      test/specs/dom_components/model/Component.js
  26. 9
      test/specs/style_manager/view/PropertyIntegerView.js
  27. 42
      yarn.lock

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

4
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
dist/grapes.min.js.map

File diff suppressed because one or more lines are too long

16
docs/api/component.md

@ -48,6 +48,7 @@ component.get('tagName');
* `selectable` **[Boolean][3]?** Allow component to be selected when clicked. Default: `true`
* `hoverable` **[Boolean][3]?** Shows a highlight outline when hovering on the element if `true`. Default: `true`
* `void` **[Boolean][3]?** This property is used by the HTML exporter as void elements don't have closing tags, eg. `<br/>`, `<hr/>`, etc. Default: `false`
* `style` **[Object][2]?** Component default style, eg. `{ width: '100px', height: '100px', 'background-color': 'red' }`
* `styles` **[String][1]?** Component related styles, eg. `.my-component-class { color: red }`
* `content` **[String][1]?** Content of the component (not escaped) which will be appended before children rendering. Default: `''`
* `icon` **[String][1]?** Component's icon, this string will be inserted before the name (in Layers and badge), eg. it can be an HTML string '<i class="fa fa-square-o"></i>'. Default: `''`
@ -632,8 +633,9 @@ Return HTML string of the component
* `opts` **[Object][2]** Options (optional, default `{}`)
* `opts.tag` **[String][1]?** Custom tagName
* `opts.attributes` **([Object][2] | [Function][4])** You can pass an object of custom attributes to replace
with the current one or you can even pass a function to generate attributes dynamically (optional, default `null`)
* `opts.attributes` **([Object][2] | [Function][4])** You can pass an object of custom attributes to replace with the current ones or you can even pass a function to generate attributes dynamically. (optional, default `null`)
* `opts.withProps` **[Boolean][3]?** Include component properties as `data-gjs-*` attributes. This allows you to have re-importable HTML.
* `opts.altQuoteAttr` **[Boolean][3]?** In case the attribute value contains a `"` char, instead of escaping it (`attr="value &quot;"`), the attribute will be quoted using single quotes (`attr='value "'`).
#### Examples
@ -662,6 +664,16 @@ component.toHTML({
Returns **[String][1]** HTML string
### getInnerHTML
Get inner HTML of the component
#### Parameters
* `opts` **[Object][2]** Same options of `toHTML` (optional, default `{}`)
Returns **[String][1]** HTML string
### getChangedProps
Return an object containing only changed props

1
docs/api/editor.md

@ -119,6 +119,7 @@ Returns CSS built inside canvas
* `opts.component` **Component?** Return the CSS of a specific Component
* `opts.json` **[Boolean][18]** Return an array of CssRules instead of the CSS string (optional, default `false`)
* `opts.avoidProtected` **[Boolean][18]** Don't include protected CSS (optional, default `false`)
* `opts.onlyMatched` **[Boolean][18]** Return only rules matched by the passed component. (optional, default `false`)
Returns **([String][16] | [Array][19]\<CssRule>)** CSS string or array of CssRules

8
docs/api/parser.md

@ -51,6 +51,8 @@ Parse HTML string and return the object containing the Component Definition
* `options` **[Object][5]?** Options (optional, default `{}`)
* `options.htmlType` **[String][6]?** [HTML mime type][7] to parse
* `options.allowScripts` **[Boolean][8]** Allow `<script>` tags (optional, default `false`)
* `options.allowUnsafeAttr` **[Boolean][8]** Allow unsafe HTML attributes (eg. `on*` inline event handlers) (optional, default `false`)
### Examples
@ -83,7 +85,7 @@ const res = Parser.parseCss('.cls { color: red }');
// [{ ... }]
```
Returns **[Array][8]<[Object][5]>** Array containing the result
Returns **[Array][9]<[Object][5]>** Array containing the result
[1]: https://github.com/artf/grapesjs/blob/master/src/parser/config/config.js
@ -99,4 +101,6 @@ Returns **[Array][8]<[Object][5]>** Array containing the result
[7]: https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

3
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.18.1",
"version": "0.18.2",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
@ -21,7 +21,6 @@
"codemirror": "^5.63.0",
"codemirror-formatting": "^1.0.0",
"promise-polyfill": "^8.1.3",
"spectrum-colorpicker": "^1.8.0",
"underscore": "^1.13.1"
},
"devDependencies": {

22
src/asset_manager/index.js

@ -360,14 +360,8 @@ export default () => {
const toRender = assts || this.getAll().models;
if (!am) {
const obj = {
collection: assetsVis, // Collection visible in asset manager
globalCollection: assets,
config: c,
module: this,
};
fu = new FileUpload(obj);
obj.fu = fu;
const obj = this.__viewParams();
obj.fu = this.FileUploader();
const el = am && am.el;
am = new AssetsView({
el,
@ -380,6 +374,15 @@ export default () => {
return this.getContainer();
},
__viewParams() {
return {
collection: assetsVis, // Collection visible in asset manager
globalCollection: assets,
config: c,
module: this,
};
},
/**
* Add new type. If you want to get more about type definition we suggest to read the [module's page](/modules/Assets.html)
* @param {string} id Type ID
@ -425,6 +428,9 @@ export default () => {
},
FileUploader() {
if (!fu) {
fu = new FileUpload(this.__viewParams());
}
return fu;
},

61
src/canvas/view/FrameView.js

@ -1,16 +1,8 @@
import Backbone from 'backbone';
import { bindAll, isString, debounce, isUndefined } from 'underscore';
import CssRulesView from 'css_composer/view/CssRulesView';
import ComponentView from 'dom_components/view/ComponentView';
import Droppable from 'utils/Droppable';
import {
appendVNodes,
empty,
append,
createEl,
createCustomEvent,
motionsEv
} from 'utils/dom';
import { appendVNodes, append, createEl, createCustomEvent, motionsEv } from 'utils/dom';
import { on, off, setViewEl, hasDnd, getPointerEvent } from 'utils/mixins';
export default Backbone.View.extend({
@ -18,22 +10,16 @@ export default Backbone.View.extend({
attributes: {
allowfullscreen: 'allowfullscreen',
'data-frame-el': true
'data-frame-el': true,
},
initialize(o) {
bindAll(
this,
'updateClientY',
'stopAutoscroll',
'autoscroll',
'_emitUpdate'
);
bindAll(this, 'updateClientY', 'stopAutoscroll', 'autoscroll', '_emitUpdate');
const { model, el } = this;
this.tools = {};
this.config = {
...(o.config || {}),
frameView: this
frameView: this,
};
this.ppfx = this.config.pStylePrefix || '';
this.em = this.config.em;
@ -62,9 +48,7 @@ export default Backbone.View.extend({
const find = (items, stack, res) => {
items.forEach(item => {
const { tag, attributes } = item;
const has = stack.some(
s => s.tag === tag && attrStr(s.attributes) === attrStr(attributes)
);
const has = stack.some(s => s.tag === tag && attrStr(s.attributes) === attrStr(attributes));
!has && res.push(item);
});
};
@ -159,7 +143,7 @@ export default Backbone.View.extend({
scrollTop,
scrollLeft,
scrollBottom: scrollTop + height,
scrollRight: scrollLeft + width
scrollRight: scrollLeft + width,
};
},
@ -234,7 +218,7 @@ export default Backbone.View.extend({
this.lastClientY = getPointerEvent(ev).clientY * this.em.getZoomDecimal();
},
showGlobalTools: debounce(function() {
showGlobalTools: debounce(function () {
this.getGlobalToolsEl().style.opacity = '';
}, 50),
@ -268,7 +252,7 @@ export default Backbone.View.extend({
const src = scripts.shift();
const scriptEl = createEl('script', {
type: 'text/javascript',
...(isString(src) ? { src } : src)
...(isString(src) ? { src } : src),
});
scriptEl.onerror = scriptEl.onload = appendScript.bind(null, scripts);
el.contentDocument.head.appendChild(scriptEl);
@ -292,8 +276,8 @@ export default Backbone.View.extend({
tag: 'link',
attributes: {
rel: 'stylesheet',
...(isString(href) ? { href } : href)
}
...(isString(href) ? { href } : href),
},
}));
const prevStyles = normalize(opts.prev || canvas.previous('styles'));
const styles = normalize(canvas.get('styles'));
@ -399,12 +383,13 @@ export default Backbone.View.extend({
</style>`
);
const component = model.getComponent();
this.wrapper = new ComponentView({
const { view } = em.get('DomComponents').getType('wrapper');
this.wrapper = new view({
model: component,
config: {
...component.config,
frameView: this
}
frameView: this,
},
}).render();
append(body, this.wrapper.el);
append(
@ -413,8 +398,8 @@ export default Backbone.View.extend({
collection: model.getStyles(),
config: {
...em.get('CssComposer').getConfig(),
frameView: this
}
frameView: this,
},
}).render().el
);
append(body, this.getJsContainer());
@ -422,11 +407,7 @@ export default Backbone.View.extend({
//this.updateOffset(); // TOFIX (check if I need it)
// Avoid some default behaviours
on(
body,
'click',
ev => ev && ev.target.tagName == 'A' && ev.preventDefault()
);
on(body, 'click', ev => ev && ev.target.tagName == 'A' && ev.preventDefault());
on(body, 'submit', ev => ev && ev.preventDefault());
// When the iframe is focused the event dispatcher is not the same so
@ -435,12 +416,10 @@ export default Backbone.View.extend({
{ event: 'keydown keyup keypress', class: 'KeyboardEvent' },
{ event: 'mousedown mousemove mouseup', class: 'MouseEvent' },
{ event: 'pointerdown pointermove pointerup', class: 'PointerEvent' },
{ event: 'wheel', class: 'WheelEvent' }
{ event: 'wheel', class: 'WheelEvent' },
].forEach(obj =>
obj.event.split(' ').forEach(event => {
doc.addEventListener(event, ev =>
this.el.dispatchEvent(createCustomEvent(ev, obj.class))
);
doc.addEventListener(event, ev => this.el.dispatchEvent(createCustomEvent(ev, obj.class)));
})
);
@ -457,5 +436,5 @@ export default Backbone.View.extend({
_emitUpdate() {
this.model._emitUpdated();
}
},
});

74
src/code_manager/model/CssGenerator.js

@ -1,5 +1,6 @@
import Backbone from 'backbone';
import { bindAll, isUndefined, each } from 'underscore';
import { hasWin } from 'utils/mixins';
const maxValue = Number.MAX_VALUE;
@ -54,16 +55,17 @@ export default Backbone.Model.extend({
this.model = model;
const codeJson = [];
let code = model ? this.buildFromModel(model, opts) : '';
const clearStyles =
isUndefined(opts.clearStyles) && em
? em.getConfig('clearStyles')
: opts.clearStyles;
const clearStyles = isUndefined(opts.clearStyles) && em ? em.getConfig('clearStyles') : opts.clearStyles;
if (cssc) {
const rules = opts.rules || cssc.getAll();
let rules = opts.rules || cssc.getAll();
const atRules = {};
const dump = [];
if (opts.onlyMatched && model && hasWin()) {
rules = this.matchedRules(model, rules);
}
rules.forEach(rule => {
const atRule = rule.getAtRule();
@ -130,11 +132,7 @@ export default Backbone.Model.extend({
// This will not render a rule if there is no its component
rule.get('selectors').each(selector => {
const name = selector.getFullName();
if (
this.compCls.indexOf(name) >= 0 ||
this.ids.indexOf(name) >= 0 ||
opts.keepUnusedStyles
) {
if (this.compCls.indexOf(name) >= 0 || this.ids.indexOf(name) >= 0 || opts.keepUnusedStyles) {
found = 1;
}
});
@ -149,6 +147,39 @@ export default Backbone.Model.extend({
return result;
},
/**
* Get matched rules of a component
* @param {Component} component
* @param {Array<CSSRule>} rules
* @returns {Array<CSSRule>}
*/
matchedRules(component, rules) {
const el = component.getEl();
let result = [];
rules.forEach(rule => {
try {
if (
rule
.selectorsToString()
.split(',')
.some(selector => el.matches(this.__cleanSelector(selector)))
) {
result.push(rule);
}
} catch (err) {}
});
component.components().forEach(component => {
result = result.concat(this.matchedRules(component, rules));
});
// Remove duplicates
result = result.filter((rule, i) => result.indexOf(rule) === i);
return result;
},
/**
* Get the numeric length of the media query string
* @param {String} mediaQuery Media query string
@ -170,9 +201,7 @@ export default Backbone.Model.extend({
const itemsArr = [];
each(items, (value, key) => itemsArr.push({ key, value }));
return itemsArr.sort((a, b) => {
const isMobFirst = [a.key, b.key].every(
mquery => mquery.indexOf('min-width') !== -1
);
const isMobFirst = [a.key, b.key].every(mquery => mquery.indexOf('min-width') !== -1);
const left = isMobFirst ? a.key : b.key;
const right = isMobFirst ? b.key : a.key;
return this.getQueryLength(left) - this.getQueryLength(right);
@ -181,11 +210,22 @@ export default Backbone.Model.extend({
sortRules(a, b) {
const getKey = rule => rule.get('mediaText');
const isMobFirst = [getKey(a), getKey(b)].every(
q => q.indexOf('min-width') !== -1
);
const isMobFirst = [getKey(a), getKey(b)].every(q => q.indexOf('min-width') !== -1);
const left = isMobFirst ? getKey(a) : getKey(b);
const right = isMobFirst ? getKey(b) : getKey(a);
return this.getQueryLength(left) - this.getQueryLength(right);
}
},
/**
* Return passed selector without states
* @param {String} selector
* @returns {String}
* @private
*/
__cleanSelector(selector) {
return selector
.split(' ')
.map(item => item.split(':')[0])
.join(' ');
},
});

28
src/commands/view/Resize.js

@ -1,15 +1,16 @@
export default {
run(editor, sender, opts) {
var opt = opts || {};
var el = opt.el || '';
var canvas = editor.Canvas;
var canvasResizer = this.canvasResizer;
var options = opt.options || {};
var canvasView = canvas.getCanvasView();
options.appendTo = canvas.getResizerEl();
options.prefix = editor.getConfig().stylePrefix;
options.posFetcher = canvasView.getElementPos.bind(canvasView);
options.mousePosFetcher = canvas.getMouseRelativePos;
const opt = opts || {};
const canvas = editor.Canvas;
const canvasView = canvas.getCanvasView();
const options = {
appendTo: canvas.getResizerEl(),
prefix: editor.getConfig().stylePrefix,
posFetcher: canvasView.getElementPos.bind(canvasView),
mousePosFetcher: canvas.getMouseRelativePos,
...(opt.options || {}),
};
let { canvasResizer } = this;
// Create the resizer for the canvas if not yet created
if (!canvasResizer || opt.forceNew) {
@ -19,12 +20,11 @@ export default {
canvasResizer.setOptions(options);
canvasResizer.blur();
canvasResizer.focus(el);
canvasResizer.focus(opt.el);
return canvasResizer;
},
stop() {
const resizer = this.canvasResizer;
resizer && resizer.blur();
}
this.canvasResizer?.blur();
},
};

55
src/dom_components/model/Component.js

@ -75,6 +75,7 @@ export const keyUpdateInside = `${keyUpdate}-inside`;
* @property {Boolean} [selectable=true] Allow component to be selected when clicked. Default: `true`
* @property {Boolean} [hoverable=true] Shows a highlight outline when hovering on the element if `true`. Default: `true`
* @property {Boolean} [void=false] This property is used by the HTML exporter as void elements don't have closing tags, eg. `<br/>`, `<hr/>`, etc. Default: `false`
* @property {Object} [style={}] Component default style, eg. `{ width: '100px', height: '100px', 'background-color': 'red' }`
* @property {String} [styles=''] Component related styles, eg. `.my-component-class { color: red }`
* @property {String} [content=''] Content of the component (not escaped) which will be appended before children rendering. Default: `''`
* @property {String} [icon=''] Component's icon, this string will be inserted before the name (in Layers and badge), eg. it can be an HTML string '<i class="fa fa-square-o"></i>'. Default: `''`
@ -116,19 +117,17 @@ export default class Component extends Model.extend(Styleable) {
// Propagate properties from parent if indicated
const parent = this.parent();
const parentAttr = parent && parent.attributes;
const propagate = this.get('propagate');
propagate && this.set('propagate', isArray(propagate) ? propagate : [propagate]);
if (parentAttr && parentAttr.propagate) {
let newAttr = {};
if (parentAttr && parentAttr.propagate && !propagate) {
const newAttr = {};
const toPropagate = parentAttr.propagate;
toPropagate.forEach(prop => (newAttr[prop] = parent.get(prop)));
newAttr.propagate = toPropagate;
newAttr = { ...newAttr, ...props };
this.set(newAttr);
this.set({ ...newAttr, ...props });
}
const propagate = this.get('propagate');
propagate && this.set('propagate', isArray(propagate) ? propagate : [propagate]);
// Check void elements
if (opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0) {
this.set('void', true);
@ -1371,8 +1370,9 @@ export default class Component extends Model.extend(Styleable) {
* Return HTML string of the component
* @param {Object} [opts={}] Options
* @param {String} [opts.tag] Custom tagName
* @param {Object|Function} [opts.attributes=null] You can pass an object of custom attributes to replace
* with the current one or you can even pass a function to generate attributes dynamically
* @param {Object|Function} [opts.attributes=null] You can pass an object of custom attributes to replace with the current ones or you can even pass a function to generate attributes dynamically.
* @param {Boolean} [opts.withProps] Include component properties as `data-gjs-*` attributes. This allows you to have re-importable HTML.
* @param {Boolean} [opts.altQuoteAttr] In case the attribute value contains a `"` char, instead of escaping it (`attr="value &quot;"`), the attribute will be quoted using single quotes (`attr='value "'`).
* @return {String} HTML string
* @example
* // Simple HTML return
@ -1415,15 +1415,33 @@ export default class Component extends Model.extend(Styleable) {
}
}
if (opts.withProps) {
const props = this.toJSON();
forEach(props, (value, key) => {
const skipProps = ['classes', 'attributes', 'components'];
if (key[0] !== '_' && skipProps.indexOf(key) < 0) {
attributes[`data-gjs-${key}`] = isArray(value) || isObject(value) ? JSON.stringify(value) : value;
}
});
}
for (let attr in attributes) {
const val = attributes[attr];
const value = isString(val) ? val.replace(/"/g, '&quot;') : val;
if (!isUndefined(value)) {
if (isBoolean(value)) {
value && attrs.push(attr);
if (!isUndefined(val) && val !== null) {
if (isBoolean(val)) {
val && attrs.push(attr);
} else {
attrs.push(`${attr}="${value}"`);
let valueRes = '';
if (opts.altQuoteAttr && isString(val) && val.indexOf('"') >= 0) {
valueRes = `'${val.replace(/'/g, '&apos;')}'`;
} else {
const value = isString(val) ? val.replace(/"/g, '&quot;') : val;
valueRes = `"${value}"`;
}
attrs.push(`${attr}=${valueRes}`);
}
}
}
@ -1436,6 +1454,15 @@ export default class Component extends Model.extend(Styleable) {
return code;
}
/**
* Get inner HTML of the component
* @param {Object} [opts={}] Same options of `toHTML`
* @returns {String} HTML string
*/
getInnerHTML(opts) {
return this.__innerHTML(opts);
}
__innerHTML(opts = {}) {
const cmps = this.components();
return !cmps.length ? this.get('content') : cmps.map(c => c.toHTML(opts)).join('');

27
src/dom_components/view/ComponentImageView.js

@ -9,7 +9,7 @@ export default ComponentView.extend({
click: 'initResize',
error: 'onError',
load: 'onLoad',
dragstart: 'noDrag'
dragstart: 'noDrag',
},
initialize(o) {
@ -24,21 +24,16 @@ export default ComponentView.extend({
*/
fetchFile() {
if (this.modelOpt.temporary) return;
const model = this.model;
const { model, em } = this;
const file = model.get('file');
if (file) {
const fu = this.em.get('AssetManager').FileUploader();
fu.uploadFile(
{
dataTransfer: { files: [file] }
},
res => {
const obj = res && res.data && res.data[0];
const src = obj && (isString(obj) ? obj : obj.src);
src && model.set({ src });
}
);
if (file && em) {
const fu = em.get('AssetManager').FileUploader();
fu?.uploadFile({ dataTransfer: { files: [file] } }, res => {
const obj = res && res.data && res.data[0];
const src = obj && (isString(obj) ? obj : obj.src);
src && model.set({ src });
});
model.set('file', '');
}
},
@ -73,7 +68,7 @@ export default ComponentView.extend({
},
target: model,
types: ['image'],
accept: 'image/*'
accept: 'image/*',
});
}
},
@ -103,5 +98,5 @@ export default ComponentView.extend({
this.postRender();
return this;
}
},
});

17
src/domain_abstract/ui/InputNumber.js

@ -144,12 +144,10 @@ export default Input.extend({
* Invoked when the up arrow is clicked
* */
upArrowClick() {
const model = this.model;
const { model } = this;
const step = model.get('step');
let value = parseFloat(model.get('value'));
value = this.normalizeValue(value + step);
var valid = this.validateInputValue(value);
model.set('value', valid.value);
this.setValue(this.normalizeValue(value + step));
this.elementUpdated();
},
@ -157,12 +155,10 @@ export default Input.extend({
* Invoked when the down arrow is clicked
* */
downArrowClick() {
const model = this.model;
const { model } = this;
const step = model.get('step');
const value = parseFloat(model.get('value'));
const val = this.normalizeValue(value - step);
var valid = this.validateInputValue(val);
model.set('value', valid.value);
this.setValue(this.normalizeValue(value - step));
this.elementUpdated();
},
@ -193,8 +189,9 @@ export default Input.extend({
const step = model.get('step');
const data = this.current;
var pos = this.normalizeValue(data.val + (data.y - ev.pageY) * step);
this.prValue = this.validateInputValue(pos).value;
model.set('value', this.prValue, { avoidStore: 1 });
const { value, unit } = this.validateInputValue(pos);
this.prValue = value;
model.set({ value, unit }, { avoidStore: 1 });
return false;
},

3
src/editor/config/config.js

@ -83,7 +83,8 @@ export default {
showToolbar: 1,
// Allow script tag importing
allowScripts: 0,
// @deprecated in favor of `config.parser.optionsHtml.allowScripts`
// allowScripts: 0,
// If true render a select of available devices
showDevices: 1,

1
src/editor/index.js

@ -168,6 +168,7 @@ export default (config = {}, opts = {}) => {
* @param {Component} [opts.component] Return the CSS of a specific Component
* @param {Boolean} [opts.json=false] Return an array of CssRules instead of the CSS string
* @param {Boolean} [opts.avoidProtected=false] Don't include protected CSS
* @param {Boolean} [opts.onlyMatched=false] Return only rules matched by the passed component.
* @returns {String|Array<CssRule>} CSS string or array of CssRules
*/
getCss(opts) {

17
src/parser/config/config.js

@ -14,8 +14,17 @@ export default {
// Here the result will be XMLDocument, which extends Node
parserHtml: null,
// DOMParser mime type (default 'text/html')
// @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
// If you use the `text/html` parser, it will fix the invalid syntax automatically
htmlType: null
// Default HTML parser options (used in `parserModule.parseHtml('<div...', options)`)
optionsHtml: {
// DOMParser mime type (default 'text/html')
// @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
// If you use the `text/html` parser, it will fix the invalid syntax automatically
htmlType: null,
// Allow <script> tags
allowScripts: false,
// Allow unsafe HTML attributes (eg. `on*` inline event handlers)
allowUnsafeAttr: false,
},
};

4
src/parser/index.js

@ -67,6 +67,8 @@ export default () => {
* @param {String} input HTML string to parse
* @param {Object} [options] Options
* @param {String} [options.htmlType] [HTML mime type](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02) to parse
* @param {Boolean} [options.allowScripts=false] Allow `<script>` tags
* @param {Boolean} [options.allowUnsafeAttr=false] Allow unsafe HTML attributes (eg. `on*` inline event handlers)
* @returns {Object} Object containing the result `{ html: ..., css: ... }`
* @example
* const resHtml = Parser.parseHtml(`<table><div>Hi</div></table>`, {
@ -100,6 +102,6 @@ export default () => {
destroy() {
[conf, pHtml, pCss].forEach(i => (i = {}));
['em', 'parserCss', 'parserHtml'].forEach(i => (this[i] = {}));
}
},
};
};

66
src/parser/model/ParserHtml.js

@ -1,4 +1,4 @@
import { each, isString, isFunction } from 'underscore';
import { each, isString, isFunction, isUndefined } from 'underscore';
import BrowserParserHtml from './BrowserParserHtml';
export default config => {
@ -35,8 +35,7 @@ export default config => {
// so put it under try/catch and let fail silently
try {
value =
(firstChar == '{' && lastChar == '}') ||
(firstChar == '[' && lastChar == ']')
(firstChar == '{' && lastChar == '}') || (firstChar == '[' && lastChar == ']')
? JSON.parse(value)
: value;
} catch (e) {}
@ -49,7 +48,7 @@ export default config => {
return {
props,
attrs
attrs,
};
},
@ -69,10 +68,7 @@ export default config => {
var decl = decls[i].trim();
if (!decl) continue;
var prop = decl.split(':');
result[prop[0].trim()] = prop
.slice(1)
.join(':')
.trim();
result[prop[0].trim()] = prop.slice(1).join(':').trim();
}
return result;
},
@ -118,8 +114,7 @@ export default config => {
// Start with understanding what kind of component it is
if (ct) {
let obj = '';
let type =
node.getAttribute && node.getAttribute(`${modelAttrStart}type`);
let type = node.getAttribute && node.getAttribute(`${modelAttrStart}type`);
// If the type is already defined, use it
if (type) {
@ -177,8 +172,7 @@ export default config => {
// so put it under try/catch and let fail silently
try {
nodeValue =
(firstChar == '{' && lastChar == '}') ||
(firstChar == '[' && lastChar == ']')
(firstChar == '{' && lastChar == '}') || (firstChar == '[' && lastChar == ']')
? JSON.parse(nodeValue)
: nodeValue;
} catch (e) {}
@ -205,12 +199,12 @@ export default config => {
!model.type && (model.type = 'text');
model.components = {
type: 'textnode',
content: firstChild.nodeValue
content: firstChild.nodeValue,
};
} else {
model.components = this.parseNode(node, {
...opts,
inSvg: opts.inSvg || model.type === 'svg'
inSvg: opts.inSvg || model.type === 'svg',
});
}
}
@ -247,10 +241,7 @@ export default config => {
const comp = comps[ci];
const cType = comp.type;
if (
['text', 'textnode'].indexOf(cType) < 0 &&
c.textTags.indexOf(comp.tagName) < 0
) {
if (['text', 'textnode'].indexOf(cType) < 0 && c.textTags.indexOf(comp.tagName) < 0) {
allTxt = 0;
break;
}
@ -287,17 +278,29 @@ export default config => {
const conf = (em && em.get('Config')) || {};
const res = { html: null, css: null };
const cf = { ...config, ...opts };
const el = isFunction(cf.parserHtml)
? cf.parserHtml(str, cf)
: BrowserParserHtml(str, cf);
const options = {
...config.optionsHtml,
// Support previous `configParser.htmlType` option
htmlType: config.optionsHtml?.htmlType || config.htmlType,
...opts,
};
const el = isFunction(cf.parserHtml) ? cf.parserHtml(str, options) : BrowserParserHtml(str, options);
const scripts = el.querySelectorAll('script');
let i = scripts.length;
// Remove all scripts
if (!conf.allowScripts) {
// Support previous `configMain.allowScripts` option
const allowScripts = !isUndefined(conf.allowScripts) ? conf.allowScripts : options.allowScripts;
// Remove script tags
if (!allowScripts) {
while (i--) scripts[i].parentNode.removeChild(scripts[i]);
}
// Remove unsafe attributes
if (!options.allowUnsafeAttr) {
this.__clearUnsafeAttr(el);
}
// Detach style tags and parse them
if (parserCss) {
const styles = el.querySelectorAll('style');
@ -315,12 +318,23 @@ export default config => {
em && em.trigger(`${event}:root`, { input: str, root: el });
const result = this.parseNode(el);
// I have to keep it otherwise it breaks the DomComponents.addComponent (returns always array)
const resHtml =
result.length === 1 && !c.returnArray ? result[0] : result;
const resHtml = result.length === 1 && !c.returnArray ? result[0] : result;
res.html = resHtml;
em && em.trigger(event, { input: str, output: res });
return res;
}
},
__clearUnsafeAttr(node) {
const attrs = node.attributes || [];
const nodes = node.childNodes || [];
const toRemove = [];
each(attrs, attr => {
const name = attr.nodeName || '';
name.indexOf('on') === 0 && toRemove.push(name);
});
toRemove.map(name => node.removeAttribute(name));
each(nodes, node => this.__clearUnsafeAttr(node));
},
};
};

2
src/selector_manager/index.js

@ -402,7 +402,7 @@ export default () => {
* console.log(targetsToStyle.map(target => target.getSelectorsString()))
*/
getSelectedTargets() {
return this.em.get('StyleManager').getSelected();
return this.em.get('StyleManager').getSelectedAll();
},
/**

10
src/selector_manager/view/ClassTagsView.js

@ -1,6 +1,7 @@
import { isEmpty, isArray, isString, debounce } from 'underscore';
import Backbone from 'backbone';
import ClassTagView from './ClassTagView';
import html from 'utils/html';
export default Backbone.View.extend({
template({ labelInfo, labelHead, iconSync, iconAdd, pfx, ppfx }) {
@ -257,19 +258,20 @@ export default Backbone.View.extend({
let result;
if (isString(target)) {
result = `<span class="${pfx}sel-gen">${target}</span>`;
result = html`<span class="${pfx}sel-gen">${target}</span>`;
} else {
const sel = target && target.get && target.getSelectors();
if (!sel) return;
const selectors = sel.getStyleable();
const state = em.get('state');
const idRes = target.getId
? `<span class="${pfx}sel-cmp">${target.getName()}</span><span class="${pfx}sel-id">#${target.getId()}</span>`
? html`<span class="${pfx}sel-cmp">${target.getName()}</span>
<span class="${pfx}sel-id">#${target.getId()}</span>`
: '';
result = this.collection.getFullString(selectors);
result = result ? `<span class="${pfx}sel-rule">${result}</span>` : target.get('selectorsAdd') || idRes;
result = result ? html`<span class="${pfx}sel-rule">${result}</span>` : target.get('selectorsAdd') || idRes;
result = componentFirst && idRes ? idRes : result;
result += state ? `<span class="${pfx}sel-state">:${state}</span>` : '';
result += state ? html`<span class="${pfx}sel-state">:${state}</span>` : '';
result = selectedName ? selectedName({ result, state, target }) : result;
}

6
src/style_manager/view/PropertyNumberView.js

@ -11,10 +11,8 @@ export default class PropertyNumberView extends PropertyView {
this.listenTo(model, 'change:units', this.render);
}
setValue(value) {
const parsed = this.model.parseValue(value);
value = `${parsed.value}${parsed.unit}`;
this.inputInst?.setValue(value, { silent: 1 });
setValue() {
// handled by this.inputInst
}
onRender() {

3
src/styles/scss/main.scss

@ -1,10 +1,9 @@
/* stylelint-disable */
@use 'sass:math';
@import "spectrum-colorpicker/spectrum";
@import "codemirror/lib/codemirror";
@import "codemirror/theme/hopscotch";
@import "spectrum.scss";
@import "gjs_variables.scss";

501
src/styles/scss/spectrum.scss

@ -0,0 +1,501 @@
/***
Spectrum Colorpicker v1.8.1
https://github.com/bgrins/spectrum
Author: Brian Grinstead
License: MIT
***/
.sp-container {
position:absolute;
top:0;
left:0;
display:inline-block;
/* https://github.com/bgrins/spectrum/issues/40 */
z-index: 9999994;
overflow: hidden;
}
.sp-container.sp-flat {
position: relative;
}
/* Fix for * { box-sizing: border-box; } */
.sp-container,
.sp-container * {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
.sp-top {
position:relative;
width: 100%;
display:inline-block;
}
.sp-top-inner {
position:absolute;
top:0;
left:0;
bottom:0;
right:0;
}
.sp-color {
position: absolute;
top:0;
left:0;
bottom:0;
right:20%;
}
.sp-hue {
position: absolute;
top:0;
right:0;
bottom:0;
left:84%;
height: 100%;
}
.sp-clear-enabled .sp-hue {
top:33px;
height: 77.5%;
}
.sp-fill {
padding-top: 80%;
}
.sp-sat, .sp-val {
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
}
.sp-alpha-enabled .sp-top {
margin-bottom: 18px;
}
.sp-alpha-enabled .sp-alpha {
display: block;
}
.sp-alpha-handle {
position:absolute;
top:-4px;
bottom: -4px;
width: 6px;
left: 50%;
cursor: pointer;
border: 1px solid black;
background: white;
opacity: .8;
}
.sp-alpha {
display: none;
position: absolute;
bottom: -14px;
right: 0;
left: 0;
height: 8px;
}
.sp-alpha-inner {
border: solid 1px #333;
}
.sp-clear {
display: none;
}
.sp-clear.sp-clear-display {
background-position: center;
}
.sp-clear-enabled .sp-clear {
display: block;
position:absolute;
top:0px;
right:0;
bottom:0;
left:84%;
height: 28px;
}
/* Don't allow text selection */
.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
-webkit-user-select:none;
-moz-user-select: -moz-none;
-o-user-select:none;
user-select: none;
}
.sp-container.sp-input-disabled .sp-input-container {
display: none;
}
.sp-container.sp-buttons-disabled .sp-button-container {
display: none;
}
.sp-container.sp-palette-buttons-disabled .sp-palette-button-container {
display: none;
}
.sp-palette-only .sp-picker-container {
display: none;
}
.sp-palette-disabled .sp-palette-container {
display: none;
}
.sp-initial-disabled .sp-initial {
display: none;
}
/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
.sp-sat {
background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
}
.sp-val {
background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
}
.sp-hue {
background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
/* IE filters do not support multiple color stops.
Generate 6 divs, line them up, and do two color gradients for each.
Yes, really.
*/
.sp-1 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
}
.sp-2 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
}
.sp-3 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
}
.sp-4 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
}
.sp-5 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
}
.sp-6 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
}
.sp-hidden {
display: none !important;
}
/* Clearfix hack */
.sp-cf:before, .sp-cf:after { content: ""; display: table; }
.sp-cf:after { clear: both; }
/* Mobile devices, make hue slider bigger so it is easier to slide */
@media (max-device-width: 480px) {
.sp-color { right: 40%; }
.sp-hue { left: 63%; }
.sp-fill { padding-top: 60%; }
}
.sp-dragger {
border-radius: 5px;
height: 5px;
width: 5px;
border: 1px solid #fff;
background: #000;
cursor: pointer;
position:absolute;
top:0;
left: 0;
}
.sp-slider {
position: absolute;
top:0;
cursor:pointer;
height: 3px;
left: -1px;
right: -1px;
border: 1px solid #000;
background: white;
opacity: .8;
}
/*
Theme authors:
Here are the basic themeable display options (colors, fonts, global widths).
See http://bgrins.github.io/spectrum/themes/ for instructions.
*/
.sp-container {
border-radius: 0;
background-color: #ECECEC;
border: solid 1px #f0c49B;
padding: 0;
}
.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear {
font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.sp-top {
margin-bottom: 3px;
}
.sp-color, .sp-hue, .sp-clear {
border: solid 1px #666;
}
/* Input */
.sp-input-container {
float:right;
width: 100px;
margin-bottom: 4px;
}
.sp-initial-disabled .sp-input-container {
width: 100%;
}
.sp-input {
font-size: 12px !important;
border: 1px inset;
padding: 4px 5px;
margin: 0;
width: 100%;
background:transparent;
border-radius: 3px;
color: #222;
}
.sp-input:focus {
border: 1px solid orange;
}
.sp-input.sp-validation-error {
border: 1px solid red;
background: #fdd;
}
.sp-picker-container , .sp-palette-container {
float:left;
position: relative;
padding: 10px;
padding-bottom: 300px;
margin-bottom: -290px;
}
.sp-picker-container {
width: 172px;
border-left: solid 1px #fff;
}
/* Palettes */
.sp-palette-container {
border-right: solid 1px #ccc;
}
.sp-palette-only .sp-palette-container {
border: 0;
}
.sp-palette .sp-thumb-el {
display: block;
position:relative;
float:left;
width: 24px;
height: 15px;
margin: 3px;
cursor: pointer;
border:solid 2px transparent;
}
.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
border-color: orange;
}
.sp-thumb-el {
position:relative;
}
/* Initial */
.sp-initial {
float: left;
border: solid 1px #333;
}
.sp-initial span {
width: 30px;
height: 25px;
border:none;
display:block;
float:left;
margin:0;
}
.sp-initial .sp-clear-display {
background-position: center;
}
/* Buttons */
.sp-palette-button-container,
.sp-button-container {
float: right;
}
/* Replacer (the little preview div that shows up instead of the <input>) */
.sp-replacer {
margin:0;
overflow:hidden;
cursor:pointer;
padding: 4px;
display:inline-block;
border: solid 1px #91765d;
background: #eee;
color: #333;
vertical-align: middle;
}
.sp-replacer:hover, .sp-replacer.sp-active {
border-color: #F0C49B;
color: #111;
}
.sp-replacer.sp-disabled {
cursor:default;
border-color: silver;
color: silver;
}
.sp-dd {
padding: 2px 0;
height: 16px;
line-height: 16px;
float:left;
font-size:10px;
}
.sp-preview {
position:relative;
width:25px;
height: 20px;
border: solid 1px #222;
margin-right: 5px;
float:left;
z-index: 0;
}
.sp-palette {
max-width: 220px;
}
.sp-palette .sp-thumb-el {
width:16px;
height: 16px;
margin:2px 1px;
border: solid 1px #d0d0d0;
}
.sp-container {
padding-bottom:0;
}
/* Buttons: http://hellohappy.org/css3-buttons/ */
.sp-container button {
background-color: #eeeeee;
background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
border: 1px solid #ccc;
border-bottom: 1px solid #bbb;
border-radius: 3px;
color: #333;
font-size: 14px;
line-height: 1;
padding: 5px 4px;
text-align: center;
text-shadow: 0 1px 0 #eee;
vertical-align: middle;
}
.sp-container button:hover {
background-color: #dddddd;
background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
border: 1px solid #bbb;
border-bottom: 1px solid #999;
cursor: pointer;
text-shadow: 0 1px 0 #ddd;
}
.sp-container button:active {
border: 1px solid #aaa;
border-bottom: 1px solid #888;
-webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
}
.sp-cancel {
font-size: 11px;
color: #d93f3f !important;
margin:0;
padding:2px;
margin-right: 5px;
vertical-align: middle;
text-decoration:none;
}
.sp-cancel:hover {
color: #d93f3f !important;
text-decoration: underline;
}
.sp-palette span:hover, .sp-palette span.sp-thumb-active {
border-color: #000;
}
.sp-preview, .sp-alpha, .sp-thumb-el {
position:relative;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner {
display:block;
position:absolute;
top:0;left:0;bottom:0;right:0;
}
.sp-palette .sp-thumb-inner {
background-position: 50% 50%;
background-repeat: no-repeat;
}
.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
}
.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
}
.sp-clear-display {
background-repeat:no-repeat;
background-position: center;
background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
}

93
test/specs/dom_components/model/Component.js

@ -18,14 +18,14 @@ let em;
describe('Component', () => {
beforeEach(() => {
em = new Editor();
em = new Editor({ avoidDefaults: true });
dcomp = em.get('DomComponents');
em.get('PageManager').onLoad();
// dcomp = new DomComponents();
compOpts = {
em,
componentTypes: dcomp.componentTypes,
domc: dcomp
domc: dcomp,
};
obj = new Component({}, compOpts);
dcomp.init({ em });
@ -51,22 +51,11 @@ describe('Component', () => {
});
test('Clones correctly with traits', () => {
obj
.get('traits')
.at(0)
.set('value', 'testTitle');
obj.get('traits').at(0).set('value', 'testTitle');
var cloned = obj.clone();
cloned.set('stylable', 0);
cloned
.get('traits')
.at(0)
.set('value', 'testTitle2');
expect(
obj
.get('traits')
.at(0)
.get('value')
).toEqual('testTitle');
cloned.get('traits').at(0).set('value', 'testTitle2');
expect(obj.get('traits').at(0).get('value')).toEqual('testTitle');
expect(obj.get('stylable')).toEqual(true);
});
@ -75,12 +64,12 @@ describe('Component', () => {
{
label: 'Title',
name: 'title',
value: 'The title'
value: 'The title',
},
{
label: 'Context',
value: 'primary'
}
value: 'primary',
},
]);
expect(obj.get('attributes')).toEqual({ title: 'The title' });
});
@ -104,27 +93,25 @@ describe('Component', () => {
tagName: 'article',
attributes: {
'data-test1': 'value1',
'data-test2': 'value2'
}
'data-test2': 'value2',
},
});
expect(obj.toHTML()).toEqual(
'<article data-test1="value1" data-test2="value2"></article>'
);
expect(obj.toHTML()).toEqual('<article data-test1="value1" data-test2="value2"></article>');
});
test('Component toHTML with value-less attribute', () => {
obj = new Component({
tagName: 'div',
attributes: {
'data-is-a-test': ''
}
'data-is-a-test': '',
},
});
expect(obj.toHTML()).toEqual('<div data-is-a-test=""></div>');
});
test('Component toHTML with classes', () => {
obj = new Component({
tagName: 'article'
tagName: 'article',
});
['class1', 'class2'].forEach(item => {
obj.get('classes').add({ name: item });
@ -157,17 +144,41 @@ describe('Component', () => {
expect(obj.toHTML()).toEqual('<div data-test="&quot;value&quot;"></div>');
});
test('Component toHTML and withProps', () => {
obj = new Component({}, { em });
obj.set({
bool: true,
boolf: false,
string: `st'ri"ng`,
array: [1, 'string', true],
object: { a: 1, b: 'string', c: true },
null: null,
undef: undefined,
empty: '',
zero: 0,
_private: 'value',
});
let resStr = `st'ri&quot;ng`;
let resArr = '[1,&quot;string&quot;,true]';
let resObj = '{&quot;a&quot;:1,&quot;b&quot;:&quot;string&quot;,&quot;c&quot;:true}';
let res = `<div data-gjs-bool data-gjs-string="${resStr}" data-gjs-array="${resArr}" data-gjs-object="${resObj}" data-gjs-empty="" data-gjs-zero="0"></div>`;
expect(obj.toHTML({ withProps: true })).toEqual(res);
resStr = `st&apos;ri"ng`;
resArr = '[1,"string",true]';
resObj = '{"a":1,"b":"string","c":true}';
res = `<div data-gjs-bool data-gjs-string='${resStr}' data-gjs-array='${resArr}' data-gjs-object='${resObj}' data-gjs-empty="" data-gjs-zero="0"></div>`;
expect(obj.toHTML({ withProps: true, altQuoteAttr: true })).toEqual(res);
});
test('Manage correctly boolean attributes', () => {
obj = new Component();
obj.set('attributes', {
'data-test': 'value',
checked: false,
required: true,
avoid: true
avoid: true,
});
expect(obj.toHTML()).toEqual(
'<div data-test="value" required avoid></div>'
);
expect(obj.toHTML()).toEqual('<div data-test="value" required avoid></div>');
});
test('Component parse empty div', () => {
@ -261,18 +272,18 @@ describe('Component', () => {
id: 'test',
'data-test': 'value',
class: 'class1 class2',
style: 'color: white; background: #fff'
style: 'color: white; background: #fff',
});
expect(obj.getAttributes()).toEqual({
id: 'test',
class: 'class1 class2',
style: 'color:white;background:#fff;',
'data-test': 'value'
'data-test': 'value',
});
expect(obj.get('classes').length).toEqual(2);
expect(obj.getStyle()).toEqual({
color: 'white',
background: '#fff'
background: '#fff',
});
});
@ -370,7 +381,7 @@ describe('Component', () => {
obj.append({
removable: false,
draggable: false,
propagate: ['removable', 'draggable']
propagate: ['removable', 'draggable'],
});
const result = obj.components();
const newObj = result.models[0];
@ -433,7 +444,7 @@ describe('Image Component', () => {
test('Component toHTML with attributes', () => {
obj = new ComponentImage({
attributes: { alt: 'AltTest' },
src: 'testPath'
src: 'testPath',
});
expect(obj.toHTML()).toEqual('<img alt="AltTest" src="testPath"/>');
});
@ -475,7 +486,7 @@ describe('Text Component', () => {
test('Component toHTML with attributes', () => {
obj = new ComponentText({
attributes: { 'data-test': 'value' },
content: 'test content'
content: 'test content',
});
expect(obj.toHTML()).toEqual('<div data-test="value">test content</div>');
});
@ -505,7 +516,7 @@ describe('Text Node Component', () => {
test('Component toHTML with attributes', () => {
obj = new ComponentTextNode({
attributes: { 'data-test': 'value' },
content: `test content &<>"'`
content: `test content &<>"'`,
});
expect(obj.toHTML()).toEqual('test content &amp;&lt;&gt;&quot;&#039;');
});
@ -555,9 +566,7 @@ describe('Map Component', () => {
});
test('Component parse not map iframe', () => {
var el = $(
'<iframe src="https://www.youtube.com/watch?v=jNQXAC9IVRw"></iframe>'
);
var el = $('<iframe src="https://www.youtube.com/watch?v=jNQXAC9IVRw"></iframe>');
obj = ComponentMap.isComponent(el.get(0));
expect(obj).toEqual('');
});
@ -593,7 +602,7 @@ describe('Components', () => {
em.get('PageManager').onLoad();
compOpts = {
em,
componentTypes: dcomp.componentTypes
componentTypes: dcomp.componentTypes,
};
});

9
test/specs/style_manager/view/PropertyIntegerView.js

@ -86,7 +86,7 @@ describe('PropertyNumberView', () => {
});
test('Update model on setValue', () => {
view.setValue(intValue + unitValue);
view.inputInst.setValue(intValue + unitValue);
expect(view.model.get('value')).toEqual(parseFloat(intValue));
expect(view.model.get('unit')).toEqual(unitValue);
expect(view.getInputEl().value).toEqual(intValue);
@ -131,12 +131,13 @@ describe('PropertyNumberView', () => {
});
test('Value as default', () => {
expect(view.model.get('value')).toEqual(parseInt(intValue));
expect(view.model.get('unit')).toEqual(units[1]);
expect(view.model.getValue()).toEqual(intValue);
expect(view.model.getUnit()).toEqual(units[1]);
});
test('Input value is as default', () => {
expect(view.getInputEl().value).toEqual(intValue);
expect(view.getInputEl().value).toEqual('');
expect(view.getInputEl().placeholder).toEqual(intValue);
expect(view.inputInst.unitEl.value).toEqual(units[1]);
});

42
yarn.lock

@ -4982,8 +4982,8 @@ fn-name@~2.0.1:
resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7"
follow-redirects@^1.0.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
version "1.14.7"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
for-each@^0.3.3:
version "0.3.3"
@ -5255,8 +5255,8 @@ glob-to-regexp@^0.3.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@ -6062,6 +6062,12 @@ is-core-module@^2.2.0:
dependencies:
has "^1.0.3"
is-core-module@^2.8.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
dependencies:
has "^1.0.3"
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@ -7836,8 +7842,8 @@ nanocolors@^0.1.5:
resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.6.tgz#bc2350d3edfdbfadd7ac018c855ae7c13905a6ad"
nanoid@^3.1.23:
version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
version "3.2.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
nanomatch@^1.2.9:
version "1.2.13"
@ -8511,7 +8517,7 @@ path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
path-parse@^1.0.6:
path-parse@^1.0.6, path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
@ -9641,13 +9647,21 @@ resolve@1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@^1.1.3, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.2.0, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.8.1:
resolve@^1.1.3, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.2.0, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.8.1:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
dependencies:
is-core-module "^2.2.0"
path-parse "^1.0.6"
resolve@^1.1.6:
version "1.21.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.21.0.tgz#b51adc97f3472e6a5cf4444d34bc9d6b9037591f"
dependencies:
is-core-module "^2.8.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
responselike@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
@ -9948,8 +9962,8 @@ shell-quote@^1.6.1:
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
shelljs@^0.8.3:
version "0.8.4"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
version "0.8.5"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c"
dependencies:
glob "^7.0.0"
interpret "^1.0.0"
@ -10560,6 +10574,10 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
svg-tags@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
@ -10810,8 +10828,8 @@ trim-newlines@^3.0.0:
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
trim-off-newlines@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
version "1.0.3"
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.3.tgz#8df24847fcb821b0ab27d58ab6efec9f2fe961a1"
trough@^1.0.0:
version "1.0.5"

Loading…
Cancel
Save