Browse Source

Merge pull request #803 from artf/dev

Merge from dev
pull/883/head
Artur Arseniev 9 years ago
committed by GitHub
parent
commit
3932bad352
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      .github/ISSUE_TEMPLATE.md
  2. 46
      CODE_OF_CONDUCT.md
  3. 52
      CONTRIBUTING.md
  4. 2
      LICENSE
  5. 1
      README.md
  6. 2
      dist/css/grapes.min.css
  7. 915
      dist/grapes.js
  8. 6
      dist/grapes.min.js
  9. 2
      package-lock.json
  10. 2
      package.json
  11. 2
      src/asset_manager/view/AssetImageView.js
  12. 3
      src/canvas/view/CanvasView.js
  13. 45
      src/code_manager/model/CssGenerator.js
  14. 1
      src/css_composer/index.js
  15. 69
      src/css_composer/model/CssRule.js
  16. 4
      src/css_composer/model/CssRules.js
  17. 12
      src/css_composer/view/CssGroupRuleView.js
  18. 45
      src/css_composer/view/CssRulesView.js
  19. 4
      src/dom_components/index.js
  20. 50
      src/dom_components/model/Component.js
  21. 5
      src/dom_components/model/ComponentImage.js
  22. 2
      src/dom_components/model/ComponentVideo.js
  23. 44
      src/dom_components/model/Components.js
  24. 4
      src/dom_components/view/ComponentTextNodeView.js
  25. 5
      src/dom_components/view/ComponentView.js
  26. 18
      src/editor/config/config.js
  27. 1
      src/editor/model/Editor.js
  28. 43
      src/index.js
  29. 134
      src/parser/model/ParserCss.js
  30. 3
      src/storage_manager/model/RemoteStorage.js
  31. 23
      src/style_manager/index.js
  32. 3
      src/style_manager/view/PropertyView.js
  33. 91
      src/style_manager/view/SectorsView.js
  34. 2
      src/styles/scss/_gjs_canvas.scss
  35. 22
      src/undo_manager/index.js
  36. 3
      src/utils/Droppable.js
  37. 12
      src/utils/Sorter.js
  38. 52
      src/utils/extender.js
  39. 6
      test/specs/css_composer/e2e/CssComposer.js
  40. 27
      test/specs/css_composer/model/CssModels.js
  41. 41
      test/specs/parser/model/ParserCss.js
  42. 9
      test/specs/selector_manager/e2e/ClassManager.js

26
.github/ISSUE_TEMPLATE.md

@ -0,0 +1,26 @@
## WARNING
READ and FOLLOW next 5 steps, then REMOVE them before posting the issue
1. Follow CONTRIBUTING Guidelines https://github.com/artf/grapesjs/blob/master/CONTRIBUTING.md
2. Use the GitHub Issues EXCLUSIVELY for BUGS, FEATURE REQUESTS or QUESTIONS. Prefix the title of the issue with its context, eg. `[Bug]: ....`
3. Do a quick SEARCH first, to see if someone else didn't open the same issue
4. DON'T ASK to create examples/code for you, read DOCS and APIs first, then you can post what you have tried (we'd like to see your code) and what you're unable to achieve
5. All relative statements/questions have to be filled/answered, otherwise, the issue might be CLOSED
## You're submitting a BUG
1. Are you using the latest release (older versions are NOT supported)?
1. If you're not sure, type `grapesjs.version` in console and press ENTER
1. Are you facing the bug with your local copy of GrapesJS or with the current demo?
1. If a local copy
1. Indicate all informations about your OS, browser and GrapesJS version.
1. Are you able to reproduce the bug from the demo?
1. What is the expected behavior?
1. What happens instead?
1. If you're able to reproduce the bug indicate all the necessary steps
1. Attach screenshots (using KAP/LICEcap), screencasts or live demo
1. JSFiddle Starter template https://jsfiddle.net/szLp8h4n
1. CodeSandbox Starter template https://codesandbox.io/s/1r0w2pk1vl
## You're submitting a FEATURE REQUEST
1. Be sure to work on the latest version, as the feature might be already there
1. Keep in mind that the feature should be considered valid to use for everyone, not only for your case

46
CODE_OF_CONDUCT.md

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at artur.catch@hotmail.it. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

52
CONTRIBUTING.md

