Browse Source

Merge branch 'dev' into flexbox-support-dev

pull/1710/head
David Polak 7 years ago
parent
commit
b841f8b07c
  1. 2
      dist/css/grapes.min.css
  2. 30
      docs/api/component.md
  3. 90
      docs/api/components.md
  4. 4
      docs/api/editor.md
  5. 1
      docs/api/style_manager.md
  6. 2
      docs/modules/Components.md
  7. 2
      docs/modules/Style-manager.md
  8. 49
      src/block_manager/index.js
  9. 12
      src/block_manager/view/BlocksView.js
  10. 11
      src/canvas/config/config.js
  11. 24
      src/canvas/index.js
  12. 2
      src/canvas/view/CanvasView.js
  13. 26
      src/commands/view/SelectComponent.js
  14. 78
      src/css_composer/index.js
  15. 3
      src/editor/index.js
  16. 1
      src/editor/model/Editor.js
  17. 22
      src/modal_dialog/index.js
  18. 2
      src/parser/index.js
  19. 2
      src/parser/model/BrowserParserCss.js
  20. 84
      src/selector_manager/index.js
  21. 2
      src/selector_manager/model/Selectors.js
  22. 4
      src/storage_manager/config/config.js
  23. 14
      src/storage_manager/model/RemoteStorage.js
  24. 6
      src/style_manager/model/PropertyFactory.js
  25. 2
      src/styles/scss/_gjs_traits.scss
  26. 9
      src/utils/mixins.js
  27. 114
      test/specs/css_composer/index.js
  28. 60
      test/specs/selector_manager/index.js
  29. 45
      test/specs/storage_manager/model/Models.js
  30. 30
      test/specs/style_manager/model/Models.js

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

30
docs/api/component.md

