Browse Source

Merge pull request #12 from artf/dev

Updates from dev
pull/15/head
Artur Arseniev 10 years ago
parent
commit
f52695380b
  1. 53
      README.md
  2. 2
      bower.json
  3. 2
      dist/css/grapes.min.css
  4. 16
      dist/grapes.min.js
  5. 2
      package.json
  6. 1
      src/class_manager/template/classTag.html
  7. 30
      src/class_manager/view/ClassTagView.js
  8. 16
      src/class_manager/view/ClassTagsView.js
  9. 25
      src/code_manager/model/JsonGenerator.js
  10. 19
      src/dom_components/model/Component.js
  11. 17
      src/dom_components/view/ComponentView.js
  12. 27
      src/editor/model/Editor.js
  13. 19
      styles/css/main.css
  14. 19
      styles/scss/main.scss

53
README.md

@ -1,14 +1,22 @@
# [GrapesJS](http://grapesjs.com) [![Build Status](https://travis-ci.org/artf/grapesjs.svg?branch=master)](https://travis-ci.org/artf/grapesjs) # [GrapesJS](http://grapesjs.com)
[![Build Status](https://travis-ci.org/artf/grapesjs.svg?branch=master)](https://travis-ci.org/artf/grapesjs)
<p align="center"><img src="http://grapesjs.com/img/grapesjs-demo-template2.jpg" alt="GrapesJS" width="500" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/grapesjs-demo-template2.jpg" alt="GrapesJS" width="500" align="center"/></p>
GrapesJS is a free and open source Web Template Editor for building HTML templates to be used inside sites, webapps, newsletters or anything else related with HTML. GrapesJS is a free and open source Web Template Editor for building HTML templates to be used inside sites, webapps, newsletters or anything else related with HTML.
Be aware that is not a 'Website Builder' but a tool to create only structure and containers for contents. The great thing about GrapesJS is that you can customize it, extend it or even integrate it with your applications. Mainly GrapesJS was designed to be used inside a [CMS] to speed up creation of dynamic templates. To better understand this concept check the image below
<p align="center"><img src="http://grapesjs.com/img/gjs-concept.png" alt="GrapesJS - Style Manager" height="400" align="center"/></p>
Try it here: http://grapesjs.com/demo.html Generally any 'template system', that you can find in various applications like CMS, is composed by the **structure** (HTML), **style** (CSS) and **variables**, which are then replaced with other templates and contents on server-side and rendered soon on client.
This demo shows an example of what is possible to achieve: http://grapesjs.com/demo.html
### Features
## Features
* Style Manager, for component styling<br/> * Style Manager, for component styling<br/>
<p align="center"><img src="http://grapesjs.com/img/grapesjs-style-manager.png" alt="GrapesJS - Style Manager" height="400" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/grapesjs-style-manager.png" alt="GrapesJS - Style Manager" height="400" align="center"/></p>
@ -27,12 +35,12 @@ Try it here: http://grapesjs.com/demo.html
* Default built-in commands (basically for creating and managing different components) * Default built-in commands (basically for creating and managing different components)
### Installation ## Installation
You can get GrapesJS with `bower install grapesjs` or via `git clone https://github.com/artf/grapesjs.git` to directly use it. For development purpose you should follow instructions below. You can get GrapesJS with `bower install grapesjs` or via `git clone https://github.com/artf/grapesjs.git` to directly use it. For development purpose you should follow instructions below.
### Development ## Development
GrapesJS uses [RequireJS](http://requirejs.org/) to organize its files inside `src` folder and [Grunt](http://gruntjs.com/) for build them to `dist` GrapesJS uses [RequireJS](http://requirejs.org/) to organize its files inside `src` folder and [Grunt](http://gruntjs.com/) for build them to `dist`
@ -67,7 +75,8 @@ Tests are already available inside browser on `localhost:8000/test`
If [Grunt](http://gruntjs.com/) is already installed globally you could change the port by using `grunt dev --port 9000` If [Grunt](http://gruntjs.com/) is already installed globally you could change the port by using `grunt dev --port 9000`
### Usage ## Usage
JQuery is the only hard dependency so you have to include it before use GrapesJS. JQuery is the only hard dependency so you have to include it before use GrapesJS.
```html ```html
@ -91,8 +100,10 @@ After that include scripts from GrapesJS with all your configurations and render
Unfortunately with the configuration above you wouldn't see a lot. This because GrapesJS it self is simply empty, adding panels, buttons and other stuff will be your job (actually it's not empty but you need buttons to show them up). Unfortunately with the configuration above you wouldn't see a lot. This because GrapesJS it self is simply empty, adding panels, buttons and other stuff will be your job (actually it's not empty but you need buttons to show them up).
The section below will explain some basic configurations but for a more practical example I suggest to look up the code inside this demo: http://grapesjs.com/demo.html The section below will explain some basic configurations but for a more practical example I suggest to look up the code inside this demo: http://grapesjs.com/demo.html
Documentation is under construction here: [wiki]
### Configuration ## Configuration
For now I only show up some general settings, for more details check source or demo. Examples will be available soon For now I only show up some general settings, for more details check source or demo. Examples will be available soon
@ -142,12 +153,12 @@ var config = {
``` ```
### API ## API
At the moment `render()` is the only available method but others will be public very soon... At the moment `render()` is the only available method but others will be public very soon...
### Testing ## Testing
**ATTENTION: tests are pretty far away from being complete** **ATTENTION: tests are pretty far away from being complete**
@ -157,7 +168,14 @@ Tests are run by [PhantomJS](http://phantomjs.org/) using [Mocha](https://mochaj
$ npm run test $ npm run test
``` ```
### Acknowledgements ## Todos before beta release
* **Class Manager** (*in development*) - Ability to assign different classes to components and style them (beacause CSS with only ids is pretty much a pain)
* **Breakpoint Manager** - Resize canvas according to breakpoints established by user (in simple terms, for responsive templates). Will be put into development immediately after Class Manager
* **Style Manager improvements** - Mainly `stack` type is not yet complete
## Acknowledgements
GrapesJS is built on top of this amazing open source projects: GrapesJS is built on top of this amazing open source projects:
@ -169,7 +187,14 @@ GrapesJS is built on top of this amazing open source projects:
* [FontAwesome] - the iconic font and CSS framework * [FontAwesome] - the iconic font and CSS framework
### Contributing ## Support
A star/fork is already a huge motivational support and I'd like to thank all of you for that, but if you want to contribute the project economically and you have this possibility you could use the link below :heart:
[![PayPalMe](http://grapesjs.com/img/ppme.png)](https://paypal.me/grapesjs)
## Contributing
Any kind of help is welcome. At the moment there is no generic guidelines so use usual pull requests and push to `dev` branch Any kind of help is welcome. At the moment there is no generic guidelines so use usual pull requests and push to `dev` branch
@ -184,4 +209,6 @@ BSD 3-clause
[Keymaster]: <https://github.com/madrobby/keymaster> [Keymaster]: <https://github.com/madrobby/keymaster>
[CodeMirror]: <http://codemirror.net/> [CodeMirror]: <http://codemirror.net/>
[Spectrum]: <https://github.com/bgrins/spectrum> [Spectrum]: <https://github.com/bgrins/spectrum>
[FontAwesome]: <https://fortawesome.github.io/Font-Awesome/> [FontAwesome]: <https://fortawesome.github.io/Font-Awesome/>
[wiki]: <https://github.com/artf/grapesjs/wiki>
[CMS]: <https://it.wikipedia.org/wiki/Content_management_system>

2
bower.json

@ -1,7 +1,7 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Open source Web Template Editor", "description": "Open source Web Template Editor",
"version": "0.1.1", "version": "0.1.2",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",
"main": [ "main": [

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

16
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
package.json

@ -1,7 +1,7 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Open source Web Template Editor", "description": "Open source Web Template Editor",
"version": "0.1.1", "version": "0.1.2",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",

1
src/class_manager/template/classTag.html

@ -0,0 +1 @@
<%= label %> <span id="<%= pfx %>close">&Cross;</span>

30
src/class_manager/view/ClassTagView.js

@ -1,19 +1,43 @@
define(['backbone'], define(['backbone', 'text!./../template/classTag.html'],
function (Backbone) { function (Backbone, tagTemplate) {
/** /**
* @class ClassTagView * @class ClassTagView
* */ * */
return Backbone.View.extend({ return Backbone.View.extend({
template: _.template(tagTemplate),
events: {},
initialize: function(o) { initialize: function(o) {
this.config = o.config || {}; this.config = o.config || {};
this.pfx = this.config.stylePrefix; this.pfx = this.config.stylePrefix;
this.className = this.pfx + 'tag'; this.className = this.pfx + 'tag';
this.closeId = this.pfx + 'close';
this.events['click #' + this.closeId ] = 'removeTag';
this.delegateEvents();
},
/**
* Remove tag from the selected component
* @param {Object} e
*/
removeTag: function(e){
var comp = this.config.target.get('selectedComponent');
if(comp)
comp.get('classes').remove(this.model);
this.remove();
}, },
/** @inheritdoc */ /** @inheritdoc */
render : function(){ render : function(){
this.$el.html(this.model.get('name')+': ' + this.model.get('label')); this.$el.html( this.template({
label: this.model.get('label'),
pfx: this.pfx,
}));
this.$el.attr('class', this.className); this.$el.attr('class', this.className);
return this; return this;
}, },

16
src/class_manager/view/ClassTagsView.js

@ -18,8 +18,8 @@ define(['backbone', 'text!./../template/classTags.html', './ClassTagView'],
this.addBtnId = this.pfx + 'add-tag'; this.addBtnId = this.pfx + 'add-tag';
this.newInputId = this.pfx + 'new'; this.newInputId = this.pfx + 'new';
this.events['click #' + this.addBtnId] = 'startNewTag'; this.events['click #' + this.addBtnId] = 'startNewTag';
this.events['blur #'+this.newInputId] = 'endNewTag'; this.events['blur #' + this.newInputId] = 'endNewTag';
this.events['keyup #'+this.newInputId] = 'onInputKeyUp'; this.events['keyup #' + this.newInputId] = 'onInputKeyUp';
this.target = this.config.target; this.target = this.config.target;
@ -74,6 +74,9 @@ define(['backbone', 'text!./../template/classTags.html', './ClassTagView'],
* @param {Object} e * @param {Object} e
*/ */
componentChanged: function(e){ componentChanged: function(e){
this.compTarget = this.target.get('selectedComponent');
var models = this.compTarget ? this.compTarget.get('classes').models : [];
this.collection.reset(models);
}, },
/** /**
@ -102,7 +105,10 @@ define(['backbone', 'text!./../template/classTags.html', './ClassTagView'],
if(this.target){ if(this.target){
var cm = this.target.get('ClassManager'); var cm = this.target.get('ClassManager');
var model = cm.addClass(name); var model = cm.addClass(name);
console.log(model);
if(this.compTarget)
this.compTarget.get('classes').add(model);
} }
this.endNewTag(); this.endNewTag();
}, },
@ -142,7 +148,9 @@ define(['backbone', 'text!./../template/classTags.html', './ClassTagView'],
this.addToClasses(model, fragment); this.addToClasses(model, fragment);
},this); },this);
this.$classes.empty().append(fragment); if(this.$classes)
this.$classes.empty().append(fragment);
return this; return this;
}, },

25
src/code_manager/model/JsonGenerator.js

@ -1,21 +1,22 @@
define(['backbone'], define(['backbone'],
function (Backbone) { function (Backbone) {
/** /**
* @class JsonGenerator * @class JsonGenerator
* */ * */
return Backbone.Model.extend({ return Backbone.Model.extend({
/** @inheritdoc */ /** @inheritdoc */
getId : function() getId : function()
{ {
return 'json'; return 'json';
}, },
/** @inheritdoc */ /** @inheritdoc */
build: function(model) build: function(model)
{ {
var json = model.toJSON(); var json = model.toJSON();
this.beforeEach(json);
// Avoid jshint 'loopfunc' error // Avoid jshint 'loopfunc' error
_.each(json,function(v, attr){ _.each(json,function(v, attr){
var obj = json[attr]; var obj = json[attr];
@ -30,11 +31,19 @@ define(['backbone'],
}, this); }, this);
} }
} }
}, this); }, this);
return json; return json;
}, },
/**
* Execute on each object
* @param {Object} obj
*/
beforeEach: function(obj) {
delete obj.status;
}
}); });
}); });

19
src/dom_components/model/Component.js

@ -1,5 +1,5 @@
define(['backbone','./Components'], define(['backbone','./Components', 'ClassManager/model/ClassTags'],
function (Backbone, Components) { function (Backbone, Components, ClassTags) {
/** /**
* @class Component * @class Component
* */ * */
@ -25,8 +25,10 @@ define(['backbone','./Components'],
initialize: function(o) { initialize: function(o) {
this.config = o || {}; this.config = o || {};
this.defaultC = this.config.components || []; this.defaultC = this.config.components || [];
this.defaultCl = this.config.classes || [];
this.components = new Components(this.defaultC); this.components = new Components(this.defaultC);
this.set('components', this.components); this.set('components', this.components);
this.set('classes', new ClassTags(this.defaultCl));
}, },
/** /**
@ -34,14 +36,21 @@ define(['backbone','./Components'],
*/ */
clone: function() clone: function()
{ {
var attr = _.clone(this.attributes), var attr = _.clone(this.attributes),
comp = this.get('components'); comp = this.get('components'),
attr.components = []; cls = this.get('classes');
attr.components = [];
attr.classes = [];
if(comp.length){ if(comp.length){
comp.each(function(md,i){ comp.each(function(md,i){
attr.components[i] = md.clone(); attr.components[i] = md.clone();
}); });
} }
if(cls.length){
cls.each(function(md,i){
attr.classes[i] = md.clone();
});
}
return new this.constructor(attr); return new this.constructor(attr);
}, },

17
src/dom_components/view/ComponentView.js

@ -23,6 +23,7 @@ define(['backbone', './ComponentsView'],
this.listenTo(this.model, 'change:style', this.updateStyle); this.listenTo(this.model, 'change:style', this.updateStyle);
this.listenTo(this.model, 'change:attributes', this.updateAttributes); this.listenTo(this.model, 'change:attributes', this.updateAttributes);
this.listenTo(this.model, 'change:status', this.updateStatus); this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(this.model.get('classes'), 'add remove change', this.updateClasses);
this.$el.data("model", this.model); this.$el.data("model", this.model);
this.$el.data("model-comp", this.components); this.$el.data("model-comp", this.components);
}, },
@ -109,12 +110,18 @@ define(['backbone', './ComponentsView'],
/** /**
* Update classe attribute * Update classe attribute
*
* @return void
* */ * */
updateClasses: function(){ updateClasses: function(){
if(this.classe.length) var str = '';
this.$el.attr('class', this.classe.join(" "));
this.model.get('classes').each(function(model){
str += model.get('name') + ' ';
});
this.$el.attr('class',str.trim());
// Regenerate status class
this.updateStatus();
}, },
/** /**
@ -127,12 +134,14 @@ define(['backbone', './ComponentsView'],
render: function() { render: function() {
this.updateAttributes(); this.updateAttributes();
this.updateClasses();
this.$el.html(this.model.get('content')); this.$el.html(this.model.get('content'));
var view = new ComponentsView({ var view = new ComponentsView({
collection : this.components, collection : this.components,
config : this.config, config : this.config,
}); });
this.$components = view; this.$components = view;
// With childNodes lets avoid wrapping 'div' // With childNodes lets avoid wrapping 'div'
this.$el.append(view.render(this.$el).el.childNodes); this.$el.append(view.render(this.$el).el.childNodes);
return this; return this;

27
src/editor/model/Editor.js

@ -266,24 +266,28 @@ define([
model.set(af); model.set(af);
} }
}); });
//TODO when, for example, undo delete cant redelete it, so need to
//recall 'remove command'
} }
}, },
/** /**
* Triggered when components are updated * Triggered when components are updated
* @param {Object} model
* @param {Mixed} val Value
* @param {Object} opt Options
* */ * */
componentsUpdated: function() componentsUpdated: function(model, val, opt)
{ {
var updatedCount = this.get('changesCount') + 1; var updatedCount = this.get('changesCount') + 1,
avSt = opt ? opt.avoidStore : 0;
this.set('changesCount', updatedCount); this.set('changesCount', updatedCount);
if(this.stm.isAutosave() && updatedCount < this.stm.getChangesBeforeSave()){ if(this.stm.isAutosave() && updatedCount < this.stm.getChangesBeforeSave()){
return; return;
} }
this.storeComponents();
this.set('changesCount', 0 ); if(!avSt){
this.storeComponents();
this.set('changesCount', 0);
}
}, },
/** /**
@ -338,6 +342,7 @@ define([
* */ * */
updateComponents: function(model, val, opt){ updateComponents: function(model, val, opt){
var comps = model.get('components'), var comps = model.get('components'),
classes = model.get('classes'),
avSt = opt ? opt.avoidStore : 0; avSt = opt ? opt.avoidStore : 0;
// Observe component with Undo Manager // Observe component with Undo Manager
@ -350,8 +355,12 @@ define([
this.listenTo(comps, 'add', this.updateComponents); this.listenTo(comps, 'add', this.updateComponents);
this.listenTo(comps, 'remove', this.rmComponents); this.listenTo(comps, 'remove', this.rmComponents);
this.stopListening(model, 'change:style change:content', this.updateComponents); this.stopListening(classes, 'add remove', this.componentsUpdated);
this.listenTo(model, 'change:style change:content', this.updateComponents); this.listenTo(classes, 'add remove', this.componentsUpdated);
var evn = 'change:style change:content';
this.stopListening(model, evn, this.componentsUpdated);
this.listenTo(model, evn, this.componentsUpdated);
if(!avSt) if(!avSt)
this.componentsUpdated(); this.componentsUpdated();

19
styles/css/main.css

@ -2568,7 +2568,7 @@ See http://bgrins.github.io/spectrum/themes/ for instructions.
.wte-test::btn { .wte-test::btn {
color: '#fff'; } color: '#fff'; }
.opac50 { .opac50, .wte-clm-tags #wte-clm-close {
opacity: 0.5; opacity: 0.5;
filter: alpha(opacity=50); } filter: alpha(opacity=50); }
@ -2587,7 +2587,7 @@ html, body, #wte-app, .wte-editor {
.clear { .clear {
clear: both; } clear: both; }
.no-select, .wte-com-no-select, .wte-com-no-select img { .no-select, .wte-com-no-select, .wte-com-no-select img, .wte-clm-tags #wte-clm-close {
-moz-user-select: none; -moz-user-select: none;
-khtml-user-select: none; -khtml-user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
@ -3216,6 +3216,9 @@ ol.example li.placeholder:before {
clear: both; clear: both;
padding: 5px; padding: 5px;
margin-bottom: 5px; } margin-bottom: 5px; }
.wte-clm-tags #wte-clm-tags-c {
display: inline-block;
vertical-align: top; }
.wte-clm-tags #wte-clm-add-tag { .wte-clm-tags #wte-clm-add-tag {
background-color: #535353; background-color: #535353;
border-radius: 2px; border-radius: 2px;
@ -3229,10 +3232,20 @@ ol.example li.placeholder:before {
display: none; } display: none; }
.wte-clm-tags .wte-clm-tag { .wte-clm-tags .wte-clm-tag {
background-color: #804f7b; background-color: #804f7b;
border: 1px solid #5a3857;
box-shadow: 1px 1px #9c6196 inset;
text-shadow: 1px 1px #6a4166;
display: inline-block; display: inline-block;
border-radius: 2px; border-radius: 3px;
margin: 0 3px 3px 0; margin: 0 3px 3px 0;
padding: 4px; } padding: 4px; }
.wte-clm-tags #wte-clm-close {
font-size: 20px;
line-height: 0;
cursor: pointer; }
.wte-clm-tags #wte-clm-close:hover {
opacity: 0.7;
filter: alpha(opacity=70); }
/********* END Class manager **********/ /********* END Class manager **********/
/********* Modal dialog **********/ /********* Modal dialog **********/

19
styles/scss/main.scss

@ -747,6 +747,10 @@ $tagBg: #804f7b;
padding: 5px; padding: 5px;
margin-bottom: 5px; margin-bottom: 5px;
} }
##{$clm-prefix}tags-c{
display: inline-block;
vertical-align: top;
}
##{$clm-prefix}add-tag{ ##{$clm-prefix}add-tag{
background-color: $addBtnBg; background-color: $addBtnBg;
@ -766,11 +770,24 @@ $tagBg: #804f7b;
.#{$clm-prefix}tag { .#{$clm-prefix}tag {
background-color: $tagBg; background-color: $tagBg;
border: 1px solid darken($tagBg, 12%);
box-shadow: 1px 1px lighten($tagBg, 9%) inset;
text-shadow: 1px 1px darken($tagBg, 7%);
display: inline-block; display: inline-block;
border-radius:2px; border-radius:3px;
margin: 0 3px 3px 0; margin: 0 3px 3px 0;
padding: 4px; padding: 4px;
} }
##{$clm-prefix}close {
@extend .opac50;
font-size: 20px;
line-height: 0;
cursor:pointer;
@extend .no-select;
&:hover{
@include opacity(0.7);
}
}
} }
/********* END Class manager **********/ /********* END Class manager **********/

Loading…
Cancel
Save