├── .gitignore ├── LICENSE ├── README.md ├── dist └── grapesjs-plugin-bootstrap.min.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── blocks │ ├── basics.js │ ├── components.js │ ├── index.js │ └── layout.js ├── components │ ├── alert.js │ ├── basic.js │ ├── container.js │ ├── dropdown.js │ ├── grid.js │ ├── index.js │ ├── label.js │ ├── media.js │ ├── panel.js │ ├── thumbnail.js │ └── well.js ├── constants.js ├── devices.js ├── index.js ├── traits │ ├── index.js │ └── selectClass.js └── utils.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | private/ 3 | node_modules/ 4 | .eslintrc 5 | *.log 6 | _index.html 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, (YOUR NAME) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | - Neither the name "GrapesJS" nor the names of its contributors may be 13 | used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GrapesJS Plugin Twitter Bootstrap 2 | 3 | Plugin Grapesjs with Twitter Bootstrap v3.3.7 4 | 5 | ### Usage 6 | 1. Clone this repository `git clone https://github.com/olivmonnier/grapesjs-plugin-bootstrap.git` 7 | 1. Replace in all files `grapesjs-plugin-bootstrap` with your plugin name 8 | 1. Update `package.json` 9 | 1. Install dependencies `npm i` and run the local server `npm start` 10 | 1. Start creating your plugin from `src/index.js` 11 | 1. Show some gif/demo if possible 12 | 1. Update README 13 | 1. When you're ready update the production file `npm run build` 14 | 1. Publish 15 | 16 | 17 | 18 | ## Summary 19 | 20 | * Plugin name: `grapesjs-plugin-bootstrap` 21 | * Components 22 | * `alert` 23 | * `container` 24 | * `dropdown` 25 | * `grid` 26 | * `label` 27 | * `media` 28 | * `panel` 29 | * `thumbnail` 30 | * `well` 31 | * Blocks 32 | * `address` 33 | * `blockquote` 34 | * `button` 35 | * `header` 36 | * `image` 37 | * `link` 38 | * `list` 39 | * `paragraph` 40 | * `alert` 41 | * `dropdown` 42 | * `media` 43 | * `panel` 44 | * `thumbnail` 45 | * `well` 46 | * `container` 47 | * `row` 48 | * `column` 49 | * `columns-2` 50 | * `columns-3` 51 | * `columns-4` 52 | * `columns-4-8` 53 | * `columns-8-4` 54 | ... 55 | 56 | 57 | 58 | ## Download 59 | 60 | * GIT 61 | * `git clone https://github.com/olivmonnier/grapesjs-plugin-bootstrap.git` 62 | 63 | 64 | ## Usage 65 | 66 | ```html 67 | 68 | 69 | 70 | 71 |
72 | 73 | 85 | ``` 86 | 87 | 88 | 89 | 90 | 91 | ## Development 92 | 93 | Clone the repository 94 | 95 | ```sh 96 | $ git clone https://github.com/olivmonnier/grapesjs-plugin-bootstrap.git 97 | $ cd grapesjs-plugin-bootstrap 98 | ``` 99 | 100 | Install dependencies 101 | 102 | ```sh 103 | $ npm i 104 | ``` 105 | 106 | Start the dev server 107 | 108 | ```sh 109 | $ npm start 110 | ``` 111 | 112 | ## License 113 | 114 | BSD 3-Clause 115 | -------------------------------------------------------------------------------- /dist/grapesjs-plugin-bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! grapesjs-plugin-bootstrap - 0.1.351 */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("grapesjs")):"function"==typeof define&&define.amd?define(["grapesjs"],t):"object"==typeof exports?exports["grapesjs-plugin-bootstrap"]=t(require("grapesjs")):e["grapesjs-plugin-bootstrap"]=t(e.grapesjs)}(this,function(e){return function(e){function t(n){if(a[n])return a[n].exports;var o=a[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var a={};return t.m=e,t.c=a,t.d=function(e,a,n){t.o(e,a)||Object.defineProperty(e,a,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var a=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(a,"a",a),a},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=1)}([function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.capitalize=function(e){return e.charAt(0).toUpperCase()+e.slice(1)},t.getModel=function(e,t){return e.DomComponents.getType(t).model},t.getView=function(e,t){return e.DomComponents.getType(t).view}},function(e,t,a){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{},a=o({},d.default,t);a.addBasicStyle&&e.addComponents('\n \n '),(0,r.default)(e,a),(0,v.default)(e,a),(0,p.default)(e,a),(0,f.default)(e,a)})},function(t,a){t.exports=e},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={addBasicStyle:!0,blocks:["address","alert","blockquote","button","container","column","columns-2","columns-3","columns-4","columns-4-8","columns-8-4","dropdown","header","image","label","link","list","media","panel","paragraph","row","thumbnail","well"],category:{basics:"Basics",components:"Components",layout:"Layout"}}},function(e,t,a){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=a(5),l=n(o),s=a(6),i=n(s),d=a(7),c=n(d),r=a(8),u=n(r),p=a(9),m=n(p),f=a(10),g=n(f),v=a(11),b=n(v),y=a(12),h=n(y),w=a(13),x=n(w),j=a(14),C=n(j);t.default=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};(0,l.default)(e,t),(0,i.default)(e,t),(0,c.default)(e,t),(0,u.default)(e,t),(0,m.default)(e,t),(0,g.default)(e,t),(0,b.default)(e,t),(0,h.default)(e,t),(0,x.default)(e,t),(0,C.default)(e,t)}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=a(0);t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("text"),o=a.model,l=a.view,s=["success","info","warning","danger"];t.addType("alert",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Alert",attributes:{role:"alert"},classes:["alert"],traits:o.prototype.defaults.traits.concat([{type:"select-class",label:"Context",options:s.map(function(e){return{value:"alert-"+e,name:(0,n.capitalize)(e)}})}])})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("alert"))return{type:"alert"}}}),view:l})}},function(e,t,a){"use strict";function n(e){if(Array.isArray(e)){for(var t=0,a=Array(e.length);t1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=(0,o.getModel)(e,"default"),l=(0,o.getView)(e,"default"),s=(0,o.getModel)(e,"text"),i=(0,o.getView)(e,"text"),d=(0,o.getModel)(e,"image"),c=(0,o.getView)(e,"image"),r=(0,o.getModel)(e,"link"),u=(0,o.getView)(e,"link"),p=["primary","success","info","warning","danger"],m=["left","center","right","justify"],f=["lowercase","uppercase","capitalize"],g=["rounded","circle","thumbnail"],v=[["lg","large"],["sm","small"],["xs","extra small"]];t.addType("default",{model:a.extend({defaults:Object.assign({},a.prototype.defaults,{traits:a.prototype.defaults.traits.concat([{type:"select-class",label:"Float",options:[{value:"",name:"None"},{value:"pull-left",name:"Left"},{value:"pull-right",name:"Right"}]},{type:"select-class",label:"Color",options:[{value:"",name:"None"}].concat(n(["muted"].concat(p).map(function(e){return{value:"text-"+e,name:(0,o.capitalize)(e)}})))},{type:"select-class",label:"Background",options:[{value:"",name:"None"}].concat(n(p.map(function(e){return{value:"bg-"+e,name:(0,o.capitalize)(e)}})))}])})}),view:l}),a=(0,o.getModel)(e,"default"),l=(0,o.getView)(e,"default"),t.addType("image",{model:d.extend({defaults:Object.assign({},d.prototype.defaults,{"custom-name":"Image",attributes:{src:"https://dummyimage.com/450x250/999/222"},traits:[{type:"text",label:"Source (URL)",name:"src"},{type:"text",label:"Alternate text",name:"alt"},{type:"select-class",label:"Responsive",options:[{value:"",name:"No"},{value:"img-responsive",name:"Yes"}]},{type:"select-class",label:"Shape",options:[{value:"",name:"none"}].concat(n(g.map(function(e){return{value:"img-"+e,name:(0,o.capitalize)(e)}})))}]})},{isComponent:function(e){if(e&&e.tagName&&"IMG"===e.tagName)return{type:"image"}}}),view:c}),t.addType("text",{model:a.extend({defaults:Object.assign({},s.prototype.defaults,{droppable:!0,traits:a.prototype.defaults.traits.concat([{type:"select-class",label:"Alignment",options:[].concat(n(m.map(function(e){return{value:"text-"+e,name:(0,o.capitalize)(e)}})),[{value:"text-nowrap",name:"No wrap"}])},{type:"select-class",label:"Transform",options:[{value:"",name:"None"}].concat(n(f.map(function(e){return{value:"text-"+e,name:(0,o.capitalize)(e)}})))}])})}),view:i}),s=(0,o.getModel)(e,"text"),i=(0,o.getView)(e,"text"),t.addType("header",{model:s.extend({defaults:Object.assign({},s.prototype.defaults,{"custom-name":"Header",tagName:"h1",traits:s.prototype.defaults.traits.concat([{type:"select",options:[{value:"h1",name:"One (largest)"},{value:"h2",name:"Two"},{value:"h3",name:"Three"},{value:"h4",name:"Four"},{value:"h5",name:"Five"},{value:"h6",name:"Six (smallest)"}],label:"Size",name:"tagName",changeProp:1}])})},{isComponent:function(e){if(e&&e.tagName&&["H1","H2","H3","H4","H5","H6"].includes(e.tagName))return{type:"header"}}}),view:i}),t.addType("link",{model:r.extend({defaults:Object.assign({},r.prototype.defaults,{traits:[{type:"text",name:"id",label:"Id",placeholder:"eg. Text here"}].concat(r.prototype.defaults.traits,[{type:"select",label:"Toggles",name:"data-toggle",options:[{value:"",name:"None"},{value:"button",name:"Self"},{value:"collapse",name:"Collapse"},{value:"dropdown",name:"Dropdown"}],changeProp:1}])})}),view:u}),r=(0,o.getModel)(e,"link"),u=(0,o.getView)(e,"link"),t.addType("button",{model:r.extend({defaults:Object.assign({},r.prototype.defaults,{"custom-name":"Button",attributes:{role:"button"},classes:["btn"],traits:r.prototype.defaults.traits.concat([{type:"select-class",label:"Context",options:[{value:"btn-default",name:"Default"}].concat(n(p.map(function(e){return{value:"btn-"+e,name:(0,o.capitalize)(e)}})))},{type:"select-class",label:"Size",options:[{value:"",name:"Default"}].concat(n(v.map(function(e){return{value:"btn-"+e[0],name:(0,o.capitalize)(e[1])}})))},{type:"select-class",label:"Width",options:[{value:"",name:"Inline"},{value:"btn-block",name:"Block"}]}])},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("btn"))return{type:"button"}}})}),view:u}),t.addType("list",{model:a.extend({defaults:Object.assign({},a.prototype.defaults,{"custom-name":"List",droppable:!0,traits:a.prototype.defaults.traits.concat([{type:"select",label:"Type",name:"tagName",options:[{value:"ul",name:"Unordered"},{value:"ol",name:"Ordered"}],changeProp:1},{type:"select-class",label:"Style",options:[{value:"",name:"none"},{value:"list-unstyled",name:"Unstyled"},{value:"list-inline",name:"Inline"}]}])})},{isComponent:function(e){if(e&&e.tagName&&("UL"===e.tagName||"OL"===e.tagName))return{type:"list"}}}),view:l}),t.addType("list-item",{model:s.extend({defaults:Object.assign({},s.prototype.defaults,{"custom-name":"Item",tagName:"li",draggable:"ul, ol"})},{isComponent:function(e){if(e&&e.tagName&&"LI"===e.tagName)return{type:"list-item"}}}),view:i}),t.addType("paragraph",{model:s.extend({defaults:Object.assign({},s.prototype.defaults,{"custom-name":"Paragraph",tagName:"p",traits:s.prototype.defaults.traits.concat([{type:"select-class",label:"Lead",options:[{value:"",name:"No"},{value:"lead",name:"Yes"}]}])})},{isComponent:function(e){if(e&&e.tagName&&"P"===e.tagName)return{type:"paragraph"}}}),view:i}),t.addType("blockquote",{model:a.extend({defaults:Object.assign({},a.prototype.defaults,{tagName:"blockquote",traits:a.prototype.defaults.traits.concat([{type:"select-class",label:"Reversed",options:[{value:"",name:"No"},{value:"blockquote-reverse",name:"Yes"}]}])})},{isComponent:function(e){if(e&&e.tagName&&"BLOCKQUOTE"===e.tagName)return{type:"blockquote"}}}),view:l})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("default"),n=a.model,o=a.view;t.addType("container",{model:n.extend({defaults:Object.assign({},n.prototype.defaults,{"custom-name":"Container",tagName:"div",droppable:!0,traits:n.prototype.defaults.traits.concat([{type:"select-class",label:"Type",options:[{value:"container",name:"Fixed"},{value:"container-fluid",name:"Fluid"}]}])})},{isComponent:function(e){if(e&&e.classList&&(e.classList.contains("container")||e.classList.contains("container-fluid")))return{type:"container"}}}),view:o})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("default"),n=a.model,o=a.view,l=t.getType("text"),s=l.model,i=l.view,d=t.getType("link"),c=d.model,r=t.getType("list-item"),u=r.model,p=r.view;t.addType("dropdown",{model:n.extend({defaults:Object.assign({},n.prototype.defaults,{"custom-name":"Dropdown",droppable:"a, button, .dropdown-menu",traits:n.prototype.defaults.traits.concat([{type:"select-class",label:"State",options:[{value:"",name:"Closed"},{value:"open",name:"Open"}]}])})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("dropdown"))return{type:"dropdown"}}}),view:o}),t.addType("dropdown-toggle",{model:s.extend({defaults:Object.assign({},s.prototype.defaults,{"custom-name":"Dropdown Toggle",draggable:".dropdown",droppable:!0,traits:[{type:"checkbox",name:"aria-haspopup",label:"Popup"},{type:"checkbox",name:"aria-expanded",label:"Expanded"},{type:"select",label:"Type",name:"tagName",options:[{value:"button",name:"Button"},{value:"a",name:"Link"}],changeProp:1}]}),init:function(){this.listenTo(this,"change:tagName",this.changeTag)},changeTag:function(e){var t=this.get("attributes"),a=this.get("traits"),n=["tagName","aria-haspopup","aria-expanded"];a.models=a.models.filter(function(e){return n.indexOf(e.get("name"))>=0}),"a"===this.get("tagName")?a.add(c.prototype.defaults.traits):t.href&&delete t.href,this.set("attributes",Object.assign({},t)),this.sm.trigger("change:selectedComponent")}},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("dropdown-toggle"))return{type:"dropdown-toggle"}}}),view:i}),t.addType("dropdown-menu",{model:n.extend({defaults:Object.assign({},n.prototype.defaults,{"custom-name":"Dropdown Menu",draggable:".dropdown, .btn-group",droppable:"li"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("dropdown-menu"))return{type:"dropdown-menu"}}}),view:o}),t.addType("dropdown-item",{model:u.extend({defaults:Object.assign({},u.prototype.defaults,{"custom-name":"Dropdown Item",draggable:".dropdown-menu"})},{isComponent:function(e){var t=e.parentNode;if(t&&t.classList&&t.classList.contains("dropdown-menu"))return{type:"dropdown-item"}}}),view:p})}},function(e,t,a){"use strict";function n(e){if(Array.isArray(e)){for(var t=0,a=Array(e.length);t1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("default"),o=a.model,l=a.view,s=[1,2,3,4,5,6,7,8,9,10,11,12];t.addType("row",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Row",tagName:"div",draggable:".container, .container-fluid",droppable:'[class*="col-xs"], [class*="col-sm"], [class*="col-md"], [class*="col-lg"]'})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("row"))return{type:"row"}}}),view:l}),t.addType("column",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Column",draggable:".row",droppable:!0,traits:o.prototype.defaults.traits.concat([{type:"select-class",options:[{value:"",name:"None"}].concat(n(s.map(function(e){return{value:"col-xs-"+e,name:e+"/12"}}))),label:"XS size"},{type:"select-class",options:[{value:"",name:"None"}].concat(n(s.map(function(e){return{value:"col-sm-"+e,name:e+"/12"}}))),label:"SM size"},{type:"select-class",options:[{value:"",name:"None"}].concat(n(s.map(function(e){return{value:"col-md-"+e,name:e+"/12"}}))),label:"MD size"},{type:"select-class",options:[{value:"",name:"None"}].concat(n(s.map(function(e){return{value:"col-lg-"+e,name:e+"/12"}}))),label:"LG size"},{type:"select-class",options:[{value:"",name:"None"}].concat(n(s.map(function(e){return{value:"col-xs-offset-"+e,name:e+"/12"}}))),label:"XS offset"},{type:"select-class",options:[{value:"",name:"None"}].concat(n(s.map(function(e){return{value:"col-sm-offset-"+e,name:e+"/12"}}))),label:"SM offset"},{type:"select-class",options:[{value:"",name:"None"}].concat(n(s.map(function(e){return{value:"col-md-offset-"+e,name:e+"/12"}}))),label:"MD offset"},{type:"select-class",options:[{value:"",name:"None"}].concat(n(s.map(function(e){return{value:"col-lg-offset-"+e,name:e+"/12"}}))),label:"LG offset"}])})},{isComponent:function(e){if(e&&e.className&&e.className.match(/col-(xs|sm|md|lg)-\d+/))return{type:"column"}}}),view:l})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=a(0);t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("text"),o=a.model,l=a.view,s=["default","primary","success","info","warning","danger"];t.addType("label",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Label",classes:["label"],traits:[{type:"select-class",label:"Context",options:s.map(function(e){return{value:"label-"+e,name:(0,n.capitalize)(e)}})}]})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("label"))return{type:"label"}}}),view:l})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=a(0);t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("default"),o=a.model,l=a.view,s=t.getType("header"),i=s.model,d=s.view,c=["left","right"],r=["top","middle","bottom"];t.addType("media",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Media",classes:["media"],droppable:".media-left, .media-right, .media-body"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("media"))return{type:"media"}}}),view:l}),t.addType("media-side",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Media Side",draggable:".media",traits:[{type:"select-class",label:"Side",options:c.map(function(e){return{value:"media-"+e,name:(0,n.capitalize)(e)}})},{type:"select-class",label:"Position",options:r.map(function(e){return{value:"media-"+e,name:(0,n.capitalize)(e)}})}]})},{isComponent:function(e){if(e&&e.className&&e.className.match(/media-(left|right)/))return{type:"media-side"}}}),view:l}),t.addType("media-body",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Media Body",draggable:".media"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("media-body"))return{type:"media-body"}}}),view:l}),t.addType("media-heading",{model:i.extend({defaults:Object.assign({},i.prototype.defaults,{"custom-name":"Media Heading",draggable:".media-body"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("media-heading"))return{type:"media-heading"}}}),view:d})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=a(0);t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("default"),o=a.model,l=a.view,s=t.getType("header"),i=s.model,d=s.view,c=["default","primary","success","info","warning","danger"];t.addType("panel",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Panel",droppable:".panel-heading, .panel-body, .panel-footer, .table, .list-group",traits:o.prototype.defaults.traits.concat([{type:"select-class",label:"Context",name:"context",options:c.map(function(e){return{value:"panel-"+e,name:(0,n.capitalize)(e)}})}])})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("panel"))return{type:"panel"}}}),view:l}),t.addType("panel-body",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Panel Body",draggable:".panel"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("panel-body"))return{type:"panel-body"}}}),view:l}),t.addType("panel-footer",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Panel Footer",draggable:".panel"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("panel-footer"))return{type:"panel-footer"}}}),view:l}),t.addType("panel-heading",{model:o.extend({defaults:Object.assign({},o.prototype.defaults,{"custom-name":"Panel Heading",draggable:".panel"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("panel-heading"))return{type:"panel-heading"}}}),view:l}),t.addType("panel-title",{model:i.extend({defaults:Object.assign({},i.prototype.defaults,{"custom-name":"Panel Title",draggable:".panel-heading"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("panel-title"))return{type:"panel-title"}}}),view:d})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("default"),n=a.model,o=a.view;t.addType("thumbnail",{model:n.extend({defaults:Object.assign({},n.prototype.defaults,{"custom-name":"Thumbnail",droppable:"img, .caption"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("thumbnail"))return{type:"thumbnail"}}}),view:o}),t.addType("thumbnail-caption",{model:n.extend({defaults:Object.assign({},n.prototype.defaults,{"custom-name":"Thumbnail Caption",draggable:".thumbnail"})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("caption")&&e.parentNode.classList.contains("thumbnail"))return{type:"thumbnail-caption"}}}),view:o})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DomComponents),a=t.getType("default"),n=a.model,o=a.view;t.addType("well",{model:n.extend({defaults:Object.assign({},n.prototype.defaults,{"custom-name":"Well",traits:n.prototype.defaults.traits.concat([{type:"select-class",label:"Size",name:"size",options:[{value:"",name:"Default"},{value:"well-sm",name:"Small"},{value:"well-lg",name:"Large"}]}])})},{isComponent:function(e){if(e&&e.classList&&e.classList.contains("well"))return{type:"well"}}}),view:o})}},function(e,t,a){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=a(16),l=n(o),s=a(17),i=n(s),d=a(18),c=n(d);t.default=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};(0,l.default)(e,t),(0,i.default)(e,t),(0,c.default)(e,t)}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},a=e.BlockManager,n=t.blocks,o=t.category,l=o.basics,s=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return n.indexOf(e)>=0?a.add(e,t):null};s("address",{label:"Address",category:l,attributes:{class:"fa fa-location-arrow"},content:"\n
\n Twitter, Inc.
\n 1355 Market Street, Suite 900
\n San Francisco, CA 94103
\n Phone: (123) 456-7890\n
\n "}),s("blockquote",{label:"Blockquote",category:l,attributes:{class:"fa fa-quote-left"},content:'\n
\n

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