@ -0,0 +1,52 @@
# Contribute
## Introduction
First of all, thank you for considering contributing to GrapesJS!
We welcome any type of contribution, not only code. Like for example:
- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
- **Marketing**: writing blog posts, howto's, tutorials, etc.
- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, etc.
- **Money**: We welcome financial contributions in full transparency on our [Open Collective].
## Your First Contribution
Working on your first Pull Request? You can learn how from this **free** series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
## Submitting code
Any code change should be submitted as a pull request. Before start working on something make always a search in opened issues and pull requests, this might help you to avoid waisting time.
A pull request could be a bug fix, new feature and much more, but in all cases, **open a new issue** and talk about what you want to do. Often happens to work on something already fixed (ready to release) or in progress.
The title should be brief but comprehensive, the description contains a link to the opened issue and the proposed solution. The pull request should contain tests whenever possible. Keep in mind that the bigger is the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
## Styleguide
The code is auto formatted with [prettier](https://github.com/prettier/prettier) on any commit, therefore you can write in any style you prefer
## Expenses
Anyone can file an expense (code, marketing, etc.) via our [Open Collective]. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
Before submitting an expense contact core contributors via the current active chat room ([Discord](https://discord.gg/QAbgGXq)) and explain your intents
## Questions
If you have any questions, create an [issue](https://github.com/artf/grapesjs/issues) (protip: do a quick search first to see if someone else didn't ask the same question before!).
## Credits
Thank you to all the people who have already contributed to GrapesJS!
<a href="/artf/grapesjs/graphs/contributors"><img src="https://opencollective.com/grapesjs/contributors.svg?width=890" /></a>
[Open Collective]: <https://opencollective.com/grapesjs>

2
LICENSE

@ -1,4 +1,4 @@
Copyright (c) 2017, Artur Arseniev
Copyright (c) 2017-current, Artur Arseniev
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,

1
README.md

@ -36,6 +36,7 @@ Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
* [Plugins](#plugins)
* [Support](#support)
* [Changelog](https://github.com/artf/grapesjs/releases)
* [Contributing](https://github.com/artf/grapesjs/blob/master/CONTRIBUTING.md)
* [License](#license)

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

915
dist/grapes.js

File diff suppressed because it is too large

6
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
package-lock.json

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

2
package.json

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

2
src/asset_manager/view/AssetImageView.js

@ -9,7 +9,7 @@ module.exports = require('./AssetView').extend({
const pfx = this.pfx;
const src = this.model.get('src');
return `
<div class="${pfx}preview" style="background-image: url(${src});"></div>
<div class="${pfx}preview" style="background-image: url('${src}');"></div>
<div class="${pfx}preview-bg ${this.ppfx}checker-bg"></div>
`;
},

3
src/canvas/view/CanvasView.js

@ -1,10 +1,11 @@
import { on, off } from 'utils/mixins';
const FrameView = require('./FrameView');
const $ = Backbone.$;
module.exports = Backbone.View.extend({
initialize(o) {
_.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff');
window.onscroll = this.clearOff;
on(window, 'scroll resize', this.clearOff);
this.config = o.config || {};
this.em = this.config.em || {};
this.ppfx = this.config.pStylePrefix || '';

45
src/code_manager/model/CssGenerator.js

@ -35,42 +35,45 @@ module.exports = require('backbone').Model.extend({
build(model, opts = {}) {
const cssc = opts.cssc;
this.em = opts.em || '';
const em = opts.em || '';
this.em = em;
this.compCls = [];
this.ids = [];
var code = this.buildFromModel(model, opts);
if (cssc) {
const rules = cssc.getAll();
const mediaRules = {};
const atRules = {};
const dump = [];
rules.each(rule => {
const media = rule.get('mediaText');
const atRule = rule.getAtRule();
// If media is setted, I'll render it later
if (media) {
const mRules = mediaRules[media];
if (atRule) {
const mRules = atRules[atRule];
if (mRules) {
mRules.push(rule);
} else {
mediaRules[media] = [rule];
atRules[atRule] = [rule];
}
return;
}
code += this.buildFromRule(rule);
code += this.buildFromRule(rule, dump);
});
// Get media rules
for (let media in mediaRules) {
// Get at-rules
for (let atRule in atRules) {
let rulesStr = '';
const mRules = mediaRules[media];
mRules.forEach(rule => (rulesStr += this.buildFromRule(rule)));
const mRules = atRules[atRule];
mRules.forEach(rule => (rulesStr += this.buildFromRule(rule, dump)));
if (rulesStr) {
code += `@media ${media}{${rulesStr}}`;
code += `${atRule}{${rulesStr}}`;
}
}
em && em.getConfig('clearStyles') && rules.remove(dump);
}
return code;
@ -81,10 +84,11 @@ module.exports = require('backbone').Model.extend({
* @param {Model} rule
* @return {string} CSS string
*/
buildFromRule(rule) {
buildFromRule(rule, dump) {
let result = '';
const selectorStr = rule.selectorsToString();
const selectorStrNoAdd = rule.selectorsToString({ skipAdd: 1 });
const selectorsAdd = rule.get('selectorsAdd');
const singleAtRule = rule.get('singleAtRule');
let found;
// This will not render a rule if there is no its component
@ -95,12 +99,11 @@ module.exports = require('backbone').Model.extend({
}
});
if ((selectorStrNoAdd && found) || rule.get('selectorsAdd')) {
const style = rule.styleToString();
if (style) {
result += `${selectorStr}{${style}}`;
}
if ((selectorStrNoAdd && found) || selectorsAdd || singleAtRule) {
const block = rule.getDeclaration();
block && (result += block);
} else {
dump.push(rule);
}
return result;

1
src/css_composer/index.js

@ -68,7 +68,6 @@ module.exports = () => {
var elStyle = (c.em && c.em.config.style) || '';
c.rules = elStyle || c.rules;
c.sm = c.em;
em = c.em;
rules = new CssRules([], c);
rulesView = new CssRulesView({

69
src/css_composer/model/CssRule.js

@ -23,6 +23,13 @@ module.exports = Backbone.Model.extend(Styleable).extend({
// Indicates if the rule is stylable
stylable: true,
// Type of at-rule, eg. 'media', 'font-face', etc.
atRuleType: '',
// This particolar property is used only on at-rules, like 'page' or
// 'font-face', where the block containes only style declarations
singleAtRule: 0,
// If true, sets '!important' on all properties
// You can use an array to specify properties to set important
// Used in view
@ -47,6 +54,19 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.set('selectors', new Selectors(selectors));
},
/**
* Returns an at-rule statement if possible, eg. '@media (...)', '@keyframes'
* @return {string}
*/
getAtRule() {
const type = this.get('atRuleType');
const condition = this.get('mediaText');
// Avoid breaks with the last condition
const typeStr = type ? `@${type}` : condition ? '@media' : '';
return typeStr + (condition && typeStr ? ` ${condition}` : '');
},
/**
* Return selectors fo the rule as a string
* @return {string}
@ -63,22 +83,36 @@ module.exports = Backbone.Model.extend(Styleable).extend({
},
/**
* Returns CSS string of the rule
* Get declaration block
* @param {Object} [opts={}] Options
* @return {string}
*/
toCSS(opts = {}) {
getDeclaration(opts = {}) {
let result = '';
const media = this.get('mediaText');
const style = this.styleToString(opts);
const selectors = this.selectorsToString();
const style = this.styleToString(opts);
const singleAtRule = this.get('singleAtRule');
if (selectors && style) {
result = `${selectors}{${style}}`;
if ((selectors || singleAtRule) && style) {
result = singleAtRule ? style : `${selectors}{${style}}`;
}
if (media && result) {
result = `@media ${media}{${result}}`;
return result;
},
/**
* Returns CSS string of the rule
* @param {Object} [opts={}] Options
* @return {string}
*/
toCSS(opts = {}) {
let result = '';
const atRule = this.getAtRule();
const block = this.getDeclaration(opts);
block && (result = block);
if (atRule && result) {
result = `${atRule}{${result}}`;
}
return result;
@ -93,11 +127,11 @@ module.exports = Backbone.Model.extend(Styleable).extend({
* @return {Boolean}
* @private
*/
compare(selectors, state, width, ruleProps) {
var otherRule = ruleProps || {};
compare(selectors, state, width, ruleProps = {}) {
var st = state || '';
var wd = width || '';
var selectorsAdd = otherRule.selectorsAdd || '';
var selectorsAdd = ruleProps.selectorsAdd || '';
var atRuleType = ruleProps.atRuleType || '';
var cId = 'cid';
//var a1 = _.pluck(selectors.models || selectors, cId);
//var a2 = _.pluck(this.get('selectors').models, cId);
@ -117,11 +151,14 @@ module.exports = Backbone.Model.extend(Styleable).extend({
if (re === 0) return f;
}
if (this.get('state') !== st) return f;
if (this.get('mediaText') !== wd) return f;
if (this.get('selectorsAdd') !== selectorsAdd) return f;
if (
this.get('state') !== st ||
this.get('mediaText') !== wd ||
this.get('selectorsAdd') !== selectorsAdd ||
this.get('atRuleType') !== atRuleType
) {
return f;
}
return true;
}

4
src/css_composer/model/CssRules.js

@ -4,13 +4,13 @@ var CssRule = require('./CssRule');
module.exports = Backbone.Collection.extend({
initialize(models, opt) {
// Inject editor
if (opt && opt.sm) this.editor = opt.sm;
if (opt && opt.em) this.editor = opt.em;
// Not used
this.model = (attrs, options) => {
var model;
if (!options.sm && opt && opt.sm) options.sm = opt.sm;
if (!options.em && opt && opt.em) options.em = opt.em;
switch (1) {
default:

12
src/css_composer/view/CssGroupRuleView.js

@ -0,0 +1,12 @@
module.exports = require('./CssRuleView').extend({
_createElement: function(tagName) {
return document.createTextNode('');
},
render() {
const model = this.model;
const important = model.get('important');
this.el.textContent = model.getDeclaration({ important });
return this;
}
});

45
src/css_composer/view/CssRulesView.js

@ -1,9 +1,10 @@
var Backbone = require('backbone');
var CssRuleView = require('./CssRuleView');
const CssRuleView = require('./CssRuleView');
const CssGroupRuleView = require('./CssGroupRuleView');
module.exports = Backbone.View.extend({
module.exports = require('backbone').View.extend({
initialize(o) {
const config = o.config || {};
this.atRules = {};
this.config = config;
this.em = config.em;
this.pfx = config.stylePrefix || '';
@ -32,20 +33,44 @@ module.exports = Backbone.View.extend({
addToCollection(model, fragmentEl) {
var fragment = fragmentEl || null;
var viewObject = CssRuleView;
var config = this.config;
let rendered, view;
const opts = { model, config };
var view = new viewObject({
model,
config: this.config
});
var rendered = view.render().el;
// I have to render keyframes of the same name together
// Unfortunately at the moment I didn't find the way of appending them
// if not staticly, via appendData
if (model.get('atRuleType') == 'keyframes') {
const atRule = model.getAtRule();
let atRuleEl = this.atRules[atRule];
if (fragment) fragment.appendChild(rendered);
else this.$el.append(rendered);
if (!atRuleEl) {
const styleEl = document.createElement('style');
atRuleEl = document.createTextNode('');
styleEl.appendChild(document.createTextNode(`${atRule}{`));
styleEl.appendChild(atRuleEl);
styleEl.appendChild(document.createTextNode(`}`));
this.atRules[atRule] = atRuleEl;
rendered = styleEl;
}
view = new CssGroupRuleView(opts);
atRuleEl.appendData(view.render().el.textContent);
} else {
view = new CssRuleView(opts);
rendered = view.render().el;
}
if (rendered) {
if (fragment) fragment.appendChild(rendered);
else this.$el.append(rendered);
}
return rendered;
},
render() {
this.atRules = {};
const $el = this.$el;
const frag = document.createDocumentFragment();
$el.empty();

4
src/dom_components/index.js

@ -211,7 +211,7 @@ module.exports = () => {
}
component = new Component(wrapper, {
sm: em,
em,
config: c,
componentTypes
});
@ -230,7 +230,7 @@ module.exports = () => {
* @private
*/
onLoad() {
this.getComponents().reset(c.components);
this.setComponents(c.components);
},
/**

50
src/dom_components/model/Component.js

@ -141,7 +141,7 @@ const Component = Backbone.Model.extend(Styleable).extend(
},
initialize(props = {}, opt = {}) {
const em = opt.sm || opt.em || '';
const em = opt.em;
// Propagate properties from parent if indicated
const parent = this.parent();
@ -171,12 +171,11 @@ const Component = Backbone.Model.extend(Styleable).extend(
opt.em = em;
this.opt = opt;
this.sm = em;
this.em = em;
this.config = opt.config || {};
this.ccid = Component.createId(this);
this.set('attributes', this.get('attributes') || {});
this.on('destroy', this.handleRemove);
this.on('remove', this.handleRemove);
this.listenTo(this, 'change:script', this.scriptUpdated);
this.listenTo(this, 'change:traits', this.traitsUpdated);
this.listenTo(this, 'change:tagName', this.tagUpdated);
@ -235,6 +234,19 @@ const Component = Backbone.Model.extend(Styleable).extend(
return result;
},
/**
* Find closest model by query string
* ATTENTION: this method works only with alredy rendered component
* @param {string} query Query string
* @return {Component}
* @example
* model.closest('div');
*/
closest(query) {
const result = this.view.$el.closest(query);
return result.length && result.data('model');
},
/**
* Once the tag is updated I have to remove the node and replace it
*/
@ -277,6 +289,17 @@ const Component = Backbone.Model.extend(Styleable).extend(
this.set('attributes', attrs);
},
/**
* Add attributes to the model
* @param {Object} attrs Key value attributes
* @example
* model.addAttributes({id: 'test'});
*/
addAttributes(attrs) {
const newAttrs = { ...this.getAttributes(), ...attrs };
this.setAttributes(newAttrs);
},
getStyle() {
const em = this.em;
@ -555,10 +578,11 @@ const Component = Backbone.Model.extend(Styleable).extend(
*/
normalizeClasses(arr) {
var res = [];
const em = this.em;
if (!this.sm.get) return;
if (!em) return;
var clm = this.sm.get('SelectorManager');
var clm = em.get('SelectorManager');
if (!clm) return;
arr.forEach(val => {
@ -577,10 +601,11 @@ const Component = Backbone.Model.extend(Styleable).extend(
* Override original clone method
* @private
*/
clone(reset) {
clone() {
const em = this.em;
const style = this.getStyle();
const attr = { ...this.attributes };
const opts = { ...this.opt };
attr.attributes = { ...attr.attributes };
delete attr.attributes.id;
attr.components = [];
@ -588,7 +613,7 @@ const Component = Backbone.Model.extend(Styleable).extend(
attr.traits = [];
this.get('components').each((md, i) => {
attr.components[i] = md.clone(1);
attr.components[i] = md.clone();
});
this.get('traits').each((md, i) => {
attr.traits[i] = md.clone();
@ -599,16 +624,13 @@ const Component = Backbone.Model.extend(Styleable).extend(
attr.status = '';
attr.view = '';
if (reset) {
this.opt.collection = null;
}
opts.collection = null;
if (em && em.getConfig('avoidInlineStyle') && !isEmpty(style)) {
attr.style = style;
}
return new this.constructor(attr, this.opt);
return new this.constructor(attr, opts);
},
/**
@ -734,7 +756,7 @@ const Component = Backbone.Model.extend(Styleable).extend(
scr = scrStr.trim();
}
var config = this.sm.config || {};
var config = this.em.getConfig();
var tagVarStart = escapeRegExp(config.tagVarStart || '{[ ');
var tagVarEnd = escapeRegExp(config.tagVarEnd || ' ]}');
var reg = new RegExp(`${tagVarStart}([\\w\\d-]*)${tagVarEnd}`, 'g');
@ -751,7 +773,7 @@ const Component = Backbone.Model.extend(Styleable).extend(
emitUpdate(property) {
const em = this.em;
const event = 'component:update' + (property ? `:${property}` : '');
em && em.trigger(event, this.model);
em && em.trigger(event, this);
}
},
{

5
src/dom_components/model/ComponentImage.js

@ -26,9 +26,10 @@ module.exports = Component.extend(
initToolbar(...args) {
Component.prototype.initToolbar.apply(this, args);
const em = this.em;
if (this.sm && this.sm.get) {
var cmd = this.sm.get('Commands');
if (em) {
var cmd = em.get('Commands');
var cmdName = 'image-editor';
// Add Image Editor button only if the default command exists

2
src/dom_components/model/ComponentVideo.js

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

44
src/dom_components/model/Components.js

@ -3,27 +3,17 @@ import { isEmpty } from 'underscore';
const Backbone = require('backbone');
module.exports = Backbone.Collection.extend({
initialize(models, opt) {
this.on('add', this.onAdd);
this.config = opt && opt.config ? opt.config : null;
// Inject editor
if (opt && (opt.sm || opt.em)) this.editor = opt.sm || opt.em;
initialize(models, opt = {}) {
this.listenTo(this, 'add', this.onAdd);
this.config = opt.config;
this.em = opt.em;
this.model = (attrs, options) => {
var model;
if (!options.sm && opt && opt.sm) options.sm = opt.sm;
if (!options.em && opt && opt.em) options.em = opt.em;
if (opt && opt.config) options.config = opt.config;
if (opt && opt.componentTypes)
options.componentTypes = opt.componentTypes;
var df = opt.componentTypes;
options.em = opt.em;
options.config = opt.config;
options.componentTypes = df;
for (var it = 0; it < df.length; it++) {
var dfId = df[it].id;
@ -44,10 +34,10 @@ module.exports = Backbone.Collection.extend({
add(models, opt = {}) {
if (typeof models === 'string') {
var parsed = this.editor.get('Parser').parseHtml(models);
var parsed = this.em.get('Parser').parseHtml(models);
models = parsed.html;
var cssc = this.editor.get('CssComposer');
var cssc = this.em.get('CssComposer');
if (parsed.css && cssc) {
var { avoidUpdateStyle } = opt;
@ -62,8 +52,8 @@ module.exports = Backbone.Collection.extend({
},
onAdd(model, c, opts) {
const em = this.editor;
const style = model.get('style');
const em = this.em;
const style = model.getStyle();
const avoidInline = em && em.getConfig('avoidInlineStyle');
if (
@ -71,14 +61,12 @@ module.exports = Backbone.Collection.extend({
!avoidInline &&
em &&
em.get &&
em.get('Config').forceClass
em.getConfig('forceClass')
) {
var cssC = this.editor.get('CssComposer');
var newClass = this.editor.get('SelectorManager').add(model.cid);
model.set({ style: {} });
model.get('classes').add(newClass);
var rule = cssC.add(newClass);
rule.set('style', style);
const name = model.cid;
const rule = em.get('CssComposer').setClassRule(name, style);
model.setStyle({});
model.addClass(name);
}
}
});

4
src/dom_components/view/ComponentTextNodeView.js

@ -1,3 +1 @@
var Backbone = require('backbone');
module.exports = Backbone.View.extend({});
module.exports = require('backbone').View.extend({});

5
src/dom_components/view/ComponentView.js

@ -338,6 +338,9 @@ module.exports = Backbone.View.extend({
this.updateContent();
this.renderChildren();
this.updateScript();
this.onRender();
return this;
}
},
onRender() {}
});

18
src/editor/config/config.js

@ -2,9 +2,15 @@ module.exports = {
// Style prefix
stylePrefix: 'gjs-',
//TEMP
// HTML string or object of components
components: '',
// CSS string or object of rules
style: '',
// If true, will fetch HTML and CSS from selected container
fromElement: 0,
// Show an alert before unload the page with unsaved changes
noticeOnUnload: true,
@ -74,6 +80,16 @@ module.exports = {
// When `avoidInlineStyle` is true all styles are inserted inside the css rule
avoidInlineStyle: 0,
// (experimental)
// The structure of components is always on the screen but it's not the same
// for style rules. When you delete a component you might leave a lot of styles
// which will never be used again, therefore they might be removed.
// With this option set to true, styles not used from the CSS generator (so in
// any case where `CssGenerator.build` is used) will be removed automatically.
// But be careful, not always leaving the style not used mean you wouldn't
// use it later, but this option comes really handy when deal with big templates.
clearStyles: 0,
// Dom element
el: '',

1
src/editor/model/Editor.js

@ -50,6 +50,7 @@ module.exports = Backbone.Model.extend({
this.config = c;
this.set('Config', c);
this.set('modules', []);
this.set('toLoad', []);
if (c.el && c.fromElement) this.config.components = c.el.innerHTML;

43
src/index.js

@ -1,33 +1,17 @@
import $ from 'cash-dom';
import { defaults } from 'underscore';
import Editor from './editor';
import PluginManager from './plugin_manager';
import polyfills from 'utils/polyfills';
polyfills();
module.exports = (() => {
const Editor = require('editor');
const PluginManager = require('plugin_manager');
const plugins = new PluginManager();
const editors = [];
const defaultConfig = {
// If true renders editor on init
autorender: 1,
// Where init the editor
container: '',
// HTML string or object of components
components: '',
// CSS string or object of rules
style: '',
// If true, will fetch HTML and CSS from selected container
fromElement: 0,
// Storage Manager
storageManager: {},
// Array of plugins to init
plugins: [],
@ -49,12 +33,10 @@ module.exports = (() => {
* Initializes an editor based on passed options
* @param {Object} config Configuration object
* @param {string|HTMLElement} config.container Selector which indicates where render the editor
* @param {Object|string} config.components='' HTML string or Component model in JSON format
* @param {Object|string} config.style='' CSS string or CSS model in JSON format
* @param {Boolean} [config.fromElement=false] If true, will fetch HTML and CSS from selected container
* @param {Boolean} [config.undoManager=true] Enable/Disable undo manager
* @param {Boolean} [config.autorender=true] If true, auto-render the content
* @param {Array} [config.plugins=[]] Array of plugins to execute on start
* @return {grapesjs.Editor} GrapesJS editor instance
* @param {Object} [config.pluginsOpts={}] Custom options for plugins
* @return {Editor} Editor instance
* @example
* var editor = grapesjs.init({
* container: '#myeditor',
@ -64,14 +46,10 @@ module.exports = (() => {
*/
init(config = {}) {
const els = config.container;
if (!els) {
throw new Error("'container' is required");
}
defaults(config, defaultConfig);
config.el =
els instanceof window.HTMLElement ? els : document.querySelector(els);
if (!els) throw new Error("'container' is required");
config = { ...defaultConfig, ...config };
const ilEl = els instanceof window.HTMLElement;
config.el = ilEl ? els : document.querySelector(els);
const editor = new Editor(config).init();
// Load plugins
@ -89,10 +67,9 @@ module.exports = (() => {
// A plugin might have extended/added some custom type so this
// is a good point to load stuff like components, css rules, etc.
editor.getModel().loadOnStart();
config.autorender && editor.render();
editors.push(editor);
return editor;
}
};

134
src/parser/model/ParserCss.js

@ -1,3 +1,21 @@
import { keys } from 'underscore';
// At-rules
// https://developer.mozilla.org/it/docs/Web/API/CSSRule#Type_constants
const atRules = {
4: 'media',
5: 'font-face',
6: 'page',
7: 'keyframes',
11: 'counter-style',
12: 'supports',
13: 'document',
14: 'font-feature-values',
15: 'viewport'
};
const atRuleKeys = keys(atRules);
const singleAtRules = ['5', '6', '11', '15'];
module.exports = config => ({
/**
* Parse selector string to array.
@ -14,7 +32,7 @@ module.exports = config => ({
* //add: ['.test2 .test3']
* //}
*/
parseSelector(str) {
parseSelector(str = '') {
var add = [];
var result = [];
var sels = str.split(',');
@ -35,55 +53,89 @@ module.exports = config => ({
};
},
/**
* Parse style declarations of the node
* @param {CSSRule} node
* @return {Object}
*/
parseStyle(node) {
const stl = node.style;
const style = {};
for (var i = 0, len = stl.length; i < len; i++) {
const propName = stl[i];
const propValue = stl.getPropertyValue(propName);
const important = stl.getPropertyPriority(propName);
style[propName] = `${propValue}${important ? ` !${important}` : ''}`;
}
return style;
},
/**
* Get the condition when possible
* @param {CSSRule} node
* @return {string}
*/
parseCondition(node) {
const condition =
node.conditionText ||
(node.media && node.media.mediaText) ||
node.name ||
node.selectorText ||
'';
return condition.trim();
},
/**
* Fetch data from node
* @param {StyleSheet|CSSMediaRule} el
* @param {StyleSheet|CSSRule} el
* @return {Array<Object>}
*/
parseNode(el) {
var result = [];
var nodes = el.cssRules;
var nodes = el.cssRules || [];
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var sels = node.selectorText;
var selsAdd = [];
// It's a CSSMediaRule
if (node.cssRules) {
const node = nodes[i];
const type = node.type.toString();
let singleAtRule = 0;
let atRuleType = '';
let condition = '';
// keyText is for CSSKeyframeRule
let sels = node.selectorText || node.keyText;
const isSingleAtRule = singleAtRules.indexOf(type) >= 0;
// Check if the node is an at-rule
if (isSingleAtRule) {
singleAtRule = 1;
atRuleType = atRules[type];
condition = this.parseCondition(node);
} else if (atRuleKeys.indexOf(type) >= 0) {
var subRules = this.parseNode(node);
var mediaText = node.media.mediaText;
condition = this.parseCondition(node);
for (var s = 0, lens = subRules.length; s < lens; s++) {
var subRule = subRules[s];
subRule.mediaText = mediaText ? mediaText.trim() : '';
condition && (subRule.mediaText = condition);
subRule.atRuleType = atRules[type];
}
result = result.concat(subRules);
}
if (!sels) continue;
var selsParsed = this.parseSelector(sels);
if (!sels && !isSingleAtRule) continue;
const style = this.parseStyle(node);
const selsParsed = this.parseSelector(sels);
const selsAdd = selsParsed.add;
sels = selsParsed.result;
selsAdd = selsParsed.add;
// Create style object from the big one
var stl = node.style;
var style = {};
for (var j = 0, len2 = stl.length; j < len2; j++) {
const propName = stl[j];
const propValue = stl.getPropertyValue(propName);
const important = stl.getPropertyPriority(propName);
style[propName] = `${propValue}${important ? ` !${important}` : ''}`;
}
var lastRule = '';
let lastRule;
// For each group of selectors
for (var k = 0, len3 = sels.length; k < len3; k++) {
var selArr = sels[k];
var model = {};
singleAtRule && (model.singleAtRule = singleAtRule);
atRuleType && (model.atRuleType = atRuleType);
//Isolate state from selector
var stateArr = selArr[selArr.length - 1].split(/:(.+)/);
@ -106,13 +158,18 @@ module.exports = config => ({
if (lastRule) {
lastRule.selectorsAdd = selsAddStr;
} else {
result.push({
const model = {
selectors: [],
selectorsAdd: selsAddStr,
style
});
};
singleAtRule && (model.singleAtRule = singleAtRule);
atRuleType && (model.atRuleType = atRuleType);
condition && (model.mediaText = condition);
result.push(model);
}
}
// console.log('LAST PUSH', result[result.length - 1]);
}
return result;
@ -124,22 +181,15 @@ module.exports = config => ({
* @return {Object|Array<Object>}
*/
parse(str) {
var el = document.createElement('style');
/*
el.innerHTML = ".cssClass {border: 2px solid black; background-color: blue;} " +
".red, .red2 {color:red; padding:5px} .test1.red {color:black} .red:hover{color: blue} " +
"@media screen and (min-width: 480px){ .red{color:white} }";
*/
const el = document.createElement('style');
el.innerHTML = str;
// There is no .sheet without adding it to the <head>
// There is no .sheet before adding it to the <head>
document.head.appendChild(el);
var sheet = el.sheet;
const sheet = el.sheet;
document.head.removeChild(el);
var result = this.parseNode(sheet);
const result = this.parseNode(sheet);
if (result.length == 1) result = result[0];
return result;
return result.length == 1 ? result[0] : result;
}
});

3
src/storage_manager/model/RemoteStorage.js

@ -55,7 +55,8 @@ module.exports = require('backbone').Model.extend({
const em = this.get('em');
const complete = this.get('onComplete');
const typeJson = this.get('contentTypeJson');
const res = typeJson && typeof text === 'string' ? JSON.parse(text) : text;
const parsable = text && typeof text === 'string';
const res = typeJson && parsable ? JSON.parse(text) : text;
complete && complete(res);
clb && clb(res);
em && em.trigger('storage:response', res);

23
src/style_manager/index.js

@ -251,20 +251,35 @@ module.exports = () => {
if (em) {
const config = em.getConfig();
const um = em.get('UndoManager');
const cssC = em.get('CssComposer');
const state = !config.devicePreviewMode ? model.get('state') : '';
const valid = classes.getStyleable();
const hasClasses = valid.length;
const opts = { state };
let rule;
if (hasClasses) {
const deviceW = em.getCurrentMedia();
const CssRule = cssC.get(valid, state, deviceW);
if (CssRule) return CssRule;
rule = cssC.get(valid, state, deviceW);
if (!rule) {
// I stop undo manager here as after adding the CSSRule (generally after
// selecting the component) and calling undo() it will remove the rule from
// the collection, therefore updating it in style manager will not affect it
// #268
um.stop();
rule = cssC.add(valid, state, deviceW);
rule.setStyle(model.getStyle());
model.setStyle({});
um.start();
}
} else if (config.avoidInlineStyle) {
const rule = cssC.getIdRule(id, opts);
return rule ? rule : cssC.setIdRule(id, {}, opts);
rule = cssC.getIdRule(id, opts);
!rule && (rule = cssC.setIdRule(id, {}, opts));
}
rule && (model = rule);
}
return model;

3
src/style_manager/view/PropertyView.js

@ -373,9 +373,6 @@ module.exports = Backbone.View.extend({
* @return {Boolean}
*/
isTargetStylable(target) {
if (this.model.get('id') == 'flex-width') {
//debugger;
}
const trg = target || this.getTarget();
const model = this.model;
const property = model.get('property');

91
src/style_manager/view/SectorsView.js

@ -19,7 +19,7 @@ module.exports = Backbone.View.extend({
this.propTarget = target;
const coll = this.collection;
const events =
'change:selectedComponent component:update:classes change:device';
'change:selectedComponent component:update:classes component:update:state change:device';
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.render);
this.listenTo(this.target, events, this.targetUpdated);
@ -40,44 +40,33 @@ module.exports = Backbone.View.extend({
* @private
*/
targetUpdated() {
var em = this.target;
const em = this.target;
const pt = this.propTarget;
let model = em.getSelected();
const um = em.get('UndoManager');
const cc = em.get('CssComposer');
const avoidInline = em.getConfig('avoidInlineStyle');
if (!model) return;
if (!model) {
return;
}
const id = model.getId();
const config = em.get('Config');
var classes = model.get('classes');
var pt = this.propTarget;
const state = !config.devicePreviewMode ? model.get('state') : '';
const opts = { state };
var stateStr = state ? `:${state}` : null;
var view = model.view;
const media = em.getCurrentMedia();
const el = model.getEl();
pt.helper = null;
if (view) {
pt.computed = window.getComputedStyle(
view.el,
state ? `:${state}` : null
);
// Create computed style container
if (el) {
const stateStr = state ? `:${state}` : null;
pt.computed = window.getComputedStyle(el, stateStr);
}
// Create a new rule for the state as a helper
const appendStateRule = (style = {}) => {
const sm = em.get('SelectorManager');
const helperClass = sm.add('hc-state');
let helperRule = cc.get([helperClass]);
const cc = em.get('CssComposer');
const helperCls = 'hc-state';
const rules = cc.getAll();
let helperRule = cc.getClassRule(helperCls);
if (!helperRule) {
helperRule = cc.add([helperClass]);
helperRule = cc.setClassRule(helperCls);
} else {
// I will make it last again, otherwise it could be overridden
const rules = cc.getAll();
rules.remove(helperRule);
rules.add(helperRule);
}
@ -87,54 +76,8 @@ module.exports = Backbone.View.extend({
pt.helper = helperRule;
};
// If true the model will be always a rule
if (avoidInline) {
const ruleId = cc.getIdRule(id, opts);
if (!ruleId) {
model = cc.setIdRule(id, {}, opts);
} else {
model = ruleId;
}
}
if (classes.length) {
var valid = classes.getStyleable();
var iContainer = cc.get(valid, state, media);
if (!iContainer && valid.length) {
// I stop undo manager here as after adding the CSSRule (generally after
// selecting the component) and calling undo() it will remove the rule from
// the collection, therefore updating it in style manager will not affect it
// #268
um.stop();
iContainer = cc.add(valid, state, media);
iContainer.setStyle(model.getStyle());
model.setStyle({});
um.start();
}
if (!iContainer) {
// In this case it's just a Component without any valid selector
pt.model = model;
pt.trigger('update');
return;
}
// If the state is not empty, there should be a helper rule in play
// The helper rule will get the same style of the iContainer
state && appendStateRule(iContainer.getStyle());
pt.model = iContainer;
pt.trigger('update');
return;
}
if (state) {
const ruleState = cc.getIdRule(id, opts);
state && appendStateRule(ruleState && ruleState.getStyle());
}
model = em.get('StyleManager').getModelToStyle(model);
state && appendStateRule(model.getStyle());
pt.model = model;
pt.trigger('update');
},

2
src/styles/scss/_gjs_canvas.scss

@ -5,7 +5,7 @@ $canvasTop: 40px;
background-color: rgba(0, 0, 0, 0.15);
box-sizing: border-box;
width: (100% - $leftWidth);
height: calc(100% - $canvasTop);
height: calc(100% - #{$canvasTop});
bottom: 0;
overflow: hidden;
z-index: 1;

22
src/undo_manager/index.js

@ -30,6 +30,28 @@ module.exports = () => {
this.em = em;
um = new UndoManager({ track: true, register: [] });
um.changeUndoType('change', { condition: false });
um.changeUndoType('add', {
on(model, collection, options = {}) {
if (options.avoidStore) return;
return {
object: collection,
before: undefined,
after: model,
options: { ...options }
};
}
});
um.changeUndoType('remove', {
on(model, collection, options = {}) {
if (options.avoidStore) return;
return {
object: collection,
before: model,
after: undefined,
options: { ...options }
};
}
});
const customUndoType = {
on(object, value, opt = {}) {
!beforeCache && (beforeCache = object.previousAttributes());

3
src/utils/Droppable.js

@ -119,6 +119,7 @@ export default class Droppable {
const em = this.em;
const types = dataTransfer.types;
const files = dataTransfer.files;
const dragContent = em.get('dragContent');
let content = dataTransfer.getData('text');
if (files.length) {
@ -135,6 +136,8 @@ export default class Droppable {
});
}
}
} else if (dragContent) {
content = dragContent;
} else if (types.indexOf('text/html') >= 0) {
content = dataTransfer
.getData('text/html')

12
src/utils/Sorter.js

@ -311,12 +311,14 @@ module.exports = Backbone.View.extend({
if (dropContent && em) {
if (!dropModel) {
let comps = em.get('DomComponents').getComponents();
let tempModel = comps.add(dropContent, {
const opts = {
avoidStore: 1,
avoidChildren: 1,
avoidUpdateStyle: 1,
temporary: 1
});
dropModel = comps.remove(tempModel, { temporary: 1 });
};
let tempModel = comps.add(dropContent, opts);
dropModel = comps.remove(tempModel, opts);
this.dropModel = dropModel instanceof Array ? dropModel[0] : dropModel;
}
return dropModel;
@ -972,7 +974,7 @@ module.exports = Backbone.View.extend({
var opts = { at: index, noIncrement: 1 };
if (!dropContent) {
modelTemp = targetCollection.add({}, opts);
modelTemp = targetCollection.add({}, { ...opts, avoidStore: 1 });
if (model) {
modelToDrop = model.collection.remove(model);
@ -986,7 +988,7 @@ module.exports = Backbone.View.extend({
created = targetCollection.add(modelToDrop, opts);
if (!dropContent) {
targetCollection.remove(modelTemp);
targetCollection.remove(modelTemp, { avoidStore: 1 });
} else {
this.dropContent = null;
}

52
src/utils/extender.js

@ -1,3 +1,5 @@
import { isObject } from 'underscore';
module.exports = ({ $, Backbone }) => {
if (Backbone) {
const ViewProt = Backbone.View.prototype;
@ -60,46 +62,6 @@ module.exports = ({ $, Backbone }) => {
? [namespaceArray[0], namespaceArray.slice(1)]
: [null, namespaceArray];
};
/*
const CashEvent = function(node, eventName, namespaces, delegate, originalCallback, runOnce) {
const eventCache = getData(node,'_cashEvents') || setData(node, '_cashEvents', {});
const remove = function(c, namespace){
if ( c && originalCallback !== c ) { return; }
if ( namespace && this.namespaces.indexOf(namespace) < 0 ) { return; }
node.removeEventListener(eventName, callback);
};
const callback = function(e) {
var t = this;
if (delegate) {
t = e.target;
while (t && !matches(t, delegate)) {
if (t === this) {
return (t = false);
}
t = t.parentNode;
}
}
if (t) {
originalCallback.call(t, e, e.data);
if ( runOnce ) { remove(); }
}
};
this.remove = remove;
this.namespaces = namespaces;
node.addEventListener(eventName, callback);
eventCache[eventName] = eventCache[eventName] || [];
eventCache[eventName].push(this);
return this;
}
*/
const on = $.prototype.on;
const off = $.prototype.off;
@ -226,7 +188,15 @@ module.exports = ({ $, Backbone }) => {
});
fn.unbind = function(ev, h) {
return this.off(ev, h);
if (isObject(ev)) {
for (let name in ev) {
ev.hasOwnProperty(name) && this.off(name, ev[name]);
}
return this;
} else {
return this.off(ev, h);
}
};
fn.click = function(h) {

6
test/specs/css_composer/e2e/CssComposer.js

@ -140,6 +140,8 @@ module.exports = {
protected: false
}
],
atRuleType: '',
singleAtRule: 0,
important: 0,
selectorsAdd: '',
state: '',
@ -178,6 +180,8 @@ module.exports = {
rule1Out = JSON.parse(JSON.stringify(rule1Out));
rule2Out = JSON.parse(JSON.stringify(rule2Out));
var rule1Result = {
atRuleType: '',
singleAtRule: 0,
important: 0,
mediaText: '',
selectors: [],
@ -190,6 +194,8 @@ module.exports = {
}
};
var rule2Result = {
atRuleType: '',
singleAtRule: 0,
important: 0,
mediaText: '',
selectors: [],

27
test/specs/css_composer/model/CssModels.js

@ -74,11 +74,38 @@ module.exports = {
it('toCSS wraps correctly inside media rule', () => {
const media = '(max-width: 768px)';
obj.set('atRuleType', 'media');
obj.set('mediaText', media);
obj.get('selectors').add({ name: 'test1' });
obj.setStyle({ color: 'red' });
expect(obj.toCSS()).toEqual(`@media ${media}{.test1{color:red;}}`);
});
it('toCSS with a generic at-rule', () => {
obj.set('atRuleType', 'supports');
obj.get('selectors').add({ name: 'test1' });
obj.setStyle({ 'font-family': 'Open Sans' });
expect(obj.toCSS()).toEqual(
`@supports{.test1{font-family:Open Sans;}}`
);
});
it('toCSS with a generic single at-rule', () => {
obj.set('atRuleType', 'font-face');
obj.set('singleAtRule', 1);
obj.setStyle({ 'font-family': 'Sans' });
expect(obj.toCSS()).toEqual(`@font-face{font-family:Sans;}`);
});
it('toCSS with a generic at-rule and condition', () => {
obj.set('atRuleType', 'font-face');
obj.set('mediaText', 'some-condition');
obj.get('selectors').add({ name: 'test1' });
obj.setStyle({ 'font-family': 'Open Sans' });
expect(obj.toCSS()).toEqual(
`@font-face some-condition{.test1{font-family:Open Sans;}}`
);
});
});
describe('CssRules', () => {

41
test/specs/parser/model/ParserCss.js

@ -133,6 +133,7 @@ module.exports = {
var str =
'@media only screen and (max-width: 992px){ .test1.test2:hover{ color:red }}';
var result = {
atRuleType: 'media',
selectors: ['test1', 'test2'],
style: { color: 'red' },
state: 'hover',
@ -145,6 +146,7 @@ module.exports = {
it('Parse rule inside media query', () => {
var str = '@media (max-width: 992px){ .test1.test2:hover{ color:red }}';
var result = {
atRuleType: 'media',
selectors: ['test1', 'test2'],
style: { color: 'red' },
state: 'hover',
@ -167,11 +169,13 @@ module.exports = {
selectors: ['test1', 'test2'],
style: { color: 'red' },
state: 'hover',
atRuleType: 'media',
mediaText: '(max-width: 992px)'
},
{
selectors: ['test2'],
style: { color: 'blue' },
atRuleType: 'media',
mediaText: '(max-width: 992px)'
}
];
@ -234,6 +238,43 @@ module.exports = {
};
expect(obj.parse(str)).toEqual(result);
});
// Can't test keyframes https://github.com/NV/CSSOM/issues/95
it.skip('Parse rule with a keyframes at-rule', () => {
var str = `@keyframes {
from {opacity: 0;}
to {opacity: 1;}
}`;
var result = [
{
selectors: [],
atRuleType: 'keyframes',
selectorsAdd: 'from',
style: { opacity: '0' }
},
{
selectors: [],
atRuleType: 'keyframes',
selectorsAdd: 'to',
style: { opacity: '1' }
}
];
expect(obj.parse(str)).toEqual(result);
});
it('Parse rule with font-face at-rule', () => {
var str = `@font-face {
font-family: "Open Sans";
}`;
var result = {
selectors: [],
selectorsAdd: '',
atRuleType: 'font-face',
singleAtRule: 1,
style: { 'font-family': '"Open Sans"' }
};
expect(obj.parse(str)).toEqual(result);
});
});
}
};

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

@ -35,7 +35,7 @@ module.exports = {
fixtures = document.body.firstChild;
gjs = grapesjs.init({
stylePrefix: '',
storageManager: { autoload: 0, type: 'none' },
storageManager: { autoload: 0, type: 0 },
assetManager: {
storageType: 'none'
},
@ -45,17 +45,14 @@ module.exports = {
describe('Interaction with Components', () => {
beforeEach(function() {
components = gjs.editor
.get('DomComponents')
.getWrapper()
.get('components');
components = gjs.getComponents();
tagEl = instClassTagViewer(gjs, fixtures);
});
it('Assign correctly new class to component', function() {
var model = components.add({});
expect(model.get('classes').length).toEqual(0);
gjs.editor.set('selectedComponent', model);
gjs.select(model);
tagEl.addNewTag('test');
expect(model.get('classes').length).toEqual(1);
expect(

Loading…
Cancel
Save