[IMP] web_editor: update summernote up to 2015-08-31

update summernote library up to revision:
 https://github.com/summernote/summernote/commit/a0f26e300

see https://github.com/summernote/summernote/ for history

closes #8704
This commit is contained in:
Nicolas Lempereur
2015-09-29 14:47:24 +02:00
parent e9ac1bff8b
commit d33086c03f
38 changed files with 3461 additions and 1549 deletions
@@ -57,7 +57,7 @@
<td valign="center" style="text-align:center"> <td valign="center" style="text-align:center">
&amp;nbsp; &amp;nbsp;
<a t-att-href="(company_id.website) or '#'" style="text-decoration:none;"> <a t-att-href="(company_id.website) or '#'" style="text-decoration:none;">
<img src="/logo.png" style="padding:10px;height:auto;max-width:600px;width:200px" alt="Your Logo" /> <img class="padding-medium" src="/logo.png" style="height:auto;max-width:600px;width:200px" alt="Your Logo" />
</a> </a>
&amp;nbsp; &amp;nbsp;
</td> </td>
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,12 @@
define([ define([
'summernote/core/agent', 'summernote/core/dom', 'summernote/core/func' 'summernote/core/agent',
], function (agent, dom, func) { 'summernote/core/dom',
'summernote/core/func',
'summernote/core/list'
], function (agent, dom, func, list) {
/** /**
* @class Renderer
*
* renderer * renderer
* *
* rendering toolbar and editable * rendering toolbar and editable
@@ -10,14 +15,15 @@ define([
/** /**
* bootstrap button template * bootstrap button template
* * @private
* @param {String} label * @param {String} label button name
* @param {Object} [options] * @param {Object} [options] button options
* @param {String} [options.event] * @param {String} [options.event] data-event
* @param {String} [options.value] * @param {String} [options.className] button's class name
* @param {String} [options.title] * @param {String} [options.value] data-value
* @param {String} [options.dropdown] * @param {String} [options.title] button's title for popup
* @param {String} [options.hide] * @param {String} [options.dropdown] dropdown html
* @param {String} [options.hide] data-hide
*/ */
var tplButton = function (label, options) { var tplButton = function (label, options) {
var event = options.event; var event = options.event;
@@ -27,9 +33,11 @@ define([
var dropdown = options.dropdown; var dropdown = options.dropdown;
var hide = options.hide; var hide = options.hide;
return '<button type="button"' + return (dropdown ? '<div class="btn-group' +
' class="btn btn-default btn-sm btn-small' + (className ? ' ' + className : '') + '">' : '') +
(className ? ' ' + className : '') + '<button type="button"' +
' class="btn btn-default btn-sm' +
((!dropdown && className) ? ' ' + className : '') +
(dropdown ? ' dropdown-toggle' : '') + (dropdown ? ' dropdown-toggle' : '') +
'"' + '"' +
(dropdown ? ' data-toggle="dropdown"' : '') + (dropdown ? ' data-toggle="dropdown"' : '') +
@@ -38,15 +46,16 @@ define([
(value ? ' data-value=\'' + value + '\'' : '') + (value ? ' data-value=\'' + value + '\'' : '') +
(hide ? ' data-hide=\'' + hide + '\'' : '') + (hide ? ' data-hide=\'' + hide + '\'' : '') +
' tabindex="-1">' + ' tabindex="-1">' +
label + label +
(dropdown ? ' <span class="caret"></span>' : '') + (dropdown ? ' <span class="caret"></span>' : '') +
'</button>' + '</button>' +
(dropdown || ''); (dropdown || '') +
(dropdown ? '</div>' : '');
}; };
/** /**
* bootstrap icon button template * bootstrap icon button template
* * @private
* @param {String} iconClassName * @param {String} iconClassName
* @param {Object} [options] * @param {Object} [options]
* @param {String} [options.event] * @param {String} [options.event]
@@ -61,26 +70,28 @@ define([
/** /**
* bootstrap popover template * bootstrap popover template
* * @private
* @param {String} className * @param {String} className
* @param {String} content * @param {String} content
*/ */
var tplPopover = function (className, content) { var tplPopover = function (className, content) {
return '<div class="' + className + ' popover bottom in" style="display: none;">' + var $popover = $('<div class="' + className + ' popover bottom in" style="display: none;">' +
'<div class="arrow"></div>' + '<div class="arrow"></div>' +
'<div class="popover-content">' + '<div class="popover-content">' +
content +
'</div>' + '</div>' +
'</div>'; '</div>');
$popover.find('.popover-content').append(content);
return $popover;
}; };
/** /**
* bootstrap dialog template * bootstrap dialog template
* *
* @param {String} className * @param {String} className
* @param {String} [title] * @param {String} [title='']
* @param {String} body * @param {String} body
* @param {String} [footer] * @param {String} [footer='']
*/ */
var tplDialog = function (className, title, body, footer) { var tplDialog = function (className, title, body, footer) {
return '<div class="' + className + ' modal" aria-hidden="false">' + return '<div class="' + className + ' modal" aria-hidden="false">' +
@@ -92,44 +103,60 @@ define([
'<h4 class="modal-title">' + title + '</h4>' + '<h4 class="modal-title">' + title + '</h4>' +
'</div>' : '' '</div>' : ''
) + ) +
'<form class="note-modal-form">' + '<div class="modal-body">' + body + '</div>' +
'<div class="modal-body">' + body + '</div>' + (footer ?
(footer ? '<div class="modal-footer">' + footer + '</div>' : ''
'<div class="modal-footer">' + footer + '</div>' : '' ) +
) +
'</form>' +
'</div>' + '</div>' +
'</div>' + '</div>' +
'</div>'; '</div>';
}; };
/**
* bootstrap dropdown template
*
* @param {String|String[]} contents
* @param {String} [className='']
* @param {String} [nodeName='']
*/
var tplDropdown = function (contents, className, nodeName) {
var classes = 'dropdown-menu' + (className ? ' ' + className : '');
nodeName = nodeName || 'ul';
if (contents instanceof Array) {
contents = contents.join('');
}
return '<' + nodeName + ' class="' + classes + '">' + contents + '</' + nodeName + '>';
};
var tplButtonInfo = { var tplButtonInfo = {
picture: function (lang) { picture: function (lang, options) {
return tplIconButton('fa fa-picture-o', { return tplIconButton(options.iconPrefix + options.icons.image.image, {
event: 'showImageDialog', event: 'showImageDialog',
title: lang.image.image, title: lang.image.image,
hide: true hide: true
}); });
}, },
link: function (lang) { link: function (lang, options) {
return tplIconButton('fa fa-link', { return tplIconButton(options.iconPrefix + options.icons.link.link, {
event: 'showLinkDialog', event: 'showLinkDialog',
title: lang.link.link, title: lang.link.link,
hide: true hide: true
}); });
}, },
table: function (lang) { table: function (lang, options) {
var dropdown = '<ul class="note-table dropdown-menu">' + var dropdown = [
'<div class="note-dimension-picker">' + '<div class="note-dimension-picker">',
'<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>' + '<div class="note-dimension-picker-mousecatcher" data-event="insertTable" data-value="1x1"></div>',
'<div class="note-dimension-picker-highlighted"></div>' + '<div class="note-dimension-picker-highlighted"></div>',
'<div class="note-dimension-picker-unhighlighted"></div>' + '<div class="note-dimension-picker-unhighlighted"></div>',
'</div>' + '</div>',
'<div class="note-dimension-display"> 1 x 1 </div>' + '<div class="note-dimension-display"> 1 x 1 </div>'
'</ul>'; ];
return tplIconButton('fa fa-table', {
return tplIconButton(options.iconPrefix + options.icons.table.table, {
title: lang.table.table, title: lang.table.table,
dropdown: dropdown dropdown: tplDropdown(dropdown, 'note-table')
}); });
}, },
style: function (lang, options) { style: function (lang, options) {
@@ -143,28 +170,54 @@ define([
'</a></li>'; '</a></li>';
}, ''); }, '');
return tplIconButton('fa fa-magic', { return tplIconButton(options.iconPrefix + options.icons.style.style, {
title: lang.style.style, title: lang.style.style,
dropdown: '<ul class="dropdown-menu">' + items + '</ul>' dropdown: tplDropdown(items)
}); });
}, },
fontname: function (lang, options) { fontname: function (lang, options) {
var realFontList = [];
var items = options.fontNames.reduce(function (memo, v) { var items = options.fontNames.reduce(function (memo, v) {
if (!agent.isFontInstalled(v)) { return memo; } if (!agent.isFontInstalled(v) && !list.contains(options.fontNamesIgnoreCheck, v)) {
return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '">' + return memo;
'<i class="fa fa-check"></i> ' + v + }
realFontList.push(v);
return memo + '<li><a data-event="fontName" href="#" data-value="' + v + '" style="font-family:\'' + v + '\'">' +
'<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v +
'</a></li>'; '</a></li>';
}, ''); }, '');
var hasDefaultFont = agent.isFontInstalled(options.defaultFontName);
var defaultFontName = (hasDefaultFont) ? options.defaultFontName : realFontList[0];
var label = '<span class="note-current-fontname">' + var label = '<span class="note-current-fontname">' +
options.defaultFontName + defaultFontName +
'</span>'; '</span>';
return tplButton(label, { return tplButton(label, {
title: lang.font.name, title: lang.font.name,
dropdown: '<ul class="dropdown-menu">' + items + '</ul>' className: 'note-fontname',
dropdown: tplDropdown(items, 'note-check')
}); });
}, },
color: function (lang) { fontsize: function (lang, options) {
var colorButtonLabel = '<i class="fa fa-font" style="color:black;background-color:yellow;"></i>'; var items = options.fontSizes.reduce(function (memo, v) {
return memo + '<li><a data-event="fontSize" href="#" data-value="' + v + '">' +
'<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v +
'</a></li>';
}, '');
var label = '<span class="note-current-fontsize">11</span>';
return tplButton(label, {
title: lang.font.size,
className: 'note-fontsize',
dropdown: tplDropdown(items, 'note-check')
});
},
color: function (lang, options) {
var colorButtonLabel = '<i class="' +
options.iconPrefix + options.icons.color.recent +
'" style="color:black;background-color:yellow;"></i>';
var colorButton = tplButton(colorButtonLabel, { var colorButton = tplButton(colorButtonLabel, {
className: 'note-recent-color', className: 'note-recent-color',
title: lang.color.recent, title: lang.color.recent,
@@ -172,156 +225,168 @@ define([
value: '{"backColor":"yellow"}' value: '{"backColor":"yellow"}'
}); });
var dropdown = '<ul class="dropdown-menu">' + var items = [
'<li>' + '<li><div class="btn-group">',
'<div class="btn-group">' + '<div class="note-palette-title">' + lang.color.background + '</div>',
'<div class="note-palette-title">' + lang.color.background + '</div>' + '<div class="note-color-reset" data-event="backColor"',
'<div class="note-color-reset" data-event="backColor"' + ' data-value="inherit" title="' + lang.color.transparent + '">' + lang.color.setTransparent + '</div>',
' data-value="inherit" title="' + lang.color.transparent + '">' + '<div class="note-color-palette" data-target-event="backColor"></div>',
lang.color.setTransparent + '</div><div class="btn-group">',
'</div>' + '<div class="note-palette-title">' + lang.color.foreground + '</div>',
'<div class="note-color-palette" data-target-event="backColor"></div>' + '<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">',
'</div>' + lang.color.resetToDefault,
'<div class="btn-group">' + '</div>',
'<div class="note-palette-title">' + lang.color.foreground + '</div>' + '<div class="note-color-palette" data-target-event="foreColor"></div>',
'<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="' + lang.color.reset + '">' + '</div></li>'
lang.color.resetToDefault + ];
'</div>' +
'<div class="note-color-palette" data-target-event="foreColor"></div>' +
'</div>' +
'</li>' +
'</ul>';
var moreButton = tplButton('', { var moreButton = tplButton('', {
title: lang.color.more, title: lang.color.more,
dropdown: dropdown dropdown: tplDropdown(items)
}); });
return colorButton + moreButton; return colorButton + moreButton;
}, },
bold: function (lang) { bold: function (lang, options) {
return tplIconButton('fa fa-bold', { return tplIconButton(options.iconPrefix + options.icons.font.bold, {
event: 'bold', event: 'bold',
title: lang.font.bold title: lang.font.bold
}); });
}, },
italic: function (lang) { italic: function (lang, options) {
return tplIconButton('fa fa-italic', { return tplIconButton(options.iconPrefix + options.icons.font.italic, {
event: 'italic', event: 'italic',
title: lang.font.italic title: lang.font.italic
}); });
}, },
underline: function (lang) { underline: function (lang, options) {
return tplIconButton('fa fa-underline', { return tplIconButton(options.iconPrefix + options.icons.font.underline, {
event: 'underline', event: 'underline',
title: lang.font.underline title: lang.font.underline
}); });
}, },
clear: function (lang) { strikethrough: function (lang, options) {
return tplIconButton('fa fa-eraser', { return tplIconButton(options.iconPrefix + options.icons.font.strikethrough, {
event: 'strikethrough',
title: lang.font.strikethrough
});
},
superscript: function (lang, options) {
return tplIconButton(options.iconPrefix + options.icons.font.superscript, {
event: 'superscript',
title: lang.font.superscript
});
},
subscript: function (lang, options) {
return tplIconButton(options.iconPrefix + options.icons.font.subscript, {
event: 'subscript',
title: lang.font.subscript
});
},
clear: function (lang, options) {
return tplIconButton(options.iconPrefix + options.icons.font.clear, {
event: 'removeFormat', event: 'removeFormat',
title: lang.font.clear title: lang.font.clear
}); });
}, },
ul: function (lang) { ul: function (lang, options) {
return tplIconButton('fa fa-list-ul', { return tplIconButton(options.iconPrefix + options.icons.lists.unordered, {
event: 'insertUnorderedList', event: 'insertUnorderedList',
title: lang.lists.unordered title: lang.lists.unordered
}); });
}, },
ol: function (lang) { ol: function (lang, options) {
return tplIconButton('fa fa-list-ol', { return tplIconButton(options.iconPrefix + options.icons.lists.ordered, {
event: 'insertOrderedList', event: 'insertOrderedList',
title: lang.lists.ordered title: lang.lists.ordered
}); });
}, },
paragraph: function (lang) { paragraph: function (lang, options) {
var leftButton = tplIconButton('fa fa-align-left', { var leftButton = tplIconButton(options.iconPrefix + options.icons.paragraph.left, {
title: lang.paragraph.left, title: lang.paragraph.left,
event: 'justifyLeft' event: 'justifyLeft'
}); });
var centerButton = tplIconButton('fa fa-align-center', { var centerButton = tplIconButton(options.iconPrefix + options.icons.paragraph.center, {
title: lang.paragraph.center, title: lang.paragraph.center,
event: 'justifyCenter' event: 'justifyCenter'
}); });
var rightButton = tplIconButton('fa fa-align-right', { var rightButton = tplIconButton(options.iconPrefix + options.icons.paragraph.right, {
title: lang.paragraph.right, title: lang.paragraph.right,
event: 'justifyRight' event: 'justifyRight'
}); });
var justifyButton = tplIconButton('fa fa-align-justify', { var justifyButton = tplIconButton(options.iconPrefix + options.icons.paragraph.justify, {
title: lang.paragraph.justify, title: lang.paragraph.justify,
event: 'justifyFull' event: 'justifyFull'
}); });
var outdentButton = tplIconButton('fa fa-outdent', { var outdentButton = tplIconButton(options.iconPrefix + options.icons.paragraph.outdent, {
title: lang.paragraph.outdent, title: lang.paragraph.outdent,
event: 'outdent' event: 'outdent'
}); });
var indentButton = tplIconButton('fa fa-indent', { var indentButton = tplIconButton(options.iconPrefix + options.icons.paragraph.indent, {
title: lang.paragraph.indent, title: lang.paragraph.indent,
event: 'indent' event: 'indent'
}); });
var dropdown = '<div class="dropdown-menu">' + var dropdown = [
'<div class="note-align btn-group">' + '<div class="note-align btn-group">',
leftButton + centerButton + rightButton + justifyButton + leftButton + centerButton + rightButton + justifyButton,
'</div>' + '</div><div class="note-list btn-group">',
'<div class="note-list btn-group">' + indentButton + outdentButton,
indentButton + outdentButton + '</div>'
'</div>' + ];
'</div>';
return tplIconButton('fa fa-align-left', { return tplIconButton(options.iconPrefix + options.icons.paragraph.paragraph, {
title: lang.paragraph.paragraph, title: lang.paragraph.paragraph,
dropdown: dropdown dropdown: tplDropdown(dropdown, '', 'div')
}); });
}, },
height: function (lang, options) { height: function (lang, options) {
var items = options.lineHeights.reduce(function (memo, v) { var items = options.lineHeights.reduce(function (memo, v) {
return memo + '<li><a data-event="lineHeight" href="#" data-value="' + parseFloat(v) + '">' + return memo + '<li><a data-event="lineHeight" href="#" data-value="' + parseFloat(v) + '">' +
'<i class="fa fa-check"></i> ' + v + '<i class="' + options.iconPrefix + options.icons.misc.check + '"></i> ' + v +
'</a></li>'; '</a></li>';
}, ''); }, '');
return tplIconButton('fa fa-text-height', { return tplIconButton(options.iconPrefix + options.icons.font.height, {
title: lang.font.height, title: lang.font.height,
dropdown: '<ul class="dropdown-menu">' + items + '</ul>' dropdown: tplDropdown(items, 'note-check')
}); });
}, },
help: function (lang) { help: function (lang, options) {
return tplIconButton('fa fa-question', { return tplIconButton(options.iconPrefix + options.icons.options.help, {
event: 'showHelpDialog', event: 'showHelpDialog',
title: lang.options.help, title: lang.options.help,
hide: true hide: true
}); });
}, },
fullscreen: function (lang) { fullscreen: function (lang, options) {
return tplIconButton('fa fa-arrows-alt', { return tplIconButton(options.iconPrefix + options.icons.options.fullscreen, {
event: 'fullscreen', event: 'fullscreen',
title: lang.options.fullscreen title: lang.options.fullscreen
}); });
}, },
codeview: function (lang) { codeview: function (lang, options) {
return tplIconButton('fa fa-code', { return tplIconButton(options.iconPrefix + options.icons.options.codeview, {
event: 'codeview', event: 'codeview',
title: lang.options.codeview title: lang.options.codeview
}); });
}, },
undo: function (lang) { undo: function (lang, options) {
return tplIconButton('fa fa-undo', { return tplIconButton(options.iconPrefix + options.icons.history.undo, {
event: 'undo', event: 'undo',
title: lang.history.undo title: lang.history.undo
}); });
}, },
redo: function (lang) { redo: function (lang, options) {
return tplIconButton('fa fa-repeat', { return tplIconButton(options.iconPrefix + options.icons.history.redo, {
event: 'redo', event: 'redo',
title: lang.history.redo title: lang.history.redo
}); });
}, },
hr: function (lang) { hr: function (lang, options) {
return tplIconButton('fa fa-minus', { return tplIconButton(options.iconPrefix + options.icons.hr.insert, {
event: 'insertHorizontalRule', event: 'insertHorizontalRule',
title: lang.hr.insert title: lang.hr.insert
}); });
@@ -330,12 +395,12 @@ define([
var tplPopovers = function (lang, options) { var tplPopovers = function (lang, options) {
var tplLinkPopover = function () { var tplLinkPopover = function () {
var linkButton = tplIconButton('fa fa-edit', { var linkButton = tplIconButton(options.iconPrefix + options.icons.link.edit, {
title: lang.link.edit, title: lang.link.edit,
event: 'showLinkDialog', event: 'showLinkDialog',
hide: true hide: true
}); });
var unlinkButton = tplIconButton('fa fa-unlink', { var unlinkButton = tplIconButton(options.iconPrefix + options.icons.link.unlink, {
title: lang.link.unlink, title: lang.link.unlink,
event: 'unlink' event: 'unlink'
}); });
@@ -363,50 +428,50 @@ define([
value: '0.25' value: '0.25'
}); });
var leftButton = tplIconButton('fa fa-align-left', { var leftButton = tplIconButton(options.iconPrefix + options.icons.image.floatLeft, {
title: lang.image.floatLeft, title: lang.image.floatLeft,
event: 'floatMe', event: 'floatMe',
value: 'left' value: 'left'
}); });
var rightButton = tplIconButton('fa fa-align-right', { var rightButton = tplIconButton(options.iconPrefix + options.icons.image.floatRight, {
title: lang.image.floatRight, title: lang.image.floatRight,
event: 'floatMe', event: 'floatMe',
value: 'right' value: 'right'
}); });
var justifyButton = tplIconButton('fa fa-align-justify', { var justifyButton = tplIconButton(options.iconPrefix + options.icons.image.floatNone, {
title: lang.image.floatNone, title: lang.image.floatNone,
event: 'floatMe', event: 'floatMe',
value: 'none' value: 'none'
}); });
var roundedButton = tplIconButton('fa fa-square', { var roundedButton = tplIconButton(options.iconPrefix + options.icons.image.shapeRounded, {
title: lang.image.shapeRounded, title: lang.image.shapeRounded,
event: 'imageShape', event: 'imageShape',
value: 'img-rounded' value: 'img-rounded'
}); });
var circleButton = tplIconButton('fa fa-circle', { var circleButton = tplIconButton(options.iconPrefix + options.icons.image.shapeCircle, {
title: lang.image.shapeCircle, title: lang.image.shapeCircle,
event: 'imageShape', event: 'imageShape',
value: 'img-circle' value: 'img-circle'
}); });
var thumbnailButton = tplIconButton('fa fa-picture-o', { var thumbnailButton = tplIconButton(options.iconPrefix + options.icons.image.shapeThumbnail, {
title: lang.image.shapeThumbnail, title: lang.image.shapeThumbnail,
event: 'imageShape', event: 'imageShape',
value: 'img-thumbnail' value: 'img-thumbnail'
}); });
var noneButton = tplIconButton('fa fa-times', { var noneButton = tplIconButton(options.iconPrefix + options.icons.image.shapeNone, {
title: lang.image.shapeNone, title: lang.image.shapeNone,
event: 'imageShape', event: 'imageShape',
value: '' value: ''
}); });
var removeButton = tplIconButton('fa fa-trash-o', { var removeButton = tplIconButton(options.iconPrefix + options.icons.image.remove, {
title: lang.image.remove, title: lang.image.remove,
event: 'removeMedia', event: 'removeMedia',
value: 'none' value: 'none'
}); });
var content = '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>' + var content = (options.disableResizeImage ? '' : '<div class="btn-group">' + fullButton + halfButton + quarterButton + '</div>') +
'<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div>' + '<div class="btn-group">' + leftButton + rightButton + justifyButton + '</div>' +
'<div class="btn-group">' + roundedButton + circleButton + thumbnailButton + noneButton + '</div>' + '<div class="btn-group">' + roundedButton + circleButton + thumbnailButton + noneButton + '</div>' +
'<div class="btn-group">' + removeButton + '</div>'; '<div class="btn-group">' + removeButton + '</div>';
@@ -414,38 +479,50 @@ define([
}; };
var tplAirPopover = function () { var tplAirPopover = function () {
var content = ''; var $content = $('<div />');
for (var idx = 0, len = options.airPopover.length; idx < len; idx ++) { for (var idx = 0, len = options.airPopover.length; idx < len; idx ++) {
var group = options.airPopover[idx]; var group = options.airPopover[idx];
content += '<div class="note-' + group[0] + ' btn-group">';
var $group = $('<div class="note-' + group[0] + ' btn-group">');
for (var i = 0, lenGroup = group[1].length; i < lenGroup; i++) { for (var i = 0, lenGroup = group[1].length; i < lenGroup; i++) {
content += tplButtonInfo[group[1][i]](lang, options); var $button = $(tplButtonInfo[group[1][i]](lang, options));
$button.attr('data-name', group[1][i]);
$group.append($button);
} }
content += '</div>'; $content.append($group);
} }
return tplPopover('note-air-popover', content); return tplPopover('note-air-popover', $content.children());
}; };
return '<div class="note-popover">' + var $notePopover = $('<div class="note-popover" />');
tplLinkPopover() +
tplImagePopover() + $notePopover.append(tplLinkPopover());
(options.airMode ? tplAirPopover() : '') + $notePopover.append(tplImagePopover());
'</div>';
if (options.airMode) {
$notePopover.append(tplAirPopover());
}
return $notePopover;
}; };
this.tplButtonInfo = tplButtonInfo; // odoo change for overwrite this.tplButtonInfo = tplButtonInfo; // ODOO: allow access for override
this.tplPopovers = tplPopovers; // odoo change for overwrite this.tplPopovers = tplPopovers; // ODOO: allow access for override
var tplHandles = function () { var tplHandles = function (options) {
return '<div class="note-handle">' + return '<div class="note-handle">' +
'<div class="note-control-selection">' + '<div class="note-control-selection">' +
'<div class="note-control-selection-bg"></div>' + '<div class="note-control-selection-bg"></div>' +
'<div class="note-control-holder note-control-nw"></div>' + '<div class="note-control-holder note-control-nw"></div>' +
'<div class="note-control-holder note-control-ne"></div>' + '<div class="note-control-holder note-control-ne"></div>' +
'<div class="note-control-holder note-control-sw"></div>' + '<div class="note-control-holder note-control-sw"></div>' +
'<div class="note-control-sizing note-control-se"></div>' + '<div class="' +
'<div class="note-control-selection-info"></div>' + (options.disableResizeImage ? 'note-control-holder' : 'note-control-sizing') +
' note-control-se"></div>' +
(options.disableResizeImage ? '' : '<div class="note-control-selection-info"></div>') +
'</div>' + '</div>' +
'</div>'; '</div>';
}; };
@@ -477,7 +554,6 @@ define([
{ kbd: '⌘ + B', text: lang.font.bold }, { kbd: '⌘ + B', text: lang.font.bold },
{ kbd: '⌘ + I', text: lang.font.italic }, { kbd: '⌘ + I', text: lang.font.italic },
{ kbd: '⌘ + U', text: lang.font.underline }, { kbd: '⌘ + U', text: lang.font.underline },
{ kbd: '⌘ + ⇧ + S', text: lang.font.sdivikethrough },
{ kbd: '⌘ + \\', text: lang.font.clear } { kbd: '⌘ + \\', text: lang.font.clear }
]; ];
@@ -489,7 +565,7 @@ define([
{ kbd: '⌘ + Z', text: lang.history.undo }, { kbd: '⌘ + Z', text: lang.history.undo },
{ kbd: '⌘ + ⇧ + Z', text: lang.history.redo }, { kbd: '⌘ + ⇧ + Z', text: lang.history.redo },
{ kbd: '⌘ + ]', text: lang.paragraph.indent }, { kbd: '⌘ + ]', text: lang.paragraph.indent },
{ kbd: '⌘ + [', text: lang.paragraph.oudivent }, { kbd: '⌘ + [', text: lang.paragraph.outdent },
{ kbd: '⌘ + ENTER', text: lang.hr.insert } { kbd: '⌘ + ENTER', text: lang.hr.insert }
]; ];
@@ -568,27 +644,27 @@ define([
imageLimitation = '<small>' + lang.image.maximumFileSize + ' : ' + readableSize + '</small>'; imageLimitation = '<small>' + lang.image.maximumFileSize + ' : ' + readableSize + '</small>';
} }
var body = '<div class="form-group row-fluid note-group-select-from-files">' + var body = '<div class="form-group row note-group-select-from-files">' +
'<label>' + lang.image.selectFromFiles + '</label>' + '<label>' + lang.image.selectFromFiles + '</label>' +
'<input class="note-image-input" type="file" name="files" accept="image/*" multiple="multiple" />' + '<input class="note-image-input form-control" type="file" name="files" accept="image/*" multiple="multiple" />' +
imageLimitation + imageLimitation +
'</div>' + '</div>' +
'<div class="form-group row-fluid">' + '<div class="form-group row">' +
'<label>' + lang.image.url + '</label>' + '<label>' + lang.image.url + '</label>' +
'<input class="note-image-url form-control span12" type="text" />' + '<input class="note-image-url form-control col-md-12" type="text" />' +
'</div>'; '</div>';
var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>'; var footer = '<button href="#" class="btn btn-primary note-image-btn disabled" disabled>' + lang.image.insert + '</button>';
return tplDialog('note-image-dialog', lang.image.insert, body, footer); return tplDialog('note-image-dialog', lang.image.insert, body, footer);
}, },
link: function (lang, options) { link: function (lang, options) {
var body = '<div class="form-group row-fluid">' + var body = '<div class="form-group row">' +
'<label>' + lang.link.textToDisplay + '</label>' + '<label>' + lang.link.textToDisplay + '</label>' +
'<input class="note-link-text form-control span12" type="text" />' + '<input class="note-link-text form-control col-md-12" type="text" />' +
'</div>' + '</div>' +
'<div class="form-group row-fluid">' + '<div class="form-group row">' +
'<label>' + lang.link.url + '</label>' + '<label>' + lang.link.url + '</label>' +
'<input class="note-link-url form-control span12" type="text" />' + '<input class="note-link-url form-control col-md-12" type="text" value="http://" />' +
'</div>' + '</div>' +
(!options.disableLinkTarget ? (!options.disableLinkTarget ?
'<div class="checkbox">' + '<div class="checkbox">' +
@@ -606,9 +682,9 @@ define([
'<div class="title">' + lang.shortcut.shortcuts + '</div>' + '<div class="title">' + lang.shortcut.shortcuts + '</div>' +
(agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) + (agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
'<p class="text-center">' + '<p class="text-center">' +
'<a href="//hackerwins.github.io/summernote/" target="_blank">Summernote @VERSION</a> · ' + '<a href="//summernote.org/" target="_blank">Summernote @VERSION</a> · ' +
'<a href="//github.com/HackerWins/summernote" target="_blank">Project</a> · ' + '<a href="//github.com/summernote/summernote" target="_blank">Project</a> · ' +
'<a href="//github.com/HackerWins/summernote/issues" target="_blank">Issues</a>' + '<a href="//github.com/summernote/summernote/issues" target="_blank">Issues</a>' +
'</p>'; '</p>';
return tplDialog('note-help-dialog', '', body, ''); return tplDialog('note-help-dialog', '', body, '');
} }
@@ -696,7 +772,7 @@ define([
}); });
}; };
this.createPalette = createPalette; // odoo change for overwrite this.createPalette = createPalette; // ODOO: allow access for override
/** /**
* create summernote layout (air mode) * create summernote layout (air mode)
@@ -709,24 +785,25 @@ define([
var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc']; var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
var id = func.uniqueId(); var id = func.uniqueId();
$holder.addClass('note-air-editor note-editable'); $holder.addClass('note-air-editor note-editable panel-body');
$holder.attr({ $holder.attr({
'data-note-id': id, 'data-note-id': id, // ODOO: we use [data-note-id="{id}"] instead of [id="{id}"]
// 'id': 'note-editor-' + id,
'contentEditable': true 'contentEditable': true
}); });
var body = document.body; var body = document.body;
// create Popover // create Popover
var $popover = $(this.tplPopovers(langInfo, options)); // odoo change for overwrite var $popover = $(this.tplPopovers(langInfo, options)); // ODOO: user (maybe) overrided method
$popover.addClass('note-air-layout'); $popover.addClass('note-air-layout');
$popover.attr('id', 'note-popover-' + id); $popover.attr('id', 'note-popover-' + id);
$popover.appendTo(body); $popover.appendTo(body);
createTooltip($popover, keyMap); createTooltip($popover, keyMap);
this.createPalette($popover, options); // odoo change for overwrite this.createPalette($popover, options); // ODOO: use (maybe) overrided method
// create Handle // create Handle
var $handle = $(tplHandles()); var $handle = $(tplHandles(options));
$handle.addClass('note-air-layout'); $handle.addClass('note-air-layout');
$handle.attr('id', 'note-handle-' + id); $handle.attr('id', 'note-handle-' + id);
$handle.appendTo(body); $handle.appendTo(body);
@@ -751,7 +828,7 @@ define([
var langInfo = options.langInfo; var langInfo = options.langInfo;
//01. create Editor //01. create Editor
var $editor = $('<div class="note-editor"></div>'); var $editor = $('<div class="note-editor panel panel-default" />');
if (options.width) { if (options.width) {
$editor.width(options.width); $editor.width(options.width);
} }
@@ -761,70 +838,81 @@ define([
$('<div class="note-statusbar">' + (options.disableResizeEditor ? '' : tplStatusbar()) + '</div>').prependTo($editor); $('<div class="note-statusbar">' + (options.disableResizeEditor ? '' : tplStatusbar()) + '</div>').prependTo($editor);
} }
//03. create Editable //03 editing area
var $editingArea = $('<div class="note-editing-area" />');
//03. create editable
var isContentEditable = !$holder.is(':disabled'); var isContentEditable = !$holder.is(':disabled');
var $editable = $('<div class="note-editable" contentEditable="' + isContentEditable + '"></div>') var $editable = $('<div class="note-editable panel-body" contentEditable="' + isContentEditable + '"></div>').prependTo($editingArea);
.prependTo($editor);
if (options.height) { if (options.height) {
$editable.height(options.height); $editable.height(options.height);
} }
if (options.direction) { if (options.direction) {
$editable.attr('dir', options.direction); $editable.attr('dir', options.direction);
} }
if (options.placeholder) { var placeholder = $holder.attr('placeholder') || options.placeholder;
$editable.attr('data-placeholder', options.placeholder); if (placeholder) {
$editable.attr('data-placeholder', placeholder);
} }
$editable.html(dom.html($holder)); $editable.html(dom.html($holder) || dom.emptyPara);
//031. create codable //031. create codable
$('<textarea class="note-codable"></textarea>').prependTo($editor); $('<textarea class="note-codable"></textarea>').prependTo($editingArea);
//04. create Toolbar //04. create Popover
var toolbarHTML = ''; var $popover = $(this.tplPopovers(langInfo, options)).prependTo($editingArea); // ODOO: use (maybe) overrided method
this.createPalette($popover, options); // ODOO: use (maybe) overrided method
createTooltip($popover, keyMap);
//05. handle(control selection, ...)
$(tplHandles(options)).prependTo($editingArea);
$editingArea.prependTo($editor);
//06. create Toolbar
var $toolbar = $('<div class="note-toolbar panel-heading" />');
for (var idx = 0, len = options.toolbar.length; idx < len; idx ++) { for (var idx = 0, len = options.toolbar.length; idx < len; idx ++) {
var groupName = options.toolbar[idx][0]; var groupName = options.toolbar[idx][0];
var groupButtons = options.toolbar[idx][1]; var groupButtons = options.toolbar[idx][1];
toolbarHTML += '<div class="note-' + groupName + ' btn-group">'; var $group = $('<div class="note-' + groupName + ' btn-group" />');
for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) { for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) {
var buttonInfo = tplButtonInfo[groupButtons[i]]; var buttonInfo = tplButtonInfo[groupButtons[i]];
// continue creating toolbar even if a button doesn't exist // continue creating toolbar even if a button doesn't exist
if (!$.isFunction(buttonInfo)) { continue; } if (!$.isFunction(buttonInfo)) { continue; }
toolbarHTML += buttonInfo(langInfo, options);
var $button = $(buttonInfo(langInfo, options));
$button.attr('data-name', groupButtons[i]); // set button's alias, becuase to get button element from $toolbar
$group.append($button);
} }
toolbarHTML += '</div>'; $toolbar.append($group);
} }
toolbarHTML = '<div class="note-toolbar btn-toolbar">' + toolbarHTML + '</div>';
var $toolbar = $(toolbarHTML).prependTo($editor);
var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc']; var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
this.createPalette($toolbar, options); // odoo change for overwrite this.createPalette($toolbar, options); // ODOO: use (maybe) overrided method
createTooltip($toolbar, keyMap, 'bottom'); createTooltip($toolbar, keyMap, 'bottom');
$toolbar.prependTo($editor);
//05. create Popover //07. create Dropzone
var $popover = $(this.tplPopovers(langInfo, options)).prependTo($editor); // odoo change for overwrite $('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor);
this.createPalette($popover, options); // odoo change for overwrite
createTooltip($popover, keyMap);
//06. handle(control selection, ...) //08. create Dialog
$(tplHandles()).prependTo($editor); var $dialogContainer = options.dialogsInBody ? $(document.body) : $editor;
var $dialog = $(tplDialogs(langInfo, options)).prependTo($dialogContainer);
//07. create Dialog
var $dialog = $(tplDialogs(langInfo, options)).prependTo($editor);
$dialog.find('button.close, a.modal-close').click(function () { $dialog.find('button.close, a.modal-close').click(function () {
$(this).closest('.modal').modal('hide'); $(this).closest('.modal').modal('hide');
}); });
//08. create Dropzone
$('<div class="note-dropzone"><div class="note-dropzone-message"></div></div>').prependTo($editor);
//09. Editor/Holder switch //09. Editor/Holder switch
$editor.insertAfter($holder); $editor.insertAfter($holder);
$holder.hide(); $holder.hide();
}; };
this.hasNoteEditor = function ($holder) {
return this.noteEditorFromHolder($holder).length > 0;
};
this.noteEditorFromHolder = function ($holder) { this.noteEditorFromHolder = function ($holder) {
if ($holder.hasClass('note-air-editor')) { if ($holder.hasClass('note-air-editor')) {
return $holder; return $holder;
@@ -842,10 +930,6 @@ define([
* @param {Object} options * @param {Object} options
*/ */
this.createLayout = function ($holder, options) { this.createLayout = function ($holder, options) {
if (this.noteEditorFromHolder($holder).length) {
return;
}
if (options.airMode) { if (options.airMode) {
this.createLayoutByAirMode($holder, options); this.createLayoutByAirMode($holder, options);
} else { } else {
@@ -857,20 +941,18 @@ define([
* returns layoutInfo from holder * returns layoutInfo from holder
* *
* @param {jQuery} $holder - placeholder * @param {jQuery} $holder - placeholder
* @returns {Object} * @return {Object}
*/ */
this.layoutInfoFromHolder = function ($holder) { this.layoutInfoFromHolder = function ($holder) {
var $editor = this.noteEditorFromHolder($holder); var $editor = this.noteEditorFromHolder($holder);
if (!$editor.length) { return; } if (!$editor.length) {
return;
var layoutInfo = dom.buildLayoutInfo($editor);
// cache all properties.
for (var key in layoutInfo) {
if (layoutInfo.hasOwnProperty(key)) {
layoutInfo[key] = layoutInfo[key].call();
}
} }
return layoutInfo;
// connect $holder to $editor
$editor.data('holder', $holder);
return dom.buildLayoutInfo($editor);
}; };
/** /**
@@ -884,31 +966,53 @@ define([
this.removeLayout = function ($holder, layoutInfo, options) { this.removeLayout = function ($holder, layoutInfo, options) {
if (options.airMode) { if (options.airMode) {
$holder.removeClass('note-air-editor note-editable') $holder.removeClass('note-air-editor note-editable')
.removeAttr('contentEditable'); .removeAttr('contentEditable'); // ODOO: removed id 'id contentEditable'
layoutInfo.popover.remove(); layoutInfo.popover().remove();
layoutInfo.handle.remove(); layoutInfo.handle().remove();
layoutInfo.dialog.remove(); layoutInfo.dialog().remove();
} else { } else {
$holder.html(layoutInfo.editable.html()); $holder.html(layoutInfo.editable().html());
layoutInfo.editor.remove(); if (options.dialogsInBody) {
layoutInfo.dialog().remove();
}
layoutInfo.editor().remove();
$holder.show(); $holder.show();
} }
}; };
/**
*
* @return {Object}
* @return {function(label, options=):string} return.button {@link #tplButton function to make text button}
* @return {function(iconClass, options=):string} return.iconButton {@link #tplIconButton function to make icon button}
* @return {function(className, title=, body=, footer=):string} return.dialog {@link #tplDialog function to make dialog}
*/
this.getTemplate = function () { this.getTemplate = function () {
return { return {
button: tplButton, button: tplButton,
iconButton: tplIconButton, iconButton: tplIconButton,
dialog: tplDialog dialog: tplDialog,
dropdown: tplDropdown // ODOO: suggest upstream
}; };
}; };
/**
* add button information
*
* @param {String} name button name
* @param {Function} buttonInfo function to make button, reference to {@link #tplButton},{@link #tplIconButton}
*/
this.addButtonInfo = function (name, buttonInfo) { this.addButtonInfo = function (name, buttonInfo) {
tplButtonInfo[name] = buttonInfo; tplButtonInfo[name] = buttonInfo;
}; };
/**
*
* @param {String} name
* @param {Function} dialogInfo function to make dialog, reference to {@link #tplDialog}
*/
this.addDialogInfo = function (name, dialogInfo) { this.addDialogInfo = function (name, dialogInfo) {
tplDialogInfo[name] = dialogInfo; tplDialogInfo[name] = dialogInfo;
}; };
@@ -1,8 +1,8 @@
require.config({ require.config({
baseUrl: 'src/js', baseUrl: 'src/js',
paths: { paths: {
jquery: '//code.jquery.com/jquery-1.9.1.min', jquery: '//code.jquery.com/jquery-1.11.3',
bootstrap: '//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min', bootstrap: '//netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min',
summernotevideo: '/../../plugin/summernote-ext-video', summernotevideo: '/../../plugin/summernote-ext-video',
CodeMirror: '//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/codemirror', CodeMirror: '//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/codemirror',
CodeMirrorXml: '//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/mode/xml/xml.min', CodeMirrorXml: '//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/mode/xml/xml.min',
@@ -1,51 +1,54 @@
define(['jquery'], function ($) { define(['jquery'], function ($) {
if ('function' !== typeof Array.prototype.reduce) { if (!Array.prototype.reduce) {
/** /**
* Array.prototype.reduce fallback * Array.prototype.reduce polyfill
* *
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce * @param {Function} callback
* @param {Value} [initialValue]
* @return {Value}
*
* @see http://goo.gl/WNriQD
*/ */
Array.prototype.reduce = function (callback, optInitialValue) { Array.prototype.reduce = function (callback) {
var idx, value, length = this.length >>> 0, isValueSet = false; var t = Object(this), len = t.length >>> 0, k = 0, value;
if (1 < arguments.length) { if (arguments.length === 2) {
value = optInitialValue; value = arguments[1];
isValueSet = true; } else {
} while (k < len && !(k in t)) {
for (idx = 0; length > idx; ++idx) { k++;
if (this.hasOwnProperty(idx)) {
if (isValueSet) {
value = callback(value, this[idx], idx, this);
} else {
value = this[idx];
isValueSet = true;
}
} }
if (k >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
value = t[k++];
} }
if (!isValueSet) { for (; k < len; k++) {
throw new TypeError('Reduce of empty array with no initial value'); if (k in t) {
value = callback(value, t[k], k, t);
}
} }
return value; return value;
}; };
} }
if ('function' !== typeof Array.prototype.filter) { if ('function' !== typeof Array.prototype.filter) {
Array.prototype.filter = function (fun/*, thisArg*/) { /**
if (this === void 0 || this === null) { * Array.prototype.filter polyfill
throw new TypeError(); *
} * @param {Function} func
* @return {Array}
var t = Object(this); *
var len = t.length >>> 0; * @see http://goo.gl/T1KFnq
if (typeof fun !== 'function') { */
throw new TypeError(); Array.prototype.filter = function (func) {
} var t = Object(this), len = t.length >>> 0;
var res = []; var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0; var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
if (i in t) { if (i in t) {
var val = t[i]; var val = t[i];
if (fun.call(thisArg, val, i, t)) { if (func.call(thisArg, val, i, t)) {
res.push(val); res.push(val);
} }
} }
@@ -55,10 +58,52 @@ define(['jquery'], function ($) {
}; };
} }
if (!Array.prototype.map) {
/**
* Array.prototype.map polyfill
*
* @param {Function} callback
* @return {Array}
*
* @see https://goo.gl/SMWaMK
*/
Array.prototype.map = function (callback, thisArg) {
var T, A, k;
if (this === null) {
throw new TypeError(' this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = thisArg;
}
A = new Array(len);
k = 0;
while (k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
A[k] = mappedValue;
}
k++;
}
return A;
};
}
var isSupportAmd = typeof define === 'function' && define.amd; var isSupportAmd = typeof define === 'function' && define.amd;
/** /**
* returns whether font is installed or not. * returns whether font is installed or not.
*
* @param {String} fontName * @param {String} fontName
* @return {Boolean} * @return {Boolean}
*/ */
@@ -79,13 +124,41 @@ define(['jquery'], function ($) {
return originalWidth !== width; return originalWidth !== width;
}; };
var userAgent = navigator.userAgent;
var isMSIE = /MSIE|Trident/i.test(userAgent);
var browserVersion;
if (isMSIE) {
var matches = /MSIE (\d+[.]\d+)/.exec(userAgent);
if (matches) {
browserVersion = parseFloat(matches[1]);
}
matches = /Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent);
if (matches) {
browserVersion = parseFloat(matches[1]);
}
}
/** /**
* @class core.agent
*
* Object which check platform and agent * Object which check platform and agent
*
* @singleton
* @alternateClassName agent
*/ */
var agent = { var agent = {
/** @property {Boolean} [isMac=false] true if this agent is Mac */
isMac: navigator.appVersion.indexOf('Mac') > -1, isMac: navigator.appVersion.indexOf('Mac') > -1,
isMSIE: navigator.userAgent.indexOf('MSIE') > -1 || navigator.userAgent.indexOf('Trident') > -1, /** @property {Boolean} [isMSIE=false] true if this agent is a Internet Explorer */
isFF: navigator.userAgent.indexOf('Firefox') > -1, isMSIE: isMSIE,
/** @property {Boolean} [isFF=false] true if this agent is a Firefox */
isFF: /firefox/i.test(userAgent),
isWebkit: /webkit/i.test(userAgent),
/** @property {Boolean} [isSafari=false] true if this agent is a Safari */
isSafari: /safari/i.test(userAgent),
/** @property {Float} browserVersion current browser version */
browserVersion: browserVersion,
/** @property {String} jqueryVersion current jQuery version string */
jqueryVersion: parseFloat($.fn.jquery), jqueryVersion: parseFloat($.fn.jquery),
isSupportAmd: isSupportAmd, isSupportAmd: isSupportAmd,
hasCodeMirror: isSupportAmd ? require.specified('CodeMirror') : !!window.CodeMirror, hasCodeMirror: isSupportAmd ? require.specified('CodeMirror') : !!window.CodeMirror,
@@ -1,9 +1,16 @@
define('summernote/core/async', function () { define('summernote/core/async', function () {
/** /**
* @class core.async
*
* Async functions which returns `Promise` * Async functions which returns `Promise`
*
* @singleton
* @alternateClassName async
*/ */
var async = (function () { var async = (function () {
/** /**
* @method readFileAsDataURL
*
* read contents of file as representing URL * read contents of file as representing URL
* *
* @param {File} file * @param {File} file
@@ -24,22 +31,30 @@ define('summernote/core/async', function () {
}; };
/** /**
* @method createImage
*
* create `<image>` from url string * create `<image>` from url string
* *
* @param {String} sUrl * @param {String} sUrl
* @param {String} filename
* @return {Promise} - then: $image * @return {Promise} - then: $image
*/ */
var createImage = function (sUrl, filename) { var createImage = function (sUrl, filename) {
return $.Deferred(function (deferred) { return $.Deferred(function (deferred) {
$('<img>').one('load', function () { var $img = $('<img>');
deferred.resolve($(this));
$img.one('load', function () {
$img.off('error abort');
deferred.resolve($img);
}).one('error abort', function () { }).one('error abort', function () {
deferred.reject($(this).detach()); $img.off('load').detach();
deferred.reject($img);
}).css({ }).css({
display: 'none' display: 'none'
}).appendTo(document.body) }).appendTo(document.body).attr({
.attr('src', sUrl) 'src': sUrl,
.attr('data-filename', filename); 'data-filename': filename
});
}).promise(); }).promise();
}; };
@@ -8,10 +8,17 @@ define([
var ZERO_WIDTH_NBSP_CHAR = '\ufeff'; var ZERO_WIDTH_NBSP_CHAR = '\ufeff';
/** /**
* @class core.dom
*
* Dom functions * Dom functions
*
* @singleton
* @alternateClassName dom
*/ */
var dom = (function () { var dom = (function () {
/** /**
* @method isEditable
*
* returns whether node is `note-editable` or not. * returns whether node is `note-editable` or not.
* *
* @param {Node} node * @param {Node} node
@@ -22,6 +29,8 @@ define([
}; };
/** /**
* @method isControlSizing
*
* returns whether node is `note-control-sizing` or not. * returns whether node is `note-control-sizing` or not.
* *
* @param {Node} node * @param {Node} node
@@ -32,16 +41,28 @@ define([
}; };
/** /**
* @method buildLayoutInfo
*
* build layoutInfo from $editor(.note-editor) * build layoutInfo from $editor(.note-editor)
* *
* @param {jQuery} $editor * @param {jQuery} $editor
* @return {Object} * @return {Object}
* @return {Function} return.editor
* @return {Node} return.dropzone
* @return {Node} return.toolbar
* @return {Node} return.editable
* @return {Node} return.codable
* @return {Node} return.popover
* @return {Node} return.handle
* @return {Node} return.dialog
*/ */
var buildLayoutInfo = function ($editor) { var buildLayoutInfo = function ($editor) {
var makeFinder; var makeFinder;
// air mode // air mode
if ($editor.hasClass('note-air-editor')) { if ($editor.hasClass('note-air-editor')) {
// ODOO: editor on [data-note-id] attribute
// var id = list.last($editor.attr('id').split('-'));
var id = list.last($editor.attr('data-note-id').split('-')); var id = list.last($editor.attr('data-note-id').split('-'));
makeFinder = function (sIdPrefix) { makeFinder = function (sIdPrefix) {
return function () { return $(sIdPrefix + id); }; return function () { return $(sIdPrefix + id); };
@@ -49,6 +70,7 @@ define([
return { return {
editor: function () { return $editor; }, editor: function () { return $editor; },
holder : function () { return $editor.data('holder'); },
editable: function () { return $editor; }, editable: function () { return $editor; },
popover: makeFinder('#note-popover-'), popover: makeFinder('#note-popover-'),
handle: makeFinder('#note-handle-'), handle: makeFinder('#note-handle-'),
@@ -57,11 +79,17 @@ define([
// frame mode // frame mode
} else { } else {
makeFinder = function (sClassName) { makeFinder = function (className, $base) {
return function () { return $editor.find(sClassName); }; $base = $base || $editor;
return function () { return $base.find(className); };
}; };
var options = $editor.data('options');
var $dialogHolder = (options && options.dialogsInBody) ? $(document.body) : null;
return { return {
editor: function () { return $editor; }, editor: function () { return $editor; },
holder : function () { return $editor.data('holder'); },
dropzone: makeFinder('.note-dropzone'), dropzone: makeFinder('.note-dropzone'),
toolbar: makeFinder('.note-toolbar'), toolbar: makeFinder('.note-toolbar'),
editable: makeFinder('.note-editable'), editable: makeFinder('.note-editable'),
@@ -69,16 +97,44 @@ define([
statusbar: makeFinder('.note-statusbar'), statusbar: makeFinder('.note-statusbar'),
popover: makeFinder('.note-popover'), popover: makeFinder('.note-popover'),
handle: makeFinder('.note-handle'), handle: makeFinder('.note-handle'),
dialog: makeFinder('.note-dialog') dialog: makeFinder('.note-dialog', $dialogHolder)
}; };
} }
}; };
/** /**
* returns makeLayoutInfo from editor's descendant node.
*
* @private
* @param {Node} descendant
* @return {Object}
*/
var makeLayoutInfo = function (descendant) {
var $target = $(descendant).closest('.note-editor, .note-air-editor, .note-air-layout');
if (!$target.length) {
return null;
}
var $editor;
if ($target.is('.note-editor, .note-air-editor')) {
$editor = $target;
} else {
// ODOO: editor on [data-note-id] attribute
// $editor = $('#note-editor-' + list.last($target.attr('id').split('-')));
$editor = $('[data-note-id="' + list.last($target.attr('id')) + '"]');
}
return buildLayoutInfo($editor);
};
/**
* @method makePredByNodeName
*
* returns predicate which judge whether nodeName is same * returns predicate which judge whether nodeName is same
* *
* @param {String} nodeName * @param {String} nodeName
* @return {String} * @return {Function}
*/ */
var makePredByNodeName = function (nodeName) { var makePredByNodeName = function (nodeName) {
nodeName = nodeName.toUpperCase(); nodeName = nodeName.toUpperCase();
@@ -87,6 +143,14 @@ define([
}; };
}; };
/**
* @method isText
*
*
*
* @param {Node} node
* @return {Boolean} true if node's type is text(3)
*/
var isText = function (node) { var isText = function (node) {
return node && node.nodeType === 3; return node && node.nodeType === 3;
}; };
@@ -96,7 +160,7 @@ define([
* @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements * @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
*/ */
var isVoid = function (node) { var isVoid = function (node) {
return node && /^BR|^IMG|^HR/.test(node.nodeName.toUpperCase()); return node && /^BR|^IMG|^HR|^IFRAME|^BUTTON/.test(node.nodeName.toUpperCase());
}; };
var isPara = function (node) { var isPara = function (node) {
@@ -114,14 +178,23 @@ define([
return isPara(node) && !isLi(node); return isPara(node) && !isLi(node);
}; };
var isTable = makePredByNodeName('TABLE');
var isInline = function (node) { var isInline = function (node) {
return !isBodyContainer(node) && !isList(node) && !isPara(node); return !isBodyContainer(node) &&
!isList(node) &&
!isHr(node) &&
!isPara(node) &&
!isTable(node) &&
!isBlockquote(node);
}; };
var isList = function (node) { var isList = function (node) {
return node && /^UL|^OL/.test(node.nodeName.toUpperCase()); return node && /^UL|^OL/.test(node.nodeName.toUpperCase());
}; };
var isHr = makePredByNodeName('HR');
var isCell = function (node) { var isCell = function (node) {
return node && /^TD|^TH/.test(node.nodeName.toUpperCase()); return node && /^TD|^TH/.test(node.nodeName.toUpperCase());
}; };
@@ -145,11 +218,48 @@ define([
var isBody = makePredByNodeName('BODY'); var isBody = makePredByNodeName('BODY');
/** /**
* blank HTML for cursor position * returns whether nodeB is closest sibling of nodeA
*
* @param {Node} nodeA
* @param {Node} nodeB
* @return {Boolean}
*/ */
var blankHTML = agent.isMSIE ? '&nbsp;' : '<br>'; var isClosestSibling = function (nodeA, nodeB) {
return nodeA.nextSibling === nodeB ||
nodeA.previousSibling === nodeB;
};
/** /**
* returns array of closest siblings with node
*
* @param {Node} node
* @param {function} [pred] - predicate function
* @return {Node[]}
*/
var withClosestSiblings = function (node, pred) {
pred = pred || func.ok;
var siblings = [];
if (node.previousSibling && pred(node.previousSibling)) {
siblings.push(node.previousSibling);
}
siblings.push(node);
if (node.nextSibling && pred(node.nextSibling)) {
siblings.push(node.nextSibling);
}
return siblings;
};
/**
* blank HTML for cursor position
* - [workaround] old IE only works with &nbsp;
* - [workaround] IE11 and other browser works with bogus br
*/
var blankHTML = agent.isMSIE && agent.browserVersion < 11 ? '&nbsp;' : '<br>';
/**
* @method nodeLength
*
* returns #text's text size or element's childNodes size * returns #text's text size or element's childNodes size
* *
* @param {Node} node * @param {Node} node
@@ -173,9 +283,12 @@ define([
if (len === 0) { if (len === 0) {
return true; return true;
} else if (!dom.isText(node) && len === 1 && node.innerHTML === blankHTML) { } else if (!isText(node) && len === 1 && node.innerHTML === blankHTML) {
// ex) <p><br></p>, <span><br></span> // ex) <p><br></p>, <span><br></span>
return true; return true;
} else if (list.all(node.childNodes, isText) && node.innerHTML === '') {
// ex) <p></p>, <span></span>
return true;
} }
return false; return false;
@@ -206,6 +319,25 @@ define([
return null; return null;
}; };
/**
* find nearest ancestor only single child blood line and predicate hit
*
* @param {Node} node
* @param {Function} pred - predicate function
*/
var singleChildAncestor = function (node, pred) {
node = node.parentNode;
while (node) {
if (nodeLength(node) !== 1) { break; }
if (pred(node)) { return node; }
if (isEditable(node)) { break; }
node = node.parentNode;
}
return null;
};
/** /**
* returns new array of ancestor nodes (until predicate hit). * returns new array of ancestor nodes (until predicate hit).
* *
@@ -419,6 +551,26 @@ define([
return true; return true;
}; };
/**
* returns whether point is left edge of ancestor or not.
* @param {BoundaryPoint} point
* @param {Node} ancestor
* @return {Boolean}
*/
var isLeftEdgePointOf = function (point, ancestor) {
return isLeftEdgePoint(point) && isLeftEdgeOf(point.node, ancestor);
};
/**
* returns whether point is right edge of ancestor or not.
* @param {BoundaryPoint} point
* @param {Node} ancestor
* @return {Boolean}
*/
var isRightEdgePointOf = function (point, ancestor) {
return isRightEdgePoint(point) && isRightEdgeOf(point.node, ancestor);
};
/** /**
* returns offset from parent. * returns offset from parent.
* *
@@ -530,6 +682,8 @@ define([
}; };
/** /**
* @method prevPointUtil
*
* @param {BoundaryPoint} point * @param {BoundaryPoint} point
* @param {Function} pred * @param {Function} pred
* @return {BoundaryPoint} * @return {BoundaryPoint}
@@ -547,6 +701,8 @@ define([
}; };
/** /**
* @method nextPointUntil
*
* @param {BoundaryPoint} point * @param {BoundaryPoint} point
* @param {Function} pred * @param {Function} pred
* @return {BoundaryPoint} * @return {BoundaryPoint}
@@ -564,6 +720,23 @@ define([
}; };
/** /**
* returns whether point has character or not.
*
* @param {Point} point
* @return {Boolean}
*/
var isCharPoint = function (point) {
if (!isText(point.node)) {
return false;
}
var ch = point.node.nodeValue.charAt(point.offset - 1);
return ch && (ch !== ' ' && ch !== NBSP_CHAR);
};
/**
* @method walkPoint
*
* @param {BoundaryPoint} startPoint * @param {BoundaryPoint} startPoint
* @param {BoundaryPoint} endPoint * @param {BoundaryPoint} endPoint
* @param {Function} handler * @param {Function} handler
@@ -587,6 +760,8 @@ define([
}; };
/** /**
* @method makeOffsetPath
*
* return offsetPath(array of offset) from ancestor * return offsetPath(array of offset) from ancestor
* *
* @param {Node} ancestor - ancestor node * @param {Node} ancestor - ancestor node
@@ -594,95 +769,145 @@ define([
*/ */
var makeOffsetPath = function (ancestor, node) { var makeOffsetPath = function (ancestor, node) {
var ancestors = listAncestor(node, func.eq(ancestor)); var ancestors = listAncestor(node, func.eq(ancestor));
return $.map(ancestors, position).reverse(); return ancestors.map(position).reverse();
}; };
/** /**
* @method fromOffsetPath
*
* return element from offsetPath(array of offset) * return element from offsetPath(array of offset)
* *
* @param {Node} ancestor - ancestor node * @param {Node} ancestor - ancestor node
* @param {array} aOffset - offsetPath * @param {array} offsets - offsetPath
*/ */
var fromOffsetPath = function (ancestor, aOffset) { var fromOffsetPath = function (ancestor, offsets) {
var current = ancestor; var current = ancestor;
for (var i = 0, len = aOffset.length; i < len; i++) { for (var i = 0, len = offsets.length; i < len; i++) {
if (current.childNodes.length <= aOffset[i]) { if (current.childNodes.length <= offsets[i]) {
current = current.childNodes[current.childNodes.length - 1]; current = current.childNodes[current.childNodes.length - 1];
} else { } else {
current = current.childNodes[aOffset[i]]; current = current.childNodes[offsets[i]];
} }
} }
return current; return current;
}; };
/** /**
* @method splitNode
*
* split element or #text * split element or #text
* *
* @param {BoundaryPoint} point * @param {BoundaryPoint} point
* @param {Boolean} [isSkipPaddingBlankHTML] * @param {Object} [options]
* @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false
* @param {Boolean} [options.isNotSplitEdgePoint] - default: false
* @return {Node} right node of boundaryPoint * @return {Node} right node of boundaryPoint
*/ */
var splitNode = function (point, isSkipPaddingBlankHTML) { var splitNode = function (point, options) {
// split #text var isSkipPaddingBlankHTML = options && options.isSkipPaddingBlankHTML;
if (isText(point.node)) { var isNotSplitEdgePoint = options && options.isNotSplitEdgePoint;
// edge case
// edge case
if (isEdgePoint(point) && (isText(point.node) || isNotSplitEdgePoint)) {
if (isLeftEdgePoint(point)) { if (isLeftEdgePoint(point)) {
return point.node; return point.node;
} else if (isRightEdgePoint(point)) { } else if (isRightEdgePoint(point)) {
return point.node.nextSibling; return point.node.nextSibling;
} }
}
// split #text
if (isText(point.node)) {
return point.node.splitText(point.offset); return point.node.splitText(point.offset);
} else {
var childNode = point.node.childNodes[point.offset];
var clone = insertAfter(point.node.cloneNode(false), point.node);
appendChildNodes(clone, listNext(childNode));
if (!isSkipPaddingBlankHTML) {
paddingBlankHTML(point.node);
paddingBlankHTML(clone);
}
return clone;
} }
// split element
var childNode = point.node.childNodes[point.offset];
var clone = insertAfter(point.node.cloneNode(false), point.node);
appendChildNodes(clone, listNext(childNode));
if (!isSkipPaddingBlankHTML) {
paddingBlankHTML(point.node);
paddingBlankHTML(clone);
}
return clone;
}; };
/** /**
* @method splitTree
*
* split tree by point * split tree by point
* *
* @param {Node} root - split root * @param {Node} root - split root
* @param {BoundaryPoint} point * @param {BoundaryPoint} point
* @param {Boolean} [isSkipPaddingBlankHTML] * @param {Object} [options]
* @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false
* @param {Boolean} [options.isNotSplitEdgePoint] - default: false
* @return {Node} right node of boundaryPoint * @return {Node} right node of boundaryPoint
*/ */
var splitTree = function (root, point, isSkipPaddingBlankHTML) { var splitTree = function (root, point, options) {
// ex) [#text, <span>, <p>] // ex) [#text, <span>, <p>]
var ancestors = listAncestor(point.node, func.eq(root)); var ancestors = listAncestor(point.node, func.eq(root));
if (!ancestors.length) { if (!ancestors.length) {
return null; return null;
} else if (ancestors.length === 1) { } else if (ancestors.length === 1) {
return splitNode(point, isSkipPaddingBlankHTML); return splitNode(point, options);
} }
return ancestors.reduce(function (node, parent) { return ancestors.reduce(function (node, parent) {
var clone = insertAfter(parent.cloneNode(false), parent);
if (node === point.node) { if (node === point.node) {
node = splitNode(point, isSkipPaddingBlankHTML); node = splitNode(point, options);
} }
appendChildNodes(clone, listNext(node)); return splitNode({
node: parent,
if (!isSkipPaddingBlankHTML) { offset: node ? dom.position(node) : nodeLength(parent)
paddingBlankHTML(parent); }, options);
paddingBlankHTML(clone);
}
return clone;
}); });
}; };
/**
* split point
*
* @param {Point} point
* @param {Boolean} isInline
* @return {Object}
*/
var splitPoint = function (point, isInline) {
// find splitRoot, container
// - inline: splitRoot is a child of paragraph
// - block: splitRoot is a child of bodyContainer
var pred = isInline ? isPara : isBodyContainer;
var ancestors = listAncestor(point.node, pred);
var topAncestor = list.last(ancestors) || point.node;
var splitRoot, container;
if (pred(topAncestor)) {
splitRoot = ancestors[ancestors.length - 2];
container = topAncestor;
} else {
splitRoot = topAncestor;
container = splitRoot.parentNode;
}
// if splitRoot is exists, split with splitTree
var pivot = splitRoot && splitTree(splitRoot, point, {
isSkipPaddingBlankHTML: isInline,
isNotSplitEdgePoint: isInline
});
// if container is point.node, find pivot with point.offset
if (!pivot && container === point.node) {
pivot = point.node.childNodes[point.offset];
}
return {
rightNode: pivot,
container: container
};
};
var create = function (nodeName) { var create = function (nodeName) {
return document.createElement(nodeName); return document.createElement(nodeName);
}; };
@@ -692,7 +917,10 @@ define([
}; };
/** /**
* @method remove
*
* remove node, (isRemoveChild: remove child or not) * remove node, (isRemoveChild: remove child or not)
*
* @param {Node} node * @param {Node} node
* @param {Boolean} isRemoveChild * @param {Boolean} isRemoveChild
*/ */
@@ -717,6 +945,8 @@ define([
}; };
/** /**
* @method removeWhile
*
* @param {Node} node * @param {Node} node
* @param {Function} pred * @param {Function} pred
*/ */
@@ -733,6 +963,8 @@ define([
}; };
/** /**
* @method replace
*
* replace node with provided nodeName * replace node with provided nodeName
* *
* @param {Node} node * @param {Node} node
@@ -760,13 +992,27 @@ define([
var isTextarea = makePredByNodeName('TEXTAREA'); var isTextarea = makePredByNodeName('TEXTAREA');
/** /**
* @param {jQuery} $node
* @param {Boolean} [stripLinebreaks] - default: false
*/
var value = function ($node, stripLinebreaks) {
var val = isTextarea($node[0]) ? $node.val() : $node.html();
if (stripLinebreaks) {
return val.replace(/[\n\r]/g, '');
}
return val;
};
/**
* @method html
*
* get the HTML contents of node * get the HTML contents of node
* *
* @param {jQuery} $node * @param {jQuery} $node
* @param {Boolean} [isNewlineOnBlock] * @param {Boolean} [isNewlineOnBlock]
*/ */
var html = function ($node, isNewlineOnBlock) { var html = function ($node, isNewlineOnBlock) {
var markup = isTextarea($node[0]) ? $node.val() : $node.html(); var markup = value($node);
if (isNewlineOnBlock) { if (isNewlineOnBlock) {
var regexTag = /<(\/?)(\b(?!!)[^>\s]*)(.*?)(\s*\/?>)/g; var regexTag = /<(\/?)(\b(?!!)[^>\s]*)(.*?)(\s*\/?>)/g;
@@ -784,30 +1030,31 @@ define([
return markup; return markup;
}; };
var value = function ($textarea) {
var val = $textarea.val();
// strip line breaks
return val.replace(/[\n\r]/g, '');
};
return { return {
/** @property {String} NBSP_CHAR */
NBSP_CHAR: NBSP_CHAR, NBSP_CHAR: NBSP_CHAR,
/** @property {String} ZERO_WIDTH_NBSP_CHAR */
ZERO_WIDTH_NBSP_CHAR: ZERO_WIDTH_NBSP_CHAR, ZERO_WIDTH_NBSP_CHAR: ZERO_WIDTH_NBSP_CHAR,
/** @property {String} blank */
blank: blankHTML, blank: blankHTML,
/** @property {String} emptyPara */
emptyPara: '<p>' + blankHTML + '</p>', emptyPara: '<p>' + blankHTML + '</p>',
makePredByNodeName: makePredByNodeName,
isEditable: isEditable, isEditable: isEditable,
isControlSizing: isControlSizing, isControlSizing: isControlSizing,
buildLayoutInfo: buildLayoutInfo, buildLayoutInfo: buildLayoutInfo,
makeLayoutInfo: makeLayoutInfo,
isText: isText, isText: isText,
isVoid: isVoid, isVoid: isVoid,
isPara: isPara, isPara: isPara,
isPurePara: isPurePara, isPurePara: isPurePara,
isInline: isInline, isInline: isInline,
isBlock: func.not(isInline),
isBodyInline: isBodyInline, isBodyInline: isBodyInline,
isBody: isBody, isBody: isBody,
isParaInline: isParaInline, isParaInline: isParaInline,
isList: isList, isList: isList,
isTable: makePredByNodeName('TABLE'), isTable: isTable,
isCell: isCell, isCell: isCell,
isBlockquote: isBlockquote, isBlockquote: isBlockquote,
isBodyContainer: isBodyContainer, isBodyContainer: isBodyContainer,
@@ -824,20 +1071,26 @@ define([
isTextarea: isTextarea, isTextarea: isTextarea,
isEmpty: isEmpty, isEmpty: isEmpty,
isEmptyAnchor: func.and(isAnchor, isEmpty), isEmptyAnchor: func.and(isAnchor, isEmpty),
isClosestSibling: isClosestSibling,
withClosestSiblings: withClosestSiblings,
nodeLength: nodeLength, nodeLength: nodeLength,
isLeftEdgePoint: isLeftEdgePoint, isLeftEdgePoint: isLeftEdgePoint,
isRightEdgePoint: isRightEdgePoint, isRightEdgePoint: isRightEdgePoint,
isEdgePoint: isEdgePoint, isEdgePoint: isEdgePoint,
isLeftEdgeOf: isLeftEdgeOf, isLeftEdgeOf: isLeftEdgeOf,
isRightEdgeOf: isRightEdgeOf, isRightEdgeOf: isRightEdgeOf,
isLeftEdgePointOf: isLeftEdgePointOf,
isRightEdgePointOf: isRightEdgePointOf,
prevPoint: prevPoint, prevPoint: prevPoint,
nextPoint: nextPoint, nextPoint: nextPoint,
isSamePoint: isSamePoint, isSamePoint: isSamePoint,
isVisiblePoint: isVisiblePoint, isVisiblePoint: isVisiblePoint,
prevPointUntil: prevPointUntil, prevPointUntil: prevPointUntil,
nextPointUntil: nextPointUntil, nextPointUntil: nextPointUntil,
isCharPoint: isCharPoint,
walkPoint: walkPoint, walkPoint: walkPoint,
ancestor: ancestor, ancestor: ancestor,
singleChildAncestor: singleChildAncestor,
listAncestor: listAncestor, listAncestor: listAncestor,
lastAncestor: lastAncestor, lastAncestor: lastAncestor,
listNext: listNext, listNext: listNext,
@@ -852,6 +1105,7 @@ define([
makeOffsetPath: makeOffsetPath, makeOffsetPath: makeOffsetPath,
fromOffsetPath: fromOffsetPath, fromOffsetPath: fromOffsetPath,
splitTree: splitTree, splitTree: splitTree,
splitPoint: splitPoint,
create: create, create: create,
createText: createText, createText: createText,
remove: remove, remove: remove,
@@ -1,6 +1,11 @@
define('summernote/core/func', function () { define('summernote/core/func', function () {
/** /**
* @class core.func
*
* func utils (for high-order func's arg) * func utils (for high-order func's arg)
*
* @singleton
* @alternateClassName func
*/ */
var func = (function () { var func = (function () {
var eq = function (itemA) { var eq = function (itemA) {
@@ -93,6 +98,18 @@ define('summernote/core/func', function () {
return inverted; return inverted;
}; };
/**
* @param {String} namespace
* @param {String} [prefix]
* @return {String}
*/
var namespaceToCamel = function (namespace, prefix) {
prefix = prefix || '';
return prefix + namespace.split('.').map(function (name) {
return name.substring(0, 1).toUpperCase() + name.substring(1);
}).join('');
};
return { return {
eq: eq, eq: eq,
eq2: eq2, eq2: eq2,
@@ -104,7 +121,8 @@ define('summernote/core/func', function () {
and: and, and: and,
uniqueId: uniqueId, uniqueId: uniqueId,
rect2bnd: rect2bnd, rect2bnd: rect2bnd,
invertObject: invertObject invertObject: invertObject,
namespaceToCamel: namespaceToCamel
}; };
})(); })();
@@ -1,49 +1,96 @@
define([ define([
'summernote/core/list' 'summernote/core/list',
], function (list) { 'summernote/core/func'
], function (list, func) {
/** /**
* @class core.key
*
* Object for keycodes. * Object for keycodes.
*
* @singleton
* @alternateClassName key
*/ */
var key = { var key = (function () {
isEdit: function (keyCode) { var keyMap = {
return list.contains([8, 9, 13, 32], keyCode); 'BACKSPACE': 8,
}, 'TAB': 9,
nameFromCode: { 'ENTER': 13,
'8': 'BACKSPACE', 'SPACE': 32,
'9': 'TAB',
'13': 'ENTER', // Arrow
'32': 'SPACE', 'LEFT': 37,
'UP': 38,
'RIGHT': 39,
'DOWN': 40,
// Number: 0-9 // Number: 0-9
'48': 'NUM0', 'NUM0': 48,
'49': 'NUM1', 'NUM1': 49,
'50': 'NUM2', 'NUM2': 50,
'51': 'NUM3', 'NUM3': 51,
'52': 'NUM4', 'NUM4': 52,
'53': 'NUM5', 'NUM5': 53,
'54': 'NUM6', 'NUM6': 54,
'55': 'NUM7', 'NUM7': 55,
'56': 'NUM8', 'NUM8': 56,
// Alphabet: a-z // Alphabet: a-z
'66': 'B', 'B': 66,
'69': 'E', 'E': 69,
'73': 'I', 'I': 73,
'74': 'J', 'J': 74,
'75': 'K', 'K': 75,
'76': 'L', 'L': 76,
'82': 'R', 'R': 82,
'83': 'S', 'S': 83,
'85': 'U', 'U': 85,
'89': 'Y', 'V': 86,
'90': 'Z', 'Y': 89,
'Z': 90,
'191': 'SLASH', 'SLASH': 191,
'219': 'LEFTBRACKET', 'LEFTBRACKET': 219,
'220': 'BACKSLASH', 'BACKSLASH': 220,
'221': 'RIGHTBRACKET' 'RIGHTBRACKET': 221
} };
};
return {
/**
* @method isEdit
*
* @param {Number} keyCode
* @return {Boolean}
*/
isEdit: function (keyCode) {
return list.contains([
keyMap.BACKSPACE,
keyMap.TAB,
keyMap.ENTER,
keyMap.SPACe
], keyCode);
},
/**
* @method isMove
*
* @param {Number} keyCode
* @return {Boolean}
*/
isMove: function (keyCode) {
return list.contains([
keyMap.LEFT,
keyMap.UP,
keyMap.RIGHT,
keyMap.DOWN
], keyCode);
},
/**
* @property {Object} nameFromCode
* @property {String} nameFromCode.8 "BACKSPACE"
*/
nameFromCode: func.invertObject(keyMap),
code: keyMap
};
})();
return key; return key;
}); });
@@ -1,6 +1,11 @@
define(['summernote/core/func'], function (func) { define(['summernote/core/func'], function (func) {
/** /**
* @class core.list
*
* list utils * list utils
*
* @singleton
* @alternateClassName list
*/ */
var list = (function () { var list = (function () {
/** /**
@@ -63,11 +68,18 @@ define(['summernote/core/func'], function (func) {
return true; return true;
}; };
/**
* returns index of item
*/
var indexOf = function (array, item) {
return $.inArray(item, array);
};
/** /**
* returns true if the value is present in the list. * returns true if the value is present in the list.
*/ */
var contains = function (array, item) { var contains = function (array, item) {
return $.inArray(item, array) !== -1; return indexOf(array, item) !== -1;
}; };
/** /**
@@ -152,7 +164,7 @@ define(['summernote/core/func'], function (func) {
* @param {Array} array * @param {Array} array
*/ */
var next = function (array, item) { var next = function (array, item) {
var idx = array.indexOf(item); var idx = indexOf(array, item);
if (idx === -1) { return null; } if (idx === -1) { return null; }
return array[idx + 1]; return array[idx + 1];
@@ -163,13 +175,12 @@ define(['summernote/core/func'], function (func) {
* @param {Array} array * @param {Array} array
*/ */
var prev = function (array, item) { var prev = function (array, item) {
var idx = array.indexOf(item); var idx = indexOf(array, item);
if (idx === -1) { return null; } if (idx === -1) { return null; }
return array[idx - 1]; return array[idx - 1];
}; };
return { head: head, last: last, initial: initial, tail: tail, return { head: head, last: last, initial: initial, tail: tail,
prev: prev, next: next, find: find, contains: contains, prev: prev, next: next, find: find, contains: contains,
all: all, sum: sum, from: from, all: all, sum: sum, from: from,
@@ -5,13 +5,6 @@ define([
'summernote/core/dom' 'summernote/core/dom'
], function (agent, func, list, dom) { ], function (agent, func, list, dom) {
/**
* Data structure
* - {BoundaryPoint}: a point of dom tree
* - {BoundaryPoints}: two boundaryPoints corresponding to the start and the end of the Range
*
* @see http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Position
*/
var range = (function () { var range = (function () {
/** /**
@@ -118,6 +111,7 @@ define([
/** /**
* Wrapped Range * Wrapped Range
* *
* @constructor
* @param {Node} sc - start container * @param {Node} sc - start container
* @param {Number} so - start offset * @param {Number} so - start offset
* @param {Node} ec - end container * @param {Node} ec - end container
@@ -189,25 +183,48 @@ define([
} else { } else {
nativeRng.select(); nativeRng.select();
} }
return this;
}; };
/** /**
* @return {WrappedRange} * @return {WrappedRange}
*/ */
this.normalize = function () { this.normalize = function () {
var getVisiblePoint = function (point) {
if (!dom.isVisiblePoint(point)) { /**
if (dom.isLeftEdgePoint(point)) { * @param {BoundaryPoint} point
point = dom.nextPointUntil(point, dom.isVisiblePoint); * @param {Boolean} isLeftToRight
} else if (dom.isRightEdgePoint(point)) { * @return {BoundaryPoint}
point = dom.prevPointUntil(point, dom.isVisiblePoint); */
} var getVisiblePoint = function (point, isLeftToRight) {
if ((dom.isVisiblePoint(point) && !dom.isEdgePoint(point)) ||
(dom.isVisiblePoint(point) && dom.isRightEdgePoint(point) && !isLeftToRight) ||
(dom.isVisiblePoint(point) && dom.isLeftEdgePoint(point) && isLeftToRight) ||
(dom.isVisiblePoint(point) && dom.isBlock(point.node) && dom.isEmpty(point.node))) {
return point;
} }
return point;
// point on block's edge
var block = dom.ancestor(point.node, dom.isBlock);
if (((dom.isLeftEdgePointOf(point, block) || dom.isVoid(dom.prevPoint(point).node)) && !isLeftToRight) ||
((dom.isRightEdgePointOf(point, block) || dom.isVoid(dom.nextPoint(point).node)) && isLeftToRight)) {
// returns point already on visible point
if (dom.isVisiblePoint(point)) {
return point;
}
// reverse direction
isLeftToRight = !isLeftToRight;
}
var nextPoint = isLeftToRight ? dom.nextPointUntil(dom.nextPoint(point), dom.isVisiblePoint) :
dom.prevPointUntil(dom.prevPoint(point), dom.isVisiblePoint);
return nextPoint || point;
}; };
var startPoint = getVisiblePoint(this.getStartPoint()); var endPoint = getVisiblePoint(this.getEndPoint(), false);
var endPoint = getVisiblePoint(this.getStartPoint()); var startPoint = this.isCollapsed() ? endPoint : getVisiblePoint(this.getStartPoint(), true);
return new WrappedRange( return new WrappedRange(
startPoint.node, startPoint.node,
@@ -353,42 +370,44 @@ define([
* delete contents on range * delete contents on range
* @return {WrappedRange} * @return {WrappedRange}
*/ */
// this.deleteContents = function () { if(_.isUndefined(this.deleteContents)) // ODOO: ability to override by prototype
// if (this.isCollapsed()) { this.deleteContents = function () {
// return this; if (this.isCollapsed()) {
// } return this;
}
// var rng = this.splitText(); var rng = this.splitText();
// var nodes = rng.nodes(null, { var nodes = rng.nodes(null, {
// fullyContains: true fullyContains: true
// }); });
// var point = dom.prevPointUntil(rng.getStartPoint(), function (point) { // find new cursor point
// return !list.contains(nodes, point.node); var point = dom.prevPointUntil(rng.getStartPoint(), function (point) {
// }); return !list.contains(nodes, point.node);
});
// var emptyParents = []; var emptyParents = [];
// $.each(nodes, function (idx, node) { $.each(nodes, function (idx, node) {
// // find empty parents // find empty parents
// var parent = node.parentNode; var parent = node.parentNode;
// if (point.node !== parent && dom.nodeLength(parent) === 1) { if (point.node !== parent && dom.nodeLength(parent) === 1) {
// emptyParents.push(parent); emptyParents.push(parent);
// } }
// dom.remove(node, false); dom.remove(node, false);
// }); });
// // remove empty parents // remove empty parents
// $.each(emptyParents, function (idx, node) { $.each(emptyParents, function (idx, node) {
// dom.remove(node, false); dom.remove(node, false);
// }); });
// return new WrappedRange( return new WrappedRange(
// point.node, point.node,
// point.offset, point.offset,
// point.node, point.node,
// point.offset point.offset
// ); ).normalize();
// }; };
/** /**
* makeIsOn: return isOn(pred) function * makeIsOn: return isOn(pred) function
@@ -408,36 +427,6 @@ define([
this.isOnAnchor = makeIsOn(dom.isAnchor); this.isOnAnchor = makeIsOn(dom.isAnchor);
// isOnAnchor: judge whether range is on cell node or not // isOnAnchor: judge whether range is on cell node or not
this.isOnCell = makeIsOn(dom.isCell); this.isOnCell = makeIsOn(dom.isCell);
// isOnAnchor: judge whether range is an image node or not
this.isOnImg = function () {
var nb = 0;
var image;
var startPoint = {node: sc.childNodes.length && sc.childNodes[so] || sc};
startPoint.offset = startPoint.node === sc ? so : 0;
var endPoint = {node: ec.childNodes.length && ec.childNodes[eo] || ec};
endPoint.offset = endPoint.node === ec ? eo : 0;
if (dom.isImg(startPoint.node)) {
nb ++;
image = startPoint.node;
}
dom.walkPoint(startPoint, endPoint, function (point) {
if (!dom.isText(endPoint.node) && point.node === endPoint.node && point.offset === endPoint.offset) {
return;
}
var node = point.node.childNodes.length && point.node.childNodes[point.offset] || point.node;
var offset = node === point.node ? point.offset : 0;
var isImg = dom.ancestor(node, dom.isImg);
if (!isImg && ((!dom.isBR(node) && !dom.isText(node)) || (offset && node.textContent.length !== offset && node.textContent.match(/\S|\u00A0/)))) {
nb++;
}
if (isImg && image !== isImg) {
image = isImg;
nb ++;
}
});
return nb === 1 && image;
};
/** /**
* @param {Function} pred * @param {Function} pred
@@ -467,13 +456,20 @@ define([
this.wrapBodyInlineWithPara = function () { this.wrapBodyInlineWithPara = function () {
if (dom.isBodyContainer(sc) && dom.isEmpty(sc)) { if (dom.isBodyContainer(sc) && dom.isEmpty(sc)) {
sc.innerHTML = dom.emptyPara; sc.innerHTML = dom.emptyPara;
return new WrappedRange(sc.firstChild, 0); return new WrappedRange(sc.firstChild, 0, sc.firstChild, 0);
} }
/**
* [workaround] firefox often create range on not visible point. so normalize here.
* - firefox: |<p>text</p>|
* - chrome: <p>|text|</p>
*/
var rng = this.normalize();
if (dom.isParaInline(sc) || dom.isPara(sc)) { if (dom.isParaInline(sc) || dom.isPara(sc)) {
return this.normalize(); return rng;
} }
// ODOO: insert a p tag when try to insert a br with insertNode method, if the editor is inside a p, li, h1... (start_modification
// if apply the editor to a P, LI... or inside a P, LI... // if apply the editor to a P, LI... or inside a P, LI...
if (dom.isText(sc)) { if (dom.isText(sc)) {
var node = sc; var node = sc;
@@ -484,17 +480,18 @@ define([
} }
} }
} }
// ODOO: end_modification)
// find inline top ancestor // find inline top ancestor
var topAncestor; var topAncestor;
if (dom.isInline(sc)) { if (dom.isInline(rng.sc)) {
var ancestors = dom.listAncestor(sc, func.not(dom.isInline)); var ancestors = dom.listAncestor(rng.sc, func.not(dom.isInline));
topAncestor = list.last(ancestors); topAncestor = list.last(ancestors);
if (!dom.isInline(topAncestor)) { if (!dom.isInline(topAncestor)) {
topAncestor = ancestors[ancestors.length - 2] || sc.childNodes[so]; topAncestor = ancestors[ancestors.length - 2] || rng.sc.childNodes[rng.so];
} }
} else { } else {
topAncestor = sc.childNodes[so - 1]; topAncestor = rng.sc.childNodes[rng.so > 0 ? rng.so - 1 : 0];
} }
// siblings not in paragraph // siblings not in paragraph
@@ -514,52 +511,81 @@ define([
* insert node at current cursor * insert node at current cursor
* *
* @param {Node} node * @param {Node} node
* @param {Boolean} [isInline]
* @return {Node} * @return {Node}
*/ */
this.insertNode = function (node, isInline) { this.insertNode = function (node) {
var rng = this.wrapBodyInlineWithPara(); var rng = this.wrapBodyInlineWithPara().deleteContents();
var point = rng.getStartPoint(); // ODOO: override to not split world for inserting inline
// original: var info = dom.splitPoint(rng.getStartPoint(), dom.isInline(node));
var info = dom.splitPoint(rng.getStartPoint(), !dom.isBodyContainer(dom.ancestor(rng.sc, function(node) { return dom.isBodyContainer(node) || dom.isPara(node) })));
var splitRoot, container, pivot; if (info.rightNode) {
if (isInline) { info.rightNode.parentNode.insertBefore(node, info.rightNode);
container = dom.isPara(point.node) ? point.node : point.node.parentNode;
if (dom.isPara(point.node)) {
pivot = point.node.childNodes[point.offset];
} else {
pivot = dom.splitTree(point.node, point);
}
} else { } else {
// splitRoot will be childNode of container info.container.appendChild(node);
var ancestors = dom.listAncestor(point.node, dom.isBodyContainer);
var topAncestor = list.last(ancestors) || point.node;
if (dom.isBodyContainer(topAncestor)) {
splitRoot = ancestors[ancestors.length - 2];
container = topAncestor;
} else {
splitRoot = topAncestor;
container = splitRoot.parentNode;
}
pivot = splitRoot && dom.splitTree(splitRoot, point);
}
if (pivot) {
pivot.parentNode.insertBefore(node, pivot);
} else {
container.appendChild(node);
} }
return node; return node;
}; };
/**
* insert html at current cursor
*/
this.pasteHTML = function (markup) {
var contentsContainer = $('<div></div>').html(markup)[0];
var childNodes = list.from(contentsContainer.childNodes);
var rng = this.wrapBodyInlineWithPara().deleteContents();
return childNodes.reverse().map(function (childNode) {
return rng.insertNode(childNode);
}).reverse();
};
/**
* returns text in range
*
* @return {String}
*/
this.toString = function () { this.toString = function () {
var nativeRng = nativeRange(); var nativeRng = nativeRange();
return agent.isW3CRangeSupport ? nativeRng.toString() : nativeRng.text; return agent.isW3CRangeSupport ? nativeRng.toString() : nativeRng.text;
}; };
/**
* returns range for word before cursor
*
* @param {Boolean} [findAfter] - find after cursor, default: false
* @return {WrappedRange}
*/
this.getWordRange = function (findAfter) {
var endPoint = this.getEndPoint();
if (!dom.isCharPoint(endPoint)) {
return this;
}
var startPoint = dom.prevPointUntil(endPoint, function (point) {
return !dom.isCharPoint(point);
});
if (findAfter) {
endPoint = dom.nextPointUntil(endPoint, function (point) {
return !dom.isCharPoint(point);
});
}
return new WrappedRange(
startPoint.node,
startPoint.offset,
endPoint.node,
endPoint.offset
);
};
/** /**
* create offsetPath bookmark * create offsetPath bookmark
*
* @param {Node} editable * @param {Node} editable
*/ */
this.bookmark = function (editable) { this.bookmark = function (editable) {
@@ -575,6 +601,24 @@ define([
}; };
}; };
/**
* create offsetPath bookmark base on paragraph
*
* @param {Node[]} paras
*/
this.paraBookmark = function (paras) {
return {
s: {
path: list.tail(dom.makeOffsetPath(list.head(paras), sc)),
offset: so
},
e: {
path: list.tail(dom.makeOffsetPath(list.last(paras), ec)),
offset: eo
}
};
};
/** /**
* getClientRects * getClientRects
* @return {Rect[]} * @return {Rect[]}
@@ -585,23 +629,36 @@ define([
}; };
}; };
/**
* @class core.range
*
* Data structure
* * BoundaryPoint: a point of dom tree
* * BoundaryPoints: two boundaryPoints corresponding to the start and the end of the Range
*
* See to http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Position
*
* @singleton
* @alternateClassName range
*/
return { return {
WrappedRange: WrappedRange, // ODOO: give access to WrappedRange
WrappedRange: WrappedRange, // odoo change for overwrite
/** /**
* @method
*
* create Range Object From arguments or Browser Selection * create Range Object From arguments or Browser Selection
* *
* @param {Node} sc - start container * @param {Node} sc - start container
* @param {Number} so - start offset * @param {Number} so - start offset
* @param {Node} ec - end container * @param {Node} ec - end container
* @param {Number} eo - end offset * @param {Number} eo - end offset
* @return {WrappedRange}
*/ */
create : function (sc, so, ec, eo) { create : function (sc, so, ec, eo) {
if (!arguments.length) { // from Browser Selection if (!arguments.length) { // from Browser Selection
if (agent.isW3CRangeSupport) { if (agent.isW3CRangeSupport) {
var selection = document.getSelection(); var selection = document.getSelection();
if (selection.rangeCount === 0) { if (!selection || selection.rangeCount === 0) {
return null; return null;
} else if (dom.isBody(selection.anchorNode)) { } else if (dom.isBody(selection.anchorNode)) {
// Firefox: returns entire body as range on initialization. We won't never need it. // Firefox: returns entire body as range on initialization. We won't never need it.
@@ -635,7 +692,6 @@ define([
ec = endPoint.cont; ec = endPoint.cont;
eo = endPoint.offset; eo = endPoint.offset;
} }
} else if (arguments.length === 2) { //collapsed } else if (arguments.length === 2) { //collapsed
ec = sc; ec = sc;
eo = so; eo = so;
@@ -644,6 +700,8 @@ define([
}, },
/** /**
* @method
*
* create WrappedRange from node * create WrappedRange from node
* *
* @param {Node} node * @param {Node} node
@@ -657,13 +715,13 @@ define([
// browsers can't target a picture or void node // browsers can't target a picture or void node
if (dom.isVoid(sc)) { if (dom.isVoid(sc)) {
so = dom.listPrev(sc).length-1; so = dom.listPrev(sc).length - 1;
sc = sc.parentNode; sc = sc.parentNode;
} }
if (dom.isBR(ec)) { if (dom.isBR(ec)) {
eo = dom.listPrev(ec).length-1; eo = dom.listPrev(ec).length - 1;
ec = ec.parentNode; ec = ec.parentNode;
} else if (dom.isVoid(ec) || dom.isImg(sc)) { } else if (dom.isVoid(ec)) {
eo = dom.listPrev(ec).length; eo = dom.listPrev(ec).length;
ec = ec.parentNode; ec = ec.parentNode;
} }
@@ -672,10 +730,32 @@ define([
}, },
/** /**
* create WrappedRange from Bookmark * create WrappedRange from node after position
*
* @param {Node} node
* @return {WrappedRange}
*/
createFromNodeBefore: function (node) {
return this.createFromNode(node).collapse(true);
},
/**
* create WrappedRange from node after position
*
* @param {Node} node
* @return {WrappedRange}
*/
createFromNodeAfter: function (node) {
return this.createFromNode(node).collapse();
},
/**
* @method
*
* create WrappedRange from bookmark
* *
* @param {Node} editable * @param {Node} editable
* @param {Obkect} bookmark * @param {Object} bookmark
* @return {WrappedRange} * @return {WrappedRange}
*/ */
createFromBookmark : function (editable, bookmark) { createFromBookmark : function (editable, bookmark) {
@@ -683,6 +763,24 @@ define([
var so = bookmark.s.offset; var so = bookmark.s.offset;
var ec = dom.fromOffsetPath(editable, bookmark.e.path); var ec = dom.fromOffsetPath(editable, bookmark.e.path);
var eo = bookmark.e.offset; var eo = bookmark.e.offset;
return new WrappedRange(sc, so, ec, eo);
},
/**
* @method
*
* create WrappedRange from paraBookmark
*
* @param {Object} bookmark
* @param {Node[]} paras
* @return {WrappedRange}
*/
createFromParaBookmark: function (bookmark, paras) {
var so = bookmark.s.offset;
var eo = bookmark.e.offset;
var sc = dom.fromOffsetPath(list.head(paras), bookmark.s.path);
var ec = dom.fromOffsetPath(list.last(paras), bookmark.e.path);
return new WrappedRange(sc, so, ec, eo); return new WrappedRange(sc, so, ec, eo);
} }
}; };
@@ -5,9 +5,17 @@ define([
'summernote/core/range' 'summernote/core/range'
], function (list, func, dom, range) { ], function (list, func, dom, range) {
/**
* @class editing.Bullet
*
* @alternateClassName Bullet
*/
var Bullet = function () { var Bullet = function () {
/** /**
* @method insertOrderedList
*
* toggle ordered list * toggle ordered list
*
* @type command * @type command
*/ */
this.insertOrderedList = function () { this.insertOrderedList = function () {
@@ -15,7 +23,10 @@ define([
}; };
/** /**
* @method insertUnorderedList
*
* toggle unordered list * toggle unordered list
*
* @type command * @type command
*/ */
this.insertUnorderedList = function () { this.insertUnorderedList = function () {
@@ -23,7 +34,10 @@ define([
}; };
/** /**
* @method indent
*
* indent * indent
*
* @type command * @type command
*/ */
this.indent = function () { this.indent = function () {
@@ -50,7 +64,10 @@ define([
}; };
/** /**
* @method outdent
*
* outdent * outdent
*
* @type command * @type command
*/ */
this.outdent = function () { this.outdent = function () {
@@ -78,7 +95,10 @@ define([
}; };
/** /**
* @method toggleList
*
* toggle list * toggle list
*
* @param {String} listName - OL or UL * @param {String} listName - OL or UL
*/ */
this.toggleList = function (listName) { this.toggleList = function (listName) {
@@ -86,13 +106,16 @@ define([
var rng = range.create().wrapBodyInlineWithPara(); var rng = range.create().wrapBodyInlineWithPara();
var paras = rng.nodes(dom.isPara, { includeAncestor: true }); var paras = rng.nodes(dom.isPara, { includeAncestor: true });
var bookmark = rng.paraBookmark(paras);
var clustereds = list.clusterBy(paras, func.peq2('parentNode')); var clustereds = list.clusterBy(paras, func.peq2('parentNode'));
// paragraph to list // paragraph to list
if (list.find(paras, dom.isPurePara)) { if (list.find(paras, dom.isPurePara)) {
var wrappedParas = [];
$.each(clustereds, function (idx, paras) { $.each(clustereds, function (idx, paras) {
self.wrapList(paras, listName); wrappedParas = wrappedParas.concat(self.wrapList(paras, listName));
}); });
paras = wrappedParas;
// list to paragraph or change list style // list to paragraph or change list style
} else { } else {
var diffLists = rng.nodes(dom.isList, { var diffLists = rng.nodes(dom.isList, {
@@ -106,16 +129,19 @@ define([
dom.replace(listNode, listName); dom.replace(listNode, listName);
}); });
} else { } else {
this.releaseList(clustereds, true); paras = this.releaseList(clustereds, true);
} }
} }
rng.select(); range.createFromParaBookmark(bookmark, paras).select();
}; };
/** /**
* @method wrapList
*
* @param {Node[]} paras * @param {Node[]} paras
* @param {String} listName * @param {String} listName
* @return {Node[]}
*/ */
this.wrapList = function (paras, listName) { this.wrapList = function (paras, listName) {
var head = list.head(paras); var head = list.head(paras);
@@ -127,7 +153,7 @@ define([
var listNode = prevList || dom.insertAfter(dom.create(listName || 'UL'), last); var listNode = prevList || dom.insertAfter(dom.create(listName || 'UL'), last);
// P to LI // P to LI
paras = $.map(paras, function (para) { paras = paras.map(function (para) {
return dom.isPurePara(para) ? dom.replace(para, 'LI') : para; return dom.isPurePara(para) ? dom.replace(para, 'LI') : para;
}); });
@@ -138,9 +164,13 @@ define([
dom.appendChildNodes(listNode, list.from(nextList.childNodes)); dom.appendChildNodes(listNode, list.from(nextList.childNodes));
dom.remove(nextList); dom.remove(nextList);
} }
return paras;
}; };
/** /**
* @method releaseList
*
* @param {Array[]} clustereds * @param {Array[]} clustereds
* @param {Boolean} isEscapseToBody * @param {Boolean} isEscapseToBody
* @return {Node[]} * @return {Node[]}
@@ -157,19 +187,23 @@ define([
var lastList = headList.childNodes.length > 1 ? dom.splitTree(headList, { var lastList = headList.childNodes.length > 1 ? dom.splitTree(headList, {
node: last.parentNode, node: last.parentNode,
offset: dom.position(last) + 1 offset: dom.position(last) + 1
}, true) : null; }, {
isSkipPaddingBlankHTML: true
}) : null;
var middleList = dom.splitTree(headList, { var middleList = dom.splitTree(headList, {
node: head.parentNode, node: head.parentNode,
offset: dom.position(head) offset: dom.position(head)
}, true); }, {
isSkipPaddingBlankHTML: true
});
paras = isEscapseToBody ? dom.listDescendant(middleList, dom.isLi) : paras = isEscapseToBody ? dom.listDescendant(middleList, dom.isLi) :
list.from(middleList.childNodes).filter(dom.isLi); list.from(middleList.childNodes).filter(dom.isLi);
// LI to P // LI to P
if (isEscapseToBody || !dom.isList(headList.parentNode)) { if (isEscapseToBody || !dom.isList(headList.parentNode)) {
paras = $.map(paras, function (para) { paras = paras.map(function (para) {
return dom.replace(para, 'P'); return dom.replace(para, 'P');
}); });
} }
@@ -11,46 +11,70 @@ define([
'summernote/editing/Bullet' 'summernote/editing/Bullet'
], function (agent, func, list, dom, range, async, ], function (agent, func, list, dom, range, async,
Style, Typing, Table, Bullet) { Style, Typing, Table, Bullet) {
/**
* Editor
* @class
*/
var Editor = function () {
var KEY_BOGUS = 'bogus';
/**
* @class editing.Editor
*
* Editor
*
*/
var Editor = function (handler) {
var self = this;
var style = new Style(); var style = new Style();
var table = new Table(); var table = new Table();
var typing = new Typing(); var typing = new Typing();
var bullet = new Bullet(); var bullet = new Bullet();
this.style = style; // odoo change for overwrite this.style = style; // ODOO: allow access for override
this.table = table; // odoo change for overwrite this.table = table; // ODOO: allow access for override
this.typing = typing; // odoo change for overwrite this.typing = typing; // ODOO: allow access for override
this.bullet = bullet; // odoo change for overwrite this.bullet = bullet; // ODOO: allow access for override
/** /**
* @method createRange
*
* create range * create range
*
* @param {jQuery} $editable
* @return {WrappedRange}
*/ */
this.createRange = function ($editable) { this.createRange = function ($editable) {
$editable.focus(); this.focus($editable);
return range.create(); return range.create();
}; };
/** /**
* @method saveRange
*
* save current range * save current range
* *
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {Boolean} [thenCollapse=false]
*/ */
this.saveRange = function ($editable, thenCollapse) { this.saveRange = function ($editable, thenCollapse) {
// ODOO: scroll to top when click on input in editable m (start_modification
// this.focus($editable);
var r = range.create(); var r = range.create();
if (!r || ($editable[0] !== r.sc && !$.contains($editable[0], r.sc))) { if (!r || ($editable[0] !== r.sc && !$.contains($editable[0], r.sc))) {
$editable.focus(); $editable.focus();
} }
// ODOO: end_modication)
$editable.data('range', range.create()); $editable.data('range', range.create());
if (thenCollapse) { if (thenCollapse) {
range.create().collapse().select(); range.create().collapse().select();
} }
}; };
/**
* @method saveRange
*
* save current node list to $editable.data('childNodes')
*
* @param {jQuery} $editable
*/
this.saveNode = function ($editable) { this.saveNode = function ($editable) {
// copy child node reference // copy child node reference
var copy = []; var copy = [];
@@ -61,6 +85,8 @@ define([
}; };
/** /**
* @method restoreRange
*
* restore lately range * restore lately range
* *
* @param {jQuery} $editable * @param {jQuery} $editable
@@ -69,10 +95,17 @@ define([
var rng = $editable.data('range'); var rng = $editable.data('range');
if (rng) { if (rng) {
rng.select(); rng.select();
$editable.focus(); this.focus($editable);
} }
}; };
/**
* @method restoreNode
*
* restore lately node list
*
* @param {jQuery} $editable
*/
this.restoreNode = function ($editable) { this.restoreNode = function ($editable) {
$editable.html(''); $editable.html('');
var child = $editable.data('childNodes'); var child = $editable.data('childNodes');
@@ -80,106 +113,271 @@ define([
$editable[0].appendChild(child[index]); $editable[0].appendChild(child[index]);
} }
}; };
/** /**
* @method currentStyle
*
* current style * current style
*
* @param {Node} target
* @return {Object|Boolean} unfocus
*/ */
this.currentStyle = function () { this.currentStyle = function (target) {
var rng = range.create(); var rng = range.create();
return rng ? rng.isOnEditable() && style.current(rng) : false; var styleInfo = rng && rng.isOnEditable() ? style.current(rng.normalize()) : {};
}; if (dom.isImg(target)) {
styleInfo.image = target;
var triggerOnChange = this.triggerOnChange = function ($editable) {
var onChange = $editable.data('callbacks').onChange;
if (onChange) {
onChange($editable.html(), $editable);
} }
return styleInfo;
}; };
/** /**
* style from node
*
* @param {jQuery} $node
* @return {Object}
*/
this.styleFromNode = function ($node) {
return style.fromNode($node);
};
var triggerOnBeforeChange = function ($editable) {
var $holder = dom.makeLayoutInfo($editable).holder();
handler.bindCustomEvent(
$holder, $editable.data('callbacks'), 'before.command'
)($editable.html(), $editable);
};
var triggerOnChange = function ($editable) {
var $holder = dom.makeLayoutInfo($editable).holder();
handler.bindCustomEvent(
$holder, $editable.data('callbacks'), 'change'
)($editable.html(), $editable);
};
/**
* @method undo
* undo * undo
* @param {jQuery} $editable * @param {jQuery} $editable
*/ */
this.undo = function ($editable) { this.undo = function ($editable) {
triggerOnBeforeChange($editable);
$editable.data('NoteHistory').undo(); $editable.data('NoteHistory').undo();
triggerOnChange($editable); triggerOnChange($editable);
}; };
/** /**
* @method redo
* redo * redo
* @param {jQuery} $editable * @param {jQuery} $editable
*/ */
this.redo = function ($editable) { this.redo = function ($editable) {
triggerOnBeforeChange($editable);
$editable.data('NoteHistory').redo(); $editable.data('NoteHistory').redo();
triggerOnChange($editable); triggerOnChange($editable);
}; };
/** /**
* after command * @method beforeCommand
* before command
* @param {jQuery} $editable * @param {jQuery} $editable
*/ */
var afterCommand = this.afterCommand = function ($editable) { var beforeCommand = this.beforeCommand = function ($editable) {
$editable.data('NoteHistory').recordUndo(); triggerOnBeforeChange($editable);
triggerOnChange($editable); // keep focus on editable before command execution
self.focus($editable);
}; };
/**
* @method afterCommand
* after command
* @param {jQuery} $editable
* @param {Boolean} isPreventTrigger
*/
var afterCommand = this.afterCommand = function ($editable, isPreventTrigger) {
$editable.data('NoteHistory').recordUndo();
if (!isPreventTrigger) {
triggerOnChange($editable);
}
};
/**
* @method bold
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method italic
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method underline
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method strikethrough
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method formatBlock
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method superscript
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method subscript
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method justifyLeft
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method justifyCenter
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method justifyRight
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method justifyFull
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method formatBlock
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method removeFormat
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method backColor
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method foreColor
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method insertHorizontalRule
* @param {jQuery} $editable
* @param {Mixed} value
*/
/**
* @method fontName
*
* change font name
*
* @param {jQuery} $editable
* @param {Mixed} value
*/
/* jshint ignore:start */ /* jshint ignore:start */
// native commands(with execCommand), generate function for execCommand // native commands(with execCommand), generate function for execCommand
var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript',
'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull',
'formatBlock', 'removeFormat', 'formatBlock', 'removeFormat',
'backColor', 'foreColor', 'insertHorizontalRule', 'fontName']; 'backColor', 'foreColor', 'fontName'];
for (var idx = 0, len = commands.length; idx < len; idx ++) { for (var idx = 0, len = commands.length; idx < len; idx ++) {
this[commands[idx]] = (function (sCmd) { this[commands[idx]] = (function (sCmd) {
return function ($editable, value) { return function ($editable, value) {
beforeCommand($editable);
document.execCommand(sCmd, false, value); document.execCommand(sCmd, false, value);
afterCommand($editable); afterCommand($editable, true);
}; };
})(commands[idx]); })(commands[idx]);
} }
/* jshint ignore:end */ /* jshint ignore:end */
/** /**
* @method tab
*
* handle tab key * handle tab key
* *
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {Object} options * @param {Object} options
*/ */
this.tab = function ($editable, options) { this.tab = function ($editable, options) {
var rng = range.create(); var rng = this.createRange($editable);
if (rng.isCollapsed() && rng.isOnCell()) { if (rng.isCollapsed() && rng.isOnCell()) {
table.tab(rng); table.tab(rng);
} else { } else {
beforeCommand($editable);
typing.insertTab($editable, rng, options.tabsize); typing.insertTab($editable, rng, options.tabsize);
afterCommand($editable); afterCommand($editable);
} }
}; };
/** /**
* @method untab
*
* handle shift+tab key * handle shift+tab key
*
*/ */
this.untab = function () { this.untab = function ($editable) {
var rng = range.create(); var rng = this.createRange($editable);
if (rng.isCollapsed() && rng.isOnCell()) { if (rng.isCollapsed() && rng.isOnCell()) {
table.tab(rng, true); table.tab(rng, true);
} }
}; };
/** /**
* @method insertParagraph
*
* insert paragraph * insert paragraph
* *
* @param {Node} $editable * @param {Node} $editable
*/ */
this.insertParagraph = function ($editable) { this.insertParagraph = function ($editable) {
beforeCommand($editable);
typing.insertParagraph($editable); typing.insertParagraph($editable);
afterCommand($editable); afterCommand($editable);
}; };
/** /**
* @method insertOrderedList
*
* @param {jQuery} $editable * @param {jQuery} $editable
*/ */
this.insertOrderedList = function ($editable) { this.insertOrderedList = function ($editable) {
beforeCommand($editable);
bullet.insertOrderedList($editable); bullet.insertOrderedList($editable);
afterCommand($editable); afterCommand($editable);
}; };
@@ -188,6 +386,7 @@ define([
* @param {jQuery} $editable * @param {jQuery} $editable
*/ */
this.insertUnorderedList = function ($editable) { this.insertUnorderedList = function ($editable) {
beforeCommand($editable);
bullet.insertUnorderedList($editable); bullet.insertUnorderedList($editable);
afterCommand($editable); afterCommand($editable);
}; };
@@ -196,6 +395,7 @@ define([
* @param {jQuery} $editable * @param {jQuery} $editable
*/ */
this.indent = function ($editable) { this.indent = function ($editable) {
beforeCommand($editable);
bullet.indent($editable); bullet.indent($editable);
afterCommand($editable); afterCommand($editable);
}; };
@@ -204,6 +404,7 @@ define([
* @param {jQuery} $editable * @param {jQuery} $editable
*/ */
this.outdent = function ($editable) { this.outdent = function ($editable) {
beforeCommand($editable);
bullet.outdent($editable); bullet.outdent($editable);
afterCommand($editable); afterCommand($editable);
}; };
@@ -216,28 +417,32 @@ define([
*/ */
this.insertImage = function ($editable, sUrl, filename) { this.insertImage = function ($editable, sUrl, filename) {
async.createImage(sUrl, filename).then(function ($image) { async.createImage(sUrl, filename).then(function ($image) {
beforeCommand($editable);
$image.css({ $image.css({
display: '', display: '',
width: Math.min($editable.width(), $image.width()) width: Math.min($editable.width(), $image.width())
}); });
range.create().insertNode($image[0]); range.create().insertNode($image[0]);
range.createFromNodeAfter($image[0]).select();
afterCommand($editable); afterCommand($editable);
}).fail(function () { }).fail(function () {
var callbacks = $editable.data('callbacks'); var $holder = dom.makeLayoutInfo($editable).holder();
if (callbacks.onImageUploadError) { handler.bindCustomEvent(
callbacks.onImageUploadError(); $holder, $editable.data('callbacks'), 'image.upload.error'
} )();
}); });
}; };
/** /**
* @method insertNode
* insert node * insert node
* @param {Node} $editable * @param {Node} $editable
* @param {Node} node * @param {Node} node
* @param {Boolean} [isInline]
*/ */
this.insertNode = function ($editable, node, isInline) { this.insertNode = function ($editable, node) {
range.create().insertNode(node, isInline); beforeCommand($editable);
range.create().insertNode(node);
range.createFromNodeAfter(node).select();
afterCommand($editable); afterCommand($editable);
}; };
@@ -247,11 +452,24 @@ define([
* @param {String} text * @param {String} text
*/ */
this.insertText = function ($editable, text) { this.insertText = function ($editable, text) {
var textNode = this.createRange($editable).insertNode(dom.createText(text), true); beforeCommand($editable);
var textNode = range.create().insertNode(dom.createText(text));
range.create(textNode, dom.nodeLength(textNode)).select(); range.create(textNode, dom.nodeLength(textNode)).select();
afterCommand($editable); afterCommand($editable);
}; };
/**
* paste HTML
* @param {Node} $editable
* @param {String} markup
*/
this.pasteHTML = function ($editable, markup) {
beforeCommand($editable);
var contents = range.create().pasteHTML(markup);
range.createFromNodeAfter(list.last(contents)).select();
afterCommand($editable);
};
/** /**
* formatBlock * formatBlock
* *
@@ -259,12 +477,15 @@ define([
* @param {String} tagName * @param {String} tagName
*/ */
this.formatBlock = function ($editable, tagName) { this.formatBlock = function ($editable, tagName) {
beforeCommand($editable);
// [workaround] for MSIE, IE need `<`
tagName = agent.isMSIE ? '<' + tagName + '>' : tagName; tagName = agent.isMSIE ? '<' + tagName + '>' : tagName;
document.execCommand('FormatBlock', false, tagName); document.execCommand('FormatBlock', false, tagName);
afterCommand($editable); afterCommand($editable);
}; };
this.formatPara = function ($editable) { this.formatPara = function ($editable) {
beforeCommand($editable);
this.formatBlock($editable, 'P'); this.formatBlock($editable, 'P');
afterCommand($editable); afterCommand($editable);
}; };
@@ -280,33 +501,84 @@ define([
/* jshint ignore:end */ /* jshint ignore:end */
/** /**
* fontsize * fontSize
* FIXME: Still buggy
* *
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {String} value - px * @param {String} value - px
*/ */
this.fontSize = function ($editable, value) { this.fontSize = function ($editable, value) {
document.execCommand('fontSize', false, 3); var rng = range.create();
if (agent.isFF) {
// firefox: <font size="3"> to <span style='font-size={value}px;'>, buggy if (rng.isCollapsed()) {
$editable.find('font[size=3]').removeAttr('size').css('font-size', value + 'px'); var spans = style.styleNodes(rng);
var firstSpan = list.head(spans);
$(spans).css({
'font-size': value + 'px'
});
// [workaround] added styled bogus span for style
// - also bogus character needed for cursor position
if (firstSpan && !dom.nodeLength(firstSpan)) {
firstSpan.innerHTML = dom.ZERO_WIDTH_NBSP_CHAR;
range.createFromNodeAfter(firstSpan.firstChild).select();
$editable.data(KEY_BOGUS, firstSpan);
}
} else { } else {
// chrome: <span style="font-size: medium"> to <span style='font-size={value}px;'> beforeCommand($editable);
$editable.find('span').filter(function () { $(style.styleNodes(rng)).css({
return this.style.fontSize === 'medium'; 'font-size': value + 'px'
}).css('font-size', value + 'px'); });
afterCommand($editable);
}
};
/**
* insert horizontal rule
* @param {jQuery} $editable
*/
this.insertHorizontalRule = function ($editable) {
beforeCommand($editable);
var rng = range.create();
var hrNode = rng.insertNode($('<HR/>')[0]);
if (hrNode.nextSibling) {
range.create(hrNode.nextSibling, 0).normalize().select();
} }
afterCommand($editable); afterCommand($editable);
}; };
/**
* remove bogus node and character
*/
this.removeBogus = function ($editable) {
var bogusNode = $editable.data(KEY_BOGUS);
if (!bogusNode) {
return;
}
var textNode = list.find(list.from(bogusNode.childNodes), dom.isText);
var bogusCharIdx = textNode.nodeValue.indexOf(dom.ZERO_WIDTH_NBSP_CHAR);
if (bogusCharIdx !== -1) {
textNode.deleteData(bogusCharIdx, 1);
}
if (dom.isEmpty(bogusNode)) {
dom.remove(bogusNode);
}
$editable.removeData(KEY_BOGUS);
};
/** /**
* lineHeight * lineHeight
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {String} value * @param {String} value
*/ */
this.lineHeight = function ($editable, value) { this.lineHeight = function ($editable, value) {
beforeCommand($editable);
style.stylePara(range.create(), { style.stylePara(range.create(), {
lineHeight: value lineHeight: value
}); });
@@ -321,21 +593,20 @@ define([
* @param {jQuery} $editable * @param {jQuery} $editable
*/ */
this.unlink = function ($editable) { this.unlink = function ($editable) {
var rng = range.create(); var rng = this.createRange($editable);
if (rng.isOnAnchor()) { if (rng.isOnAnchor()) {
var anchor = dom.ancestor(rng.sc, dom.isAnchor); var anchor = dom.ancestor(rng.sc, dom.isAnchor);
rng = range.createFromNode(anchor); rng = range.createFromNode(anchor);
rng.select(); rng.select();
document.execCommand('unlink');
beforeCommand($editable);
document.execCommand('unlink');
afterCommand($editable); afterCommand($editable);
} }
}; };
/** /**
* create link * create link (command)
*
* @type command
* *
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {Object} linkInfo * @param {Object} linkInfo
@@ -344,28 +615,54 @@ define([
this.createLink = function ($editable, linkInfo, options) { this.createLink = function ($editable, linkInfo, options) {
var linkUrl = linkInfo.url; var linkUrl = linkInfo.url;
var linkText = linkInfo.text; var linkText = linkInfo.text;
var isNewWindow = linkInfo.newWindow; var isNewWindow = linkInfo.isNewWindow;
var className = linkInfo.className || null; var className = linkInfo.className || null; // ODOO: addition
var rng = linkInfo.range; var rng = linkInfo.range || this.createRange($editable);
var isTextChanged = rng.toString() !== linkText;
options = options || dom.makeLayoutInfo($editable).editor().data('options');
beforeCommand($editable);
if (options.onCreateLink) { if (options.onCreateLink) {
linkUrl = options.onCreateLink(linkUrl); linkUrl = options.onCreateLink(linkUrl);
} }
rng = rng.deleteContents(true); var anchors = [];
var anchor = dom.ancestor(rng.sc, dom.isAnchor); if (isTextChanged) {
// Create a new link when text changed.
// Create a new link when there is no anchor on range. var anchor = rng.insertNode($('<A>' + linkText + '</A>')[0]);
if (!anchor) { anchors.push(anchor);
anchor = rng.insertNode($('<A>' + linkText + '</A>')[0], true); } else {
anchors = style.styleNodes(rng, {
nodeName: 'A',
expandClosestSibling: true,
onlyPartialContains: true
});
} }
$(anchor).html(linkText).attr({
'href': linkUrl, $.each(anchors, function (idx, anchor) {
'target': isNewWindow ? '_blank' : null, $(anchor).attr('href', linkUrl);
'class': className $(anchor).attr('class', className); // ODOO: addition
if (isNewWindow) {
$(anchor).attr('target', '_blank');
} else {
$(anchor).removeAttr('target');
}
}); });
range.createFromNode(anchor).select(); var startRange = range.createFromNodeBefore(list.head(anchors));
var startPoint = startRange.getStartPoint();
var endRange = range.createFromNodeAfter(list.last(anchors));
var endPoint = endRange.getEndPoint();
range.create(
startPoint.node,
startPoint.offset,
endPoint.node,
endPoint.offset
).select();
afterCommand($editable); afterCommand($editable);
}; };
@@ -373,9 +670,13 @@ define([
* returns link info * returns link info
* *
* @return {Object} * @return {Object}
* @return {WrappedRange} return.range
* @return {String} return.text
* @return {Boolean} [return.isNewWindow=true]
* @return {String} [return.url=""]
*/ */
this.getLinkInfo = function ($editable) { this.getLinkInfo = function ($editable) {
$editable.focus(); this.focus($editable);
var rng = range.create().expand(dom.isAnchor); var rng = range.create().expand(dom.isAnchor);
@@ -385,50 +686,76 @@ define([
return { return {
range: rng, range: rng,
text: rng.toString(), text: rng.toString(),
isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : true, isNewWindow: $anchor.length ? $anchor.attr('target') === '_blank' : false,
url: $anchor.length ? $anchor.attr('href') : '' url: $anchor.length ? $anchor.attr('href') : ''
}; };
}; };
/**
* setting color
*
* @param {Node} $editable
* @param {Object} sObjColor color code
* @param {String} sObjColor.foreColor foreground color
* @param {String} sObjColor.backColor background color
*/
this.color = function ($editable, sObjColor) { this.color = function ($editable, sObjColor) {
var oColor = JSON.parse(sObjColor); var oColor = JSON.parse(sObjColor);
var foreColor = oColor.foreColor, backColor = oColor.backColor; var foreColor = oColor.foreColor, backColor = oColor.backColor;
beforeCommand($editable);
if (foreColor) { document.execCommand('foreColor', false, foreColor); } if (foreColor) { document.execCommand('foreColor', false, foreColor); }
if (backColor) { document.execCommand('backColor', false, backColor); } if (backColor) { document.execCommand('backColor', false, backColor); }
afterCommand($editable); afterCommand($editable);
}; };
/**
* insert Table
*
* @param {Node} $editable
* @param {String} sDim dimension of table (ex : "5x5")
*/
this.insertTable = function ($editable, sDim) { this.insertTable = function ($editable, sDim) {
var dimension = sDim.split('x'); var dimension = sDim.split('x');
var rng = range.create(); beforeCommand($editable);
rng = rng.deleteContents();
var rng = range.create().deleteContents();
rng.insertNode(table.createTable(dimension[0], dimension[1])); rng.insertNode(table.createTable(dimension[0], dimension[1]));
afterCommand($editable); afterCommand($editable);
}; };
/** /**
* @return {Node} image from the current range selection * float me
*/ *
function getImgTarget () {
var rng = range.create();
var target = rng.sc.childNodes.length && rng.sc.childNodes[rng.so] || rng.sc;
return target;
}
/**
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {String} value * @param {String} value
* @param {jQuery} $target
*/ */
this.floatMe = function ($editable, value) { this.floatMe = function ($editable, value, $target) {
var $target = $( getImgTarget() ); beforeCommand($editable);
// bootstrap
$target.removeClass('pull-left pull-right');
if (value && value !== 'none') {
$target.addClass('pull-' + value);
}
// fallback for non-bootstrap
$target.css('float', value); $target.css('float', value);
afterCommand($editable); afterCommand($editable);
}; };
this.imageShape = function ($editable, value) { /**
var $target = $( getImgTarget() ); * change image shape
*
* @param {jQuery} $editable
* @param {String} value css class
* @param {Node} $target
*/
this.imageShape = function ($editable, value, $target) {
beforeCommand($editable);
$target.removeClass('img-rounded img-circle img-thumbnail'); $target.removeClass('img-rounded img-circle img-thumbnail');
if (value) { if (value) {
@@ -442,9 +769,11 @@ define([
* resize overlay element * resize overlay element
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {String} value * @param {String} value
* @param {jQuery} $target - target element
*/ */
this.resize = function ($editable, value) { this.resize = function ($editable, value, $target) {
var $target = $( getImgTarget() ); beforeCommand($editable);
$target.css({ $target.css({
width: value * 100 + '%', width: value * 100 + '%',
height: '' height: ''
@@ -482,13 +811,50 @@ define([
* *
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {String} value - dummy argument (for keep interface) * @param {String} value - dummy argument (for keep interface)
* @param {jQuery} $target - target element
*/ */
this.removeMedia = function ($editable, value) { this.removeMedia = function ($editable, value, $target) {
var $target = $( getImgTarget() ); beforeCommand($editable);
$target.detach(); $target.detach();
handler.bindCustomEvent(
$(), $editable.data('callbacks'), 'media.delete'
)($target, $editable);
afterCommand($editable); afterCommand($editable);
}; };
/**
* set focus
*
* @param $editable
*/
this.focus = function ($editable) {
$editable.focus();
// [workaround] for firefox bug http://goo.gl/lVfAaI
if (agent.isFF) {
var rng = range.create();
if (!rng || rng.isOnEditable()) {
return;
}
range.createFromNode($editable[0])
.normalize()
.collapse()
.select();
}
};
/**
* returns whether contents is empty or not.
*
* @param {jQuery} $editable
* @return {Boolean}
*/
this.isEmpty = function ($editable) {
return dom.isEmpty($editable[0]) || dom.emptyPara === $editable.html();
};
}; };
return Editor; return Editor;
@@ -1,10 +1,12 @@
define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) { // ODOO: suggest upstream
/** /**
* History * @class editing.History
* @class *
* Editor History
*
*/ */
var History = function ($editable) { var History = function ($editable) {
var stack = [], stackOffset = -1, hasUndo = false; var stack = [], stackOffset = -1;
var editable = $editable[0]; var editable = $editable[0];
var makeSnapshot = function () { var makeSnapshot = function () {
@@ -14,6 +16,7 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) {
return { return {
contents: $editable.html(), contents: $editable.html(),
bookmark: (rng && dom.ancestor(rng.sc, dom.isEditable) ? rng.bookmark(editable) : emptyBookmark) bookmark: (rng && dom.ancestor(rng.sc, dom.isEditable) ? rng.bookmark(editable) : emptyBookmark)
// ODOO: suggest upstream added " && dom.ancestor(rng.sc, dom.isEditable) "
}; };
}; };
@@ -26,21 +29,29 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) {
} }
}; };
/**
* undo
*/
this.undo = function () { this.undo = function () {
if (0 < stackOffset || (stack.length - 1 === stackOffset && hasUndo)) { // Create snap shot if not yet recorded
if (stack.length - 1 === stackOffset) { if ($editable.html() !== stack[stackOffset].contents) {
this.recordUndo(); this.recordUndo();
} }
if (0 < stackOffset) {
stackOffset--; stackOffset--;
applySnapshot(stack[stackOffset]); applySnapshot(stack[stackOffset]);
hasUndo = !!stackOffset;
} }
}; };
/* ODOO: to suggest upstream */
this.hasUndo = function () { this.hasUndo = function () {
return 0 < stackOffset || hasUndo; return 0 < stackOffset;
}; };
/**
* redo
*/
this.redo = function () { this.redo = function () {
if (stack.length - 1 > stackOffset) { if (stack.length - 1 > stackOffset) {
stackOffset++; stackOffset++;
@@ -48,12 +59,17 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) {
} }
}; };
/* ODOO: to suggest upstream */
this.hasRedo = function () { this.hasRedo = function () {
return stack.length - 1 > stackOffset; return stack.length - 1 > stackOffset;
}; };
var last; var last; // ODOO: to suggest upstream (since we may have several editor)
/**
* recorded undo
*/
this.recordUndo = function () { this.recordUndo = function () {
// ODOO: method totally rewritten
// test event for firefox: remove stack of history because event doesn't exists // test event for firefox: remove stack of history because event doesn't exists
var key = typeof event !== 'undefined' ? event : false; var key = typeof event !== 'undefined' ? event : false;
if (key && !event.metaKey && !event.ctrlKey && !event.altKey && event.type === "keydown") { if (key && !event.metaKey && !event.ctrlKey && !event.altKey && event.type === "keydown") {
@@ -79,13 +95,14 @@ define(['summernote/core/range', 'summernote/core/dom'], function (range, dom) {
// Create new snapshot and push it to the end // Create new snapshot and push it to the end
stack.push(makeSnapshot()); stack.push(makeSnapshot());
return true;
}; };
/* ODOO: to suggest upstream */
this.splitNext = function () { this.splitNext = function () {
last = false; last = false;
}; };
/* ODOO: to suggest upstream */
this.reset = function () { this.reset = function () {
last = false; last = false;
stack = []; stack = [];
@@ -1,20 +1,28 @@
define([ define([
'summernote/core/agent', 'summernote/core/agent',
'summernote/core/func',
'summernote/core/list',
'summernote/core/dom' 'summernote/core/dom'
], function (agent, dom) { ], function (agent, func, list, dom) {
/** /**
* @class editing.Style
*
* Style * Style
* @class *
*/ */
var Style = function () { var Style = function () {
/** /**
* @method jQueryCSS
*
* [workaround] for old jQuery
* passing an array of style properties to .css() * passing an array of style properties to .css()
* will result in an object of property-value pairs. * will result in an object of property-value pairs.
* (compability with version < 1.9) * (compability with version < 1.9)
* *
* @private
* @param {jQuery} $obj * @param {jQuery} $obj
* @param {Array} propertyNames - An array of one or more CSS properties. * @param {Array} propertyNames - An array of one or more CSS properties.
* @returns {Object} * @return {Object}
*/ */
var jQueryCSS = function ($obj, propertyNames) { var jQueryCSS = function ($obj, propertyNames) {
if (agent.jqueryVersion < 1.9) { if (agent.jqueryVersion < 1.9) {
@@ -27,6 +35,19 @@ define([
return $obj.css.call($obj, propertyNames); return $obj.css.call($obj, propertyNames);
}; };
/**
* returns style object from node
*
* @param {jQuery} $node
* @return {Object}
*/
this.fromNode = function ($node) {
var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height'];
var styleInfo = jQueryCSS($node, properties) || {};
styleInfo['font-size'] = parseInt(styleInfo['font-size'], 10);
return styleInfo;
};
/** /**
* paragraph level style * paragraph level style
* *
@@ -41,6 +62,58 @@ define([
}); });
}; };
/**
* insert and returns styleNodes on range.
*
* @param {WrappedRange} rng
* @param {Object} [options] - options for styleNodes
* @param {String} [options.nodeName] - default: `SPAN`
* @param {Boolean} [options.expandClosestSibling] - default: `false`
* @param {Boolean} [options.onlyPartialContains] - default: `false`
* @return {Node[]}
*/
this.styleNodes = function (rng, options) {
rng = rng.splitText();
var nodeName = options && options.nodeName || 'SPAN';
var expandClosestSibling = !!(options && options.expandClosestSibling);
var onlyPartialContains = !!(options && options.onlyPartialContains);
if (rng.isCollapsed()) {
return [rng.insertNode(dom.create(nodeName))];
}
var pred = dom.makePredByNodeName(nodeName);
var nodes = rng.nodes(dom.isText, {
fullyContains: true
}).map(function (text) {
return dom.singleChildAncestor(text, pred) || dom.wrap(text, nodeName);
});
if (expandClosestSibling) {
if (onlyPartialContains) {
var nodesInRange = rng.nodes();
// compose with partial contains predication
pred = func.and(pred, function (node) {
return list.contains(nodesInRange, node);
});
}
return nodes.map(function (node) {
var siblings = dom.withClosestSiblings(node, pred);
var head = list.head(siblings);
var tails = list.tail(siblings);
$.each(tails, function (idx, elem) {
dom.appendChildNodes(head, elem.childNodes);
dom.remove(elem);
});
return list.head(siblings);
});
} else {
return nodes;
}
};
/** /**
* get current style on cursor * get current style on cursor
* *
@@ -49,10 +122,7 @@ define([
*/ */
this.current = function (rng) { this.current = function (rng) {
var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc); var $cont = $(dom.isText(rng.sc) ? rng.sc.parentNode : rng.sc);
var properties = ['font-family', 'font-size', 'text-align', 'list-style-type', 'line-height']; var styleInfo = this.fromNode($cont);
var styleInfo = jQueryCSS($cont, properties) || {};
styleInfo['font-size'] = parseInt(styleInfo['font-size'], 10);
// document.queryCommandState for toggle state // document.queryCommandState for toggle state
styleInfo['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal'; styleInfo['font-bold'] = document.queryCommandState('bold') ? 'bold' : 'normal';
@@ -79,7 +149,6 @@ define([
styleInfo['line-height'] = lineHeight.toFixed(1); styleInfo['line-height'] = lineHeight.toFixed(1);
} }
styleInfo.image = rng.isOnImg();
styleInfo.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor); styleInfo.anchor = rng.isOnAnchor() && dom.ancestor(rng.sc, dom.isAnchor);
styleInfo.ancestors = dom.listAncestor(rng.sc, dom.isEditable); styleInfo.ancestors = dom.listAncestor(rng.sc, dom.isEditable);
styleInfo.range = rng; styleInfo.range = rng;
@@ -2,8 +2,10 @@ define([
'summernote/core/dom', 'summernote/core/range', 'summernote/core/list' 'summernote/core/dom', 'summernote/core/range', 'summernote/core/list'
], function (dom, range, list) { ], function (dom, range, list) {
/** /**
* @class editing.Table
*
* Table * Table
* @class *
*/ */
var Table = function () { var Table = function () {
/** /**
@@ -1,11 +1,23 @@
define([ define([
'summernote/core/dom', 'summernote/core/dom',
'summernote/core/range' 'summernote/core/range',
], function (dom, range) { 'summernote/editing/Bullet'
], function (dom, range, Bullet) {
/**
* @class editing.Typing
*
* Typing
*
*/
var Typing = function () { var Typing = function () {
// a Bullet instance to toggle lists off
var bullet = new Bullet();
/** /**
* insert tab
*
* @param {jQuery} $editable * @param {jQuery} $editable
* @param {WrappedRange} rng * @param {WrappedRange} rng
* @param {Number} tabsize * @param {Number} tabsize
@@ -37,14 +49,22 @@ define([
var nextPara; var nextPara;
// on paragraph: split paragraph // on paragraph: split paragraph
if (splitRoot) { if (splitRoot) {
nextPara = dom.splitTree(splitRoot, rng.getStartPoint()); // if it is an empty line with li
if (dom.isEmpty(splitRoot) && dom.isLi(splitRoot)) {
// disable UL/OL and escape!
bullet.toggleList(splitRoot.parentNode.nodeName);
return;
// if new line has content (not a line break)
} else {
nextPara = dom.splitTree(splitRoot, rng.getStartPoint());
var emptyAnchors = dom.listDescendant(splitRoot, dom.isEmptyAnchor); var emptyAnchors = dom.listDescendant(splitRoot, dom.isEmptyAnchor);
emptyAnchors = emptyAnchors.concat(dom.listDescendant(nextPara, dom.isEmptyAnchor)); emptyAnchors = emptyAnchors.concat(dom.listDescendant(nextPara, dom.isEmptyAnchor));
$.each(emptyAnchors, function (idx, anchor) { $.each(emptyAnchors, function (idx, anchor) {
dom.remove(anchor); dom.remove(anchor);
}); });
}
// no paragraph: insert empty paragraph // no paragraph: insert empty paragraph
} else { } else {
var next = rng.sc.childNodes[rng.so]; var next = rng.sc.childNodes[rng.so];
@@ -57,6 +77,7 @@ define([
} }
range.create(nextPara, 0).normalize().select(); range.create(nextPara, 0).normalize().select();
}; };
}; };
@@ -1,9 +1,9 @@
/** /**
* Super simple wysiwyg editor on Bootstrap v@VERSION * Super simple wysiwyg editor on Bootstrap v@VERSION
* http://hackerwins.github.io/summernote/ * http://summernote.org/
* *
* summernote.js * summernote.js
* Copyright 2013-2014 Alan Hong. and other contributors * Copyright 2013-2015 Alan Hong. and other contributors
* summernote may be freely distributed under the MIT license./ * summernote may be freely distributed under the MIT license./
* *
* Date: @DATE * Date: @DATE
@@ -1,7 +1,10 @@
define([ define([
'summernote/core/list' 'summernote/core/list',
], function (list) { 'summernote/core/agent'
], function (list, agent) {
/** /**
* @class module.Button
*
* Button * Button
*/ */
var Button = function () { var Button = function () {
@@ -28,6 +31,7 @@ define([
/** /**
* update button state(active or not). * update button state(active or not).
* *
* @private
* @param {String} selector * @param {String} selector
* @param {Function} pred * @param {Function} pred
*/ */
@@ -40,30 +44,30 @@ define([
var $img = $(styleInfo.image); var $img = $(styleInfo.image);
btnState('button[data-event="imageShape"][data-value="img-rounded"]', function () { btnState('button[data-event="imageShape"][data-value="img-rounded"]', function () {
return $img.hasClass("img-rounded"); return $img.hasClass('img-rounded');
}); });
btnState('button[data-event="imageShape"][data-value="img-circle"]', function () { btnState('button[data-event="imageShape"][data-value="img-circle"]', function () {
return $img.hasClass("img-circle"); return $img.hasClass('img-circle');
}); });
btnState('button[data-event="imageShape"][data-value="img-thumbnail"]', function () { btnState('button[data-event="imageShape"][data-value="img-thumbnail"]', function () {
return $img.hasClass("img-thumbnail"); return $img.hasClass('img-thumbnail');
}); });
btnState('button[data-event="imageShape"]:not([data-value])', function () { btnState('button[data-event="imageShape"]:not([data-value])', function () {
return !$img.is(".img-rounded, .img-circle, .img-thumbnail"); return !$img.is('.img-rounded, .img-circle, .img-thumbnail');
}); });
var imgFloat = $img.css("float"); var imgFloat = $img.css('float');
btnState('button[data-event="floatMe"][data-value="left"]', function () { btnState('button[data-event="floatMe"][data-value="left"]', function () {
return imgFloat === "left"; return imgFloat === 'left';
}); });
btnState('button[data-event="floatMe"][data-value="right"]', function () { btnState('button[data-event="floatMe"][data-value="right"]', function () {
return imgFloat === "right"; return imgFloat === 'right';
}); });
btnState('button[data-event="floatMe"][data-value="none"]', function () { btnState('button[data-event="floatMe"][data-value="none"]', function () {
return imgFloat !== "left" && imgFloat !== "right"; return imgFloat !== 'left' && imgFloat !== 'right';
}); });
var style = $img.attr("style"); var style = $img.attr('style');
btnState('button[data-event="resize"][data-value="1"]', function () { btnState('button[data-event="resize"][data-value="1"]', function () {
return !!/(^|\s)(max-)?width\s*:\s*100%/.test(style); return !!/(^|\s)(max-)?width\s*:\s*100%/.test(style);
}); });
@@ -81,10 +85,18 @@ define([
if ($fontname.length) { if ($fontname.length) {
var selectedFont = styleInfo['font-family']; var selectedFont = styleInfo['font-family'];
if (!!selectedFont) { if (!!selectedFont) {
selectedFont = list.head(selectedFont.split(','));
selectedFont = selectedFont.replace(/\'/g, ''); var list = selectedFont.split(',');
for (var i = 0, len = list.length; i < len; i++) {
selectedFont = list[i].replace(/[\'\"]/g, '').replace(/\s+$/, '').replace(/^\s+/, '');
if (agent.isFontInstalled(selectedFont)) {
break;
}
}
$fontname.find('.note-current-fontname').text(selectedFont); $fontname.find('.note-current-fontname').text(selectedFont);
checkDropdownMenu($fontname, selectedFont); checkDropdownMenu($fontname, selectedFont);
} }
} }
@@ -140,7 +152,7 @@ define([
* *
* @param {Node} button * @param {Node} button
* @param {String} eventName * @param {String} eventName
* @param {value} value * @param {Mixed} value
*/ */
this.updateRecentColor = function (button, eventName, value) { this.updateRecentColor = function (button, eventName, value) {
var $color = $(button).closest('.note-color'); var $color = $(button).closest('.note-color');
@@ -0,0 +1,90 @@
define([
'summernote/core/list',
'summernote/core/dom',
'summernote/core/key',
'summernote/core/agent'
], function (list, dom, key, agent) {
var Clipboard = function (handler) {
var $paste;
this.attach = function (layoutInfo) {
// [workaround] getting image from clipboard
// - IE11 and Firefox: CTRL+v hook
// - Webkit: event.clipboardData
if ((agent.isMSIE && agent.browserVersion > 10) || agent.isFF) {
$paste = $('<div />').attr('contenteditable', true).css({
position : 'absolute',
left : -100000,
opacity : 0
});
layoutInfo.editable().on('keydown', function (e) {
if (e.ctrlKey && e.keyCode === key.code.V) {
handler.invoke('saveRange', layoutInfo.editable());
$paste.focus();
setTimeout(function () {
pasteByHook(layoutInfo);
}, 0);
}
});
layoutInfo.editable().before($paste);
} else {
layoutInfo.editable().on('paste', pasteByEvent);
}
};
var pasteByHook = function (layoutInfo) {
var $editable = layoutInfo.editable();
var node = $paste[0].firstChild;
if (dom.isImg(node)) {
var dataURI = node.src;
var decodedData = atob(dataURI.split(',')[1]);
var array = new Uint8Array(decodedData.length);
for (var i = 0; i < decodedData.length; i++) {
array[i] = decodedData.charCodeAt(i);
}
var blob = new Blob([array], { type : 'image/png' });
blob.name = 'clipboard.png';
handler.invoke('restoreRange', $editable);
handler.invoke('focus', $editable);
handler.insertImages(layoutInfo, [blob]);
} else {
var pasteContent = $('<div />').html($paste.html()).html();
handler.invoke('restoreRange', $editable);
handler.invoke('focus', $editable);
if (pasteContent) {
handler.invoke('pasteHTML', $editable, pasteContent);
}
}
$paste.empty();
};
/**
* paste by clipboard event
*
* @param {Event} event
*/
var pasteByEvent = function (event) {
var clipboardData = event.originalEvent.clipboardData;
var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target);
var $editable = layoutInfo.editable();
if (clipboardData && clipboardData.items && clipboardData.items.length) {
var item = list.head(clipboardData.items);
if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
handler.insertImages(layoutInfo, [item.getAsFile()]);
}
handler.invoke('editor.afterCommand', $editable);
}
};
};
return Clipboard;
});
@@ -0,0 +1,137 @@
define([
'summernote/core/agent',
'summernote/core/dom'
], function (agent, dom) {
var CodeMirror;
if (agent.hasCodeMirror) {
if (agent.isSupportAmd) {
require(['CodeMirror'], function (cm) {
CodeMirror = cm;
});
} else {
CodeMirror = window.CodeMirror;
}
}
/**
* @class Codeview
*/
var Codeview = function (handler) {
this.sync = function (layoutInfo) {
var isCodeview = handler.invoke('codeview.isActivated', layoutInfo);
if (isCodeview && agent.hasCodeMirror) {
layoutInfo.codable().data('cmEditor').save();
}
};
/**
* @param {Object} layoutInfo
* @return {Boolean}
*/
this.isActivated = function (layoutInfo) {
var $editor = layoutInfo.editor();
return $editor.hasClass('codeview');
};
/**
* toggle codeview
*
* @param {Object} layoutInfo
*/
this.toggle = function (layoutInfo) {
if (this.isActivated(layoutInfo)) {
this.deactivate(layoutInfo);
} else {
this.activate(layoutInfo);
}
};
/**
* activate code view
*
* @param {Object} layoutInfo
*/
this.activate = function (layoutInfo) {
var $editor = layoutInfo.editor(),
$toolbar = layoutInfo.toolbar(),
$editable = layoutInfo.editable(),
$codable = layoutInfo.codable(),
$popover = layoutInfo.popover(),
$handle = layoutInfo.handle();
var options = $editor.data('options');
$codable.val(dom.html($editable, options.prettifyHtml));
$codable.height($editable.height());
handler.invoke('toolbar.updateCodeview', $toolbar, true);
handler.invoke('popover.hide', $popover);
handler.invoke('handle.hide', $handle);
$editor.addClass('codeview');
$codable.focus();
// activate CodeMirror as codable
if (agent.hasCodeMirror) {
var cmEditor = CodeMirror.fromTextArea($codable[0], options.codemirror);
// CodeMirror TernServer
if (options.codemirror.tern) {
var server = new CodeMirror.TernServer(options.codemirror.tern);
cmEditor.ternServer = server;
cmEditor.on('cursorActivity', function (cm) {
server.updateArgHints(cm);
});
}
// CodeMirror hasn't Padding.
cmEditor.setSize(null, $editable.outerHeight());
$codable.data('cmEditor', cmEditor);
}
};
/**
* deactivate code view
*
* @param {Object} layoutInfo
*/
this.deactivate = function (layoutInfo) {
var $holder = layoutInfo.holder(),
$editor = layoutInfo.editor(),
$toolbar = layoutInfo.toolbar(),
$editable = layoutInfo.editable(),
$codable = layoutInfo.codable();
var options = $editor.data('options');
// deactivate CodeMirror as codable
if (agent.hasCodeMirror) {
var cmEditor = $codable.data('cmEditor');
$codable.val(cmEditor.getValue());
cmEditor.toTextArea();
}
var value = dom.value($codable, options.prettifyHtml) || dom.emptyPara;
var isChange = $editable.html() !== value;
$editable.html(value);
$editable.height(options.height ? $codable.height() : 'auto');
$editor.removeClass('codeview');
if (isChange) {
handler.bindCustomEvent(
$holder, $editable.data('callbacks'), 'change'
)($editable.html(), $editable);
}
$editable.focus();
handler.invoke('toolbar.updateCodeview', $toolbar, false);
};
};
return Codeview;
});
@@ -1,158 +1 @@
define('summernote/module/Dialog', function () { /** TODO master: drop this file (splitted in several files) */
/**
* Dialog
*
* @class
*/
var Dialog = function () {
/**
* toggle button status
*
* @param {jQuery} $btn
* @param {Boolean} isEnable
*/
var toggleBtn = function ($btn, isEnable) {
$btn.toggleClass('disabled', !isEnable);
$btn.attr('disabled', !isEnable);
};
/**
* show image dialog
*
* @param {jQuery} $editable
* @param {jQuery} $dialog
* @return {Promise}
*/
this.showImageDialog = function ($editable, $dialog) {
return $.Deferred(function (deferred) {
var $imageDialog = $dialog.find('.note-image-dialog');
var $imageInput = $dialog.find('.note-image-input'),
$imageUrl = $dialog.find('.note-image-url'),
$imageBtn = $dialog.find('.note-image-btn');
$imageDialog.one('shown.bs.modal', function () {
// Cloning imageInput to clear element.
$imageInput.replaceWith($imageInput.clone()
.on('change', function () {
deferred.resolve(this.files || this.value);
$imageDialog.modal('hide');
})
.val('')
);
$imageBtn.click(function (event) {
event.preventDefault();
deferred.resolve($imageUrl.val());
$imageDialog.modal('hide');
});
$imageUrl.on('keyup paste', function (event) {
var url;
if (event.type === 'paste') {
url = event.originalEvent.clipboardData.getData('text');
} else {
url = $imageUrl.val();
}
toggleBtn($imageBtn, url);
}).val('').trigger('focus');
}).one('hidden.bs.modal', function () {
$imageInput.off('change');
$imageUrl.off('keyup paste');
$imageBtn.off('click');
if (deferred.state() === 'pending') {
deferred.reject();
}
}).modal('show');
});
};
/**
* Show link dialog and set event handlers on dialog controls.
*
* @param {jQuery} $dialog
* @param {Object} linkInfo
* @return {Promise}
*/
this.showLinkDialog = function ($editable, $dialog, linkInfo) {
return $.Deferred(function (deferred) {
var $linkDialog = $dialog.find('.note-link-dialog');
var $linkText = $linkDialog.find('.note-link-text'),
$linkUrl = $linkDialog.find('.note-link-url'),
$linkBtn = $linkDialog.find('.note-link-btn'),
$openInNewWindow = $linkDialog.find('input[type=checkbox]');
$linkDialog.one('shown.bs.modal', function () {
$linkText.val(linkInfo.text);
$linkText.on('input', function () {
// if linktext was modified by keyup,
// stop cloning text from linkUrl
linkInfo.text = $linkText.val();
});
// if no url was given, copy text to url
if (!linkInfo.url) {
linkInfo.url = linkInfo.text;
toggleBtn($linkBtn, linkInfo.text);
}
$linkUrl.on('input', function () {
toggleBtn($linkBtn, $linkUrl.val());
// display same link on `Text to display` input
// when create a new link
if (!linkInfo.text) {
$linkText.val($linkUrl.val());
}
}).val(linkInfo.url).trigger('focus').trigger('select');
$openInNewWindow.prop('checked', linkInfo.newWindow);
$linkBtn.one('click', function (event) {
event.preventDefault();
deferred.resolve({
range: linkInfo.range,
url: $linkUrl.val(),
text: $linkText.val(),
newWindow: $openInNewWindow.is(':checked')
});
$linkDialog.modal('hide');
});
}).one('hidden.bs.modal', function () {
// detach events
$linkText.off('input');
$linkUrl.off('input');
$linkBtn.off('click');
if (deferred.state() === 'pending') {
deferred.reject();
}
}).modal('show');
}).promise();
};
/**
* show help dialog
*
* @param {jQuery} $dialog
*/
this.showHelpDialog = function ($editable, $dialog) {
return $.Deferred(function (deferred) {
var $helpDialog = $dialog.find('.note-help-dialog');
$helpDialog.one('hidden.bs.modal', function () {
deferred.resolve();
}).modal('show');
}).promise();
};
};
return Dialog;
});
@@ -0,0 +1,98 @@
define([
'summernote/core/dom'
], function (dom) {
var DragAndDrop = function (handler) {
var $document = $(document);
/**
* attach Drag and Drop Events
*
* @param {Object} layoutInfo - layout Informations
* @param {Object} options
*/
this.attach = function (layoutInfo, options) {
if (options.airMode || options.disableDragAndDrop) {
// prevent default drop event
$document.on('drop', function (e) {
e.preventDefault();
});
} else {
this.attachDragAndDropEvent(layoutInfo, options);
}
};
/**
* attach Drag and Drop Events
*
* @param {Object} layoutInfo - layout Informations
* @param {Object} options
*/
this.attachDragAndDropEvent = function (layoutInfo, options) {
var collection = $(),
$editor = layoutInfo.editor(),
$dropzone = layoutInfo.dropzone(),
$dropzoneMessage = $dropzone.find('.note-dropzone-message');
// show dropzone on dragenter when dragging a object to document
// -but only if the editor is visible, i.e. has a positive width and height
$document.on('dragenter', function (e) {
var isCodeview = handler.invoke('codeview.isActivated', layoutInfo);
var hasEditorSize = $editor.width() > 0 && $editor.height() > 0;
if (!isCodeview && !collection.length && hasEditorSize) {
$editor.addClass('dragover');
$dropzone.width($editor.width());
$dropzone.height($editor.height());
$dropzoneMessage.text(options.langInfo.image.dragImageHere);
}
collection = collection.add(e.target);
}).on('dragleave', function (e) {
collection = collection.not(e.target);
if (!collection.length) {
$editor.removeClass('dragover');
}
}).on('drop', function () {
collection = $();
$editor.removeClass('dragover');
});
// change dropzone's message on hover.
$dropzone.on('dragenter', function () {
$dropzone.addClass('hover');
$dropzoneMessage.text(options.langInfo.image.dropImage);
}).on('dragleave', function () {
$dropzone.removeClass('hover');
$dropzoneMessage.text(options.langInfo.image.dragImageHere);
});
// attach dropImage
$dropzone.on('drop', function (event) {
var dataTransfer = event.originalEvent.dataTransfer;
var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target);
if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
event.preventDefault();
layoutInfo.editable().focus();
handler.insertImages(layoutInfo, dataTransfer.files);
} else {
var insertNodefunc = function () {
layoutInfo.holder().summernote('insertNode', this);
};
for (var i = 0, len = dataTransfer.types.length; i < len; i++) {
var type = dataTransfer.types[i];
var content = dataTransfer.getData(type);
if (type.toLowerCase().indexOf('text') > -1) {
layoutInfo.holder().summernote('pasteHTML', content);
} else {
$(content).each(insertNodefunc);
}
}
}
}).on('dragover', false); // prevent default dragover event
};
};
return DragAndDrop;
});
@@ -0,0 +1,51 @@
define([], function () { // ODOO: remove error from Odoo define
var Fullscreen = function (handler) {
var $window = $(window);
var $scrollbar = $('html, body');
/**
* toggle fullscreen
*
* @param {Object} layoutInfo
*/
this.toggle = function (layoutInfo) {
var $editor = layoutInfo.editor(),
$toolbar = layoutInfo.toolbar(),
$editable = layoutInfo.editable(),
$codable = layoutInfo.codable();
var resize = function (size) {
$editable.css('height', size.h);
$codable.css('height', size.h);
if ($codable.data('cmeditor')) {
$codable.data('cmeditor').setsize(null, size.h);
}
};
$editor.toggleClass('fullscreen');
var isFullscreen = $editor.hasClass('fullscreen');
if (isFullscreen) {
$editable.data('orgheight', $editable.css('height'));
$window.on('resize', function () {
resize({
h: $window.height() - $toolbar.outerHeight()
});
}).trigger('resize');
$scrollbar.css('overflow', 'hidden');
} else {
$window.off('resize');
resize({
h: $editable.data('orgheight')
});
$scrollbar.css('overflow', 'visible');
}
handler.invoke('toolbar.updateFullscreen', $toolbar, isFullscreen);
};
};
return Fullscreen;
});
@@ -1,8 +1,60 @@
define('summernote/module/Handle', function () { define([
'summernote/core/dom'
], function (dom) {
/** /**
* @class module.Handle
*
* Handle * Handle
*/ */
var Handle = function () { var Handle = function (handler) {
var $document = $(document);
/**
* `mousedown` event handler on $handle
* - controlSizing: resize image
*
* @param {MouseEvent} event
*/
var hHandleMousedown = function (event) {
if (dom.isControlSizing(event.target)) {
event.preventDefault();
event.stopPropagation();
var layoutInfo = dom.makeLayoutInfo(event.target),
$handle = layoutInfo.handle(),
$popover = layoutInfo.popover(),
$editable = layoutInfo.editable(),
$editor = layoutInfo.editor();
var target = $handle.find('.note-control-selection').data('target'),
$target = $(target), posStart = $target.offset(),
scrollTop = $document.scrollTop();
var isAirMode = $editor.data('options').airMode;
$document.on('mousemove', function (event) {
handler.invoke('editor.resizeTo', {
x: event.clientX - posStart.left,
y: event.clientY - (posStart.top - scrollTop)
}, $target, !event.shiftKey);
handler.invoke('handle.update', $handle, {image: target}, isAirMode);
handler.invoke('popover.update', $popover, {image: target}, isAirMode);
}).one('mouseup', function () {
$document.off('mousemove');
handler.invoke('editor.afterCommand', $editable);
});
if (!$target.data('ratio')) { // original ratio.
$target.data('ratio', $target.height() / $target.width());
}
}
};
this.attach = function (layoutInfo) {
layoutInfo.handle().on('mousedown', hHandleMousedown);
};
/** /**
* update handle * update handle
* @param {jQuery} $handle * @param {jQuery} $handle
@@ -35,6 +87,11 @@ define('summernote/module/Handle', function () {
} }
}; };
/**
* hide
*
* @param {jQuery} $handle
*/
this.hide = function ($handle) { this.hide = function ($handle) {
$handle.children().hide(); $handle.children().hide();
}; };
@@ -0,0 +1,35 @@
define([], function () { // ODOO: remove error from Odoo define
var HelpDialog = function (handler) {
/**
* show help dialog
*
* @param {jQuery} $editable
* @param {jQuery} $dialog
* @return {Promise}
*/
this.showHelpDialog = function ($editable, $dialog) {
return $.Deferred(function (deferred) {
var $helpDialog = $dialog.find('.note-help-dialog');
$helpDialog.one('hidden.bs.modal', function () {
deferred.resolve();
}).modal('show');
}).promise();
};
/**
* @param {Object} layoutInfo
*/
this.show = function (layoutInfo) {
var $dialog = layoutInfo.dialog(),
$editable = layoutInfo.editable();
handler.invoke('editor.saveRange', $editable, true);
this.showHelpDialog($editable, $dialog).then(function () {
handler.invoke('editor.restoreRange', $editable);
});
};
};
return HelpDialog;
});
@@ -0,0 +1,110 @@
define([
'summernote/core/key'
], function (key) {
var ImageDialog = function (handler) {
/**
* toggle button status
*
* @private
* @param {jQuery} $btn
* @param {Boolean} isEnable
*/
var toggleBtn = function ($btn, isEnable) {
$btn.toggleClass('disabled', !isEnable);
$btn.attr('disabled', !isEnable);
};
/**
* bind enter key
*
* @private
* @param {jQuery} $input
* @param {jQuery} $btn
*/
var bindEnterKey = function ($input, $btn) {
$input.on('keypress', function (event) {
if (event.keyCode === key.code.ENTER) {
$btn.trigger('click');
}
});
};
this.show = function (layoutInfo) {
var $dialog = layoutInfo.dialog(),
$editable = layoutInfo.editable();
handler.invoke('editor.saveRange', $editable);
this.showImageDialog($editable, $dialog).then(function (data) {
handler.invoke('editor.restoreRange', $editable);
if (typeof data === 'string') {
// image url
handler.invoke('editor.insertImage', $editable, data);
} else {
// array of files
handler.insertImages(layoutInfo, data);
}
}).fail(function () {
handler.invoke('editor.restoreRange', $editable);
});
};
/**
* show image dialog
*
* @param {jQuery} $editable
* @param {jQuery} $dialog
* @return {Promise}
*/
this.showImageDialog = function ($editable, $dialog) {
return $.Deferred(function (deferred) {
var $imageDialog = $dialog.find('.note-image-dialog');
var $imageInput = $dialog.find('.note-image-input'),
$imageUrl = $dialog.find('.note-image-url'),
$imageBtn = $dialog.find('.note-image-btn');
$imageDialog.one('shown.bs.modal', function () {
// Cloning imageInput to clear element.
$imageInput.replaceWith($imageInput.clone()
.on('change', function () {
deferred.resolve(this.files || this.value);
$imageDialog.modal('hide');
})
.val('')
);
$imageBtn.click(function (event) {
event.preventDefault();
deferred.resolve($imageUrl.val());
$imageDialog.modal('hide');
});
$imageUrl.on('keyup paste', function (event) {
var url;
if (event.type === 'paste') {
url = event.originalEvent.clipboardData.getData('text');
} else {
url = $imageUrl.val();
}
toggleBtn($imageBtn, url);
}).val('').trigger('focus');
bindEnterKey($imageUrl, $imageBtn);
}).one('hidden.bs.modal', function () {
$imageInput.off('change');
$imageUrl.off('keyup paste keypress');
$imageBtn.off('click');
if (deferred.state() === 'pending') {
deferred.reject();
}
}).modal('show');
});
};
};
return ImageDialog;
});
@@ -0,0 +1,129 @@
define([
'summernote/core/key'
], function (key) {
var LinkDialog = function (handler) {
/**
* toggle button status
*
* @private
* @param {jQuery} $btn
* @param {Boolean} isEnable
*/
var toggleBtn = function ($btn, isEnable) {
$btn.toggleClass('disabled', !isEnable);
$btn.attr('disabled', !isEnable);
};
/**
* bind enter key
*
* @private
* @param {jQuery} $input
* @param {jQuery} $btn
*/
var bindEnterKey = function ($input, $btn) {
$input.on('keypress', function (event) {
if (event.keyCode === key.code.ENTER) {
$btn.trigger('click');
}
});
};
/**
* Show link dialog and set event handlers on dialog controls.
*
* @param {jQuery} $editable
* @param {jQuery} $dialog
* @param {Object} linkInfo
* @return {Promise}
*/
this.showLinkDialog = function ($editable, $dialog, linkInfo) {
return $.Deferred(function (deferred) {
var $linkDialog = $dialog.find('.note-link-dialog');
var $linkText = $linkDialog.find('.note-link-text'),
$linkUrl = $linkDialog.find('.note-link-url'),
$linkBtn = $linkDialog.find('.note-link-btn'),
$openInNewWindow = $linkDialog.find('input[type=checkbox]');
$linkDialog.one('shown.bs.modal', function () {
$linkText.val(linkInfo.text);
$linkText.on('input', function () {
toggleBtn($linkBtn, $linkText.val() && $linkUrl.val());
// if linktext was modified by keyup,
// stop cloning text from linkUrl
linkInfo.text = $linkText.val();
});
// if no url was given, copy text to url
if (!linkInfo.url) {
linkInfo.url = linkInfo.text || 'http://';
toggleBtn($linkBtn, linkInfo.text);
}
$linkUrl.on('input', function () {
toggleBtn($linkBtn, $linkText.val() && $linkUrl.val());
// display same link on `Text to display` input
// when create a new link
if (!linkInfo.text) {
$linkText.val($linkUrl.val());
}
}).val(linkInfo.url).trigger('focus').trigger('select');
bindEnterKey($linkUrl, $linkBtn);
bindEnterKey($linkText, $linkBtn);
$openInNewWindow.prop('checked', linkInfo.isNewWindow);
$linkBtn.one('click', function (event) {
event.preventDefault();
deferred.resolve({
range: linkInfo.range,
url: $linkUrl.val(),
text: $linkText.val(),
isNewWindow: $openInNewWindow.is(':checked')
});
$linkDialog.modal('hide');
});
}).one('hidden.bs.modal', function () {
// detach events
$linkText.off('input keypress');
$linkUrl.off('input keypress');
$linkBtn.off('click');
if (deferred.state() === 'pending') {
deferred.reject();
}
}).modal('show');
}).promise();
};
/**
* @param {Object} layoutInfo
*/
this.show = function (layoutInfo) {
var $editor = layoutInfo.editor(),
$dialog = layoutInfo.dialog(),
$editable = layoutInfo.editable(),
$popover = layoutInfo.popover(),
linkInfo = handler.invoke('editor.getLinkInfo', $editable);
var options = $editor.data('options');
handler.invoke('editor.saveRange', $editable);
this.showLinkDialog($editable, $dialog, linkInfo).then(function (linkInfo) {
handler.invoke('editor.restoreRange', $editable);
handler.invoke('editor.createLink', $editable, linkInfo, options);
// hide popover after creating link
handler.invoke('popover.hide', $popover);
}).fail(function () {
handler.invoke('editor.restoreRange', $editable);
});
};
};
return LinkDialog;
});
@@ -4,22 +4,32 @@ define([
'summernote/module/Button' 'summernote/module/Button'
], function (func, list, Button) { ], function (func, list, Button) {
/** /**
* @class module.Popover
*
* Popover (http://getbootstrap.com/javascript/#popovers) * Popover (http://getbootstrap.com/javascript/#popovers)
*
*/ */
var Popover = function () { var Popover = function () {
var button = new Button(); var button = new Button();
this.button = button; // odoo change for overwrite this.button = button; // ODOO: allow access for override
/** /**
* returns position from placeholder * returns position from placeholder
*
* @private
* @param {Node} placeholder * @param {Node} placeholder
* @param {Boolean} isAirMode * @param {Object} options
* @param {Boolean} options.isAirMode
* @return {Position}
*/ */
var posFromPlaceholder = function (placeholder, isAirMode) { var posFromPlaceholder = function (placeholder, options) {
var isAirMode = options && options.isAirMode;
var isLeftTop = options && options.isLeftTop;
var $placeholder = $(placeholder); var $placeholder = $(placeholder);
var pos = isAirMode ? $placeholder.offset() : $placeholder.position(); var pos = isAirMode ? $placeholder.offset() : $placeholder.position();
var height = $placeholder.outerHeight(true); // include margin var height = isLeftTop ? 0 : $placeholder.outerHeight(true); // include margin
// popover below placeholder. // popover below placeholder.
return { return {
@@ -30,6 +40,8 @@ define([
/** /**
* show popover * show popover
*
* @private
* @param {jQuery} popover * @param {jQuery} popover
* @param {Position} pos * @param {Position} pos
*/ */
@@ -56,21 +68,32 @@ define([
if (styleInfo.anchor) { if (styleInfo.anchor) {
var $anchor = $linkPopover.find('a'); var $anchor = $linkPopover.find('a');
var href = $(styleInfo.anchor).attr('href'); var href = $(styleInfo.anchor).attr('href');
var target = $(styleInfo.anchor).attr('target');
$anchor.attr('href', href).html(href); $anchor.attr('href', href).html(href);
showPopover($linkPopover, posFromPlaceholder(styleInfo.anchor, isAirMode)); if (!target) {
$anchor.removeAttr('target');
} else {
$anchor.attr('target', '_blank');
}
showPopover($linkPopover, posFromPlaceholder(styleInfo.anchor, {
isAirMode: isAirMode
}));
} else { } else {
$linkPopover.hide(); $linkPopover.hide();
} }
var $imagePopover = $popover.find('.note-image-popover'); var $imagePopover = $popover.find('.note-image-popover');
if (styleInfo.image) { if (styleInfo.image) {
showPopover($imagePopover, posFromPlaceholder(styleInfo.image, isAirMode)); showPopover($imagePopover, posFromPlaceholder(styleInfo.image, {
isAirMode: isAirMode,
isLeftTop: true
}));
} else { } else {
$imagePopover.hide(); $imagePopover.hide();
} }
var $airPopover = $popover.find('.note-air-popover'); var $airPopover = $popover.find('.note-air-popover');
if (isAirMode && !styleInfo.range.isCollapsed()) { if (isAirMode && styleInfo.range && !styleInfo.range.isCollapsed()) {
var rect = list.last(styleInfo.range.getClientRects()); var rect = list.last(styleInfo.range.getClientRects());
if (rect) { if (rect) {
var bnd = func.rect2bnd(rect); var bnd = func.rect2bnd(rect);
@@ -0,0 +1,44 @@
define([
'summernote/core/dom'
], function (dom) {
var EDITABLE_PADDING = 24;
var Statusbar = function () {
var $document = $(document);
this.attach = function (layoutInfo, options) {
if (!options.disableResizeEditor) {
layoutInfo.statusbar().on('mousedown', hStatusbarMousedown);
}
};
/**
* `mousedown` event handler on statusbar
*
* @param {MouseEvent} event
*/
var hStatusbarMousedown = function (event) {
event.preventDefault();
event.stopPropagation();
var $editable = dom.makeLayoutInfo(event.target).editable();
var editableTop = $editable.offset().top - $document.scrollTop();
var layoutInfo = dom.makeLayoutInfo(event.currentTarget || event.target);
var options = layoutInfo.editor().data('options');
$document.on('mousemove', function (event) {
var nHeight = event.clientY - (editableTop + EDITABLE_PADDING);
nHeight = (options.minHeight > 0) ? Math.max(nHeight, options.minHeight) : nHeight;
nHeight = (options.maxHeight > 0) ? Math.min(nHeight, options.maxHeight) : nHeight;
$editable.height(nHeight);
}).one('mouseup', function () {
$document.off('mousemove');
});
};
};
return Statusbar;
});
@@ -1,14 +1,17 @@
define([ define([
'summernote/core/list', 'summernote/core/list',
'summernote/core/dom',
'summernote/module/Button' 'summernote/module/Button'
], function (list, Button) { ], function (list, dom, Button) {
/** /**
* @class module.Toolbar
*
* Toolbar * Toolbar
*/ */
var Toolbar = function () { var Toolbar = function () {
var button = new Button(); var button = new Button();
this.button = button; // odoo change for overwrite this.button = button; // ODOO: allow access for override
this.update = function ($toolbar, styleInfo) { this.update = function ($toolbar, styleInfo) {
button.update($toolbar, styleInfo); button.update($toolbar, styleInfo);
@@ -43,14 +46,54 @@ define([
.addClass('disabled'); .addClass('disabled');
}; };
/**
* @param {jQuery} $container
* @param {Boolean} [bFullscreen=false]
*/
this.updateFullscreen = function ($container, bFullscreen) { this.updateFullscreen = function ($container, bFullscreen) {
var $btn = $container.find('button[data-event="fullscreen"]'); var $btn = $container.find('button[data-event="fullscreen"]');
$btn.toggleClass('active', bFullscreen); $btn.toggleClass('active', bFullscreen);
}; };
/**
* @param {jQuery} $container
* @param {Boolean} [isCodeview=false]
*/
this.updateCodeview = function ($container, isCodeview) { this.updateCodeview = function ($container, isCodeview) {
var $btn = $container.find('button[data-event="codeview"]'); var $btn = $container.find('button[data-event="codeview"]');
$btn.toggleClass('active', isCodeview); $btn.toggleClass('active', isCodeview);
if (isCodeview) {
this.deactivate($container);
} else {
this.activate($container);
}
};
/**
* get button in toolbar
*
* @param {jQuery} $editable
* @param {String} name
* @return {jQuery}
*/
this.get = function ($editable, name) {
var $toolbar = dom.makeLayoutInfo($editable).toolbar();
return $toolbar.find('[data-name=' + name + ']');
};
/**
* set button state
* @param {jQuery} $editable
* @param {String} name
* @param {Boolean} [isActive=true]
*/
this.setButtonState = function ($editable, name, isActive) {
isActive = (isActive === false) ? false : true;
var $button = this.get($editable, name);
$button.toggleClass('active', isActive);
}; };
}; };
@@ -1,10 +1,36 @@
define('summernote/settings', function () { define('summernote/defaults', function () {
var settings = { /**
// version * @class defaults
*
* @singleton
*/
var defaults = {
/** @property */
version: '@VERSION', version: '@VERSION',
/** /**
* options *
* for event options, reference to EventHandler.attach
*
* @property {Object} options
* @property {String/Number} [options.width=null] set editor width
* @property {String/Number} [options.height=null] set editor height, ex) 300
* @property {String/Number} options.minHeight set minimum height of editor
* @property {String/Number} options.maxHeight
* @property {String/Number} options.focus
* @property {Number} options.tabsize
* @property {Boolean} options.styleWithSpan
* @property {Object} options.codemirror
* @property {Object} [options.codemirror.mode='text/html']
* @property {Object} [options.codemirror.htmlMode=true]
* @property {Object} [options.codemirror.lineNumbers=true]
* @property {String} [options.lang=en-US] language 'en-US', 'ko-KR', ...
* @property {String} [options.direction=null] text direction, ex) 'rtl'
* @property {Array} [options.toolbar]
* @property {Boolean} [options.airMode=false]
* @property {Array} [options.airPopover]
* @property {Fucntion} [options.onInit] initialize
* @property {Fucntion} [options.onsubmit]
*/ */
options: { options: {
width: null, // set editor width width: null, // set editor width
@@ -21,10 +47,84 @@ define('summernote/settings', function () {
disableLinkTarget: false, // hide link Target Checkbox disableLinkTarget: false, // hide link Target Checkbox
disableDragAndDrop: false, // disable drag and drop event disableDragAndDrop: false, // disable drag and drop event
disableResizeEditor: false, // disable resizing editor disableResizeEditor: false, // disable resizing editor
disableResizeImage: false, // disable resizing image
shortcuts: true, // enable keyboard shortcuts shortcuts: true, // enable keyboard shortcuts
textareaAutoSync: true, // enable textarea auto sync
placeholder: false, // enable placeholder text placeholder: false, // enable placeholder text
prettifyHtml: true, // enable prettifying html while toggling codeview
iconPrefix: 'fa fa-', // prefix for css icon classes
icons: {
font: {
bold: 'bold',
italic: 'italic',
underline: 'underline',
clear: 'eraser',
height: 'text-height',
strikethrough: 'strikethrough',
superscript: 'superscript',
subscript: 'subscript'
},
image: {
image: 'picture-o',
floatLeft: 'align-left',
floatRight: 'align-right',
floatNone: 'align-justify',
shapeRounded: 'square',
shapeCircle: 'circle-o',
shapeThumbnail: 'picture-o',
shapeNone: 'times',
remove: 'trash-o'
},
link: {
link: 'link',
unlink: 'unlink',
edit: 'edit'
},
table: {
table: 'table'
},
hr: {
insert: 'minus'
},
style: {
style: 'magic'
},
lists: {
unordered: 'list-ul',
ordered: 'list-ol'
},
options: {
help: 'question',
fullscreen: 'arrows-alt',
codeview: 'code'
},
paragraph: {
paragraph: 'align-left',
outdent: 'outdent',
indent: 'indent',
left: 'align-left',
center: 'align-center',
right: 'align-right',
justify: 'align-justify'
},
color: {
recent: 'font'
},
history: {
undo: 'undo',
redo: 'repeat'
},
misc: {
check: 'check'
}
},
dialogsInBody: false, // false will add dialogs into editor
codemirror: { // codemirror options codemirror: { // codemirror options
mode: 'text/html', mode: 'text/html',
@@ -40,7 +140,9 @@ define('summernote/settings', function () {
toolbar: [ toolbar: [
['style', ['style']], ['style', ['style']],
['font', ['bold', 'italic', 'underline', 'clear']], ['font', ['bold', 'italic', 'underline', 'clear']],
// ['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear']],
['fontname', ['fontname']], ['fontname', ['fontname']],
['fontsize', ['fontsize']],
['color', ['color']], ['color', ['color']],
['para', ['ul', 'ol', 'paragraph']], ['para', ['ul', 'ol', 'paragraph']],
['height', ['height']], ['height', ['height']],
@@ -50,6 +152,8 @@ define('summernote/settings', function () {
['help', ['help']] ['help', ['help']]
], ],
plugin : { },
// air mode: inline editor // air mode: inline editor
airMode: false, airMode: false,
// airPopover: [ // airPopover: [
@@ -80,9 +184,12 @@ define('summernote/settings', function () {
// fontName // fontName
fontNames: [ fontNames: [
'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New',
'Helvetica Neue', 'Impact', 'Lucida Grande', 'Helvetica Neue', 'Helvetica', 'Impact', 'Lucida Grande',
'Tahoma', 'Times New Roman', 'Verdana' 'Tahoma', 'Times New Roman', 'Verdana'
], ],
fontNamesIgnoreCheck: [],
fontSizes: ['8', '9', '10', '11', '12', '14', '18', '24', '36'],
// pallete colors(n x n) // pallete colors(n x n)
colors: [ colors: [
@@ -117,6 +224,7 @@ define('summernote/settings', function () {
onkeydown: null, // keydown onkeydown: null, // keydown
onImageUpload: null, // imageUpload onImageUpload: null, // imageUpload
onImageUploadError: null, // imageUploadError onImageUploadError: null, // imageUploadError
onMediaDelete: null, // media delete
onToolbarClick: null, onToolbarClick: null,
onsubmit: null, onsubmit: null,
@@ -128,8 +236,6 @@ define('summernote/settings', function () {
onCreateLink: function (sLinkUrl) { onCreateLink: function (sLinkUrl) {
if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) { if (sLinkUrl.indexOf('@') !== -1 && sLinkUrl.indexOf(':') === -1) {
sLinkUrl = 'mailto:' + sLinkUrl; sLinkUrl = 'mailto:' + sLinkUrl;
} else if (sLinkUrl.indexOf('://') === -1) {
sLinkUrl = 'http://' + sLinkUrl;
} }
return sLinkUrl; return sLinkUrl;
@@ -207,7 +313,11 @@ define('summernote/settings', function () {
underline: 'Underline', underline: 'Underline',
clear: 'Remove Font Style', clear: 'Remove Font Style',
height: 'Line Height', height: 'Line Height',
name: 'Font Family' name: 'Font Family',
strikethrough: 'Strikethrough',
subscript: 'Subscript',
superscript: 'Superscript',
size: 'Font Size'
}, },
image: { image: {
image: 'Picture', image: 'Picture',
@@ -291,7 +401,8 @@ define('summernote/settings', function () {
textFormatting: 'Text formatting', textFormatting: 'Text formatting',
action: 'Action', action: 'Action',
paragraphFormatting: 'Paragraph formatting', paragraphFormatting: 'Paragraph formatting',
documentStyle: 'Document Style' documentStyle: 'Document Style',
extraKeys: 'Extra keys'
}, },
history: { history: {
undo: 'Undo', undo: 'Undo',
@@ -301,5 +412,5 @@ define('summernote/settings', function () {
} }
}; };
return settings; return defaults;
}); });
@@ -1,39 +1,140 @@
define([ define([
'summernote/core/agent', 'summernote/core/dom', 'summernote/core/agent',
'summernote/core/list',
'summernote/core/dom',
'summernote/core/range', 'summernote/core/range',
'summernote/settings', 'summernote/defaults',
'summernote/EventHandler', 'summernote/Renderer', 'summernote/EventHandler',
'summernote/core/list', // odoo change for overwrite 'summernote/Renderer',
'summernote/core/key' // odoo change for overwrite 'summernote/core/key' // ODOO: change for override
], function (agent, dom, range, settings, EventHandler, Renderer, list, key) { // odoo change for overwrite ], function (agent, list, dom, range,
defaults, EventHandler, Renderer, key) {
// jQuery namespace for summernote // jQuery namespace for summernote
/**
* @class $.summernote
*
* summernote attribute
*
* @mixin defaults
* @singleton
*
*/
$.summernote = $.summernote || {}; $.summernote = $.summernote || {};
// extends default `settings` // extends default settings
$.extend($.summernote, settings); // - $.summernote.version
// - $.summernote.options
// - $.summernote.lang
$.extend($.summernote, defaults);
var renderer = new Renderer(); var renderer = new Renderer();
var eventHandler = new EventHandler(); var eventHandler = new EventHandler();
$.extend($.summernote, { $.extend($.summernote, {
/** @property {Renderer} */
renderer: renderer, renderer: renderer,
/** @property {EventHandler} */
eventHandler: eventHandler, eventHandler: eventHandler,
/**
* @property {Object} core
* @property {core.agent} core.agent
* @property {core.dom} core.dom
* @property {core.range} core.range
*/
core: { core: {
agent: agent, agent: agent,
list : list,
dom: dom, dom: dom,
range: range, range: range,
list: list, // odoo change for overwrite key: key // ODOO: change for override
key: key // odoo change for overwrite
}, },
pluginEvents: {} /**
* @property {Object}
* pluginEvents event list for plugins
* event has name and callback function.
*
* ```
* $.summernote.addPlugin({
* events : {
* 'hello' : function(layoutInfo, value, $target) {
* console.log('event name is hello, value is ' + value );
* }
* }
* })
* ```
*
* * event name is data-event property.
* * layoutInfo is a summernote layout information.
* * value is data-value property.
*/
pluginEvents: {},
plugins : []
}); });
/** /**
* addPlugin * @method addPlugin
*
* add Plugin in Summernote
*
* Summernote can make a own plugin.
*
* ### Define plugin
* ```
* // get template function
* var tmpl = $.summernote.renderer.getTemplate();
*
* // add a button
* $.summernote.addPlugin({
* buttons : {
* // "hello" is button's namespace.
* "hello" : function(lang, options) {
* // make icon button by template function
* return tmpl.iconButton(options.iconPrefix + 'header', {
* // callback function name when button clicked
* event : 'hello',
* // set data-value property
* value : 'hello',
* hide : true
* });
* }
*
* },
*
* events : {
* "hello" : function(layoutInfo, value) {
* // here is event code
* }
* }
* });
* ```
* ### Use a plugin in toolbar
*
* ```
* $("#editor").summernote({
* ...
* toolbar : [
* // display hello plugin in toolbar
* ['group', [ 'hello' ]]
* ]
* ...
* });
* ```
*
* *
* @param {Object} plugin * @param {Object} plugin
* @param {Object} [plugin.buttons] define plugin button. for detail, see to Renderer.addButtonInfo
* @param {Object} [plugin.dialogs] define plugin dialog. for detail, see to Renderer.addDialogInfo
* @param {Object} [plugin.events] add event in $.summernote.pluginEvents
* @param {Object} [plugin.langs] update $.summernote.lang
* @param {Object} [plugin.options] update $.summernote.options
*/ */
$.summernote.addPlugin = function (plugin) { $.summernote.addPlugin = function (plugin) {
// save plugin list
$.summernote.plugins.push(plugin);
if (plugin.buttons) { if (plugin.buttons) {
$.each(plugin.buttons, function (name, button) { $.each(plugin.buttons, function (name, button) {
renderer.addButtonInfo(name, button); renderer.addButtonInfo(name, button);
@@ -65,107 +166,157 @@ define([
} }
}; };
/** /*
* extend jquery fn * extend $.fn
*/ */
$.fn.extend({ $.fn.extend({
/** /**
* initialize summernote * @method
* Initialize summernote
* - create editor layout and attach Mouse and keyboard events. * - create editor layout and attach Mouse and keyboard events.
* *
* @param {Object} options * ```
* @returns {this} * $("#summernote").summernote( { options ..} );
* ```
*
* @member $.fn
* @param {Object|String} options reference to $.summernote.options
* @return {this}
*/ */
summernote: function (options) { summernote: function () {
// extend default options // check first argument's type
// - {String}: External API call {{module}}.{{method}}
// - {Object}: init options
var type = $.type(list.head(arguments));
var isExternalAPICalled = type === 'string';
var hasInitOptions = type === 'object';
// extend default options with custom user options
var options = hasInitOptions ? list.head(arguments) : {};
options = $.extend({}, $.summernote.options, options); options = $.extend({}, $.summernote.options, options);
options.icons = $.extend({}, $.summernote.options.icons, options.icons);
// Include langInfo in options for later use, e.g. for image drag-n-drop // Include langInfo in options for later use, e.g. for image drag-n-drop
// Setup language info with en-US as default // Setup language info with en-US as default
options.langInfo = $.extend(true, {}, $.summernote.lang['en-US'], $.summernote.lang[options.lang]); options.langInfo = $.extend(true, {}, $.summernote.lang['en-US'], $.summernote.lang[options.lang]);
// override plugin options
if (!isExternalAPICalled && hasInitOptions) {
for (var i = 0, len = $.summernote.plugins.length; i < len; i++) {
var plugin = $.summernote.plugins[i];
if (options.plugin[plugin.name]) {
$.summernote.plugins[i] = $.extend(true, plugin, options.plugin[plugin.name]);
}
}
}
this.each(function (idx, holder) { this.each(function (idx, holder) {
var $holder = $(holder); var $holder = $(holder);
// createLayout with options // if layout isn't created yet, createLayout and attach events
renderer.createLayout($holder, options); if (!renderer.hasNoteEditor($holder)) {
renderer.createLayout($holder, options);
var info = renderer.layoutInfoFromHolder($holder); var layoutInfo = renderer.layoutInfoFromHolder($holder);
eventHandler.attach(info, options); $holder.data('layoutInfo', layoutInfo);
// Textarea: auto filling the code before form submit. eventHandler.attach(layoutInfo, options);
if (dom.isTextarea($holder[0])) { eventHandler.attachCustomEvent(layoutInfo, options);
$holder.closest('form').submit(function () {
var contents = $holder.code();
$holder.val(contents);
// callback on submit
if (options.onsubmit) {
options.onsubmit(contents);
}
});
} }
}); });
// focus on first editable element var $first = this.first();
if (this.first().length && options.focus) { if ($first.length) {
var info = renderer.layoutInfoFromHolder(this.first()); var layoutInfo = renderer.layoutInfoFromHolder($first);
info.editable.focus();
}
// callback on init // external API
if (this.length && options.oninit) { if (isExternalAPICalled) {
options.oninit(); var moduleAndMethod = list.head(list.from(arguments));
var args = list.tail(list.from(arguments));
// TODO now external API only works for editor
var params = [moduleAndMethod, layoutInfo.editable()].concat(args);
return eventHandler.invoke.apply(eventHandler, params);
} else if (options.focus) {
// focus on first editable element for initialize editor
layoutInfo.editable().focus();
}
} }
return this; return this;
}, },
//
/** /**
* @method
*
* get the HTML contents of note or set the HTML contents of note. * get the HTML contents of note or set the HTML contents of note.
* *
* @param {String} [sHTML] - HTML contents(optional, set) * * get contents
* @returns {this|String} - context(set) or HTML contents of note(get). * ```
* var content = $("#summernote").code();
* ```
* * set contents
*
* ```
* $("#summernote").code(html);
* ```
*
* @member $.fn
* @param {String} [html] - HTML contents(optional, set)
* @return {this|String} - context(set) or HTML contents of note(get).
*/ */
code: function (sHTML) { code: function (html) {
// get the HTML contents of note // get the HTML contents of note
if (sHTML === undefined) { if (html === undefined) {
var $holder = this.first(); var $holder = this.first();
if (!$holder.length) { return; } if (!$holder.length) {
var info = renderer.layoutInfoFromHolder($holder); return;
if (!!(info && info.editable)) {
var isCodeview = info.editor.hasClass('codeview');
if (isCodeview && agent.hasCodeMirror) {
info.codable.data('cmEditor').save();
}
return isCodeview ? info.codable.val() : info.editable.html();
} }
return dom.isTextarea($holder[0]) ? $holder.val() : $holder.html();
var layoutInfo = renderer.layoutInfoFromHolder($holder);
var $editable = layoutInfo && layoutInfo.editable();
if ($editable && $editable.length) {
var isCodeview = eventHandler.invoke('codeview.isActivated', layoutInfo);
eventHandler.invoke('codeview.sync', layoutInfo);
return isCodeview ? layoutInfo.codable().val() :
layoutInfo.editable().html();
}
return dom.value($holder);
} }
// set the HTML contents of note // set the HTML contents of note
this.each(function (i, holder) { this.each(function (i, holder) {
var info = renderer.layoutInfoFromHolder($(holder)); var layoutInfo = renderer.layoutInfoFromHolder($(holder));
if (info && info.editable) { info.editable.html(sHTML); } var $editable = layoutInfo && layoutInfo.editable();
if ($editable) {
$editable.html(html);
}
}); });
return this; return this;
}, },
/** /**
* @method
*
* destroy Editor Layout and detach Key and Mouse Event * destroy Editor Layout and detach Key and Mouse Event
* *
* @returns {this} * @member $.fn
* @return {this}
*/ */
destroy: function () { destroy: function () {
this.each(function (idx, holder) { this.each(function (idx, holder) {
var $holder = $(holder); var $holder = $(holder);
var info = renderer.layoutInfoFromHolder($holder); if (!renderer.hasNoteEditor($holder)) {
if (!info || !info.editable) { return; } return;
}
var options = info.editor.data('options'); var info = renderer.layoutInfoFromHolder($holder);
var options = info.editor().data('options');
eventHandler.detach(info, options); eventHandler.detach(info, options);
renderer.removeLayout($holder, info, options); renderer.removeLayout($holder, info, options);
@@ -1,4 +1,4 @@
/* @_import "elements.less"; */ /* @_import "elements.less"; ODOO: removed call directly */
/* Theme Variables /* Theme Variables
------------------------------------------*/ ------------------------------------------*/
@@ -10,6 +10,7 @@
.note-editor { .note-editor {
border: 1px solid @border-color; border: 1px solid @border-color;
position: relative; position: relative;
/* overflow: hidden; ODOO: removed for embedded editor */
/* dropzone */ /* dropzone */
@dropzone-color: lightskyblue; @dropzone-color: lightskyblue;
@@ -17,8 +18,7 @@
.note-dropzone { .note-dropzone {
position: absolute; position: absolute;
display: none; display: none;
z-index: 1; z-index: 100;
border: 2px dashed @dropzone-color;
color: @dropzone-color; color: @dropzone-color;
background-color: white; background-color: white;
opacity: 0.95; opacity: 0.95;
@@ -33,7 +33,6 @@
} }
&.hover { &.hover {
border: 2px dashed @dropzone-active-color;
color: @dropzone-active-color; color: @dropzone-active-color;
} }
} }
@@ -42,9 +41,16 @@
display: table; display: table;
} }
.note-toolbar { /* codeview mode */
background-color: @background-color; &.codeview {
border-bottom: 1px solid @border-color; .note-editing-area {
.note-editable {
display: none;
}
.note-codable {
display: block;
}
}
} }
/* fullscreen mode */ /* fullscreen mode */
@@ -62,13 +68,43 @@
} }
} }
/* codeview mode */ .note-editing-area {
&.codeview { position: relative;
overflow: hidden;
/* editable */
.note-editable { .note-editable {
display: none; background-color: #fff;
color: #000;
padding: 10px;
overflow: auto;
outline: none;
&[contenteditable=true]:empty:not(:focus):before {
content:attr(data-placeholder);
}
&[contenteditable="false"] {
background-color: #e5e5e5;
}
} }
/* codeable */
.note-codable { .note-codable {
display: block; display: none;
width: 100%;
padding: 10px;
border: none;
box-shadow: none;
font-family: Menlo, Monaco, monospace, sans-serif;
font-size: 14px;
color: #ccc;
background-color: #222;
resize: none;
/* override BS2 default style */
.box-sizing(border-box);
.rounded(0);
margin-bottom: 0;
} }
} }
@@ -76,9 +112,9 @@
.note-statusbar { .note-statusbar {
background-color: @background-color; background-color: @background-color;
.note-resizebar { .note-resizebar {
padding-top: 1px;
height: 8px; height: 8px;
width: 100%; width: 100%;
border-top: 1px solid @border-color;
cursor: ns-resize; cursor: ns-resize;
.note-icon-bar { .note-icon-bar {
width: 20px; width: 20px;
@@ -87,50 +123,16 @@
} }
} }
} }
/* editable */
.note-editable[contenteditable=true]:empty:not(:focus):before {
content:attr(data-placeholder);
color: #a9a9a9;
}
.note-editable {
padding: 10px;
overflow: auto;
outline: none;
}
.note-editable[contenteditable="false"] {
background-color: #e5e5e5;
}
/* codeable */
.note-codable {
display: none;
width: 100%;
padding: 10px;
border: none;
box-shadow: none;
font-family: Menlo, Monaco, monospace, sans-serif;
font-size: 14px;
color: #ccc;
background-color: #222;
resize: none;
/* override BS2 default style */
.box-sizing(border-box);
.rounded(0);
margin-bottom: 0;
}
} }
/* Air Mode Layout /* Air Mode Layout
------------------------------------------*/ ------------------------------------------*/
.note-air-editor { .note-air-editor {
outline: none; outline: none;
} }
/* Popover /* Popover
------------------------------------------*/ ------------------------------------------*/
.note-popover .popover { .note-popover .popover {
max-width: none; max-width: none;
.popover-content { .popover-content {
@@ -149,8 +151,8 @@
} }
/* Popover and Toolbar (Button container) /* Popover and Toolbar (Button container)
------------------------------------------*/ ------------------------------------------*/
.note-popover .popover .popover-content, .note-toolbar { .note-popover .popover .popover-content, .panel-heading.note-toolbar {
margin: 0; margin: 0;
padding: 0 0 5px 5px; padding: 0 0 5px 5px;
@@ -258,15 +260,17 @@
&::before { right: 9px; left: auto !important; } &::before { right: 9px; left: auto !important; }
&::after { right: 10px; left: auto !important; } &::after { right: 10px; left: auto !important; }
} }
/* dropdown-menu for selectbox */ /* dropdown-menu for selectbox */
li a i { &.note-check {
color: deepskyblue; li a i {
visibility: hidden; color: deepskyblue;
} visibility: hidden;
li a.checked i { }
visibility: visible; li a.checked i {
visibility: visible;
}
} }
} }
.note-fontsize-10 { .note-fontsize-10 {
@@ -292,7 +296,7 @@
} }
/* Dialog /* Dialog
------------------------------------------*/ ------------------------------------------*/
.note-dialog { .note-dialog {
&>div { &>div {
display: none; /* BS2's hide pacth. */ display: none; /* BS2's hide pacth. */
@@ -386,7 +390,7 @@
} }
/* Handle /* Handle
------------------------------------------*/ ------------------------------------------*/
.note-handle { .note-handle {
/* control selection */ /* control selection */
.note-control-selection { .note-control-selection {
@@ -444,6 +448,12 @@
cursor: se-resize; cursor: se-resize;
} }
.note-control-se.note-control-holder {
cursor: default;
border-top: none;
border-left: none;
}
.note-control-selection-info { .note-control-selection-info {
right: 0; right: 0;
bottom: 0; bottom: 0;
+3 -1
View File
@@ -144,7 +144,9 @@ var History = function History ($editable) {
this.recordUndo = function ($editable, event, internal_history) { this.recordUndo = function ($editable, event, internal_history) {
var self = this; var self = this;
if (!$editable) { if (!$editable) {
$editable = $(range.create().sc).closest(".o_editable"); var rng = range.create();
if(!rng) return;
$editable = $(rng.sc).closest(".o_editable");
} }
if (aUndo[pos] && (event === "applySnap" || event === "activate")) { if (aUndo[pos] && (event === "applySnap" || event === "activate")) {
@@ -31,6 +31,8 @@ var options = $.summernote.options;
var tplButton = renderer.getTemplate().button; var tplButton = renderer.getTemplate().button;
var tplIconButton = renderer.getTemplate().iconButton; var tplIconButton = renderer.getTemplate().iconButton;
var tplIconButton = renderer.getTemplate().iconButton;
var tplDropdown = renderer.getTemplate().dropdown;
////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////
/* update and change the popovers content, and add history button */ /* update and change the popovers content, and add history button */
@@ -83,16 +85,17 @@ renderer.tplPopovers = function (lang, options) {
// padding button // padding button
var $padding = $('<div class="btn-group"/>'); var $padding = $('<div class="btn-group"/>');
$padding.insertBefore($imagePopover.find('.btn-group:first')); $padding.insertBefore($imagePopover.find('.btn-group:first'));
var dropdown_content = [
'<li><a data-event="padding" href="#" data-value="">'+_t('None')+'</a></li>',
'<li><a data-event="padding" href="#" data-value="small">'+_t('Small')+'</a></li>',
'<li><a data-event="padding" href="#" data-value="medium">'+_t('Medium')+'</a></li>',
'<li><a data-event="padding" href="#" data-value="large">'+_t('Large')+'</a></li>',
'<li><a data-event="padding" href="#" data-value="xl">'+_t('Xl')+'</a></li>',
];
var $button = $(tplIconButton('fa fa-plus-square-o', { var $button = $(tplIconButton('fa fa-plus-square-o', {
title: _t('Padding'), title: _t('Padding'),
dropdown: true dropdown: tplDropdown(dropdown_content)
})).appendTo($padding); })).appendTo($padding);
var $ul = $('<ul class="dropdown-menu"/>').insertAfter($button);
$ul.append('<li><a data-event="padding" href="#" data-value="">'+_t('None')+'</a></li>');
$ul.append('<li><a data-event="padding" href="#" data-value="small">'+_t('Small')+'</a></li>');
$ul.append('<li><a data-event="padding" href="#" data-value="medium">'+_t('Medium')+'</a></li>');
$ul.append('<li><a data-event="padding" href="#" data-value="large">'+_t('Large')+'</a></li>');
$ul.append('<li><a data-event="padding" href="#" data-value="xl">'+_t('Xl')+'</a></li>');
// circle, boxed... options became toggled // circle, boxed... options became toggled
$imagePopover.find('[data-event="imageShape"]:not([data-value])').remove(); $imagePopover.find('[data-event="imageShape"]:not([data-value])').remove();
@@ -178,8 +181,8 @@ renderer.tplPopovers = function (lang, options) {
return $popover; return $popover;
}; };
var fn_boutton_update = eventHandler.popover.button.update; var fn_boutton_update = eventHandler.modules.popover.button.update;
eventHandler.popover.button.update = function ($container, oStyle) { eventHandler.modules.popover.button.update = function ($container, oStyle) {
// stop animation when edit content // stop animation when edit content
var previous = $(".note-control-selection").data('target'); var previous = $(".note-control-selection").data('target');
if (previous) { if (previous) {
@@ -247,15 +250,15 @@ eventHandler.popover.button.update = function ($container, oStyle) {
} }
}; };
var fn_popover_update = eventHandler.popover.update; var fn_popover_update = eventHandler.modules.popover.update;
eventHandler.popover.update = function ($popover, oStyle, isAirMode) { eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) {
var $imagePopover = $popover.find('.note-image-popover'); var $imagePopover = $popover.find('.note-image-popover');
var $linkPopover = $popover.find('.note-link-popover'); var $linkPopover = $popover.find('.note-link-popover');
var $airPopover = $popover.find('.note-air-popover'); var $airPopover = $popover.find('.note-air-popover');
fn_popover_update.call(this, $popover, oStyle, isAirMode); fn_popover_update.call(this, $popover, oStyle, isAirMode);
if (!isAirMode || $(oStyle.range.sc).closest('[data-oe-model]:not([data-oe-model="ir.ui.view"]):not([data-oe-type="html"])').length) { if (!isAirMode || (oStyle.range && $(oStyle.range.sc).closest('[data-oe-model]:not([data-oe-model="ir.ui.view"]):not([data-oe-type="html"])').length)) {
$imagePopover.hide(); $imagePopover.hide();
$linkPopover.hide(); $linkPopover.hide();
$airPopover.hide(); $airPopover.hide();
@@ -276,19 +279,22 @@ eventHandler.popover.update = function ($popover, oStyle, isAirMode) {
$(".note-control-selection").hide(); $(".note-control-selection").hide();
} }
if (oStyle.image || (!oStyle.range.isCollapsed() || (oStyle.range.sc.tagName && !dom.isAnchor(oStyle.range.sc)) || (oStyle.image && !$(oStyle.image).closest('a').length))) { if (oStyle.image || (oStyle.range && (!oStyle.range.isCollapsed() || (oStyle.range.sc.tagName && !dom.isAnchor(oStyle.range.sc)))) || (oStyle.image && !$(oStyle.image).closest('a').length)) {
$linkPopover.hide(); $linkPopover.hide();
oStyle.anchor = false; oStyle.anchor = false;
} }
if (oStyle.image || oStyle.anchor || !$(oStyle.range.sc).closest('.note-editable').length) { if (oStyle.image || oStyle.anchor || (oStyle.range && !$(oStyle.range.sc).closest('.note-editable').length)) {
$airPopover.hide(); $airPopover.hide();
} else { } else {
$airPopover.show(); $airPopover.show();
} }
}; };
eventHandler.handle.update = function ($handle, oStyle, isAirMode) { /* TODO: remove (original method seems enough now (only border-top,left is not done) */
/*
eventHandler.modules.handle.update = function ($handle, oStyle, isAirMode) {
$handle.toggle(!!oStyle.image); $handle.toggle(!!oStyle.image);
if (oStyle.image) { if (oStyle.image) {
var $selection = $handle.find('.note-control-selection'); var $selection = $handle.find('.note-control-selection');
@@ -307,6 +313,7 @@ eventHandler.handle.update = function ($handle, oStyle, isAirMode) {
}); });
} }
}; };
*/
////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////
/* hack for image and link editor */ /* hack for image and link editor */
@@ -314,7 +321,7 @@ eventHandler.handle.update = function ($handle, oStyle, isAirMode) {
function getImgTarget () { function getImgTarget () {
return $(".note-control-selection").data('target'); return $(".note-control-selection").data('target');
} }
eventHandler.editor.padding = function ($editable, sValue) { eventHandler.modules.editor.padding = function ($editable, sValue) {
var $target = $(getImgTarget()); var $target = $(getImgTarget());
var paddings = "small medium large xl".split(/\s+/); var paddings = "small medium large xl".split(/\s+/);
$editable.data('NoteHistory').recordUndo(); $editable.data('NoteHistory').recordUndo();
@@ -324,7 +331,7 @@ eventHandler.editor.padding = function ($editable, sValue) {
} }
$target.removeClass("padding-" + paddings.join(" padding-")); $target.removeClass("padding-" + paddings.join(" padding-"));
}; };
eventHandler.editor.resize = function ($editable, sValue) { eventHandler.modules.editor.resize = function ($editable, sValue) {
var $target = $(getImgTarget()); var $target = $(getImgTarget());
$editable.data('NoteHistory').recordUndo(); $editable.data('NoteHistory').recordUndo();
var width = ($target.attr('style') || '').match(/(^|;|\s)width:\s*([0-9]+)%/); var width = ($target.attr('style') || '').match(/(^|;|\s)width:\s*([0-9]+)%/);
@@ -333,7 +340,7 @@ eventHandler.editor.resize = function ($editable, sValue) {
} }
$(getImgTarget()).css('width', width != sValue ? (sValue * 100) + '%' : ''); $(getImgTarget()).css('width', width != sValue ? (sValue * 100) + '%' : '');
}; };
eventHandler.editor.resizefa = function ($editable, sValue) { eventHandler.modules.editor.resizefa = function ($editable, sValue) {
var $target = $(getImgTarget()); var $target = $(getImgTarget());
$editable.data('NoteHistory').recordUndo(); $editable.data('NoteHistory').recordUndo();
$target.attr('class', $target.attr('class').replace(/\s*fa-[0-9]+x/g, '')); $target.attr('class', $target.attr('class').replace(/\s*fa-[0-9]+x/g, ''));
@@ -341,7 +348,7 @@ eventHandler.editor.resizefa = function ($editable, sValue) {
$target.addClass('fa-'+sValue+'x'); $target.addClass('fa-'+sValue+'x');
} }
}; };
eventHandler.editor.floatMe = function ($editable, sValue) { eventHandler.modules.editor.floatMe = function ($editable, sValue) {
var $target = $(getImgTarget()); var $target = $(getImgTarget());
$editable.data('NoteHistory').recordUndo(); $editable.data('NoteHistory').recordUndo();
switch (sValue) { switch (sValue) {
@@ -350,13 +357,13 @@ eventHandler.editor.floatMe = function ($editable, sValue) {
case 'right': $target.toggleClass('pull-right').removeClass('pull-left center-block'); break; case 'right': $target.toggleClass('pull-right').removeClass('pull-left center-block'); break;
} }
}; };
eventHandler.editor.imageShape = function ($editable, sValue) { eventHandler.modules.editor.imageShape = function ($editable, sValue) {
var $target = $(getImgTarget()); var $target = $(getImgTarget());
$editable.data('NoteHistory').recordUndo(); $editable.data('NoteHistory').recordUndo();
$target.toggleClass(sValue); $target.toggleClass(sValue);
}; };
eventHandler.dialog.showLinkDialog = function ($editable, $dialog, linkInfo) { eventHandler.modules.linkDialog.showLinkDialog = function ($editable, $dialog, linkInfo) {
$editable.data('range').select(); $editable.data('range').select();
$editable.data('NoteHistory').recordUndo(); $editable.data('NoteHistory').recordUndo();
@@ -376,7 +383,7 @@ eventHandler.dialog.showLinkDialog = function ($editable, $dialog, linkInfo) {
}); });
return def; return def;
}; };
eventHandler.dialog.showImageDialog = function ($editable) { eventHandler.modules.imageDialog.showImageDialog = function ($editable) {
var r = $editable.data('range'); var r = $editable.data('range');
if (r.sc.tagName && r.sc.childNodes.length) { if (r.sc.tagName && r.sc.childNodes.length) {
r.sc = r.sc.childNodes[r.so]; r.sc = r.sc.childNodes[r.so];
@@ -394,8 +401,9 @@ $.summernote.pluginEvents.alt = function (event, editor, layoutInfo, sorted) {
////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////
var fn_is_void = dom.isVoid;
dom.isVoid = function (node) { dom.isVoid = function (node) {
return node && /^BR|^IMG|^HR/.test(node.nodeName.toUpperCase()) || dom.isImg(node); return fn_is_void(node) || dom.isImgFont(node) || (node && node.className && node.className.match(/(^|\s)media_iframe_video(\s|$)/i));
}; };
dom.isImg = function (node) { dom.isImg = function (node) {
return dom.isImgFont(node) || (node && (node.nodeName === "IMG" || (node.className && node.className.match(/(^|\s)(media_iframe_video|o_image)(\s|$)/i)) )); return dom.isImgFont(node) || (node && (node.nodeName === "IMG" || (node.className && node.className.match(/(^|\s)(media_iframe_video|o_image)(\s|$)/i)) ));
@@ -428,7 +436,9 @@ dom.isFont = function (node) {
var fn_visible = $.summernote.pluginEvents.visible; var fn_visible = $.summernote.pluginEvents.visible;
$.summernote.pluginEvents.visible = function (event, editor, layoutInfo) { $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
var res = fn_visible.call(this, event, editor, layoutInfo); var res = fn_visible.call(this, event, editor, layoutInfo);
var $node = $(dom.node(range.create().sc)); var rng = range.create();
if(!rng) return res;
var $node = $(dom.node(rng.sc));
if (($node.is('[data-oe-type="html"]') || $node.is('[data-oe-field="arch"]')) && if (($node.is('[data-oe-type="html"]') || $node.is('[data-oe-field="arch"]')) &&
$node.hasClass("o_editable") && $node.hasClass("o_editable") &&
!$node[0].children.length && !$node[0].children.length &&
@@ -517,11 +527,11 @@ function summernote_mousedown (event) {
// remember_selection when click on non editable area // remember_selection when click on non editable area
var r = range.create(); var r = range.create();
if ($(r ? dom.node(r.sc) : event.srcElement || event.target).closest('#web_editor-top-navbar, #oe_main_menu_navbar, .note-popover, .note-toolbar, .modal').length) { if ($(r ? dom.node(r.sc) : event.srcElement || event.target).closest('#web_editor-top-navbar, #oe_main_menu_navbar, .note-popover, .note-toolbar, .modal').length) {
if (!$(event.target).is('input, select, label, button, a')) { if (!$(event.target).is('input, select, label, button:not(.note-color-btn), a')) {
if (!remember_selection && $editable[0]) { if (!remember_selection && $editable[0]) {
remember_selection = range.create(dom.firstChild($editable[0]), 0); remember_selection = range.create(dom.firstChild($editable[0]), 0);
} }
if (!remember_selection && $editable[0]) { if (remember_selection) {
try { try {
remember_selection.select(); remember_selection.select();
} catch (e) { } catch (e) {
@@ -605,19 +615,23 @@ function summernote_ie_fix (event, pred) {
var fn_attach = eventHandler.attach; var fn_attach = eventHandler.attach;
eventHandler.attach = function (oLayoutInfo, options) { eventHandler.attach = function (oLayoutInfo, options) {
fn_attach.call(this, oLayoutInfo, options); fn_attach.call(this, oLayoutInfo, options);
oLayoutInfo.editor.on('dragstart', 'img', function (e) { e.preventDefault(); }); oLayoutInfo.editor().on('dragstart', 'img', function (e) { e.preventDefault(); });
$(document).on('mousedown', summernote_mousedown); $(document).on('mousedown', summernote_mousedown);
$(document).on('mouseup', summernote_mouseup); $(document).on('mouseup', summernote_mouseup);
oLayoutInfo.editor.off('click').on('click', function (e) {e.preventDefault();}); // if the content editable is a link oLayoutInfo.editor().off('click').on('click', function (e) {e.preventDefault();}); // if the content editable is a link
oLayoutInfo.editor.on('dblclick', 'img, .media_iframe_video, span.fa, i.fa, span.fa, a.o_image', function (event) { oLayoutInfo.editor().on('dblclick', 'img, .media_iframe_video, span.fa, i.fa, span.fa, a.o_image', function (event) {
if (!$(event.target).closest(".note-toolbar").length) { // prevent icon edition of top bar for default summernote if (!$(event.target).closest(".note-toolbar").length) { // prevent icon edition of top bar for default summernote
new widgets.MediaDialog(oLayoutInfo.editor, event.target).appendTo(document.body); new widgets.MediaDialog(oLayoutInfo.editor(), event.target).appendTo(document.body);
}
});
oLayoutInfo.editable().on('mousedown', function(e) {
if(dom.isImg(e.target)) {
range.createFromNode(e.target).select();
} }
}); });
$(document).on("keyup", reRangeSelectKey); $(document).on("keyup", reRangeSelectKey);
var clone_data = false; var clone_data = false;
var $node = oLayoutInfo.editor; var $node = oLayoutInfo.editor();
if ($node.data('oe-model') || $node.data('oe-translation-id')) { if ($node.data('oe-model') || $node.data('oe-translation-id')) {
$node.on('content_changed', function () { $node.on('content_changed', function () {
var $nodes = $('[data-oe-model], [data-oe-translation-id]') var $nodes = $('[data-oe-model], [data-oe-translation-id]')
@@ -658,25 +672,26 @@ eventHandler.attach = function (oLayoutInfo, options) {
}); });
} }
var $toolbar = $(oLayoutInfo.popover).add(oLayoutInfo.toolbar); var custom_toolbar = oLayoutInfo.toolbar ? oLayoutInfo.toolbar() : undefined;
var $toolbar = $(oLayoutInfo.popover()).add(custom_toolbar);
$('button[data-event="undo"], button[data-event="redo"]', $toolbar).attr('disabled', true); $('button[data-event="undo"], button[data-event="redo"]', $toolbar).attr('disabled', true);
$(oLayoutInfo.editor) $(oLayoutInfo.editor())
.add(oLayoutInfo.handle) .add(oLayoutInfo.handle())
.add(oLayoutInfo.popover) .add(oLayoutInfo.popover())
.add(oLayoutInfo.toolbar) .add(custom_toolbar)
.on('click keyup', function () { .on('click keyup', function () {
$('button[data-event="undo"]', $toolbar).attr('disabled', !oLayoutInfo.editable.data('NoteHistory').hasUndo()); $('button[data-event="undo"]', $toolbar).attr('disabled', !oLayoutInfo.editable().data('NoteHistory').hasUndo());
$('button[data-event="redo"]', $toolbar).attr('disabled', !oLayoutInfo.editable.data('NoteHistory').hasRedo()); $('button[data-event="redo"]', $toolbar).attr('disabled', !oLayoutInfo.editable().data('NoteHistory').hasRedo());
}); });
}; };
var fn_detach = eventHandler.detach; var fn_detach = eventHandler.detach;
eventHandler.dettach = function (oLayoutInfo, options) { eventHandler.detach = function (oLayoutInfo, options) {
fn_detach.call(this, oLayoutInfo, options); fn_detach.call(this, oLayoutInfo, options);
oLayoutInfo.editor.off("dragstart"); oLayoutInfo.editor().off("dragstart");
$(document).off('mousedown', summernote_mousedown); $(document).off('mousedown', summernote_mousedown);
$(document).off('mouseup', summernote_mouseup); $(document).off('mouseup', summernote_mouseup);
oLayoutInfo.editor.off("dblclick"); oLayoutInfo.editor().off("dblclick");
$(document).off("keyup", reRangeSelectKey); $(document).off("keyup", reRangeSelectKey);
}; };
+85 -16
View File
@@ -554,7 +554,7 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
var node = dom.node(sc); var node = dom.node(sc);
if (!dom.isNotBreakable(node)) { if (!dom.isNotBreakable(node) && !dom.isVoid(sc)) {
sc = dom.splitTree(ancestor_sc, {'node': sc, 'offset': so}); sc = dom.splitTree(ancestor_sc, {'node': sc, 'offset': so});
} }
var before = dom.hasContentBefore(dom.ancestorHavePreviousSibling(sc)); var before = dom.hasContentBefore(dom.ancestorHavePreviousSibling(sc));
@@ -586,7 +586,9 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
so = 0; so = 0;
} }
if (towrite && !node.firstChild && node.parentNode && !dom.isNotBreakable(node)) { if (dom.isVoid(node)) {
// we don't need to append a br
} else if (towrite && !node.firstChild && node.parentNode && !dom.isNotBreakable(node)) {
var br = $("<br/>")[0]; var br = $("<br/>")[0];
node.appendChild(sc); node.appendChild(sc);
sc = br; sc = br;
@@ -892,6 +894,47 @@ range.WrappedRange.prototype.deleteContents = function (towrite) {
prevBP.eo prevBP.eo
); );
}; };
// isOnImg: judge whether range is an image node or not
range.WrappedRange.prototype.isOnImg = function () {
var nb = 0;
var image;
var startPoint = {node: this.sc.childNodes.length && this.sc.childNodes[this.so] || this.sc};
startPoint.offset = startPoint.node === this.sc ? this.so : 0;
var endPoint = {node: this.ec.childNodes.length && this.ec.childNodes[this.eo] || this.ec};
endPoint.offset = endPoint.node === this.ec ? this.eo : 0;
if (dom.isImg(startPoint.node)) {
nb ++;
image = startPoint.node;
}
dom.walkPoint(startPoint, endPoint, function (point) {
if (!dom.isText(endPoint.node) && point.node === endPoint.node && point.offset === endPoint.offset) {
return;
}
var node = point.node.childNodes.length && point.node.childNodes[point.offset] || point.node;
var offset = node === point.node ? point.offset : 0;
var isImg = dom.ancestor(node, dom.isImg);
if (!isImg && ((!dom.isBR(node) && !dom.isText(node)) || (offset && node.textContent.length !== offset && node.textContent.match(/\S|\u00A0/)))) {
nb++;
}
if (isImg && image !== isImg) {
image = isImg;
nb ++;
}
});
return nb === 1 && image;
};
range.WrappedRange.prototype.deleteContents = function (towrite) {
var prevBP = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite);
$(dom.node(prevBP.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
return new range.WrappedRange(
prevBP.sc,
prevBP.so,
prevBP.ec,
prevBP.eo
);
};
range.WrappedRange.prototype.clean = function (mergeFilter, all) { range.WrappedRange.prototype.clean = function (mergeFilter, all) {
var node = dom.node(this.sc === this.ec ? this.sc : this.commonAncestor()); var node = dom.node(this.sc === this.ec ? this.sc : this.commonAncestor());
node = node || $(this.sc).closest('[contenteditable]')[0]; node = node || $(this.sc).closest('[contenteditable]')[0];
@@ -1142,7 +1185,7 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
} }
// don't write in forbidden tag (like span for font awsome) // don't write in forbidden tag (like span for font awsome)
var node = dom.firstChild(r.sc.tagName && r.so ? r.sc.childNodes[r.so] : r.sc); var node = dom.firstChild(r.sc.tagName && r.so ? r.sc.childNodes[r.so] || r.sc : r.sc);
while (node.parentNode) { while (node.parentNode) {
if (dom.isForbiddenNode(node)) { if (dom.isForbiddenNode(node)) {
var text = node.previousSibling; var text = node.previousSibling;
@@ -1292,7 +1335,12 @@ $.summernote.pluginEvents.delete = function (event, editor, layoutInfo) {
dom.autoMerge(target, false); dom.autoMerge(target, false);
var next = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(target))); var next = dom.firstChild(dom.hasContentAfter(dom.ancestorHaveNextSibling(target)));
if (dom.isBR(next)) { if (dom.isBR(next)) {
range.create(next.previousSibling, next.previousSibling.textContent.length).select(); if(dom.position(next) == 0) {
range.create(next.parentNode, 0).select();
}
else {
range.create(next.previousSibling, next.previousSibling.textContent.length).select();
}
next.parentNode.removeChild(next); next.parentNode.removeChild(next);
} else { } else {
range.create(next, 0).select(); range.create(next, 0).select();
@@ -1383,10 +1431,16 @@ $.summernote.pluginEvents.backspace = function (event, editor, layoutInfo) {
var prevTr = dom.previousElementSibling(tr); var prevTr = dom.previousElementSibling(tr);
if (!$(temp.parentNode).text().match(/\S|\u00A0/)) { if (!$(temp.parentNode).text().match(/\S|\u00A0/)) {
if (prevTr) { if (prevTr) {
tr.parentNode.removeChild(tr);
node = (dom.lastElementChild(prevTr).lastChild && dom.lastElementChild(prevTr).lastChild.tagName ? dom.lastElementChild(prevTr).lastChild.previousSibling : dom.lastElementChild(prevTr).lastChild) || dom.lastElementChild(prevTr); node = (dom.lastElementChild(prevTr).lastChild && dom.lastElementChild(prevTr).lastChild.tagName ? dom.lastElementChild(prevTr).lastChild.previousSibling : dom.lastElementChild(prevTr).lastChild) || dom.lastElementChild(prevTr);
range.create(node, node.textContent.length, node, node.textContent.length).select(); } else {
node = dom.lastChild(dom.hasContentBefore(dom.ancestorHavePreviousSibling(tr)));
} }
$(tr).empty();
if(!$(tr).closest('table').has('td, th').length) {
$(tr).closest('table').remove();
}
$(tr).remove();
range.create(node, node.textContent.length, node, node.textContent.length).select();
} else { } else {
node = dom.lastElementChild(prevTr).lastChild || dom.lastElementChild(prevTr); node = dom.lastElementChild(prevTr).lastChild || dom.lastElementChild(prevTr);
} }
@@ -1793,8 +1847,8 @@ $.summernote.pluginEvents.removeFormat = function (event, editor, layoutInfo, va
event.preventDefault(); event.preventDefault();
return false; return false;
}; };
var fn_boutton_updateRecentColor = eventHandler.toolbar.button.updateRecentColor; var fn_boutton_updateRecentColor = eventHandler.modules.toolbar.button.updateRecentColor;
eventHandler.toolbar.button.updateRecentColor = function (elBtn, sEvent, sValue) { eventHandler.modules.toolbar.button.updateRecentColor = function (elBtn, sEvent, sValue) {
fn_boutton_updateRecentColor.call(this, elBtn, sEvent, sValue); fn_boutton_updateRecentColor.call(this, elBtn, sEvent, sValue);
var font = $(elBtn).closest('.note-color').find('.note-recent-color i')[0]; var font = $(elBtn).closest('.note-color').find('.note-recent-color i')[0];
@@ -1827,16 +1881,31 @@ $(document).on('click keyup', function () {
$('button[data-event="redo"]', editor).attr('disabled', !popover_history.hasRedo()); $('button[data-event="redo"]', editor).attr('disabled', !popover_history.hasRedo());
}); });
eventHandler.editor.undo = function ($popover) { eventHandler.modules.editor.undo = function ($popover) {
if(!$popover.attr('disabled')) $popover.data('NoteHistory').undo(); if(!$popover.attr('disabled')) $popover.data('NoteHistory').undo();
}; };
eventHandler.editor.redo = function ($popover) { eventHandler.modules.editor.redo = function ($popover) {
if(!$popover.attr('disabled')) $popover.data('NoteHistory').redo(); if(!$popover.attr('disabled')) $popover.data('NoteHistory').redo();
}; };
// use image toolbar if current range is on image
var fn_editor_currentstyle = eventHandler.modules.editor.currentStyle;
eventHandler.modules.editor.currentStyle = function(target) {
var styleInfo = fn_editor_currentstyle.apply(this, arguments);
// with our changes for inline editor, the targeted element could be a button of the editor
if(!styleInfo.image || !dom.isEditable(styleInfo.image)) {
styleInfo.image = undefined;
var rng = range.create();
if(rng)
styleInfo.image = rng.isOnImg();
}
return styleInfo;
}
options.fontSizes = [_t('Default'), 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 62]; options.fontSizes = [_t('Default'), 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 62];
$.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color, bgcolor, size) { $.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color, bgcolor, size) {
var rng = range.create(); var rng = range.create();
if(!rng) return;
var startPoint = rng.getStartPoint(); var startPoint = rng.getStartPoint();
var endPoint = rng.getEndPoint(); var endPoint = rng.getEndPoint();
@@ -2208,8 +2277,8 @@ function summernote_table_update (oStyle) {
'color': '#00ff00' 'color': '#00ff00'
}); });
} }
var fn_popover_update = eventHandler.popover.update; var fn_popover_update = eventHandler.modules.popover.update;
eventHandler.popover.update = function ($popover, oStyle, isAirMode) { eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) {
fn_popover_update.call(this, $popover, oStyle, isAirMode); fn_popover_update.call(this, $popover, oStyle, isAirMode);
if(!!(isAirMode ? $popover : $popover.parent()).find('.note-table').length) { if(!!(isAirMode ? $popover : $popover.parent()).find('.note-table').length) {
summernote_table_update(oStyle); summernote_table_update(oStyle);
@@ -2249,16 +2318,16 @@ function summernote_paste (event) {
var fn_attach = eventHandler.attach; var fn_attach = eventHandler.attach;
eventHandler.attach = function (oLayoutInfo, options) { eventHandler.attach = function (oLayoutInfo, options) {
var $editable = oLayoutInfo.editor.hasClass('note-editable') ? oLayoutInfo.editor : oLayoutInfo.editor.find('.note-editable'); var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
fn_attach.call(this, oLayoutInfo, options); fn_attach.call(this, oLayoutInfo, options);
oLayoutInfo.editor.on("paste", summernote_paste); oLayoutInfo.editor().on("paste", summernote_paste);
$editable.on("scroll", summernote_table_scroll); $editable.on("scroll", summernote_table_scroll);
}; };
var fn_detach = eventHandler.detach; var fn_detach = eventHandler.detach;
eventHandler.detach = function (oLayoutInfo, options) { eventHandler.detach = function (oLayoutInfo, options) {
var $editable = oLayoutInfo.editor.hasClass('note-editable') ? oLayoutInfo.editor : oLayoutInfo.editor.find('.note-editable'); var $editable = oLayoutInfo.editor().hasClass('note-editable') ? oLayoutInfo.editor() : oLayoutInfo.editor().find('.note-editable');
fn_detach.call(this, oLayoutInfo, options); fn_detach.call(this, oLayoutInfo, options);
oLayoutInfo.editor.off("paste", summernote_paste); oLayoutInfo.editor().off("paste", summernote_paste);
$editable.off("scroll", summernote_table_scroll); $editable.off("scroll", summernote_table_scroll);
$('.o_table_handler').remove(); $('.o_table_handler').remove();
}; };
+10 -50
View File
@@ -34,56 +34,16 @@
}; };
})(); })();
</script> </script>
<script>odoo.website_next_define = 'summernote/core/async';</script> <t t-set="summer_js_files" t-value="['core/async', 'core/func', 'core/agent', 'core/list',
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/core/async.js" /> 'core/dom', 'core/key', 'core/range', 'editing/Bullet', 'editing/History', 'editing/Style',
<script>odoo.website_next_define = 'summernote/core/func';</script> 'editing/Table', 'editing/Typing', 'module/Editor', 'module/Button', 'module/Clipboard',
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/core/func.js" /> 'module/Codeview', 'module/DragAndDrop', 'module/Fullscreen', 'module/Handle',
<script>odoo.website_next_define = 'summernote/core/agent';</script> 'module/HelpDialog', 'module/ImageDialog', 'module/LinkDialog', 'module/Popover',
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/core/agent.js" /> 'module/Statusbar', 'module/Toolbar', 'Renderer', 'EventHandler', 'defaults', 'summernote']" />
<script>odoo.website_next_define = 'summernote/core/list';</script> <t t-foreach="summer_js_files" t-as="file">
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/core/list.js" /> <script>odoo.website_next_define = 'summernote/<t t-esc="file" />';</script>
<script>odoo.website_next_define = 'summernote/core/dom';</script> <script type="text/javascript" t-attf-src="/web_editor/static/lib/summernote/src/js/{{ file }}.js"></script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/core/dom.js" /> </t>
<script>odoo.website_next_define = 'summernote/core/key';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/core/key.js" />
<script>odoo.website_next_define = 'summernote/core/range';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/core/range.js" />
<script>odoo.website_next_define = 'summernote/editing/Bullet';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/editing/Bullet.js" />
<script>odoo.website_next_define = 'summernote/editing/History';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/editing/History.js" />
<script>odoo.website_next_define = 'summernote/editing/Style';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/editing/Style.js" />
<script>odoo.website_next_define = 'summernote/editing/Table';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/editing/Table.js" />
<script>odoo.website_next_define = 'summernote/editing/Typing';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/editing/Typing.js" />
<script>odoo.website_next_define = 'summernote/editing/Editor';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/editing/Editor.js" />
<script>odoo.website_next_define = 'summernote/module/Button';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/module/Button.js" />
<script>odoo.website_next_define = 'summernote/module/Dialog';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/module/Dialog.js" />
<script>odoo.website_next_define = 'summernote/module/Handle';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/module/Handle.js" />
<script>odoo.website_next_define = 'summernote/module/Popover';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/module/Popover.js" />
<script>odoo.website_next_define = 'summernote/module/Toolbar';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/module/Toolbar.js" />
<script>odoo.website_next_define = 'summernote/Renderer';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/Renderer.js" />
<script>odoo.website_next_define = 'summernote/EventHandler';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/EventHandler.js" />
<script>odoo.website_next_define = 'summernote/settings';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/settings.js" />
<script>odoo.website_next_define = 'summernote/summernote';</script>
<script type="text/javascript" src="/web_editor/static/lib/summernote/src/js/summernote.js" />
<script type="text/javascript" src="/web_editor/static/src/js/summernote.js" /> <script type="text/javascript" src="/web_editor/static/src/js/summernote.js" />
<script type="text/javascript"> <script type="text/javascript">
window.define = odoo.__define__; window.define = odoo.__define__;