\n
Someone famous in Source Title
\n
\n '}),s("button",{label:"Button",category:l,attributes:{class:"fa fa-link"},content:{type:"button",content:"Button"}}),s("header",{label:"Header",category:l,attributes:{class:"fa fa-header"},content:{type:"header",content:"Insert your header text here"}}),s("image",{label:"Image",category:l,attributes:{class:"fa fa-picture-o"},content:{type:"image"}}),s("link",{label:"Link",category:l,attributes:{class:"fa fa-link"},content:{type:"link",content:"Link"}}),s("list",{label:"List",category:l,attributes:{class:"fa fa-list"},content:"\n
    \n
  • Item 1
  • \n
  • Item 2
  • \n
  • Item 3
  • \n
\n "}),s("paragraph",{label:"Paragraph",category:l,attributes:{class:"fa fa-align-left"},content:{type:"paragraph",content:"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt."}})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},a=e.BlockManager,n=t.blocks,o=t.category,l=o.components,s=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return n.indexOf(e)>=0?a.add(e,t):null};s("alert",{label:"Alert",category:l,attributes:{class:"fa fa-exclamation-triangle"},content:'\n \n '}),s("dropdown",{label:"Dropdown",category:l,attributes:{class:"fa fa-caret-square-o-down"},content:'\n \n '}),s("media",{label:"Media",category:l,content:'\n
\n
\n \n ...\n \n
\n
\n

