Browse Source

Cleanup RichTextEditor module

pull/415/head
Artur Arseniev 9 years ago
parent
commit
503ecf7ee4
  1. 17
      src/rich_text_editor/index.js
  2. 21
      src/rich_text_editor/model/CommandButton.js
  3. 6
      src/rich_text_editor/model/CommandButtons.js
  4. 42
      src/rich_text_editor/view/CommandButtonSelectView.js
  5. 16
      src/rich_text_editor/view/CommandButtonView.js
  6. 76
      src/rich_text_editor/view/CommandButtonsView.js
  7. 234
      src/rich_text_editor/view/TextEditorView.js

17
src/rich_text_editor/index.js

@ -6,7 +6,7 @@
*
* This module allows to customize the toolbar of the Rich Text Editor and use commands from the HTML Editing APIs.
* For more info about HTML Editing APIs check here:
* https://developer.mozilla.org/it/docs/Web/API/Document/execCommand
* https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
*
* It's highly recommended to keep this toolbar as small as possible, especially from styling commands (eg. 'fontSize')
* and leave this task to the Style Manager.
@ -16,9 +16,6 @@
* ```js
* var rte = editor.RichTextEditor;
* ```
* Complete list of commands
* https://developer.mozilla.org/it/docs/Web/API/Document/execCommand
* http://www.quirksmode.org/dom/execCommand.html
* @module RichTextEditor
*/
import RichTextEditor from './model/RichTextEditor';
@ -71,6 +68,12 @@ module.exports = () => {
return this;
},
/**
* Post render callback
* @param {View} ev
* @private
*/
postRender(ev) {
const canvas = ev.model.get('Canvas');
toolbar.style.pointerEvents = 'all';
@ -124,10 +127,10 @@ module.exports = () => {
* result: rte => rte.exec('bold')
* });
* rte.add('link', {
* icon: 'L',
* icon: document.getElementById('t'),
* event: 'click',
* attributes: {title: 'Link',}
* result: rte =>
* rte.insertHTML(`<a href="#">${rte.selection()}</a>`)
* result: rte => rte.insertHTML(`<a href="#">${rte.selection()}</a>`)
* });
*/
add(name, action = {}) {

21
src/rich_text_editor/model/CommandButton.js

@ -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');
},
});

6
src/rich_text_editor/model/CommandButtons.js

@ -1,6 +0,0 @@
var Backbone = require('backbone');
var CommandButton = require('./CommandButton');
module.exports = Backbone.Collection.extend({
model: CommandButton,
});

42
src/rich_text_editor/view/CommandButtonSelectView.js

@ -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;
}
});

16
src/rich_text_editor/view/CommandButtonView.js

@ -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;
}
});

76
src/rich_text_editor/view/CommandButtonsView.js

@ -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;
}
});

234
src/rich_text_editor/view/TextEditorView.js

@ -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>|&nbsp;)*$/, '');
};
$.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 = $;
Loading…
Cancel
Save