@ -57,6 +57,24 @@ component.get('tagName');
By default, when `toolbar` property is falsy the editor will add automatically commands like `move`, `delete`, etc. based on its properties.
- `components` **Collection<[Component][9]>?** Children components. Default: `null`
## init
Hook method, called once the model is created
## updated
Hook method, called when the model has been updated (eg. updated some model's property)
### Parameters
- `property` **[String][1]** Property name, if triggered after some property update
- `value` **any** Property value, if triggered after some property update
- `previous` **any** Property previous value, if triggered after some property update
## removed
Hook method, called once the model has been removed
## is
Check component's type
@ -74,6 +92,12 @@ component.is('image')
Returns **[Boolean][3]**
## index
Get the index of the component in the parent collection.
Returns **[Number][10]**
## find
Find inner components by query string.
@ -397,7 +421,7 @@ Returns **this**
Get the DOM element of the component.
This works only if the component is already rendered
Returns **[HTMLElement][10]**
Returns **[HTMLElement][11]**
## getView
@ -448,4 +472,6 @@ Returns **this**
[9]: #component
[10]: https://developer.mozilla.org/docs/Web/HTML/Element
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[11]: https://developer.mozilla.org/docs/Web/HTML/Element

90
docs/api/components.md

@ -24,7 +24,10 @@ const domComponents = editor.DomComponents;
- [clear][5]
- [load][6]
- [store][7]
- [render][8]
- [addType][8]
- [getType][9]
- [getTypes][10]
- [render][11]
## load
@ -34,9 +37,9 @@ The fetched data will be added to the collection
### Parameters
- `data` **[Object][9]** Object of data to load (optional, default `''`)
- `data` **[Object][12]** Object of data to load (optional, default `''`)
Returns **[Object][9]** Loaded data
Returns **[Object][12]** Loaded data
## store
@ -44,9 +47,9 @@ Store components on the selected storage
### Parameters
- `noStore` **[Boolean][10]** If true, won't store
- `noStore` **[Boolean][13]** If true, won't store
Returns **[Object][9]** Data to store
Returns **[Object][12]** Data to store
## getWrapper
@ -104,18 +107,18 @@ as 'domComponents.getComponents().add(...)'
### Parameters
- `component` **([Object][9] | Component | [Array][11]<[Object][9]>)** Component/s to add
- `component.tagName` **[string][12]** Tag name (optional, default `'div'`)
- `component.type` **[string][12]** Type of the component. Available: ''(default), 'text', 'image' (optional, default `''`)
- `component.removable` **[boolean][10]** If component is removable (optional, default `true`)
- `component.draggable` **[boolean][10]** If is possible to move the component around the structure (optional, default `true`)
- `component.droppable` **[boolean][10]** If is possible to drop inside other components (optional, default `true`)
- `component.badgable` **[boolean][10]** If the badge is visible when the component is selected (optional, default `true`)
- `component.stylable` **[boolean][10]** If is possible to style component (optional, default `true`)
- `component.copyable` **[boolean][10]** If is possible to copy&paste the component (optional, default `true`)
- `component.content` **[string][12]** String inside component (optional, default `''`)
- `component.style` **[Object][9]** Style object (optional, default `{}`)
- `component.attributes` **[Object][9]** Attribute object (optional, default `{}`)
- `component` **([Object][12] | Component | [Array][14]<[Object][12]>)** Component/s to add
- `component.tagName` **[string][15]** Tag name (optional, default `'div'`)
- `component.type` **[string][15]** Type of the component. Available: ''(default), 'text', 'image' (optional, default `''`)
- `component.removable` **[boolean][13]** If component is removable (optional, default `true`)
- `component.draggable` **[boolean][13]** If is possible to move the component around the structure (optional, default `true`)
- `component.droppable` **[boolean][13]** If is possible to drop inside other components (optional, default `true`)
- `component.badgable` **[boolean][13]** If the badge is visible when the component is selected (optional, default `true`)
- `component.stylable` **[boolean][13]** If is possible to style component (optional, default `true`)
- `component.copyable` **[boolean][13]** If is possible to copy&paste the component (optional, default `true`)
- `component.content` **[string][15]** String inside component (optional, default `''`)
- `component.style` **[Object][12]** Style object (optional, default `{}`)
- `component.attributes` **[Object][12]** Attribute object (optional, default `{}`)
### Examples
@ -132,7 +135,7 @@ var comp1 = domComponents.addComponent({
});
```
Returns **(Component | [Array][11]<Component>)** Component/s added
Returns **(Component | [Array][14]<Component>)** Component/s added
## render
@ -141,7 +144,7 @@ Once the wrapper is rendered, and it's what happens when you init the editor,
the all new components will be added automatically and property changes are all
updated immediately
Returns **[HTMLElement][13]**
Returns **[HTMLElement][16]**
## clear
@ -149,6 +152,35 @@ Remove all components
Returns **this**
## addType
Add new component type.
Read more about this in [Define New Component][17]
### Parameters
- `type` **[string][15]** Component ID
- `methods` **[Object][12]** Component methods
Returns **this**
## getType
Get component type.
Read more about this in [Define New Component][17]
### Parameters
- `type` **[string][15]** Component ID
Returns **[Object][12]** Component type defintion, eg. `{ model: ..., view: ... }`
## getTypes
Return the array of all types
Returns **[Array][14]**
[1]: https://github.com/artf/grapesjs/blob/master/src/dom_components/config/config.js
[2]: #getwrapper
@ -163,14 +195,22 @@ Returns **this**
[7]: #store
[8]: #render
[8]: #addtype
[9]: #gettype
[10]: #gettypes
[11]: #render
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[16]: https://developer.mozilla.org/docs/Web/HTML/Element
[13]: https://developer.mozilla.org/docs/Web/HTML/Element
[17]: https://grapesjs.com/docs/modules/Components.html#define-new-component

4
docs/api/editor.md

@ -23,11 +23,11 @@ editor.on('EVENT-NAME', (some, argument) => {
### Components
- `component:create` - Component is created (only the model, is not yet mounted in the canvas)
- `component:create` - Component is created (only the model, is not yet mounted in the canvas), called after the init() method
- `component:mount` - Component is monted to an element and rendered in canvas
- `component:add` - Triggered when a new component is added to the editor, the model is passed as an argument to the callback
- `component:remove` - Triggered when a component is removed, the model is passed as an argument to the callback
- `component:clone` - Triggered when a new component is added by a clone command, the model is passed as an argument to the callback
- `component:clone` - Triggered when a component is cloned, the new model is passed as an argument to the callback
- `component:update` - Triggered when a component is updated (moved, styled, etc.), the model is passed as an argument to the callback
- `component:update:{propertyName}` - Listen any property change, the model is passed as an argument to the callback
- `component:styleUpdate` - Triggered when the style of the component is updated, the model is passed as an argument to the callback

1
docs/api/style_manager.md

@ -29,7 +29,6 @@ const styleManager = editor.StyleManager;
- [removeProperty][9]
- [getProperties][10]
- [getModelToStyle][11]
- [getModelToStyle][11]
- [addType][12]
- [getType][13]
- [getTypes][14]

2
docs/modules/Components.md

@ -398,7 +398,7 @@ editor.on(`component:remove`, model => console.log('Global hook: component:remov
## Components & JS
If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page
[Components & JS](Components-&-JS)
[Components & JS](Components-js.html)

2
docs/modules/Style-manager.md

@ -13,7 +13,7 @@ Coming soon
Here you can find all the available built-in properties that you can use inside Style Manager via `buildProps`:
`float`, `position`, `text-align`, `display`, `font-family`, `font-weight`, `border`, `border-style`, `border-color`, `border-width`, `box-shadow`, `background-repeat`, `background-position`, `background-attachment`, `background-size`, `transition`, `transition-duration`, `transition-property`, `transition-timing-function`, `top`, `right`, `bottom`, `left`, `margin`, `margin-top`, `margin-right`, `margin-bottom`, `margin-left`, `padding`, `padding-top`, `padding-right`, `padding-bottom`, `padding-left`, `width`, `height`, `min-width`, `min-height`, `max-width`, `max-height`, `font-size`, `letter-spacing`, `line-height`, `text-shadow`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-left-radius`, `border-bottom-right-radius`, `perspective`, `transform`, `transform-rotate-x`, `transform-rotate-y`, `transform-rotate-z`, `transform-scale-x`, `transform-scale-y`, `transform-scale-z`, `color`, `background-color`, `background`, `background-image`, `cursor`, `flex-direction`, `flex-wrap`, `justify-content`, `align-items`, `align-content`, `order`, `flex-basis`, `flex-grow`, `flex-shrink`, `align-self`
`float`, `position`, `text-align`, `display`, `font-family`, `font-weight`, `border`, `border-style`, `border-color`, `border-width`, `box-shadow`, `background-repeat`, `background-position`, `background-attachment`, `background-size`, `transition`, `transition-duration`, `transition-property`, `transition-timing-function`, `top`, `right`, `bottom`, `left`, `margin`, `margin-top`, `margin-right`, `margin-bottom`, `margin-left`, `padding`, `padding-top`, `padding-right`, `padding-bottom`, `padding-left`, `width`, `height`, `min-width`, `min-height`, `max-width`, `max-height`, `font-size`, `letter-spacing`, `line-height`, `text-shadow`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-left-radius`, `border-bottom-right-radius`, `perspective`, `transform`, `transform-rotate-x`, `transform-rotate-y`, `transform-rotate-z`, `transform-scale-x`, `transform-scale-y`, `transform-scale-z`, `color`, `background-color`, `background`, `background-image`, `cursor`, `flex-direction`, `flex-wrap`, `justify-content`, `align-items`, `align-content`, `order`, `flex-basis`, `flex-grow`, `flex-shrink`, `align-self`, `overflow`, `overflow-x`, `overflow-y`
Example usage:
```js

49
src/block_manager/index.js

@ -63,15 +63,14 @@ module.exports = () => {
// Global blocks collection
blocks = new Blocks([]);
blocksVisible = new Blocks([]);
(categories = new BlockCategories()),
(blocksView = new BlocksView(
{
// Visible collection
collection: blocksVisible,
categories
},
c
));
categories = new BlockCategories();
blocksView = new BlocksView(
{
collection: blocksVisible,
categories
},
c
);
// Setup the sync between the global and public collections
blocks.listenTo(blocks, 'add', model => {
@ -205,8 +204,10 @@ module.exports = () => {
/**
* Render blocks
* @param {Array} blocks Blocks to render, without the argument will render
* all global blocks
* @param {Array} blocks Blocks to render, without the argument will render all global blocks
* @param {Object} [opts={}] Options
* @param {Boolean} [opts.external] Render blocks in a new container (HTMLElement will be returned)
* @param {Boolean} [opts.ignoreCategories] Render blocks without categories
* @return {HTMLElement} Rendered element
* @example
* // Render all blocks (inside the global collection)
@ -214,9 +215,9 @@ module.exports = () => {
*
* // Render new set of blocks
* const blocks = blockManager.getAll();
* blockManager.render(blocks.filter(
* block => block.get('category') == 'sections'
* ));
* const filtered = blocks.filter(block => block.get('category') == 'sections')
*
* blockManager.render(filtered);
* // Or a new set from an array
* blockManager.render([
* {label: 'Label text', content: '<div>Content</div>'}
@ -224,15 +225,33 @@ module.exports = () => {
*
* // Back to blocks from the global collection
* blockManager.render();
*
* // You can also render your blocks outside of the main block container
* const newBlocksEl = blockManager.render(filtered, { external: true });
* document.getElementById('some-id').appendChild(newBlocksEl);
*/
render(blocks) {
render(blocks, opts = {}) {
const toRender = blocks || this.getAll().models;
if (opts.external) {
return new BlocksView(
{
collection: new Blocks(toRender),
categories
},
{
...c,
...opts
}
).render().el;
}
if (!blocksView.rendered) {
blocksView.render();
blocksView.rendered = 1;
}
blocksView.updateConfig(opts);
blocksView.collection.reset(toRender);
return this.getContainer();
}

12
src/block_manager/view/BlocksView.js

@ -27,6 +27,13 @@ module.exports = require('backbone').View.extend({
}
},
updateConfig(opts = {}) {
this.config = {
...this.config,
...opts
};
},
/**
* Get sorter
* @private
@ -103,19 +110,20 @@ module.exports = require('backbone').View.extend({
* @private
* */
add(model, fragment) {
const { config } = this;
var frag = fragment || null;
var view = new BlockView(
{
model,
attributes: model.get('attributes')
},
this.config
config
);
var rendered = view.render().el;
var category = model.get('category');
// Check for categories
if (category && this.categories) {
if (category && this.categories && !config.ignoreCategories) {
if (isString(category)) {
category = {
id: category,

11
src/canvas/config/config.js

@ -6,7 +6,7 @@ module.exports = {
* Be aware that these scripts will not be printed in the export code
* @example
* scripts: [ 'https://...1.js', 'https://...2.js' ]
*/
*/
scripts: [],
/*
@ -14,7 +14,7 @@ module.exports = {
* Be aware that these styles will not be printed in the export code
* @example
* styles: [ 'https://...1.css', 'https://...2.css' ]
*/
*/
styles: [],
/**
@ -26,10 +26,15 @@ module.exports = {
*/
customBadgeLabel: '',
/**
* Indicate when to start the auto scroll of the canvas on component/block dragging (value in px )
*/
autoscrollLimit: 50,
/**
* When some textable component is selected and focused (eg. input or text component) the editor
* stops some commands (eg. disables the copy/paste of components with CTRL+C/V to allow the copy/paste of the text).
* This option allows to customize, by a selector, which element should not be considered textable
*/
notTextable: ['button', 'input[type=checkbox]', 'input[type=radio]']
notTextable: ['button', 'a', 'input[type=checkbox]', 'input[type=radio]']
};

24
src/canvas/index.js

@ -28,9 +28,11 @@
* @module Canvas
*/
import { on, off, hasDnd, getElement } from 'utils/mixins';
import { on, off, hasDnd, getElement, getPointerEvent } from 'utils/mixins';
import Droppable from 'utils/Droppable';
const { requestAnimationFrame } = window;
module.exports = () => {
var c = {},
defaults = require('./config/config'),
@ -81,6 +83,7 @@ module.exports = () => {
this.startAutoscroll = this.startAutoscroll.bind(this);
this.stopAutoscroll = this.stopAutoscroll.bind(this);
this.autoscroll = this.autoscroll.bind(this);
this.updateClientY = this.updateClientY.bind(this);
return this;
},
@ -452,22 +455,27 @@ module.exports = () => {
// By detaching those from the stack avoid browsers lags
// Noticeable with "fast" drag of blocks
setTimeout(() => {
on(toListen, 'mousemove', this.autoscroll);
on(toListen, 'mousemove dragover', this.updateClientY);
on(toListen, 'mouseup', this.stopAutoscroll);
requestAnimationFrame(this.autoscroll);
}, 0);
},
updateClientY(ev) {
ev.preventDefault();
this.lastClientY = getPointerEvent(ev).clientY;
},
/**
* @private
*/
autoscroll(e) {
e.preventDefault();
autoscroll() {
if (this.dragging) {
let frameWindow = this.getFrameEl().contentWindow;
let actualTop = frameWindow.document.body.scrollTop;
let nextTop = actualTop;
let clientY = e.clientY;
let limitTop = 50;
let clientY = this.lastClientY;
let limitTop = this.getConfig().autoscrollLimit;
let limitBottom = frameRect.height - limitTop;
if (clientY < limitTop) {
@ -478,8 +486,8 @@ module.exports = () => {
nextTop += clientY - limitBottom;
}
//console.log(`actualTop: ${actualTop} clientY: ${clientY} nextTop: ${nextTop} frameHeigh: ${frameRect.height}`);
frameWindow.scrollTo(0, nextTop);
requestAnimationFrame(this.autoscroll);
}
},
@ -490,7 +498,7 @@ module.exports = () => {
stopAutoscroll() {
this.dragging = 0;
let toListen = this.getScrollListeners();
off(toListen, 'mousemove', this.autoscroll);
off(toListen, 'mousemove dragover', this.updateClientY);
off(toListen, 'mouseup', this.stopAutoscroll);
},

2
src/canvas/view/CanvasView.js

@ -25,7 +25,7 @@ module.exports = Backbone.View.extend({
*/
isElInViewport(el) {
const rect = getElement(el).getBoundingClientRect();
const frameRect = this.getFrameOffset(1);
const frameRect = this.getFrameOffset();
const rTop = rect.top;
const rLeft = rect.left;
return (

26
src/commands/view/SelectComponent.js

@ -527,15 +527,33 @@ module.exports = {
* @param {Object} pos
*/
updateToolbarPos(el, elPos) {
var unit = 'px';
var toolbarEl = this.canvas.getToolbarEl();
var toolbarStyle = toolbarEl.style;
const { canvas } = this;
const unit = 'px';
const toolbarEl = canvas.getToolbarEl();
const toolbarStyle = toolbarEl.style;
toolbarStyle.opacity = 0;
var pos = this.canvas.getTargetToElementDim(toolbarEl, el, {
const pos = canvas.getTargetToElementDim(toolbarEl, el, {
elPos,
event: 'toolbarPosUpdate'
});
if (pos) {
const frameOffset = canvas.getCanvasView().getFrameOffset();
// Scroll with the window if the top edge is reached and the
// element is bigger than the canvas
if (
pos.top <= pos.canvasTop &&
!(pos.elementHeight + pos.targetHeight >= frameOffset.height)
) {
pos.top = pos.elementTop + pos.elementHeight;
}
// Check if not outside of the canvas
if (pos.left < pos.canvasLeft) {
pos.left = pos.canvasLeft;
}
var leftPos = pos.left + pos.elementWidth - pos.targetWidth;
toolbarStyle.top = pos.top + unit;
toolbarStyle.left = (leftPos < 0 ? 0 : leftPos) + unit;

78
src/css_composer/index.js

@ -21,10 +21,8 @@
* * [get](#get)
* * [getAll](#getall)
* * [clear](#clear)
* * [setIdRule](#setidrule)
* * [getIdRule](#getidrule)
* * [setClassRule](#setclassrule)
* * [getClassRule](#getclassrule)
* * [setRule](#setrule)
* * [getRule](#getrule)
*
* @module CssComposer
*/
@ -304,12 +302,81 @@ module.exports = () => {
return result;
},
/**
* Add/update the CSS rule with a generic selector
* @param {string} selectors Selector, eg. '.myclass'
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Additional properties
* @param {String} [opts.atRuleType=''] At-rule type, eg. 'media'
* @param {String} [opts.atRuleParams=''] At-rule parameters, eg. '(min-width: 500px)'
* @return {CssRule} The new/updated rule
* @example
* // Simple class-based rule
* const rule = cc.setRule('.class1.class2', { color: 'red' });
* console.log(rule.toCSS()) // output: .class1.class2 { color: red }
* // With state and other mixed selector
* const rule = cc.setRule('.class1.class2:hover, div#myid', { color: 'red' });
* // output: .class1.class2:hover, div#myid { color: red }
* // With media
* const rule = cc.setRule('.class1:hover', { color: 'red' }, {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)',
* });
* // output: @media (min-width: 500px) { .class1:hover { color: red } }
*/
setRule(selectors, style, opts = {}) {
const { atRuleType, atRuleParams } = opts;
const node = em.get('Parser').parserCss.checkNode({
selectors,
style
})[0];
const { state, selectorsAdd } = node;
const sm = em.get('SelectorManager');
const selector = sm.add(node.selectors);
const rule = this.add(selector, state, atRuleParams, {
selectorsAdd,
atRule: atRuleType
});
rule.setStyle(style, opts);
return rule;
},
/**
* Get the CSS rule by a generic selector
* @param {string} selectors Selector, eg. '.myclass:hover'
* @param {String} [opts.atRuleType=''] At-rule type, eg. 'media'
* @param {String} [opts.atRuleParams=''] At-rule parameters, eg. '(min-width: 500px)'
* @return {CssRule}
* @example
* const rule = cc.getRule('.myclass1:hover');
* const rule2 = cc.getRule('.myclass1:hover, div#myid');
* const rule3 = cc.getRule('.myclass1', {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)',
* });
*/
getRule(selectors, opts = {}) {
const sm = em.get('SelectorManager');
const node = em.get('Parser').parserCss.checkNode({ selectors })[0];
const selector = sm.get(node.selectors);
const { state, selectorsAdd } = node;
const { atRuleType, atRuleParams } = opts;
return (
selector &&
this.get(selector, state, atRuleParams, {
selectorsAdd,
atRule: atRuleType
})
);
},
/**
* Add/update the CSS rule with id selector
* @param {string} name Id selector name, eg. 'my-id'
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} The new/updated rule
* @private
* @example
* const rule = cc.setIdRule('myid', { color: 'red' });
* const ruleHover = cc.setIdRule('myid', { color: 'blue' }, { state: 'hover' });
@ -332,6 +399,7 @@ module.exports = () => {
* @param {string} name Id selector name, eg. 'my-id'
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule}
* @private
* @example
* const rule = cc.getIdRule('myid');
* const ruleHover = cc.setIdRule('myid', { state: 'hover' });
@ -349,6 +417,7 @@ module.exports = () => {
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} The new/updated rule
* @private
* @example
* const rule = cc.setClassRule('myclass', { color: 'red' });
* const ruleHover = cc.setClassRule('myclass', { color: 'blue' }, { state: 'hover' });
@ -371,6 +440,7 @@ module.exports = () => {
* @param {string} name Class selector name, eg. 'my-class'
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule}
* @private
* @example
* const rule = cc.getClassRule('myclass');
* const ruleHover = cc.getClassRule('myclass', { state: 'hover' });

3
src/editor/index.js

@ -76,6 +76,9 @@
* ### RTE
* * `rte:enable` - RTE enabled. The view, on which RTE is enabled, is passed as an argument
* * `rte:disable` - RTE disabled. The view, on which RTE is disabled, is passed as an argument
* ### Modal
* * `modal:open` - Modal is opened
* * `modal:close` - Modal is closed
* ### Commands
* * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview'))
* * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview'))

1
src/editor/model/Editor.js

@ -563,6 +563,7 @@ module.exports = Backbone.Model.extend({
* @private
*/
refreshCanvas() {
this.set('canvasOffset', null);
this.set('canvasOffset', this.get('Canvas').getOffset());
},

22
src/modal_dialog/index.js

@ -40,17 +40,22 @@ module.exports = () => {
*/
name: 'Modal',
getConfig() {
return c;
},
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @private
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c)) c[name] = defaults[name];
}
init(config = {}) {
c = {
...defaults,
...config
};
this.em = c.em;
var ppfx = c.pStylePrefix;
if (ppfx) c.stylePrefix = ppfx + c.stylePrefix;
@ -68,6 +73,11 @@ module.exports = () => {
this.render().appendTo(el);
},
triggerEvent(event) {
const { em } = this;
em && em.trigger(`modal:${event}`);
},
/**
* Open the modal window
* @param {Object} [opts={}] Options
@ -79,6 +89,7 @@ module.exports = () => {
opts.title && this.setTitle(opts.title);
opts.content && this.setContent(opts.content);
modal.show();
this.triggerEvent('open');
return this;
},
@ -88,6 +99,7 @@ module.exports = () => {
*/
close() {
modal.hide();
this.triggerEvent('close');
return this;
},

2
src/parser/index.js

@ -48,6 +48,8 @@ module.exports = () => {
pHtml = new parserHtml(conf);
pCss = new parserCss(conf);
this.em = conf.em;
this.parserCss = pCss;
this.parserHtml = pHtml;
return this;
},

2
src/parser/model/BrowserParserCss.js

@ -101,7 +101,7 @@ export const parseCondition = node => {
* @param {Object} style Key-value object of style declarations
* @return {Object}
*/
export const createNode = (selectors, style, opts = {}) => {
export const createNode = (selectors, style = {}, opts = {}) => {
const node = {};
const selLen = selectors.length;
const lastClass = selectors[selLen - 1];

84
src/selector_manager/index.js

@ -44,7 +44,7 @@
* @module SelectorManager
*/
import { isString, isElement, isObject } from 'underscore';
import { isString, isElement, isObject, isArray } from 'underscore';
const isId = str => isString(str) && str[0] == '#';
const isClass = str => isString(str) && str[0] == '.';
@ -118,22 +118,9 @@ module.exports = config => {
}
},
/**
* Add a new selector to collection if it's not already exists. Class type is a default one
* @param {String} name Selector name
* @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type=1] Type of the selector. At the moment, only 'class' (1) is available
* @return {Model}
* @example
* var selector = selectorManager.add('selectorName');
* // Same as
* var selector = selectorManager.add('selectorName', {
* type: 1,
* label: 'selectorName'
* });
* */
add(name, opts = {}) {
addSelector(name, opt = {}) {
let opts = { ...opt };
if (isObject(name)) {
opts = name;
} else {
@ -143,6 +130,8 @@ module.exports = config => {
if (isId(opts.name)) {
opts.name = opts.name.substr(1);
opts.type = Selector.TYPE_ID;
} else if (isClass(opts.name)) {
opts.name = opts.name.substr(1);
}
if (opts.label && !opts.name) {
@ -161,6 +150,42 @@ module.exports = config => {
return selector;
},
getSelector(name, type = Selector.TYPE_CLASS) {
if (isId(name)) {
name = name.substr(1);
type = Selector.TYPE_ID;
} else if (isClass(name)) {
name = name.substr(1);
}
return selectors.where({ name, type })[0];
},
/**
* Add a new selector to collection if it's not already exists. Class type is a default one
* @param {String|Array} name Selector/s name
* @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type=1] Type of the selector. At the moment, only 'class' (1) is available
* @return {Model|Array}
* @example
* const selector = selectorManager.add('selectorName');
* // Same as
* const selector = selectorManager.add('selectorName', {
* type: 1,
* label: 'selectorName'
* });
* // Multiple selectors
* const selectors = selectorManager.add(['.class1', '.class2', '#id1']);
* */
add(name, opts = {}) {
if (isArray(name)) {
return name.map(item => this.addSelector(item, opts));
} else {
return this.addSelector(name, opts);
}
},
/**
* Add class selectors
* @param {Array|string} classes Array or string of classes
@ -184,18 +209,27 @@ module.exports = config => {
/**
* Get the selector by its name
* @param {String} name Selector name
* @param {String|Array} name Selector name
* @param {String} tyoe Selector type
* @return {Model|null}
* @return {Model|Array}
* @example
* var selector = selectorManager.get('selectorName');
* const selector = selectorManager.get('selectorName');
* // or get an array
* const selectors = selectorManager.get(['class1', 'class2']);
* */
get(name, type = Selector.TYPE_CLASS) {
if (isId(name)) {
name = name.substr(1);
type = Selector.TYPE_ID;
get(name, type) {
if (isArray(name)) {
const result = [];
const selectors = name
.map(item => this.getSelector(item))
.filter(item => item);
selectors.forEach(
item => result.indexOf(item) < 0 && result.push(item)
);
return result;
} else {
return this.getSelector(name, type);
}
return selectors.where({ name, type })[0];
},
/**

2
src/selector_manager/model/Selectors.js

@ -4,6 +4,8 @@ const Selector = require('./Selector');
module.exports = require('backbone').Collection.extend({
model: Selector,
modelId: attr => `${attr.name}_${attr.type || Selector.TYPE_CLASS}`,
getStyleable() {
return filter(
this.models,

4
src/storage_manager/config/config.js

@ -53,5 +53,7 @@ module.exports = {
// set contentType paramater of $.ajax
// true: application/json; charset=utf-8'
// false: 'x-www-form-urlencoded'
contentTypeJson: false
contentTypeJson: false,
credentials: 'include'
};

14
src/storage_manager/model/RemoteStorage.js

@ -10,7 +10,8 @@ module.exports = require('backbone').Model.extend({
params: {},
beforeSend() {},
onComplete() {},
contentTypeJson: false
contentTypeJson: false,
credentials: 'include'
},
/**
@ -113,7 +114,7 @@ module.exports = require('backbone').Model.extend({
}
fetchOptions = {
method: opts.method || 'post',
credentials: 'include',
credentials: this.get('credentials'),
headers
};
@ -124,11 +125,10 @@ module.exports = require('backbone').Model.extend({
this.onStart();
this.fetch(url, fetchOptions)
.then(
res =>
((res.status / 200) | 0) == 1
? res.text()
: res.text().then(text => Promise.reject(text))
.then(res =>
((res.status / 200) | 0) == 1
? res.text()
: res.text().then(text => Promise.reject(text))
)
.then(text => this.onResponse(text, clb))
.catch(err => this.onError(err, clbErr));

6
src/style_manager/model/PropertyFactory.js

@ -91,6 +91,8 @@ module.exports = () => ({
case 'transition-timing-function':
case 'cursor':
case 'overflow':
case 'overflow-x':
case 'overflow-y':
obj.type = 'select';
break;
case 'top':
@ -299,6 +301,8 @@ module.exports = () => ({
obj.defaults = 'ease';
break;
case 'overflow':
case 'overflow-x':
case 'overflow-y':
obj.defaults = 'visible';
break;
}
@ -668,6 +672,8 @@ module.exports = () => ({
];
break;
case 'overflow':
case 'overflow-x':
case 'overflow-y':
obj.list = [
{ value: 'visible' },
{ value: 'hidden' },

2
src/styles/scss/_gjs_traits.scss

@ -15,7 +15,7 @@
.#{$trt-prefix}trait {
display: flex;
justify-content: start;
justify-content: flex-start;
padding: 5px 10px;
font-weight: lighter;

9
src/utils/mixins.js

@ -121,6 +121,14 @@ const getModel = (el, $) => {
return model;
};
/**
* Get cross-device pointer event
* @param {Event} ev
* @return {Event}
*/
const getPointerEvent = ev =>
ev.touches && ev.touches[0] ? ev.touches[0] : ev;
export {
on,
off,
@ -132,5 +140,6 @@ export {
getElement,
shallowDiff,
normalizeFloat,
getPointerEvent,
getUnitFromValue
};

114
test/specs/css_composer/index.js

@ -207,6 +207,120 @@ describe('Css Composer', () => {
const rule = obj.getClassRule(name, { state });
expect(rule.selectorsToString()).toEqual(`.${name}:${state}`);
});
test('Create a simple class-based rule with setRule', () => {
const selector = '.test';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Avoid creating multiple rules with the same selector', () => {
const selector = '.test';
obj.setRule(selector, { color: 'red' });
obj.setRule(selector, { color: 'blue' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:blue;`);
});
test('Create a class-based rule with setRule', () => {
const selector = '.test.test2';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a class-based rule with a state, by using setRule', () => {
const selector = '.test.test2:hover';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with class-based and mixed selectors', () => {
const selector = '.test.test2:hover, #test .selector';
obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with only mixed selectors', () => {
const selector = '#test1 .class1, .class2 > #id2';
obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.get('selectors').length).toEqual(0);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with atRule', () => {
const toTest = [
{
selector: '.class1:hover',
style: { color: 'blue' },
opts: {
atRuleType: 'media',
atRuleParams: 'screen and (min-width: 480px)'
}
},
{
selector: '.class1:hover',
style: { color: 'red' },
opts: {
atRuleType: 'media',
atRuleParams: 'screen and (min-width: 480px)'
}
}
];
toTest.forEach(test => {
const { selector, style, opts } = test;
const result = obj.setRule(selector, style, opts);
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector, opts);
expect(rule.getAtRule()).toEqual(
`@${opts.atRuleType} ${opts.atRuleParams}`
);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.getStyle()).toEqual(style);
});
});
test('Create different rules by using setRule', () => {
const toTest = [
{ selector: '.class1:hover', style: { color: '#111' } },
{ selector: '.class1.class2', style: { color: '#222' } },
{ selector: '.class1, .class2 .class3', style: { color: 'red' } },
{ selector: '.class1, .class2 .class4', style: { color: 'green' } },
{ selector: '.class4, .class1 .class2', style: { color: 'blue' } },
{
selector: '.class4, .class1 .class2',
style: { color: 'blue' },
opt: { atRuleType: 'media', atRuleParams: '(min-width: 480px)' }
}
];
toTest.forEach(test => {
const { selector, style, opt = {} } = test;
obj.setRule(selector, style, opt);
const rule = obj.getRule(selector, opt);
const atRule = `${opt.atRuleType || ''} ${opt.atRuleParams ||
''}`.trim();
expect(rule.getAtRule()).toEqual(atRule ? `@${atRule}` : '');
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.getStyle()).toEqual(style);
});
expect(obj.getAll().length).toEqual(toTest.length);
});
});
Models.run();

60
test/specs/selector_manager/index.js

@ -60,6 +60,13 @@ describe('SelectorManager', () => {
expect(sel.get('label')).toEqual(name);
});
test('Check name property by adding as class', () => {
var name = 'test';
var sel = obj.add(`.${name}`);
expect(sel.get('name')).toEqual(name);
expect(sel.get('label')).toEqual(name);
});
test('Add 2 selectors', () => {
obj.add('test');
obj.add('test2');
@ -72,6 +79,59 @@ describe('SelectorManager', () => {
expect(obj.getAll().length).toEqual(1);
});
test('Add multiple selectors', () => {
const cls = [
'.test1',
'test1',
'.test2',
'.test2',
'#test3',
'test3',
'test3',
'#test3'
];
const result = obj.add(cls);
expect(Array.isArray(result)).toEqual(true);
const concat = obj
.getAll()
.map(item => item.getFullName())
.join('');
expect(concat).toEqual('.test1.test2#test3.test3');
expect(obj.getAll().length).toEqual(4);
expect(
obj
.getAll()
.at(0)
.getFullName()
).toEqual('.test1');
expect(
obj
.getAll()
.at(1)
.getFullName()
).toEqual('.test2');
expect(
obj
.getAll()
.at(2)
.getFullName()
).toEqual('#test3');
expect(
obj
.getAll()
.at(3)
.getFullName()
).toEqual('.test3');
expect(obj.get(cls).length).toEqual(4);
expect(
obj
.get(cls)
.map(item => item.getFullName())
.join('')
).toEqual(concat);
});
test('Get selector', () => {
var name = 'test';
var sel = obj.add(name);

45
test/specs/storage_manager/model/Models.js

@ -96,6 +96,51 @@ module.exports = {
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[0]).toEqual(endpointLoad);
});
test("Load data with credentials option as 'include' by default", () => {
obj.load(['item1', 'item2']);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject({
credentials: 'include'
});
});
test("Store data with credentials option as 'include' by default", () => {
obj.store(data);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject({
credentials: 'include'
});
});
test('Store data with credentials option as false ', () => {
obj = new RemoteStorage({ ...storageOptions, credentials: false });
sinon
.stub(obj, 'fetch')
.returns(Promise.resolve(mockResponse({ data: 1 })));
obj.store(data);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject({
credentials: false
});
});
test('Load data with credentials option as false', () => {
obj = new RemoteStorage({ ...storageOptions, credentials: false });
sinon
.stub(obj, 'fetch')
.returns(Promise.resolve(mockResponse({ data: 1 })));
obj.load(['item1', 'item2']);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject({
credentials: false
});
});
});
}
};

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

@ -1155,6 +1155,36 @@ module.exports = {
};
expect(obj.build('overflow')).toEqual([res]);
});
test('Build overflow-x', () => {
var res = {
type: 'select',
property: 'overflow-x',
defaults: 'visible',
list: [
{ value: 'visible' },
{ value: 'hidden' },
{ value: 'scroll' },
{ value: 'auto' }
]
};
expect(obj.build('overflow-x')).toEqual([res]);
});
test('Build overflow-y', () => {
var res = {
type: 'select',
property: 'overflow-y',
defaults: 'visible',
list: [
{ value: 'visible' },
{ value: 'hidden' },
{ value: 'scroll' },
{ value: 'auto' }
]
};
expect(obj.build('overflow-y')).toEqual([res]);
});
});
}
};

Loading…
Cancel
Save