Top aligned media

\n

Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin commodo. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.

\n
\n
\n '}),s("panel",{label:"Panel",category:l,attributes:{class:"fa fa-window-maximize"},content:'\n
\n
\n

Heading

\n
\n
\n

Content

\n
\n \n
\n '}),s("thumbnail",{label:"Thumbnail",category:l,content:'\n
\n \n
\n

Thumbnail label

\n

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.

\n

Button

\n
\n
\n '}),s("well",{label:"Well",category:l,attributes:{class:"fa fa-square"},content:'\n
\n

Content

\n
\n '})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},a=e.BlockManager,n=t.blocks,o=t.category,l=o.layout,s=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return n.indexOf(e)>=0?a.add(e,t):null};s("container",{label:"Container",category:l,attributes:{class:"fa fa-square-o"},content:{type:"container",classes:["container"]}}),s("row",{label:"Row",category:l,attributes:{class:"fa fa-minus"},content:{type:"row",classes:["row"]}}),s("column",{label:"Column",category:l,attributes:{class:"fa fa-columns"},content:{type:"column",classes:["col-md-12"]}}),s("columns-2",{label:"2 Columns",category:l,attributes:{class:"fa fa-columns"},content:'\n
\n
\n
\n
\n '}),s("columns-3",{label:"3 Columns",category:l,attributes:{class:"fa fa-columns"},content:'\n
\n
\n
\n
\n
\n '}),s("columns-4",{label:"4 Columns",category:l,attributes:{class:"fa fa-columns"},content:'\n
\n
\n
\n
\n
\n
\n '}),s("columns-4-8",{label:"2 Columns 4/8",category:l,attributes:{class:"fa fa-columns"},content:'\n
\n
\n
\n
\n '}),s("columns-8-4",{label:"2 Columns 8/4",category:l,attributes:{class:"fa fa-columns"},content:'\n
\n
\n
\n
\n '})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=(arguments.length>1&&void 0!==arguments[1]&&arguments[1],e.DeviceManager);t.add("Extra Small","575px"),t.add("Small","767px"),t.add("Medium","991px"),t.add("Large","1199px"),t.add("Extra Large","100%");var a=e.Panels,n=e.Commands;a.addPanel({id:"devices-buttons"}).get("buttons").add([{id:"deviceXl",command:"set-device-xl",className:"fa fa-desktop",text:"XL",attributes:{title:"Extra Large"},active:1},{id:"deviceLg",command:"set-device-lg",className:"fa fa-desktop",attributes:{title:"Large"}},{id:"deviceMd",command:"set-device-md",className:"fa fa-tablet",attributes:{title:"Medium"}},{id:"deviceSm",command:"set-device-sm",className:"fa fa-mobile",attributes:{title:"Small"}},{id:"deviceXs",command:"set-device-xs",className:"fa fa-mobile",attributes:{title:"Extra Small"}}]),n.add("set-device-xs",{run:function(e){e.setDevice("Extra Small")}}),n.add("set-device-sm",{run:function(e){e.setDevice("Small")}}),n.add("set-device-md",{run:function(e){e.setDevice("Medium")}}),n.add("set-device-lg",{run:function(e){e.setDevice("Large")}}),n.add("set-device-xl",{run:function(e){e.setDevice("Extra Large")}})}},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};(0,o.default)(e,t)};var n=a(21),o=function(e){return e&&e.__esModule?e:{default:e}}(n)},function(e,t,a){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){arguments.length>1&&void 0!==arguments[1]&&arguments[1];e.TraitManager.addType("select-class",{events:{change:"onChange"},onValueChange:function(){for(var e=this.model.get("options").map(function(e){return e.value}),t=0;t0)for(var a=e[t].split(" "),n=0;n0&&this.target.removeClass(a[n]);var o=this.model.get("value");if(o.length>0&&"GJS_NO_CLASS"!==o)for(var l=o.split(" "),s=0;s 2 | 3 | 4 | 5 | GrapesJS Plugin Boilerplate 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 |
20 |
21 |

22 | This is a demo content from index.html. For the development, you shouldn't edit this file, instead you can 23 | copy and rename it to _index.html, on next server start the new file will be served, and it will be ignored by git. 24 |

