mirror of https://github.com/artf/grapesjs.git
25 changed files with 984 additions and 832 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,334 @@ |
|||
require(['config/require-config'], function() { |
|||
|
|||
require(['grapesjs/main'],function (grapesjs){ |
|||
|
|||
var editor = grapesjs.init( |
|||
|
|||
{ |
|||
noticeOnUnload: 0, |
|||
container : '#gjs', |
|||
height: '100%', |
|||
fromElement: true, |
|||
/* |
|||
components: [{ |
|||
type: 'text', |
|||
style:{ |
|||
width:'100px', |
|||
height:'100px', |
|||
margin: '50px auto', |
|||
}, |
|||
traits: ['title'], |
|||
components: [{ |
|||
type: 'textnode', |
|||
content: 'text node row', |
|||
},{ |
|||
type: 'textnode', |
|||
content: ', another text node', |
|||
},{ |
|||
type: 'link', |
|||
content: 'someLink', |
|||
},{ |
|||
type: 'textnode', |
|||
content: " More text node --- ", |
|||
}], |
|||
}],*/ |
|||
|
|||
storageManager:{ |
|||
autoload: 0, |
|||
storeComponents: 1, |
|||
storeStyles: 1, |
|||
}, |
|||
commands: { |
|||
defaults : [{ |
|||
id: 'open-github', |
|||
run: function(editor, sender){ |
|||
sender.set('active',false); |
|||
window.open('https://github.com/artf/grapesjs','_blank'); |
|||
} |
|||
},{ |
|||
id: 'undo', |
|||
run: function(editor, sender){ |
|||
sender.set('active',false); |
|||
editor.UndoManager.undo(true); |
|||
} |
|||
},{ |
|||
id: 'redo', |
|||
run: function(editor, sender){ |
|||
sender.set('active',false); |
|||
editor.UndoManager.redo(true); |
|||
} |
|||
},{ |
|||
id: 'clean-all', |
|||
run: function(editor, sender){ |
|||
sender.set('active',false); |
|||
if(confirm('Are you sure to clean the canvas?')){ |
|||
var comps = editor.DomComponents.clear(); |
|||
} |
|||
} |
|||
}], |
|||
}, |
|||
|
|||
assetManager: { |
|||
storageType : '', |
|||
storeOnChange : true, |
|||
storeAfterUpload : true, |
|||
assets : [ |
|||
{ type: 'image', src : 'http://placehold.it/350x250/78c5d6/fff/image1.jpg', height:350, width:250}, |
|||
{ type: 'image', src : 'http://placehold.it/350x250/459ba8/fff/image2.jpg', height:350, width:250}, |
|||
{ type: 'image', src : 'http://placehold.it/350x250/79c267/fff/image3.jpg', height:350, width:250}, |
|||
{ type: 'image', src : 'http://placehold.it/350x250/c5d647/fff/image4.jpg', height:350, width:250}, |
|||
{ type: 'image', src : 'http://placehold.it/350x250/f28c33/fff/image5.jpg', height:350, width:250}, |
|||
{ type: 'image', src : 'http://placehold.it/350x250/e868a2/fff/image6.jpg', height:350, width:250}, |
|||
{ type: 'image', src : 'http://placehold.it/350x250/cc4360/fff/image7.jpg', height:350, width:250}, |
|||
{ type: 'image', src : './img/work-desk.jpg', date: '2015-02-01',height:1080, width:1728}, |
|||
{ type: 'image', src : './img/phone-app.png', date: '2015-02-01',height:650, width:320}, |
|||
{ type: 'image', src : './img/bg-gr-v.png', date: '2015-02-01',height:1, width:1728}, |
|||
] |
|||
}, |
|||
|
|||
|
|||
styleManager : { |
|||
sectors: [{ |
|||
name: 'General', |
|||
open: false, |
|||
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'], |
|||
},{ |
|||
name: 'Dimension', |
|||
open: false, |
|||
buildProps: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'], |
|||
},{ |
|||
name: 'Typography', |
|||
open: false, |
|||
buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing', 'color', 'line-height', 'text-align', 'text-shadow'], |
|||
properties: [{ |
|||
property: 'text-align', |
|||
list : [ |
|||
{value: 'left', className: 'fa fa-align-left'}, |
|||
{value: 'center', className: 'fa fa-align-center' }, |
|||
{value: 'right', className: 'fa fa-align-right'}, |
|||
{value: 'justify', className: 'fa fa-align-justify'} |
|||
], |
|||
}] |
|||
},{ |
|||
name: 'Decorations', |
|||
open: false, |
|||
buildProps: ['border-radius-c', 'background-color', 'border-radius', 'border', 'box-shadow', 'background'], |
|||
},{ |
|||
name: 'Extra', |
|||
open: false, |
|||
buildProps: ['transition', 'perspective', 'transform'], |
|||
},{ |
|||
name: 'Dimension', |
|||
open: false, |
|||
buildProps: ['margin'], |
|||
properties:[{ |
|||
name: 'Marginnnn', |
|||
property: 'margin', |
|||
type: 'composite', |
|||
properties:[{ |
|||
name: 'Top', |
|||
property: 'margin-top', |
|||
},{ |
|||
name: 'Right', |
|||
property: 'margin-right', |
|||
},{ |
|||
name: 'Bottom', |
|||
property: 'margin-bottom', |
|||
},{ |
|||
name: 'Left', |
|||
property: 'margin-left', |
|||
},], |
|||
}/*{ |
|||
name : 'Center blocksss', |
|||
property : 'margins', |
|||
type : 'select', |
|||
defaults : '0', |
|||
list : [{ |
|||
value : '0', |
|||
name : 'Normal', |
|||
},{ |
|||
value : '0 auto', |
|||
name : 'Center', |
|||
}], |
|||
}*/], |
|||
},{ |
|||
name: 'Flex', |
|||
open: false, |
|||
properties: [{ |
|||
name : 'Flex Container', |
|||
property : 'display', |
|||
type : 'select', |
|||
defaults : 'block', |
|||
list : [{ |
|||
value : 'block', |
|||
name : 'Disable', |
|||
},{ |
|||
value : 'flex', |
|||
name : 'Enable', |
|||
}], |
|||
},{ |
|||
name: 'Flex Parent', |
|||
property: 'label-parent-flex', |
|||
},{ |
|||
name : 'Direction', |
|||
property : 'flex-direction', |
|||
type : 'radio', |
|||
defaults : 'row', |
|||
list : [{ |
|||
value : 'row', |
|||
name : 'Row', |
|||
className : 'icons-flex icon-dir-row', |
|||
title : 'Row', |
|||
},{ |
|||
value : 'row-reverse', |
|||
name : 'Row reverse', |
|||
className : 'icons-flex icon-dir-row-rev', |
|||
title : 'Row reverse', |
|||
},{ |
|||
value : 'column', |
|||
name : 'Column', |
|||
title : 'Column', |
|||
className : 'icons-flex icon-dir-col', |
|||
},{ |
|||
value : 'column-reverse', |
|||
name : 'Column reverse', |
|||
title : 'Column reverse', |
|||
className : 'icons-flex icon-dir-col-rev', |
|||
}], |
|||
},{ |
|||
name : 'Wrap', |
|||
property : 'flex-wrap', |
|||
type : 'radio', |
|||
defaults : 'nowrap', |
|||
list : [{ |
|||
value : 'nowrap', |
|||
title : 'Single line', |
|||
},{ |
|||
value : 'wrap', |
|||
title : 'Multiple lines', |
|||
},{ |
|||
value : 'wrap-reverse', |
|||
title : 'Multiple lines reverse', |
|||
}], |
|||
},{ |
|||
name : 'Justify', |
|||
property : 'justify-content', |
|||
type : 'radio', |
|||
defaults : 'flex-start', |
|||
list : [{ |
|||
value : 'flex-start', |
|||
className : 'icons-flex icon-just-start', |
|||
title : 'Start', |
|||
},{ |
|||
value : 'flex-end', |
|||
title : 'End', |
|||
className : 'icons-flex icon-just-end', |
|||
},{ |
|||
value : 'space-between', |
|||
title : 'Space between', |
|||
className : 'icons-flex icon-just-sp-bet', |
|||
},{ |
|||
value : 'space-around', |
|||
title : 'Space around', |
|||
className : 'icons-flex icon-just-sp-ar', |
|||
},{ |
|||
value : 'center', |
|||
title : 'Center', |
|||
className : 'icons-flex icon-just-sp-cent', |
|||
}], |
|||
},{ |
|||
name : 'Align', |
|||
property : 'align-items', |
|||
type : 'radio', |
|||
defaults : 'center', |
|||
list : [{ |
|||
value : 'flex-start', |
|||
title : 'Start', |
|||
className : 'icons-flex icon-al-start', |
|||
},{ |
|||
value : 'flex-end', |
|||
title : 'End', |
|||
className : 'icons-flex icon-al-end', |
|||
},{ |
|||
value : 'stretch', |
|||
title : 'Stretch', |
|||
className : 'icons-flex icon-al-str', |
|||
},{ |
|||
value : 'center', |
|||
title : 'Center', |
|||
className : 'icons-flex icon-al-center', |
|||
}], |
|||
},{ |
|||
name: 'Flex Children', |
|||
property: 'label-parent-flex', |
|||
},{ |
|||
name: 'Order', |
|||
property: 'order', |
|||
type: 'integer', |
|||
defaults : 0, |
|||
min: 0 |
|||
},{ |
|||
name : 'Flex', |
|||
property : 'flex', |
|||
type : 'composite', |
|||
properties : [{ |
|||
name: 'Grow', |
|||
property: 'flex-grow', |
|||
type: 'integer', |
|||
defaults : 0, |
|||
min: 0 |
|||
},{ |
|||
name: 'Shrink', |
|||
property: 'flex-shrink', |
|||
type: 'integer', |
|||
defaults : 0, |
|||
min: 0 |
|||
},{ |
|||
name: 'Basis', |
|||
property: 'flex-basis', |
|||
type: 'integer', |
|||
units: ['px','%',''], |
|||
unit: '', |
|||
defaults : 'auto', |
|||
}], |
|||
},{ |
|||
name : 'Align', |
|||
property : 'align-self', |
|||
type : 'radio', |
|||
defaults : 'auto', |
|||
list : [{ |
|||
value : 'auto', |
|||
name : 'Auto', |
|||
},{ |
|||
value : 'flex-start', |
|||
title : 'Start', |
|||
className : 'icons-flex icon-al-start', |
|||
},{ |
|||
value : 'flex-end', |
|||
title : 'End', |
|||
className : 'icons-flex icon-al-end', |
|||
},{ |
|||
value : 'stretch', |
|||
title : 'Stretch', |
|||
className : 'icons-flex icon-al-str', |
|||
},{ |
|||
value : 'center', |
|||
title : 'Center', |
|||
className : 'icons-flex icon-al-center', |
|||
}], |
|||
}] |
|||
} |
|||
|
|||
], |
|||
|
|||
}, |
|||
} |
|||
|
|||
|
|||
); |
|||
|
|||
|
|||
window.editor = editor; |
|||
|
|||
}); |
|||
}); |
|||
@ -1,40 +1,11 @@ |
|||
module.exports = { |
|||
|
|||
stylePrefix : 'rte-', |
|||
toolbarId : 'toolbar', |
|||
|
|||
// If true, moves the toolbar below the element when the top canvas
|
|||
// edge is reached
|
|||
adjustToolbar: 1, |
|||
|
|||
// Default toolbar commands
|
|||
commands : [{ |
|||
command: 'bold', |
|||
title: 'Bold', |
|||
class: 'fa fa-bold', |
|||
},{ |
|||
command: 'italic', |
|||
title: 'Italic', |
|||
class: 'fa fa-italic', |
|||
},{ |
|||
command: 'underline', |
|||
title: 'Underline', |
|||
class: 'fa fa-underline', |
|||
},{ |
|||
command: 'strikethrough', |
|||
title: 'Strikethrough', |
|||
class: 'fa fa-strikethrough', |
|||
group: 'format' |
|||
},{ |
|||
command: 'insertHTML', |
|||
title: 'Link', |
|||
class: 'fa fa-link', |
|||
args: '<a class="link" href="">${content}</a>', |
|||
}/*,{ |
|||
command: 'fontSize', |
|||
options: [ |
|||
{name: 'Huge', value: '7'}, |
|||
{name: 'Normal', value: '5'}, |
|||
{value: '1'} |
|||
] |
|||
}*/], |
|||
// Default RTE actions
|
|||
actions: ['bold', 'italic', 'underline', 'strikethrough', 'link'], |
|||
}; |
|||
|
|||
@ -1,21 +0,0 @@ |
|||
var Backbone = require('backbone'); |
|||
|
|||
module.exports = Backbone.Model.extend({ |
|||
|
|||
idAttribute: 'command', |
|||
|
|||
defaults: { |
|||
command: '', |
|||
type: '', |
|||
title: '', |
|||
class: '', |
|||
options: [], |
|||
}, |
|||
|
|||
initialize() { |
|||
var opts = this.get('options'); |
|||
if(opts.length) |
|||
this.set('type', 'select'); |
|||
}, |
|||
|
|||
}); |
|||
@ -1,6 +0,0 @@ |
|||
var Backbone = require('backbone'); |
|||
var CommandButton = require('./CommandButton'); |
|||
|
|||
module.exports = Backbone.Collection.extend({ |
|||
model: CommandButton, |
|||
}); |
|||
@ -0,0 +1,239 @@ |
|||
// The initial version of this RTE was borrowed from https://github.com/jaredreich/pell
|
|||
// and adapted to the GrapesJS's need
|
|||
|
|||
import {on, off} from 'utils/mixins' |
|||
|
|||
const RTE_KEY = '_rte'; |
|||
|
|||
const defActions = { |
|||
bold: { |
|||
name: 'bold', |
|||
icon: '<b>B</b>', |
|||
attributes: {title: 'Bold'}, |
|||
result: (rte) => rte.exec('bold') |
|||
}, |
|||
italic: { |
|||
name: 'italic', |
|||
icon: '<i>I</i>', |
|||
attributes: {title: 'Italic'}, |
|||
result: (rte) => rte.exec('italic') |
|||
}, |
|||
underline: { |
|||
name: 'underline', |
|||
icon: '<u>U</u>', |
|||
attributes: {title: 'Underline'}, |
|||
result: (rte) => rte.exec('underline') |
|||
}, |
|||
strikethrough: { |
|||
name: 'strikethrough', |
|||
icon: '<strike>S</strike>', |
|||
attributes: {title: 'Strike-through'}, |
|||
result: (rte) => rte.exec('strikeThrough') |
|||
}, |
|||
link: { |
|||
icon: `<span style="transform:rotate(45deg)">⫘</span>`, |
|||
name: 'link', |
|||
attributes: { |
|||
style: 'font-size:1.4rem;padding:0 4px 2px;', |
|||
title: 'Link', |
|||
}, |
|||
result: (rte) => rte.insertHTML(`<a class="link" href="">${rte.selection()}</a>`) |
|||
} |
|||
} |
|||
|
|||
export default class RichTextEditor { |
|||
|
|||
constructor(settings = {}) { |
|||
const el = settings.el; |
|||
|
|||
if (el[RTE_KEY]) { |
|||
return el[RTE_KEY]; |
|||
} |
|||
|
|||
el[RTE_KEY] = this; |
|||
this.setEl(el); |
|||
this.updateActiveActions = this.updateActiveActions.bind(this); |
|||
|
|||
const settAct = settings.actions || []; |
|||
settAct.forEach((action, i) => { |
|||
if (typeof action === 'string') { |
|||
action = defActions[action]; |
|||
} else if (defActions[action.name]) { |
|||
action = {...defActions[action.name], ...action}; |
|||
} |
|||
settAct[i] = action; |
|||
}); |
|||
const actions = settAct.length ? settAct : |
|||
Object.keys(defActions).map(action => defActions[action]) |
|||
|
|||
settings.classes = { ...{ |
|||
actionbar: 'actionbar', |
|||
button: 'action', |
|||
active: 'active', |
|||
}, ...settings.classes}; |
|||
|
|||
const classes = settings.classes; |
|||
let actionbar = settings.actionbar; |
|||
this.actionbar = actionbar; |
|||
this.settings = settings; |
|||
this.classes = classes; |
|||
this.actions = actions; |
|||
|
|||
if (!actionbar) { |
|||
const actionbarCont = settings.actionbarContainer; |
|||
actionbar = document.createElement('div'); |
|||
actionbar.className = classes.actionbar; |
|||
actionbarCont.appendChild(actionbar); |
|||
this.actionbar = actionbar; |
|||
actions.forEach(action => this.addAction(action)) |
|||
} |
|||
|
|||
settings.styleWithCSS && this.exec('styleWithCSS'); |
|||
this.syncActions(); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
setEl(el) { |
|||
this.el = el; |
|||
this.doc = el.ownerDocument; |
|||
} |
|||
|
|||
updateActiveActions() { |
|||
this.getActions().forEach(action => { |
|||
const btn = action.btn; |
|||
const active = this.classes.active; |
|||
btn.className = btn.className.replace(active, '').trim(); |
|||
|
|||
if (this.doc.queryCommandState(action.name)) { |
|||
btn.className += ` ${active}`; |
|||
} |
|||
}) |
|||
} |
|||
|
|||
enable() { |
|||
if (this.enabled) { |
|||
return this; |
|||
} |
|||
|
|||
this.actionbarEl().style.display = ''; |
|||
this.el.contentEditable = true; |
|||
on(this.el, 'mouseup keyup', this.updateActiveActions) |
|||
this.syncActions(); |
|||
this.updateActiveActions(); |
|||
this.el.focus(); |
|||
this.enabled = 1; |
|||
return this; |
|||
} |
|||
|
|||
disable() { |
|||
this.actionbarEl().style.display = 'none'; |
|||
this.el.contentEditable = false; |
|||
off(this.el, 'mouseup keyup', this.updateActiveActions); |
|||
this.enabled = 0; |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Sync actions with the current RTE |
|||
*/ |
|||
syncActions() { |
|||
this.getActions().forEach(action => { |
|||
const event = action.event || 'click'; |
|||
action.btn[`on${event}`] = e => { |
|||
action.result(this); |
|||
this.updateActiveActions(); |
|||
}; |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Add new action to the actionbar |
|||
* @param {Object} action |
|||
* @param {Object} [opts={}] |
|||
*/ |
|||
addAction(action, opts = {}) { |
|||
const sync = opts.sync; |
|||
const btn = document.createElement('span'); |
|||
const icon = action.icon; |
|||
const attr = action.attributes || {}; |
|||
btn.className = this.classes.button; |
|||
action.btn = btn; |
|||
|
|||
for (let key in attr) { |
|||
btn.setAttribute(key, attr[key]); |
|||
} |
|||
|
|||
if (typeof icon == 'string') { |
|||
btn.innerHTML = icon; |
|||
} else { |
|||
btn.appendChild(icon); |
|||
} |
|||
|
|||
this.actionbarEl().appendChild(btn); |
|||
|
|||
if (sync) { |
|||
this.actions.push(action); |
|||
this.syncActions(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get the array of current actions |
|||
* @return {Array} |
|||
*/ |
|||
getActions() { |
|||
return this.actions; |
|||
} |
|||
|
|||
/** |
|||
* Returns the Selection instance |
|||
* @return {Selection} |
|||
*/ |
|||
selection() { |
|||
return this.doc.getSelection() |
|||
} |
|||
|
|||
/** |
|||
* Execute the command |
|||
* @param {string} command Command name |
|||
* @param {any} [value=null Command's arguments |
|||
*/ |
|||
exec(command, value = null) { |
|||
this.doc.execCommand(command, false, value); |
|||
} |
|||
|
|||
/** |
|||
* Get the actionbar element |
|||
* @return {HTMLElement} |
|||
*/ |
|||
actionbarEl() { |
|||
return this.actionbar; |
|||
} |
|||
|
|||
/** |
|||
* Set custom HTML to the selection, useful as the default 'insertHTML' command |
|||
* doesn't work in the same way on all browsers |
|||
* @param {string} value HTML string |
|||
*/ |
|||
insertHTML(value) { |
|||
let lastNode; |
|||
const doc = this.doc; |
|||
const sel = doc.getSelection(); |
|||
|
|||
if (sel && sel.rangeCount) { |
|||
const node = doc.createElement('div'); |
|||
const range = sel.getRangeAt(0); |
|||
range.deleteContents(); |
|||
node.innerHTML = value; |
|||
Array.prototype.slice.call(node.childNodes).forEach(nd => { |
|||
range.insertNode(nd); |
|||
lastNode = nd; |
|||
}) |
|||
|
|||
sel.removeAllRanges(); |
|||
sel.addRange(range); |
|||
this.el.focus(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
const CommandButtonView = require('./CommandButtonView'); |
|||
const $ = Backbone.$; |
|||
|
|||
module.exports = CommandButtonView.extend({ |
|||
|
|||
initialize(o, config) { |
|||
CommandButtonView.prototype.initialize.apply(this, arguments); |
|||
}, |
|||
|
|||
getInput() { |
|||
var m = this.model; |
|||
if(!this.input){ |
|||
var cmd = m.get('command'); |
|||
var input = '<select data-edit="' + cmd +'">'; |
|||
var opts = m.get('options'); |
|||
var label = m.get('title') || m.get('command'); |
|||
input += '<option>' + label + '</option>'; |
|||
for(var i = 0, len = opts.length; i < len; i++){ |
|||
var opt = opts[i]; |
|||
var value = opt.value; |
|||
var name = opt.name || value; |
|||
input += '<option value="' + value + '">' + name + '</option>'; |
|||
} |
|||
input += '</select>'; |
|||
this.input = $(input); |
|||
} |
|||
return this.input; |
|||
}, |
|||
|
|||
getInputCont() { |
|||
var input = this.getInput(); |
|||
var pfx = this.ppfx; |
|||
var cont = $('<div class="'+pfx+'field '+pfx+'select"><div class="'+pfx+'sel-arrow"><div class="'+pfx+'d-s-arrow"></div></div></div>'); |
|||
return cont.append(input); |
|||
}, |
|||
|
|||
render(...args) { |
|||
CommandButtonView.prototype.render.apply(this, args); |
|||
this.$el.html(this.getInputCont()); |
|||
return this; |
|||
} |
|||
}); |
|||
@ -1,16 +0,0 @@ |
|||
var Backbone = require('backbone'); |
|||
|
|||
module.exports = Backbone.View.extend({ |
|||
tagName: 'a', |
|||
|
|||
initialize(o, config) { |
|||
this.config = config || {}; |
|||
this.ppfx = this.config.pStylePrefix || ''; |
|||
this.className = this.config.stylePrefix + 'btn ' + this.model.get('class'); |
|||
}, |
|||
|
|||
render() { |
|||
this.$el.addClass(this.className); |
|||
return this; |
|||
} |
|||
}); |
|||
@ -1,76 +0,0 @@ |
|||
var Backbone = require('backbone'); |
|||
var CommandButtonView = require('./CommandButtonView'); |
|||
var CommandButtonSelectView = require('./CommandButtonSelectView'); |
|||
|
|||
module.exports = Backbone.View.extend({ |
|||
|
|||
attributes : { |
|||
'data-role': 'editor-toolbar', |
|||
}, |
|||
|
|||
initialize(o) { |
|||
this.config = o.config || {}; |
|||
var pfx = this.config.stylePrefix || ''; |
|||
this.id = pfx + this.config.toolbarId; |
|||
this.listenTo(this.collection, 'add', this.addTo); |
|||
this.$el.data('helper', 1); |
|||
}, |
|||
|
|||
/** |
|||
* Add new model to the collection |
|||
* @param {Model} model |
|||
* @private |
|||
* */ |
|||
addTo(model) { |
|||
this.add(model); |
|||
}, |
|||
|
|||
/** |
|||
* Render new model inside the view |
|||
* @param {Model} model |
|||
* @param {Object} fragment Fragment collection |
|||
* @private |
|||
* */ |
|||
add(model, fragment) { |
|||
var frag = fragment || null; |
|||
var viewObj = CommandButtonView; |
|||
|
|||
switch (model.get('type')) { |
|||
case 'select': |
|||
viewObj = CommandButtonSelectView; |
|||
break; |
|||
} |
|||
var args = model.get('args'); |
|||
var attrs = { |
|||
'title': model.get('title'), |
|||
'data-edit': model.get('command'), |
|||
}; |
|||
if(args) |
|||
attrs['data-args'] = args; |
|||
var view = new viewObj({ |
|||
model, |
|||
attributes: attrs, |
|||
}, this.config); |
|||
|
|||
var rendered = view.render().el; |
|||
|
|||
if(frag) |
|||
frag.appendChild(rendered); |
|||
else |
|||
this.$el.append(rendered); |
|||
}, |
|||
|
|||
render() { |
|||
var frag = document.createDocumentFragment(); |
|||
this.$el.empty(); |
|||
|
|||
this.collection.each(function(model){ |
|||
this.add(model, frag); |
|||
}, this); |
|||
|
|||
this.$el.append(frag); |
|||
this.$el.attr('id', this.id ); |
|||
return this; |
|||
} |
|||
|
|||
}); |
|||
@ -1,234 +0,0 @@ |
|||
const Backbone = require('backbone'); |
|||
const $ = Backbone.$; |
|||
|
|||
var readFileIntoDataUrl = fileInfo => { |
|||
var loader = $.Deferred(), |
|||
fReader = new FileReader(); |
|||
fReader.onload = e => { |
|||
loader.resolve(e.target.result); |
|||
}; |
|||
fReader.onerror = loader.reject; |
|||
fReader.onprogress = loader.notify; |
|||
fReader.readAsDataURL(fileInfo); |
|||
return loader.promise(); |
|||
}; |
|||
$.fn.cleanHtml = function () { |
|||
var html = $(this).html(); |
|||
return html && html.replace(/(<br>|\s|<div><br><\/div>| )*$/, ''); |
|||
}; |
|||
$.fn.wysiwyg = function (userOptions) { |
|||
var editor = this, |
|||
selectedRange, |
|||
options, |
|||
toolbarBtnSelector, |
|||
updateToolbar = () => { |
|||
var actCls = options.activeToolbarClass; |
|||
if (actCls) { |
|||
$(options.toolbarSelector).find(toolbarBtnSelector).each(function () { |
|||
var el = $(this); |
|||
var command = el.data(options.commandRole); |
|||
var doc = editor.get(0).ownerDocument; |
|||
if (doc.queryCommandState(command)) { |
|||
el.addClass(actCls); |
|||
} else { |
|||
el.removeClass(actCls); |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
execCommand = (commandWithArgs, valueArg) => { |
|||
var commandArr = commandWithArgs.split(' '), |
|||
command = commandArr.shift(), |
|||
args = commandArr.join(' ') + (valueArg || ''); |
|||
//document.execCommand("insertHTML", false, "<span class='own-class'>"+ document.getSelection()+"</span>");
|
|||
editor.get(0).ownerDocument.execCommand("styleWithCSS", false, true); |
|||
editor.get(0).ownerDocument.execCommand(command, 0, args); |
|||
updateToolbar(); |
|||
editor.trigger('change'); |
|||
}, |
|||
/* |
|||
bindHotkeys = function (hotKeys) { |
|||
$.each(hotKeys, function (hotkey, command) { |
|||
editor.keydown(hotkey, function (e) { |
|||
if (editor.attr('contenteditable') && editor.is(':visible')) { |
|||
e.preventDefault(); |
|||
e.stopPropagation(); |
|||
execCommand(command); |
|||
} |
|||
}).keyup(hotkey, function (e) { |
|||
if (editor.attr('contenteditable') && editor.is(':visible')) { |
|||
e.preventDefault(); |
|||
e.stopPropagation(); |
|||
} |
|||
}); |
|||
}); |
|||
}, |
|||
*/ |
|||
getCurrentRange = () => { |
|||
var sel = window.getSelection(); |
|||
if (sel.getRangeAt && sel.rangeCount) { |
|||
return sel.getRangeAt(0); |
|||
} |
|||
}, |
|||
saveSelection = () => { |
|||
selectedRange = getCurrentRange(); |
|||
}, |
|||
restoreSelection = () => { |
|||
var selection = window.getSelection(); |
|||
if (selectedRange) { |
|||
try { |
|||
selection.removeAllRanges(); |
|||
} catch (ex) { |
|||
document.body.createTextRange().select(); |
|||
document.selection.empty(); |
|||
} |
|||
|
|||
selection.addRange(selectedRange); |
|||
} |
|||
}, |
|||
insertFiles = files => { |
|||
editor.focus(); |
|||
$.each(files, (idx, fileInfo) => { |
|||
if (/^image\//.test(fileInfo.type)) { |
|||
$.when(readFileIntoDataUrl(fileInfo)).done(dataUrl => { |
|||
execCommand('insertimage', dataUrl); |
|||
}).fail(e => { |
|||
options.fileUploadError("file-reader", e); |
|||
}); |
|||
} else { |
|||
options.fileUploadError("unsupported-file-type", fileInfo.type); |
|||
} |
|||
}); |
|||
}, |
|||
markSelection = (input, color) => { |
|||
restoreSelection(); |
|||
if (document.queryCommandSupported('hiliteColor')) { |
|||
document.execCommand('hiliteColor', 0, color || 'transparent'); |
|||
} |
|||
saveSelection(); |
|||
input.data(options.selectionMarker, color); |
|||
}, |
|||
bindToolbar = (toolbar, options) => { |
|||
toolbar.find(toolbarBtnSelector).off('click').on('click',function () { |
|||
restoreSelection(); |
|||
//editor.focus(); // cause defocus on selects
|
|||
var doc = editor.get(0).ownerDocument; |
|||
var el = $(this); |
|||
var comm = el.data(options.commandRole); |
|||
var args = el.data('args'); |
|||
if(args){ |
|||
args = args.replace('${content}', doc.getSelection()); |
|||
execCommand(comm, args); |
|||
}else{ |
|||
doc.execCommand(comm); |
|||
} |
|||
saveSelection(); |
|||
}); |
|||
toolbar.find('[data-toggle=dropdown]').on('click', restoreSelection); |
|||
var dName = '[data-' + options.commandRole + ']'; |
|||
toolbar.find('select'+dName).on('webkitspeechchange change', function(){ |
|||
var newValue = this.value; |
|||
restoreSelection(); |
|||
if (newValue) { |
|||
editor.focus(); |
|||
execCommand($(this).data(options.commandRole), newValue); |
|||
} |
|||
saveSelection(); |
|||
}); |
|||
toolbar.find('input[type=text]'+dName,', select'+dName).on('webkitspeechchange change', function () { |
|||
var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */ |
|||
this.value = ''; |
|||
restoreSelection(); |
|||
if (newValue) { |
|||
editor.focus(); |
|||
execCommand($(this).data(options.commandRole), newValue); |
|||
} |
|||
saveSelection(); |
|||
}).on('focus', function () { |
|||
var input = $(this); |
|||
if (!input.data(options.selectionMarker)) { |
|||
markSelection(input, options.selectionColor); |
|||
input.focus(); |
|||
} |
|||
}).on('blur', function () { |
|||
var input = $(this); |
|||
if (input.data(options.selectionMarker)) { |
|||
markSelection(input, false); |
|||
} |
|||
}); |
|||
toolbar.find('input[type=file][data-' + options.commandRole + ']').on('change', function () { |
|||
restoreSelection(); |
|||
if (this.type === 'file' && this.files && this.files.length > 0) { |
|||
insertFiles(this.files); |
|||
} |
|||
saveSelection(); |
|||
this.value = ''; |
|||
}); |
|||
}, |
|||
initFileDrops = () => { |
|||
editor.on('dragenter dragover', false) |
|||
.on('drop', e => { |
|||
var dataTransfer = e.originalEvent.dataTransfer; |
|||
e.stopPropagation(); |
|||
e.preventDefault(); |
|||
if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) { |
|||
insertFiles(dataTransfer.files); |
|||
} |
|||
}); |
|||
}; |
|||
/** Disable the editor |
|||
* @date 2015-03-19 */ |
|||
if(typeof userOptions=='string' && userOptions=='destroy'){ |
|||
editor.attr('contenteditable', false).off('mouseup keyup mouseout dragenter dragover'); |
|||
$(window).off('touchend'); |
|||
return this; |
|||
} |
|||
options = $.extend({}, $.fn.wysiwyg.defaults, userOptions); |
|||
var dName = '[data-' + options.commandRole + ']'; |
|||
toolbarBtnSelector = 'a'+dName+',button'+dName+',input[type=button]'+dName+', select'+dName; |
|||
//bindHotkeys(options.hotKeys);
|
|||
if (options.dragAndDropImages) { |
|||
initFileDrops(); |
|||
} |
|||
bindToolbar($(options.toolbarSelector), options); |
|||
editor.attr('contenteditable', true).on('mouseup keyup mouseout', () => { |
|||
saveSelection(); |
|||
updateToolbar(); |
|||
}); |
|||
$(window).on('touchend', e => { |
|||
var isInside = (editor.is(e.target) || editor.has(e.target).length > 0), |
|||
currentRange = getCurrentRange(), |
|||
clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset); |
|||
if (!clear || isInside) { |
|||
saveSelection(); |
|||
updateToolbar(); |
|||
} |
|||
}); |
|||
return this; |
|||
}; |
|||
$.fn.wysiwyg.defaults = { |
|||
/* |
|||
hotKeys: { |
|||
'ctrl+b meta+b': 'bold', |
|||
'ctrl+i meta+i': 'italic', |
|||
'ctrl+u meta+u': 'underline', |
|||
'ctrl+z meta+z': 'undo', |
|||
'ctrl+y meta+y meta+shift+z': 'redo', |
|||
'ctrl+l meta+l': 'justifyleft', |
|||
'ctrl+r meta+r': 'justifyright', |
|||
'ctrl+e meta+e': 'justifycenter', |
|||
'ctrl+j meta+j': 'justifyfull', |
|||
'shift+tab': 'outdent', |
|||
'tab': 'indent' |
|||
}, |
|||
*/ |
|||
toolbarSelector: '[data-role=editor-toolbar]', |
|||
commandRole: 'edit', |
|||
activeToolbarClass: 'btn-info', |
|||
selectionMarker: 'edit-focus-marker', |
|||
selectionColor: 'darkgrey', |
|||
dragAndDropImages: true, |
|||
fileUploadError(reason, detail) { console.log("File upload error", reason, detail); } |
|||
}; |
|||
|
|||
module.exports = $; |
|||
@ -0,0 +1,41 @@ |
|||
.#{$rte-prefix} { |
|||
&toolbar { |
|||
@extend .#{$app-prefix}bg-main; |
|||
@extend .#{$app-prefix}no-user-select; |
|||
|
|||
border: 1px solid $mainDkColor; |
|||
position: absolute; |
|||
border-radius: 3px; |
|||
z-index: 10; |
|||
} |
|||
|
|||
&actionbar { |
|||
display: flex; |
|||
} |
|||
|
|||
&action { |
|||
@extend .#{$app-prefix}color-main; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 5px; |
|||
min-width: 25px; |
|||
border-right: 1px solid $mainDkColor; |
|||
text-align: center; |
|||
cursor: pointer; |
|||
outline: none; |
|||
|
|||
&:last-child { |
|||
border-right: none; |
|||
} |
|||
|
|||
&:hover { |
|||
background-color: $mainLhColor; |
|||
} |
|||
} |
|||
|
|||
&active { |
|||
background-color: $mainDkColor; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue