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 = { |
module.exports = { |
||||
|
|
||||
stylePrefix : 'rte-', |
stylePrefix : 'rte-', |
||||
toolbarId : 'toolbar', |
|
||||
|
|
||||
// If true, moves the toolbar below the element when the top canvas
|
// If true, moves the toolbar below the element when the top canvas
|
||||
// edge is reached
|
// edge is reached
|
||||
adjustToolbar: 1, |
adjustToolbar: 1, |
||||
|
|
||||
// Default toolbar commands
|
// Default RTE actions
|
||||
commands : [{ |
actions: ['bold', 'italic', 'underline', 'strikethrough', 'link'], |
||||
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'} |
|
||||
] |
|
||||
}*/], |
|
||||
}; |
}; |
||||
|
|||||
@ -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