25 |
26 |
27 | 28 | 29 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grapesjs-plugin-bootstrap", 3 | "version": "0.1.351", 4 | "description": "GrapesJS Plugin Bootstrap", 5 | "main": "dist/grapesjs-plugin-bootstrap.min.js", 6 | "scripts": { 7 | "lint": "eslint src", 8 | "v:patch": "npm version --no-git-tag-version patch", 9 | "build": "npm run v:patch && npm run standard && webpack --env.production", 10 | "start": "webpack-dev-server --open --progress --colors" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "" 15 | }, 16 | "keywords": [ 17 | "grapesjs", 18 | "plugin", 19 | "boilerplate", 20 | "wysiwyg" 21 | ], 22 | "author": "olivmonnier", 23 | "license": "BSD-3-Clause", 24 | "babel": { 25 | "presets": [ 26 | "env" 27 | ], 28 | "plugins": [ 29 | "transform-object-rest-spread" 30 | ] 31 | }, 32 | "devDependencies": { 33 | "babel-core": "^6.26.3", 34 | "babel-loader": "^7.1.5", 35 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 36 | "babel-preset-env": "^1.7.0", 37 | "eslint": "^5.5.0", 38 | "html-webpack-plugin": "^3.2.0", 39 | "webpack": "^4.18.0", 40 | "webpack-cli": "^3.1.0", 41 | "webpack-dev-server": "^3.1.8" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/blocks/basics.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const bm = editor.BlockManager 3 | const { blocks, category } = config 4 | const { basics } = category 5 | const addBlock = (name = '', attr = {}) => (blocks.indexOf(name) >= 0) ? bm.add(name, attr) : null 6 | 7 | // Address 8 | addBlock('address', { 9 | label: 'Address', 10 | category: basics, 11 | attributes: { class: 'fa fa-location-arrow' }, 12 | content: ` 13 |
14 | Twitter, Inc.
15 | 1355 Market Street, Suite 900
16 | San Francisco, CA 94103
17 | Phone: (123) 456-7890 18 |
19 | ` 20 | }) 21 | 22 | addBlock('blockquote', { 23 | label: 'Blockquote', 24 | category: basics, 25 | attributes: { class: 'fa fa-quote-left' }, 26 | content: ` 27 |
28 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

29 |
Someone famous in Source Title
30 |
31 | ` 32 | }) 33 | 34 | // Button 35 | addBlock('button', { 36 | label: 'Button', 37 | category: basics, 38 | attributes: { class: 'fa fa-link' }, 39 | content: { 40 | type: 'button', 41 | content: 'Button' 42 | } 43 | }) 44 | 45 | // Header 46 | addBlock('header', { 47 | label: 'Header', 48 | category: basics, 49 | attributes: { class: 'fa fa-header' }, 50 | content: { 51 | type: 'header', 52 | content: 'Insert your header text here' 53 | } 54 | }) 55 | 56 | addBlock('image', { 57 | label: 'Image', 58 | category: basics, 59 | attributes: { class: 'fa fa-picture-o' }, 60 | content: { 61 | type: 'image' 62 | } 63 | }) 64 | 65 | addBlock('link', { 66 | label: 'Link', 67 | category: basics, 68 | attributes: { class: 'fa fa-link' }, 69 | content: { 70 | type: 'link', 71 | content: 'Link' 72 | } 73 | }) 74 | 75 | addBlock('list', { 76 | label: 'List', 77 | category: basics, 78 | attributes: { class: 'fa fa-list' }, 79 | content: ` 80 |
    81 |
  • Item 1
  • 82 |
  • Item 2
  • 83 |
  • Item 3
  • 84 |
85 | ` 86 | }) 87 | 88 | addBlock('paragraph', { 89 | label: 'Paragraph', 90 | category: basics, 91 | attributes: { class: 'fa fa-align-left' }, 92 | content: { 93 | type: 'paragraph', 94 | content: 95 | 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.' 96 | } 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /src/blocks/components.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const bm = editor.BlockManager 3 | const { blocks, category } = config 4 | const { components } = category 5 | const addBlock = (name = '', attr = {}) => (blocks.indexOf(name) >= 0) ? bm.add(name, attr) : null 6 | 7 | addBlock('alert', { 8 | label: 'Alert', 9 | category: components, 10 | attributes: { class: 'fa fa-exclamation-triangle' }, 11 | content: ` 12 | 15 | ` 16 | }) 17 | 18 | // Dropdown 19 | addBlock('dropdown', { 20 | label: 'Dropdown', 21 | category: components, 22 | attributes: { class: 'fa fa-caret-square-o-down' }, 23 | content: ` 24 | 35 | ` 36 | }) 37 | 38 | // Media 39 | addBlock('media', { 40 | label: 'Media', 41 | category: components, 42 | content: ` 43 |
44 |
45 | 46 | ... 47 | 48 |
49 |
50 |

Top aligned media

51 |

Cras sit amet nibh libero, in gravida nulla. Nulla vel metus scelerisque ante sollicitudin commodo. Cras purus odio, vestibulum in vulputate at, tempus viverra turpis. Fusce condimentum nunc ac nisi vulputate fringilla. Donec lacinia congue felis in faucibus.

52 |
53 |
54 | ` 55 | }) 56 | 57 | // Panel 58 | addBlock('panel', { 59 | label: 'Panel', 60 | category: components, 61 | attributes: { class: 'fa fa-window-maximize' }, 62 | content: ` 63 |
64 |
65 |

Heading

66 |
67 |
68 |

Content

69 |
70 | 73 |
74 | ` 75 | }) 76 | 77 | // Thumbnail 78 | addBlock('thumbnail', { 79 | label: 'Thumbnail', 80 | category: components, 81 | content: ` 82 |
83 | 84 |
85 |

Thumbnail label

86 |

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.

87 |

Button

88 |
89 |
90 | ` 91 | }) 92 | 93 | // Well 94 | addBlock('well', { 95 | label: 'Well', 96 | category: components, 97 | attributes: { class: 'fa fa-square' }, 98 | content: ` 99 |
100 |

Content

101 |
102 | ` 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /src/blocks/index.js: -------------------------------------------------------------------------------- 1 | import basics from './basics' 2 | import components from './components' 3 | import layout from './layout' 4 | 5 | export default (editor, config = {}) => { 6 | basics(editor, config) 7 | components(editor, config) 8 | layout(editor, config) 9 | } 10 | -------------------------------------------------------------------------------- /src/blocks/layout.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const bm = editor.BlockManager 3 | const { blocks, category } = config 4 | const { layout } = category 5 | const addBlock = (name = '', attr = {}) => (blocks.indexOf(name) >= 0) ? bm.add(name, attr) : null 6 | 7 | // Container 8 | addBlock('container', { 9 | label: 'Container', 10 | category: layout, 11 | attributes: { class: 'fa fa-square-o' }, 12 | content: { 13 | type: 'container', 14 | classes: ['container'] 15 | } 16 | }) 17 | 18 | // Row 19 | addBlock('row', { 20 | label: 'Row', 21 | category: layout, 22 | attributes: { class: 'fa fa-minus' }, 23 | content: { 24 | type: 'row', 25 | classes: ['row'] 26 | } 27 | }) 28 | 29 | // Column 30 | addBlock('column', { 31 | label: 'Column', 32 | category: layout, 33 | attributes: { class: 'fa fa-columns' }, 34 | content: { 35 | type: 'column', 36 | classes: ['col-md-12'] 37 | } 38 | }) 39 | 40 | // Columns 2 41 | addBlock('columns-2', { 42 | label: '2 Columns', 43 | category: layout, 44 | attributes: { class: 'fa fa-columns' }, 45 | content: ` 46 |
47 |
48 |
49 |
50 | ` 51 | }) 52 | 53 | // Columns 3 54 | addBlock('columns-3', { 55 | label: '3 Columns', 56 | category: layout, 57 | attributes: { class: 'fa fa-columns' }, 58 | content: ` 59 |
60 |
61 |
62 |
63 |
64 | ` 65 | }) 66 | 67 | // Columns 4 68 | addBlock('columns-4', { 69 | label: '4 Columns', 70 | category: layout, 71 | attributes: { class: 'fa fa-columns' }, 72 | content: ` 73 |
74 |
75 |
76 |
77 |
78 |
79 | ` 80 | }) 81 | 82 | // Columns 4/8 83 | addBlock('columns-4-8', { 84 | label: '2 Columns 4/8', 85 | category: layout, 86 | attributes: { class: 'fa fa-columns' }, 87 | content: ` 88 |
89 |
90 |
91 |
92 | ` 93 | }) 94 | 95 | // Columns 8/4 96 | addBlock('columns-8-4', { 97 | label: '2 Columns 8/4', 98 | category: layout, 99 | attributes: { class: 'fa fa-columns' }, 100 | content: ` 101 |
102 |
103 |
104 |
105 | ` 106 | }) 107 | } 108 | -------------------------------------------------------------------------------- /src/components/alert.js: -------------------------------------------------------------------------------- 1 | import { capitalize } from '../utils' 2 | 3 | export default (editor, config = {}) => { 4 | const domc = editor.DomComponents 5 | const textType = domc.getType('text') 6 | const textModel = textType.model 7 | const textView = textType.view 8 | const contexts = ['success', 'info', 'warning', 'danger'] 9 | 10 | domc.addType('alert', { 11 | model: textModel.extend({ 12 | defaults: Object.assign({}, textModel.prototype.defaults, { 13 | 'custom-name': 'Alert', 14 | attributes: { 15 | role: 'alert' 16 | }, 17 | classes: ['alert'], 18 | traits: textModel.prototype.defaults.traits.concat([ 19 | { 20 | type: 'select-class', 21 | label: 'Context', 22 | options: contexts.map(context => ({ value: `alert-${context}`, name: capitalize(context) })) 23 | } 24 | ]) 25 | }) 26 | }, { 27 | isComponent (el) { 28 | if (el && el.classList && el.classList.contains('alert')) { 29 | return { type: 'alert' } 30 | } 31 | } 32 | }), 33 | view: textView 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/basic.js: -------------------------------------------------------------------------------- 1 | import { capitalize, getModel, getView } from '../utils' 2 | 3 | export default (editor, config = {}) => { 4 | const domc = editor.DomComponents 5 | let defaultModel = getModel(editor, 'default') 6 | let defaultView = getView(editor, 'default') 7 | let textModel = getModel(editor, 'text') 8 | let textView = getView(editor, 'text') 9 | let imgModel = getModel(editor, 'image') 10 | let imgView = getView(editor, 'image') 11 | let linkModel = getModel(editor, 'link') 12 | let linkView = getView(editor, 'link') 13 | 14 | const contexts = ['primary', 'success', 'info', 'warning', 'danger'] 15 | const alignments = ['left', 'center', 'right', 'justify'] 16 | const textStyles = ['lowercase', 'uppercase', 'capitalize'] 17 | const imgShapes = ['rounded', 'circle', 'thumbnail'] 18 | const sizes = [ 19 | ['lg', 'large'], 20 | ['sm', 'small'], 21 | ['xs', 'extra small'] 22 | ] 23 | 24 | domc.addType('default', { 25 | model: defaultModel.extend({ 26 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 27 | traits: defaultModel.prototype.defaults.traits.concat([ 28 | { 29 | type: 'select-class', 30 | label: 'Float', 31 | options: [ 32 | { value: '', name: 'None' }, 33 | { value: 'pull-left', name: 'Left' }, 34 | { value: 'pull-right', name: 'Right' } 35 | ] 36 | }, 37 | { 38 | type: 'select-class', 39 | label: 'Color', 40 | options: [ 41 | { value: '', name: 'None' }, 42 | ...['muted'].concat(contexts).map(context => ({ value: `text-${context}`, name: capitalize(context) })) 43 | ] 44 | }, 45 | { 46 | type: 'select-class', 47 | label: 'Background', 48 | options: [ 49 | { value: '', name: 'None' }, 50 | ...contexts.map(context => ({ value: `bg-${context}`, name: capitalize(context) })) 51 | ] 52 | } 53 | ]) 54 | }) 55 | }), 56 | view: defaultView 57 | }) 58 | 59 | defaultModel = getModel(editor, 'default') 60 | defaultView = getView(editor, 'default') 61 | 62 | domc.addType('image', { 63 | model: imgModel.extend({ 64 | defaults: Object.assign({}, imgModel.prototype.defaults, { 65 | 'custom-name': 'Image', 66 | attributes: { 67 | src: 'https://dummyimage.com/450x250/999/222' 68 | }, 69 | traits: [ 70 | { 71 | type: 'text', 72 | label: 'Source (URL)', 73 | name: 'src' 74 | }, 75 | { 76 | type: 'text', 77 | label: 'Alternate text', 78 | name: 'alt' 79 | }, 80 | { 81 | type: 'select-class', 82 | label: 'Responsive', 83 | options: [ 84 | { value: '', name: 'No' }, 85 | { value: 'img-responsive', name: 'Yes' } 86 | ] 87 | }, 88 | { 89 | type: 'select-class', 90 | label: 'Shape', 91 | options: [ 92 | { value: '', name: 'none' }, 93 | ...imgShapes.map(shape => ({ value: `img-${shape}`, name: capitalize(shape) })) 94 | ] 95 | } 96 | ] 97 | }) 98 | }, { 99 | isComponent (el) { 100 | if (el && el.tagName && el.tagName === 'IMG') { 101 | return { type: 'image' } 102 | } 103 | } 104 | }), 105 | view: imgView 106 | }) 107 | 108 | domc.addType('text', { 109 | model: defaultModel.extend({ 110 | defaults: Object.assign({}, textModel.prototype.defaults, { 111 | droppable: true, 112 | traits: defaultModel.prototype.defaults.traits.concat([ 113 | { 114 | type: 'select-class', 115 | label: 'Alignment', 116 | options: [ 117 | ...alignments.map(align => ({ value: `text-${align}`, name: capitalize(align) })), 118 | { value: 'text-nowrap', name: 'No wrap' } 119 | ] 120 | }, 121 | { 122 | type: 'select-class', 123 | label: 'Transform', 124 | options: [ 125 | { value: '', name: 'None' }, 126 | ...textStyles.map(style => ({ value: `text-${style}`, name: capitalize(style) })) 127 | ] 128 | } 129 | ]) 130 | }) 131 | }), 132 | view: textView 133 | }) 134 | 135 | textModel = getModel(editor, 'text') 136 | textView = getView(editor, 'text') 137 | 138 | domc.addType('header', { 139 | model: textModel.extend( 140 | { 141 | defaults: Object.assign({}, textModel.prototype.defaults, { 142 | 'custom-name': 'Header', 143 | tagName: 'h1', 144 | traits: textModel.prototype.defaults.traits.concat([ 145 | { 146 | type: 'select', 147 | options: [ 148 | { value: 'h1', name: 'One (largest)' }, 149 | { value: 'h2', name: 'Two' }, 150 | { value: 'h3', name: 'Three' }, 151 | { value: 'h4', name: 'Four' }, 152 | { value: 'h5', name: 'Five' }, 153 | { value: 'h6', name: 'Six (smallest)' } 154 | ], 155 | label: 'Size', 156 | name: 'tagName', 157 | changeProp: 1 158 | } 159 | ]) 160 | }) 161 | }, 162 | { 163 | isComponent (el) { 164 | if ( 165 | el && 166 | el.tagName && 167 | ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(el.tagName) 168 | ) { 169 | return { type: 'header' } 170 | } 171 | } 172 | } 173 | ), 174 | view: textView 175 | }) 176 | 177 | domc.addType('link', { 178 | model: linkModel.extend({ 179 | defaults: Object.assign({}, linkModel.prototype.defaults, { 180 | traits: [ 181 | { 182 | type: 'text', 183 | name: 'id', 184 | label: 'Id', 185 | placeholder: 'eg. Text here' 186 | }].concat(linkModel.prototype.defaults.traits, [ 187 | { 188 | type: 'select', 189 | label: 'Toggles', 190 | name: 'data-toggle', 191 | options: [ 192 | {value: '', name: 'None'}, 193 | {value: 'button', name: 'Self'}, 194 | {value: 'collapse', name: 'Collapse'}, 195 | {value: 'dropdown', name: 'Dropdown'} 196 | ], 197 | changeProp: 1 198 | } 199 | ]) 200 | }) 201 | }), 202 | view: linkView 203 | }) 204 | 205 | linkModel = getModel(editor, 'link') 206 | linkView = getView(editor, 'link') 207 | 208 | domc.addType('button', { 209 | model: linkModel.extend({ 210 | defaults: Object.assign({}, linkModel.prototype.defaults, { 211 | 'custom-name': 'Button', 212 | attributes: { 213 | role: 'button' 214 | }, 215 | classes: ['btn'], 216 | traits: linkModel.prototype.defaults.traits.concat([ 217 | { 218 | type: 'select-class', 219 | label: 'Context', 220 | options: [ 221 | { value: 'btn-default', name: 'Default' }, 222 | ...contexts.map(context => ({ value: `btn-${context}`, name: capitalize(context) })) 223 | ] 224 | }, 225 | { 226 | type: 'select-class', 227 | label: 'Size', 228 | options: [ 229 | { value: '', name: 'Default' }, 230 | ...sizes.map(size => ({ value: `btn-${size[0]}`, name: capitalize(size[1]) })) 231 | ] 232 | }, 233 | { 234 | type: 'select-class', 235 | label: 'Width', 236 | options: [ 237 | { value: '', name: 'Inline' }, 238 | { value: 'btn-block', name: 'Block' } 239 | ] 240 | } 241 | ]) 242 | }, { 243 | isComponent (el) { 244 | if (el && el.classList && el.classList.contains('btn')) { 245 | return { type: 'button' } 246 | } 247 | } 248 | }) 249 | }), 250 | view: linkView 251 | }) 252 | 253 | domc.addType('list', { 254 | model: defaultModel.extend({ 255 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 256 | 'custom-name': 'List', 257 | droppable: true, 258 | traits: defaultModel.prototype.defaults.traits.concat([ 259 | { 260 | type: 'select', 261 | label: 'Type', 262 | name: 'tagName', 263 | options: [ 264 | { value: 'ul', name: 'Unordered' }, 265 | { value: 'ol', name: 'Ordered' } 266 | ], 267 | changeProp: 1 268 | }, 269 | { 270 | type: 'select-class', 271 | label: 'Style', 272 | options: [ 273 | { value: '', name: 'none' }, 274 | { value: 'list-unstyled', name: 'Unstyled' }, 275 | { value: 'list-inline', name: 'Inline' } 276 | ] 277 | } 278 | ]) 279 | }) 280 | }, { 281 | isComponent (el) { 282 | if (el && el.tagName && (el.tagName === 'UL' || el.tagName === 'OL')) { 283 | return { type: 'list' } 284 | } 285 | } 286 | }), 287 | view: defaultView 288 | }) 289 | 290 | domc.addType('list-item', { 291 | model: textModel.extend({ 292 | defaults: Object.assign({}, textModel.prototype.defaults, { 293 | 'custom-name': 'Item', 294 | tagName: 'li', 295 | draggable: 'ul, ol' 296 | }) 297 | }, { 298 | isComponent (el) { 299 | if (el && el.tagName && el.tagName === 'LI') { 300 | return { type: 'list-item' } 301 | } 302 | } 303 | }), 304 | view: textView 305 | }) 306 | 307 | domc.addType('paragraph', { 308 | model: textModel.extend({ 309 | defaults: Object.assign({}, textModel.prototype.defaults, { 310 | 'custom-name': 'Paragraph', 311 | tagName: 'p', 312 | traits: textModel.prototype.defaults.traits.concat([ 313 | { 314 | type: 'select-class', 315 | label: 'Lead', 316 | options: [ 317 | { value: '', name: 'No' }, 318 | { value: 'lead', name: 'Yes' } 319 | ] 320 | } 321 | ]) 322 | }) 323 | }, { 324 | isComponent (el) { 325 | if (el && el.tagName && el.tagName === 'P') { 326 | return { type: 'paragraph' } 327 | } 328 | } 329 | }), 330 | view: textView 331 | }) 332 | 333 | domc.addType('blockquote', { 334 | model: defaultModel.extend({ 335 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 336 | tagName: 'blockquote', 337 | traits: defaultModel.prototype.defaults.traits.concat([ 338 | { 339 | type: 'select-class', 340 | label: 'Reversed', 341 | options: [ 342 | { value: '', name: 'No' }, 343 | { value: 'blockquote-reverse', name: 'Yes' } 344 | ] 345 | } 346 | ]) 347 | }) 348 | }, { 349 | isComponent (el) { 350 | if (el && el.tagName && el.tagName === 'BLOCKQUOTE') { 351 | return { type: 'blockquote' } 352 | } 353 | } 354 | }), 355 | view: defaultView 356 | }) 357 | } 358 | -------------------------------------------------------------------------------- /src/components/container.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const domc = editor.DomComponents 3 | const defaultType = domc.getType('default') 4 | const defaultModel = defaultType.model 5 | const defaultView = defaultType.view 6 | 7 | domc.addType('container', { 8 | model: defaultModel.extend({ 9 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 10 | 'custom-name': 'Container', 11 | tagName: 'div', 12 | droppable: true, 13 | traits: defaultModel.prototype.defaults.traits.concat([ 14 | { 15 | type: 'select-class', 16 | label: 'Type', 17 | options: [ 18 | { value: 'container', name: 'Fixed' }, 19 | { value: 'container-fluid', name: 'Fluid' } 20 | ] 21 | } 22 | ]) 23 | }) 24 | }, { 25 | isComponent (el) { 26 | if (el && el.classList && (el.classList.contains('container') || el.classList.contains('container-fluid'))) { 27 | return { type: 'container' } 28 | } 29 | } 30 | }), 31 | view: defaultView 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /src/components/dropdown.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const domc = editor.DomComponents 3 | const defaultType = domc.getType('default') 4 | const defaultModel = defaultType.model 5 | const defaultView = defaultType.view 6 | const textType = domc.getType('text') 7 | const textModel = textType.model 8 | const textView = textType.view 9 | const linkType = domc.getType('link') 10 | const linkModel = linkType.model 11 | const listItemType = domc.getType('list-item') 12 | const listItemModel = listItemType.model 13 | const listItemView = listItemType.view 14 | 15 | domc.addType('dropdown', { 16 | model: defaultModel.extend({ 17 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 18 | 'custom-name': 'Dropdown', 19 | droppable: 'a, button, .dropdown-menu', 20 | traits: defaultModel.prototype.defaults.traits.concat([ 21 | { 22 | type: 'select-class', 23 | label: 'State', 24 | options: [ 25 | { value: '', name: 'Closed' }, 26 | { value: 'open', name: 'Open' } 27 | ] 28 | } 29 | ]) 30 | }) 31 | }, { 32 | isComponent (el) { 33 | if (el && el.classList && el.classList.contains('dropdown')) { 34 | return { type: 'dropdown' } 35 | } 36 | } 37 | }), 38 | view: defaultView 39 | }) 40 | 41 | domc.addType('dropdown-toggle', { 42 | model: textModel.extend({ 43 | defaults: Object.assign({}, textModel.prototype.defaults, { 44 | 'custom-name': 'Dropdown Toggle', 45 | draggable: '.dropdown', 46 | droppable: true, 47 | traits: [ 48 | { 49 | type: 'checkbox', 50 | name: 'aria-haspopup', 51 | label: 'Popup' 52 | }, 53 | { 54 | type: 'checkbox', 55 | name: 'aria-expanded', 56 | label: 'Expanded' 57 | }, 58 | { 59 | type: 'select', 60 | label: 'Type', 61 | name: 'tagName', 62 | options: [ 63 | { value: 'button', name: 'Button' }, 64 | { value: 'a', name: 'Link' } 65 | ], 66 | changeProp: 1 67 | } 68 | ] 69 | }), 70 | init () { 71 | this.listenTo(this, 'change:tagName', this.changeTag) 72 | }, 73 | changeTag (el) { 74 | const attrs = this.get('attributes') 75 | const traits = this.get('traits') 76 | const traitsToKeep = ['tagName', 'aria-haspopup', 'aria-expanded'] 77 | 78 | traits.models = traits.models.filter(model => traitsToKeep.indexOf(model.get('name')) >= 0) 79 | 80 | if (this.get('tagName') === 'a') { 81 | traits.add(linkModel.prototype.defaults.traits) 82 | } else { 83 | if (attrs.href) delete attrs.href 84 | } 85 | 86 | this.set('attributes', Object.assign({}, attrs)) 87 | this.sm.trigger('change:selectedComponent') 88 | } 89 | }, { 90 | isComponent (el) { 91 | if (el && el.classList && el.classList.contains('dropdown-toggle')) { 92 | return { type: 'dropdown-toggle' } 93 | } 94 | } 95 | }), 96 | view: textView 97 | }) 98 | 99 | domc.addType('dropdown-menu', { 100 | model: defaultModel.extend({ 101 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 102 | 'custom-name': 'Dropdown Menu', 103 | draggable: '.dropdown, .btn-group', 104 | droppable: 'li' 105 | }) 106 | }, { 107 | isComponent (el) { 108 | if (el && el.classList && el.classList.contains('dropdown-menu')) { 109 | return { type: 'dropdown-menu' } 110 | } 111 | } 112 | }), 113 | view: defaultView 114 | }) 115 | 116 | domc.addType('dropdown-item', { 117 | model: listItemModel.extend({ 118 | defaults: Object.assign({}, listItemModel.prototype.defaults, { 119 | 'custom-name': 'Dropdown Item', 120 | draggable: '.dropdown-menu' 121 | }) 122 | }, { 123 | isComponent (el) { 124 | const parent = el.parentNode 125 | if (parent && parent.classList && parent.classList.contains('dropdown-menu')) { 126 | return { type: 'dropdown-item' } 127 | } 128 | } 129 | }), 130 | view: listItemView 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /src/components/grid.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const domc = editor.DomComponents 3 | const defaultType = domc.getType('default') 4 | const defaultModel = defaultType.model 5 | const defaultView = defaultType.view 6 | const cols = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 7 | 8 | domc.addType('row', { 9 | model: defaultModel.extend({ 10 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 11 | 'custom-name': 'Row', 12 | tagName: 'div', 13 | draggable: '.container, .container-fluid', 14 | droppable: '[class*="col-xs"], [class*="col-sm"], [class*="col-md"], [class*="col-lg"]' 15 | }) 16 | }, { 17 | isComponent (el) { 18 | if (el && el.classList && el.classList.contains('row')) { 19 | return { type: 'row' } 20 | } 21 | } 22 | }), 23 | view: defaultView 24 | }) 25 | 26 | domc.addType('column', { 27 | model: defaultModel.extend({ 28 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 29 | 'custom-name': 'Column', 30 | draggable: '.row', 31 | droppable: true, 32 | traits: defaultModel.prototype.defaults.traits.concat([{ 33 | type: 'select-class', 34 | options: [ 35 | {value: '', name: 'None'}, 36 | ...cols.map((i) => ({ value: `col-xs-${i}`, name: `${i}/12` })) 37 | ], 38 | label: 'XS size' 39 | }, { 40 | type: 'select-class', 41 | options: [ 42 | {value: '', name: 'None'}, 43 | ...cols.map((i) => ({ value: `col-sm-${i}`, name: `${i}/12` })) 44 | ], 45 | label: 'SM size' 46 | }, { 47 | type: 'select-class', 48 | options: [ 49 | {value: '', name: 'None'}, 50 | ...cols.map((i) => ({ value: `col-md-${i}`, name: `${i}/12` })) 51 | ], 52 | label: 'MD size' 53 | }, { 54 | type: 'select-class', 55 | options: [ 56 | {value: '', name: 'None'}, 57 | ...cols.map((i) => ({ value: `col-lg-${i}`, name: `${i}/12` })) 58 | ], 59 | label: 'LG size' 60 | }, { 61 | type: 'select-class', 62 | options: [ 63 | {value: '', name: 'None'}, 64 | ...cols.map((i) => ({ value: `col-xs-offset-${i}`, name: `${i}/12` })) 65 | ], 66 | label: 'XS offset' 67 | }, { 68 | type: 'select-class', 69 | options: [ 70 | {value: '', name: 'None'}, 71 | ...cols.map((i) => ({ value: `col-sm-offset-${i}`, name: `${i}/12` })) 72 | ], 73 | label: 'SM offset' 74 | }, { 75 | type: 'select-class', 76 | options: [ 77 | {value: '', name: 'None'}, 78 | ...cols.map((i) => ({ value: `col-md-offset-${i}`, name: `${i}/12` })) 79 | ], 80 | label: 'MD offset' 81 | }, { 82 | type: 'select-class', 83 | options: [ 84 | {value: '', name: 'None'}, 85 | ...cols.map((i) => ({ value: `col-lg-offset-${i}`, name: `${i}/12` })) 86 | ], 87 | label: 'LG offset' 88 | }]) 89 | }) 90 | }, { 91 | isComponent (el) { 92 | if (el && el.className && el.className.match(/col-(xs|sm|md|lg)-\d+/)) { 93 | return { type: 'column' } 94 | } 95 | } 96 | }), 97 | view: defaultView 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import alert from './alert' 2 | import basic from './basic' 3 | import container from './container' 4 | import dropdown from './dropdown' 5 | import grid from './grid' 6 | import label from './label' 7 | import media from './media' 8 | import panel from './panel' 9 | import thumbnail from './thumbnail' 10 | import well from './well' 11 | 12 | export default (editor, config = {}) => { 13 | alert(editor, config) 14 | basic(editor, config) 15 | container(editor, config) 16 | dropdown(editor, config) 17 | grid(editor, config) 18 | label(editor, config) 19 | media(editor, config) 20 | panel(editor, config) 21 | thumbnail(editor, config) 22 | well(editor, config) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/label.js: -------------------------------------------------------------------------------- 1 | import { capitalize } from '../utils' 2 | 3 | export default (editor, config = {}) => { 4 | const domc = editor.DomComponents 5 | const textType = domc.getType('text') 6 | const textModel = textType.model 7 | const textView = textType.view 8 | const contexts = ['default', 'primary', 'success', 'info', 'warning', 'danger'] 9 | 10 | domc.addType('label', { 11 | model: textModel.extend({ 12 | defaults: Object.assign({}, textModel.prototype.defaults, { 13 | 'custom-name': 'Label', 14 | classes: ['label'], 15 | traits: [ 16 | { 17 | type: 'select-class', 18 | label: 'Context', 19 | options: contexts.map(context => ({ value: `label-${context}`, name: capitalize(context) })) 20 | } 21 | ] 22 | }) 23 | }, { 24 | isComponent (el) { 25 | if (el && el.classList && el.classList.contains('label')) { 26 | return { type: 'label' } 27 | } 28 | } 29 | }), 30 | view: textView 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/media.js: -------------------------------------------------------------------------------- 1 | import { capitalize } from '../utils' 2 | 3 | export default (editor, config = {}) => { 4 | const domc = editor.DomComponents 5 | const defaultType = domc.getType('default') 6 | const defaultModel = defaultType.model 7 | const defaultView = defaultType.view 8 | const headerType = domc.getType('header') 9 | const headerModel = headerType.model 10 | const headerView = headerType.view 11 | const sides = ['left', 'right'] 12 | const positions = ['top', 'middle', 'bottom'] 13 | 14 | domc.addType('media', { 15 | model: defaultModel.extend({ 16 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 17 | 'custom-name': 'Media', 18 | classes: ['media'], 19 | droppable: '.media-left, .media-right, .media-body' 20 | }) 21 | }, { 22 | isComponent (el) { 23 | if (el && el.classList && el.classList.contains('media')) { 24 | return { type: 'media' } 25 | } 26 | } 27 | }), 28 | view: defaultView 29 | }) 30 | 31 | domc.addType('media-side', { 32 | model: defaultModel.extend({ 33 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 34 | 'custom-name': 'Media Side', 35 | draggable: '.media', 36 | traits: [ 37 | { 38 | type: 'select-class', 39 | label: 'Side', 40 | options: sides.map(side => ({ value: `media-${side}`, name: capitalize(side) })) 41 | }, 42 | { 43 | type: 'select-class', 44 | label: 'Position', 45 | options: positions.map(position => ({ value: `media-${position}`, name: capitalize(position) })) 46 | } 47 | ] 48 | }) 49 | }, { 50 | isComponent (el) { 51 | if (el && el.className && el.className.match(/media-(left|right)/)) { 52 | return { type: 'media-side' } 53 | } 54 | } 55 | }), 56 | view: defaultView 57 | }) 58 | 59 | domc.addType('media-body', { 60 | model: defaultModel.extend({ 61 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 62 | 'custom-name': 'Media Body', 63 | draggable: '.media' 64 | }) 65 | }, { 66 | isComponent (el) { 67 | if (el && el.classList && el.classList.contains('media-body')) { 68 | return { type: 'media-body' } 69 | } 70 | } 71 | }), 72 | view: defaultView 73 | }) 74 | 75 | domc.addType('media-heading', { 76 | model: headerModel.extend({ 77 | defaults: Object.assign({}, headerModel.prototype.defaults, { 78 | 'custom-name': 'Media Heading', 79 | draggable: '.media-body' 80 | }) 81 | }, { 82 | isComponent (el) { 83 | if (el && el.classList && el.classList.contains('media-heading')) { 84 | return { type: 'media-heading' } 85 | } 86 | } 87 | }), 88 | view: headerView 89 | }) 90 | } 91 | -------------------------------------------------------------------------------- /src/components/panel.js: -------------------------------------------------------------------------------- 1 | import { capitalize } from '../utils' 2 | 3 | export default (editor, config = {}) => { 4 | const domc = editor.DomComponents 5 | const defaultType = domc.getType('default') 6 | const defaultModel = defaultType.model 7 | const defaultView = defaultType.view 8 | const headerType = domc.getType('header') 9 | const headerModel = headerType.model 10 | const headerView = headerType.view 11 | const contextList = ['default', 'primary', 'success', 'info', 'warning', 'danger'] 12 | 13 | domc.addType('panel', { 14 | model: defaultModel.extend({ 15 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 16 | 'custom-name': 'Panel', 17 | droppable: '.panel-heading, .panel-body, .panel-footer, .table, .list-group', 18 | traits: defaultModel.prototype.defaults.traits.concat([{ 19 | type: 'select-class', 20 | label: 'Context', 21 | name: 'context', 22 | options: contextList.map(val => 23 | ({ value: `panel-${val}`, name: capitalize(val) })) 24 | }]) 25 | }) 26 | }, { 27 | isComponent (el) { 28 | if (el && el.classList && el.classList.contains('panel')) { 29 | return { type: 'panel' } 30 | } 31 | } 32 | }), 33 | view: defaultView 34 | }) 35 | 36 | domc.addType('panel-body', { 37 | model: defaultModel.extend({ 38 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 39 | 'custom-name': 'Panel Body', 40 | draggable: '.panel' 41 | }) 42 | }, { 43 | isComponent (el) { 44 | if (el && el.classList && el.classList.contains('panel-body')) { 45 | return { type: 'panel-body' } 46 | } 47 | } 48 | }), 49 | view: defaultView 50 | }) 51 | 52 | domc.addType('panel-footer', { 53 | model: defaultModel.extend({ 54 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 55 | 'custom-name': 'Panel Footer', 56 | draggable: '.panel' 57 | }) 58 | }, { 59 | isComponent (el) { 60 | if (el && el.classList && el.classList.contains('panel-footer')) { 61 | return { type: 'panel-footer' } 62 | } 63 | } 64 | }), 65 | view: defaultView 66 | }) 67 | 68 | domc.addType('panel-heading', { 69 | model: defaultModel.extend({ 70 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 71 | 'custom-name': 'Panel Heading', 72 | draggable: '.panel' 73 | }) 74 | }, { 75 | isComponent (el) { 76 | if (el && el.classList && el.classList.contains('panel-heading')) { 77 | return { type: 'panel-heading' } 78 | } 79 | } 80 | }), 81 | view: defaultView 82 | }) 83 | 84 | domc.addType('panel-title', { 85 | model: headerModel.extend({ 86 | defaults: Object.assign({}, headerModel.prototype.defaults, { 87 | 'custom-name': 'Panel Title', 88 | draggable: '.panel-heading' 89 | }) 90 | }, { 91 | isComponent (el) { 92 | if (el && el.classList && el.classList.contains('panel-title')) { 93 | return { type: 'panel-title' } 94 | } 95 | } 96 | }), 97 | view: headerView 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /src/components/thumbnail.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const domc = editor.DomComponents 3 | const defaultType = domc.getType('default') 4 | const defaultModel = defaultType.model 5 | const defaultView = defaultType.view 6 | 7 | domc.addType('thumbnail', { 8 | model: defaultModel.extend({ 9 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 10 | 'custom-name': 'Thumbnail', 11 | droppable: 'img, .caption' 12 | }) 13 | }, { 14 | isComponent (el) { 15 | if (el && el.classList && el.classList.contains('thumbnail')) { 16 | return { type: 'thumbnail' } 17 | } 18 | } 19 | }), 20 | view: defaultView 21 | }) 22 | 23 | domc.addType('thumbnail-caption', { 24 | model: defaultModel.extend({ 25 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 26 | 'custom-name': 'Thumbnail Caption', 27 | draggable: '.thumbnail' 28 | }) 29 | }, { 30 | isComponent (el) { 31 | if (el && el.classList && el.classList.contains('caption') && el.parentNode.classList.contains('thumbnail')) { 32 | return { type: 'thumbnail-caption' } 33 | } 34 | } 35 | }), 36 | view: defaultView 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/components/well.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const domc = editor.DomComponents 3 | const defaultType = domc.getType('default') 4 | const defaultModel = defaultType.model 5 | const defaultView = defaultType.view 6 | 7 | domc.addType('well', { 8 | model: defaultModel.extend({ 9 | defaults: Object.assign({}, defaultModel.prototype.defaults, { 10 | 'custom-name': 'Well', 11 | traits: defaultModel.prototype.defaults.traits.concat([{ 12 | type: 'select-class', 13 | label: 'Size', 14 | name: 'size', 15 | options: [ 16 | { value: '', name: 'Default' }, 17 | { value: 'well-sm', name: 'Small' }, 18 | { value: 'well-lg', name: 'Large' } 19 | ] 20 | }]) 21 | }) 22 | }, { 23 | isComponent (el) { 24 | if (el && el.classList && el.classList.contains('well')) { 25 | return { type: 'well' } 26 | } 27 | } 28 | }), 29 | view: defaultView 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | addBasicStyle: true, 3 | blocks: [ 4 | 'address', 'alert', 'blockquote', 'button', 'container', 'column', 'columns-2', 'columns-3', 'columns-4', 'columns-4-8', 5 | 'columns-8-4', 'dropdown', 'header', 'image', 'label', 'link', 'list', 'media', 'panel', 'paragraph', 'row', 'thumbnail', 6 | 'well' 7 | ], 8 | category: { 9 | basics: 'Basics', 10 | components: 'Components', 11 | layout: 'Layout' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/devices.js: -------------------------------------------------------------------------------- 1 | export default (editor, config = {}) => { 2 | const deviceManager = editor.DeviceManager 3 | deviceManager.add('Extra Small', '575px') 4 | deviceManager.add('Small', '767px') 5 | deviceManager.add('Medium', '991px') 6 | deviceManager.add('Large', '1199px') 7 | deviceManager.add('Extra Large', '100%') 8 | 9 | const panels = editor.Panels 10 | const commands = editor.Commands 11 | const panelDevices = panels.addPanel({ id: 'devices-buttons' }) 12 | const deviceBtns = panelDevices.get('buttons') 13 | 14 | deviceBtns.add([ 15 | { 16 | id: 'deviceXl', 17 | command: 'set-device-xl', 18 | className: 'fa fa-desktop', 19 | text: 'XL', 20 | attributes: { title: 'Extra Large' }, 21 | active: 1 22 | }, 23 | { 24 | id: 'deviceLg', 25 | command: 'set-device-lg', 26 | className: 'fa fa-desktop', 27 | attributes: { title: 'Large' } 28 | }, 29 | { 30 | id: 'deviceMd', 31 | command: 'set-device-md', 32 | className: 'fa fa-tablet', 33 | attributes: { title: 'Medium' } 34 | }, 35 | { 36 | id: 'deviceSm', 37 | command: 'set-device-sm', 38 | className: 'fa fa-mobile', 39 | attributes: { title: 'Small' } 40 | }, 41 | { 42 | id: 'deviceXs', 43 | command: 'set-device-xs', 44 | className: 'fa fa-mobile', 45 | attributes: { title: 'Extra Small' } 46 | } 47 | ]) 48 | 49 | commands.add('set-device-xs', { 50 | run: function (editor) { 51 | editor.setDevice('Extra Small') 52 | } 53 | }) 54 | commands.add('set-device-sm', { 55 | run: function (editor) { 56 | editor.setDevice('Small') 57 | } 58 | }) 59 | commands.add('set-device-md', { 60 | run: function (editor) { 61 | editor.setDevice('Medium') 62 | } 63 | }) 64 | commands.add('set-device-lg', { 65 | run: function (editor) { 66 | editor.setDevice('Large') 67 | } 68 | }) 69 | commands.add('set-device-xl', { 70 | run: function (editor) { 71 | editor.setDevice('Extra Large') 72 | } 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import grapesjs from 'grapesjs' 2 | import constants from './constants' 3 | import loadComponents from './components' 4 | import loadBlocks from './blocks' 5 | import loadDevices from './devices' 6 | import loadTraits from './traits' 7 | 8 | export default grapesjs.plugins.add('grapesjs-plugin-bootstrap', (editor, opts = {}) => { 9 | const options = { ...constants, ...opts } 10 | 11 | if (options.addBasicStyle) { 12 | editor.addComponents(` 13 | 21 | `) 22 | } 23 | 24 | // Add components 25 | loadComponents(editor, options) 26 | 27 | // Add traits 28 | loadTraits(editor, options) 29 | 30 | // Add blocks 31 | loadBlocks(editor, options) 32 | 33 | // Add devices 34 | loadDevices(editor, options) 35 | }) 36 | -------------------------------------------------------------------------------- /src/traits/index.js: -------------------------------------------------------------------------------- 1 | import selectClass from './selectClass' 2 | 3 | export default function (editor, config = {}) { 4 | selectClass(editor, config) 5 | } 6 | -------------------------------------------------------------------------------- /src/traits/selectClass.js: -------------------------------------------------------------------------------- 1 | export default function (editor, config = {}) { 2 | const trm = editor.TraitManager 3 | 4 | trm.addType('select-class', { 5 | events: { 6 | 'change': 'onChange' 7 | }, 8 | 9 | onValueChange: function () { 10 | let classes = this.model.get('options').map(opt => opt.value) 11 | for (let i = 0; i < classes.length; i++) { 12 | if (classes[i].length > 0) { 13 | let classesN = classes[i].split(' ') 14 | for (let j = 0; j < classesN.length; j++) { 15 | if (classesN[j].length > 0) { 16 | this.target.removeClass(classesN[j]) 17 | } 18 | } 19 | } 20 | } 21 | const value = this.model.get('value') 22 | if (value.length > 0 && value !== 'GJS_NO_CLASS') { 23 | const valueN = value.split(' ') 24 | for (let i = 0; i < valueN.length; i++) { 25 | this.target.addClass(valueN[i]) 26 | } 27 | } 28 | this.target.em.trigger('change:selectedComponent') 29 | }, 30 | 31 | getInputEl: function () { 32 | if (!this.inputEl) { 33 | const model = this.model 34 | const options = model.get('options') || [] 35 | const input = document.createElement('select') 36 | const targetViewEl = this.target.view.el 37 | 38 | for (let i = 0; i < options.length; i++) { 39 | let name = options[i].name 40 | let value = options[i].value 41 | 42 | if (value === '') { 43 | value = 'GJS_NO_CLASS' 44 | } 45 | 46 | const option = document.createElement('option') 47 | option.text = name 48 | option.value = value 49 | 50 | if (targetViewEl.classList.contains(value)) { 51 | option.setAttribute('selected', 'selected') 52 | } 53 | input.append(option) 54 | } 55 | 56 | this.inputEl = input 57 | } 58 | return this.inputEl 59 | } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1) 2 | 3 | export const getModel = (editor, type) => editor.DomComponents.getType(type).model 4 | 5 | export const getView = (editor, type) => editor.DomComponents.getType(type).view 6 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const pkg = require('./package.json'); 3 | const webpack = require('webpack'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const name = pkg.name; 7 | let plugins = []; 8 | 9 | module.exports = (env = {}) => { 10 | const isProd = env.production; 11 | 12 | if (isProd) { 13 | plugins = [ 14 | new webpack.BannerPlugin(`${name} - ${pkg.version}`), 15 | ] 16 | } else { 17 | const index = 'index.html'; 18 | const indexDev = '_' + index; 19 | plugins.push(new HtmlWebpackPlugin({ 20 | template: fs.existsSync(indexDev) ? indexDev : index, 21 | inject: false, 22 | })); 23 | } 24 | 25 | return { 26 | entry: './src', 27 | mode: isProd ? 'production' : 'development', 28 | devtool: isProd ? 'source-map' : 'cheap-module-eval-source-map', 29 | output: { 30 | path: path.resolve(__dirname), 31 | filename: `dist/${name}.min.js`, 32 | library: name, 33 | libraryTarget: 'umd', 34 | }, 35 | module: { 36 | rules: [{ 37 | test: /\.js$/, 38 | loader: 'babel-loader', 39 | include: /src/, 40 | }], 41 | }, 42 | externals: {'grapesjs': 'grapesjs'}, 43 | plugins: plugins, 44 | }; 45 | } 46 | --------------------------------------------------------------------------------