jsdk-offical
Version:
JSDK is the most comprehensive TypeScript framework, like JDK.
1,647 lines (1,639 loc) • 301 kB
JavaScript
/**
* Super simple wysiwyg editor v0.8.12
* https://summernote.org
*
* Copyright 2013- Alan Hong. and other contributors
* summernote may be freely distributed under the MIT license.
*
* Date: 2019-05-16T08:16Z
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery')) :
typeof define === 'function' && define.amd ? define(['jquery'], factory) :
(global = global || self, factory(global.jQuery));
}(this, function ($$1) { 'use strict';
$$1 = $$1 && $$1.hasOwnProperty('default') ? $$1['default'] : $$1;
var Renderer = /** @class */ (function () {
function Renderer(markup, children, options, callback) {
this.markup = markup;
this.children = children;
this.options = options;
this.callback = callback;
}
Renderer.prototype.render = function ($parent) {
var $node = $$1(this.markup);
if (this.options && this.options.contents) {
$node.html(this.options.contents);
}
if (this.options && this.options.className) {
$node.addClass(this.options.className);
}
if (this.options && this.options.data) {
$$1.each(this.options.data, function (k, v) {
$node.attr('data-' + k, v);
});
}
if (this.options && this.options.click) {
$node.on('click', this.options.click);
}
if (this.children) {
var $container_1 = $node.find('.note-children-container');
this.children.forEach(function (child) {
child.render($container_1.length ? $container_1 : $node);
});
}
if (this.callback) {
this.callback($node, this.options);
}
if (this.options && this.options.callback) {
this.options.callback($node);
}
if ($parent) {
$parent.append($node);
}
return $node;
};
return Renderer;
}());
var renderer = {
create: function (markup, callback) {
return function () {
var options = typeof arguments[1] === 'object' ? arguments[1] : arguments[0];
var children = Array.isArray(arguments[0]) ? arguments[0] : [];
if (options && options.children) {
children = options.children;
}
return new Renderer(markup, children, options, callback);
};
}
};
var editor = renderer.create('<div class="note-editor note-frame card"/>');
var toolbar = renderer.create('<div class="note-toolbar card-header" role="toolbar"></div>');
var editingArea = renderer.create('<div class="note-editing-area"/>');
var codable = renderer.create('<textarea class="note-codable" role="textbox" aria-multiline="true"/>');
var editable = renderer.create('<div class="note-editable card-block" contentEditable="true" role="textbox" aria-multiline="true"/>');
var statusbar = renderer.create([
'<output class="note-status-output" aria-live="polite"/>',
'<div class="note-statusbar" role="status">',
' <output class="note-status-output" aria-live="polite"></output>',
' <div class="note-resizebar" role="seperator" aria-orientation="horizontal" aria-label="Resize">',
' <div class="note-icon-bar"/>',
' <div class="note-icon-bar"/>',
' <div class="note-icon-bar"/>',
' </div>',
'</div>',
].join(''));
var airEditor = renderer.create('<div class="note-editor"/>');
var airEditable = renderer.create([
'<div class="note-editable" contentEditable="true" role="textbox" aria-multiline="true"/>',
'<output class="note-status-output" aria-live="polite"/>',
].join(''));
var buttonGroup = renderer.create('<div class="note-btn-group btn-group">');
var dropdown = renderer.create('<div class="dropdown-menu" role="list">', function ($node, options) {
var markup = Array.isArray(options.items) ? options.items.map(function (item) {
var value = (typeof item === 'string') ? item : (item.value || '');
var content = options.template ? options.template(item) : item;
var option = (typeof item === 'object') ? item.option : undefined;
var dataValue = 'data-value="' + value + '"';
var dataOption = (option !== undefined) ? ' data-option="' + option + '"' : '';
return '<a class="dropdown-item" href="#" ' + (dataValue + dataOption) + ' role="listitem" aria-label="' + value + '">' + content + '</a>';
}).join('') : options.items;
$node.html(markup).attr({ 'aria-label': options.title });
});
var dropdownButtonContents = function (contents) {
return contents;
};
var dropdownCheck = renderer.create('<div class="dropdown-menu note-check" role="list">', function ($node, options) {
var markup = Array.isArray(options.items) ? options.items.map(function (item) {
var value = (typeof item === 'string') ? item : (item.value || '');
var content = options.template ? options.template(item) : item;
return '<a class="dropdown-item" href="#" data-value="' + value + '" role="listitem" aria-label="' + item + '">' + icon(options.checkClassName) + ' ' + content + '</a>';
}).join('') : options.items;
$node.html(markup).attr({ 'aria-label': options.title });
});
var palette = renderer.create('<div class="note-color-palette"/>', function ($node, options) {
var contents = [];
for (var row = 0, rowSize = options.colors.length; row < rowSize; row++) {
var eventName = options.eventName;
var colors = options.colors[row];
var colorsName = options.colorsName[row];
var buttons = [];
for (var col = 0, colSize = colors.length; col < colSize; col++) {
var color = colors[col];
var colorName = colorsName[col];
buttons.push([
'<button type="button" class="note-color-btn"',
'style="background-color:', color, '" ',
'data-event="', eventName, '" ',
'data-value="', color, '" ',
'title="', colorName, '" ',
'aria-label="', colorName, '" ',
'data-toggle="button" tabindex="-1"></button>',
].join(''));
}
contents.push('<div class="note-color-row">' + buttons.join('') + '</div>');
}
$node.html(contents.join(''));
if (options.tooltip) {
$node.find('.note-color-btn').tooltip({
container: options.container,
trigger: 'hover',
placement: 'bottom'
});
}
});
var dialog = renderer.create('<div class="modal" aria-hidden="false" tabindex="-1" role="dialog"/>', function ($node, options) {
if (options.fade) {
$node.addClass('fade');
}
$node.attr({
'aria-label': options.title
});
$node.html([
'<div class="modal-dialog">',
' <div class="modal-content">',
(options.title
? ' <div class="modal-header">' +
' <h4 class="modal-title">' + options.title + '</h4>' +
' <button type="button" class="close" data-dismiss="modal" aria-label="Close" aria-hidden="true">×</button>' +
' </div>' : ''),
' <div class="modal-body">' + options.body + '</div>',
(options.footer
? ' <div class="modal-footer">' + options.footer + '</div>' : ''),
' </div>',
'</div>',
].join(''));
});
var popover = renderer.create([
'<div class="note-popover popover in">',
' <div class="arrow"/>',
' <div class="popover-content note-children-container"/>',
'</div>',
].join(''), function ($node, options) {
var direction = typeof options.direction !== 'undefined' ? options.direction : 'bottom';
$node.addClass(direction);
if (options.hideArrow) {
$node.find('.arrow').hide();
}
});
var checkbox = renderer.create('<div class="form-check"></div>', function ($node, options) {
$node.html([
'<label class="form-check-label"' + (options.id ? ' for="' + options.id + '"' : '') + '>',
' <input role="checkbox" type="checkbox" class="form-check-input"' + (options.id ? ' id="' + options.id + '"' : ''),
(options.checked ? ' checked' : ''),
' aria-label="' + (options.text ? options.text : '') + '"',
' aria-checked="' + (options.checked ? 'true' : 'false') + '"/>',
' ' + (options.text ? options.text : '') + '</label>',
].join(''));
});
var icon = function (iconClassName, tagName) {
tagName = tagName || 'i';
return '<' + tagName + ' class="' + iconClassName + '"/>';
};
var ui = {
editor: editor,
toolbar: toolbar,
editingArea: editingArea,
codable: codable,
editable: editable,
statusbar: statusbar,
airEditor: airEditor,
airEditable: airEditable,
buttonGroup: buttonGroup,
dropdown: dropdown,
dropdownButtonContents: dropdownButtonContents,
dropdownCheck: dropdownCheck,
palette: palette,
dialog: dialog,
popover: popover,
icon: icon,
checkbox: checkbox,
options: {},
button: function ($node, options) {
return renderer.create('<button type="button" class="note-btn btn btn-light btn-sm" role="button" tabindex="-1">', function ($node, options) {
if (options && options.tooltip) {
$node.attr({
title: options.tooltip,
'aria-label': options.tooltip
}).tooltip({
container: (options.container !== undefined) ? options.container : 'body',
trigger: 'hover',
placement: 'bottom'
}).on('click', function (e) {
$$1(e.currentTarget).tooltip('hide');
});
}
})($node, options);
},
toggleBtn: function ($btn, isEnable) {
$btn.toggleClass('disabled', !isEnable);
$btn.attr('disabled', !isEnable);
},
toggleBtnActive: function ($btn, isActive) {
$btn.toggleClass('active', isActive);
},
onDialogShown: function ($dialog, handler) {
$dialog.one('shown.bs.modal', handler);
},
onDialogHidden: function ($dialog, handler) {
$dialog.one('hidden.bs.modal', handler);
},
showDialog: function ($dialog) {
$dialog.modal('show');
},
hideDialog: function ($dialog) {
$dialog.modal('hide');
},
createLayout: function ($note, options) {
var $editor = (options.airMode ? ui.airEditor([
ui.editingArea([
ui.airEditable(),
]),
]) : ui.editor([
ui.toolbar(),
ui.editingArea([
ui.codable(),
ui.editable(),
]),
ui.statusbar(),
])).render();
$editor.insertAfter($note);
return {
note: $note,
editor: $editor,
toolbar: $editor.find('.note-toolbar'),
editingArea: $editor.find('.note-editing-area'),
editable: $editor.find('.note-editable'),
codable: $editor.find('.note-codable'),
statusbar: $editor.find('.note-statusbar')
};
},
removeLayout: function ($note, layoutInfo) {
$note.html(layoutInfo.editable.html());
layoutInfo.editor.remove();
$note.show();
}
};
$$1.summernote = $$1.summernote || {
lang: {}
};
$$1.extend($$1.summernote.lang, {
'en-US': {
font: {
bold: 'Bold',
italic: 'Italic',
underline: 'Underline',
clear: 'Remove Font Style',
height: 'Line Height',
name: 'Font Family',
strikethrough: 'Strikethrough',
subscript: 'Subscript',
superscript: 'Superscript',
size: 'Font Size'
},
image: {
image: 'Picture',
insert: 'Insert Image',
resizeFull: 'Resize full',
resizeHalf: 'Resize half',
resizeQuarter: 'Resize quarter',
resizeNone: 'Original size',
floatLeft: 'Float Left',
floatRight: 'Float Right',
floatNone: 'Remove float',
shapeRounded: 'Shape: Rounded',
shapeCircle: 'Shape: Circle',
shapeThumbnail: 'Shape: Thumbnail',
shapeNone: 'Shape: None',
dragImageHere: 'Drag image or text here',
dropImage: 'Drop image or Text',
selectFromFiles: 'Select from files',
maximumFileSize: 'Maximum file size',
maximumFileSizeError: 'Maximum file size exceeded.',
url: 'Image URL',
remove: 'Remove Image',
original: 'Original'
},
video: {
video: 'Video',
videoLink: 'Video Link',
insert: 'Insert Video',
url: 'Video URL',
providers: '(YouTube, Vimeo, Vine, Instagram, DailyMotion or Youku)'
},
link: {
link: 'Link',
insert: 'Insert Link',
unlink: 'Unlink',
edit: 'Edit',
textToDisplay: 'Text to display',
url: 'To what URL should this link go?',
openInNewWindow: 'Open in new window'
},
table: {
table: 'Table',
addRowAbove: 'Add row above',
addRowBelow: 'Add row below',
addColLeft: 'Add column left',
addColRight: 'Add column right',
delRow: 'Delete row',
delCol: 'Delete column',
delTable: 'Delete table'
},
hr: {
insert: 'Insert Horizontal Rule'
},
style: {
style: 'Style',
p: 'Normal',
blockquote: 'Quote',
pre: 'Code',
h1: 'Header 1',
h2: 'Header 2',
h3: 'Header 3',
h4: 'Header 4',
h5: 'Header 5',
h6: 'Header 6'
},
lists: {
unordered: 'Unordered list',
ordered: 'Ordered list'
},
options: {
help: 'Help',
fullscreen: 'Full Screen',
codeview: 'Code View'
},
paragraph: {
paragraph: 'Paragraph',
outdent: 'Outdent',
indent: 'Indent',
left: 'Align left',
center: 'Align center',
right: 'Align right',
justify: 'Justify full'
},
color: {
recent: 'Recent Color',
more: 'More Color',
background: 'Background Color',
foreground: 'Foreground Color',
transparent: 'Transparent',
setTransparent: 'Set transparent',
reset: 'Reset',
resetToDefault: 'Reset to default',
cpSelect: 'Select'
},
shortcut: {
shortcuts: 'Keyboard shortcuts',
close: 'Close',
textFormatting: 'Text formatting',
action: 'Action',
paragraphFormatting: 'Paragraph formatting',
documentStyle: 'Document Style',
extraKeys: 'Extra keys'
},
help: {
'insertParagraph': 'Insert Paragraph',
'undo': 'Undoes the last command',
'redo': 'Redoes the last command',
'tab': 'Tab',
'untab': 'Untab',
'bold': 'Set a bold style',
'italic': 'Set a italic style',
'underline': 'Set a underline style',
'strikethrough': 'Set a strikethrough style',
'removeFormat': 'Clean a style',
'justifyLeft': 'Set left align',
'justifyCenter': 'Set center align',
'justifyRight': 'Set right align',
'justifyFull': 'Set full align',
'insertUnorderedList': 'Toggle unordered list',
'insertOrderedList': 'Toggle ordered list',
'outdent': 'Outdent on current paragraph',
'indent': 'Indent on current paragraph',
'formatPara': 'Change current block\'s format as a paragraph(P tag)',
'formatH1': 'Change current block\'s format as H1',
'formatH2': 'Change current block\'s format as H2',
'formatH3': 'Change current block\'s format as H3',
'formatH4': 'Change current block\'s format as H4',
'formatH5': 'Change current block\'s format as H5',
'formatH6': 'Change current block\'s format as H6',
'insertHorizontalRule': 'Insert horizontal rule',
'linkDialog.show': 'Show Link Dialog'
},
history: {
undo: 'Undo',
redo: 'Redo'
},
specialChar: {
specialChar: 'SPECIAL CHARACTERS',
select: 'Select Special characters'
}
}
});
var isSupportAmd = typeof define === 'function' && define.amd; // eslint-disable-line
/**
* returns whether font is installed or not.
*
* @param {String} fontName
* @return {Boolean}
*/
function isFontInstalled(fontName) {
var testFontName = fontName === 'Comic Sans MS' ? 'Courier New' : 'Comic Sans MS';
var testText = 'mmmmmmmmmmwwwww';
var testSize = '200px';
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.font = testSize + " '" + testFontName + "'";
var originalWidth = context.measureText(testText).width;
context.font = testSize + " '" + fontName + "', '" + testFontName + "'";
var width = context.measureText(testText).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]);
}
}
var isEdge = /Edge\/\d+/.test(userAgent);
var hasCodeMirror = !!window.CodeMirror;
var isSupportTouch = (('ontouchstart' in window) ||
(navigator.MaxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0));
// [workaround] IE doesn't have input events for contentEditable
// - see: https://goo.gl/4bfIvA
var inputEventName = (isMSIE || isEdge) ? 'DOMCharacterDataModified DOMSubtreeModified DOMNodeInserted' : 'input';
/**
* @class core.env
*
* Object which check platform and agent
*
* @singleton
* @alternateClassName env
*/
var env = {
isMac: navigator.appVersion.indexOf('Mac') > -1,
isMSIE: isMSIE,
isEdge: isEdge,
isFF: !isEdge && /firefox/i.test(userAgent),
isPhantom: /PhantomJS/i.test(userAgent),
isWebkit: !isEdge && /webkit/i.test(userAgent),
isChrome: !isEdge && /chrome/i.test(userAgent),
isSafari: !isEdge && /safari/i.test(userAgent),
browserVersion: browserVersion,
jqueryVersion: parseFloat($$1.fn.jquery),
isSupportAmd: isSupportAmd,
isSupportTouch: isSupportTouch,
hasCodeMirror: hasCodeMirror,
isFontInstalled: isFontInstalled,
isW3CRangeSupport: !!document.createRange,
inputEventName: inputEventName
};
/**
* @class core.func
*
* func utils (for high-order func's arg)
*
* @singleton
* @alternateClassName func
*/
function eq(itemA) {
return function (itemB) {
return itemA === itemB;
};
}
function eq2(itemA, itemB) {
return itemA === itemB;
}
function peq2(propName) {
return function (itemA, itemB) {
return itemA[propName] === itemB[propName];
};
}
function ok() {
return true;
}
function fail() {
return false;
}
function not(f) {
return function () {
return !f.apply(f, arguments);
};
}
function and(fA, fB) {
return function (item) {
return fA(item) && fB(item);
};
}
function self(a) {
return a;
}
function invoke(obj, method) {
return function () {
return obj[method].apply(obj, arguments);
};
}
var idCounter = 0;
/**
* generate a globally-unique id
*
* @param {String} [prefix]
*/
function uniqueId(prefix) {
var id = ++idCounter + '';
return prefix ? prefix + id : id;
}
/**
* returns bnd (bounds) from rect
*
* - IE Compatibility Issue: http://goo.gl/sRLOAo
* - Scroll Issue: http://goo.gl/sNjUc
*
* @param {Rect} rect
* @return {Object} bounds
* @return {Number} bounds.top
* @return {Number} bounds.left
* @return {Number} bounds.width
* @return {Number} bounds.height
*/
function rect2bnd(rect) {
var $document = $(document);
return {
top: rect.top + $document.scrollTop(),
left: rect.left + $document.scrollLeft(),
width: rect.right - rect.left,
height: rect.bottom - rect.top
};
}
/**
* returns a copy of the object where the keys have become the values and the values the keys.
* @param {Object} obj
* @return {Object}
*/
function invertObject(obj) {
var inverted = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
inverted[obj[key]] = key;
}
}
return inverted;
}
/**
* @param {String} namespace
* @param {String} [prefix]
* @return {String}
*/
function namespaceToCamel(namespace, prefix) {
prefix = prefix || '';
return prefix + namespace.split('.').map(function (name) {
return name.substring(0, 1).toUpperCase() + name.substring(1);
}).join('');
}
/**
* Returns a function, that, as long as it continues to be invoked, will not
* be triggered. The function will be called after it stops being called for
* N milliseconds. If `immediate` is passed, trigger the function on the
* leading edge, instead of the trailing.
* @param {Function} func
* @param {Number} wait
* @param {Boolean} immediate
* @return {Function}
*/
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
var later = function () {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
}
/**
*
* @param {String} url
* @return {Boolean}
*/
function isValidUrl(url) {
var expression = /[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/gi;
return expression.test(url);
}
var func = {
eq: eq,
eq2: eq2,
peq2: peq2,
ok: ok,
fail: fail,
self: self,
not: not,
and: and,
invoke: invoke,
uniqueId: uniqueId,
rect2bnd: rect2bnd,
invertObject: invertObject,
namespaceToCamel: namespaceToCamel,
debounce: debounce,
isValidUrl: isValidUrl
};
/**
* returns the first item of an array.
*
* @param {Array} array
*/
function head(array) {
return array[0];
}
/**
* returns the last item of an array.
*
* @param {Array} array
*/
function last(array) {
return array[array.length - 1];
}
/**
* returns everything but the last entry of the array.
*
* @param {Array} array
*/
function initial(array) {
return array.slice(0, array.length - 1);
}
/**
* returns the rest of the items in an array.
*
* @param {Array} array
*/
function tail(array) {
return array.slice(1);
}
/**
* returns item of array
*/
function find(array, pred) {
for (var idx = 0, len = array.length; idx < len; idx++) {
var item = array[idx];
if (pred(item)) {
return item;
}
}
}
/**
* returns true if all of the values in the array pass the predicate truth test.
*/
function all(array, pred) {
for (var idx = 0, len = array.length; idx < len; idx++) {
if (!pred(array[idx])) {
return false;
}
}
return true;
}
/**
* returns true if the value is present in the list.
*/
function contains(array, item) {
if (array && array.length && item) {
return array.indexOf(item) !== -1;
}
return false;
}
/**
* get sum from a list
*
* @param {Array} array - array
* @param {Function} fn - iterator
*/
function sum(array, fn) {
fn = fn || func.self;
return array.reduce(function (memo, v) {
return memo + fn(v);
}, 0);
}
/**
* returns a copy of the collection with array type.
* @param {Collection} collection - collection eg) node.childNodes, ...
*/
function from(collection) {
var result = [];
var length = collection.length;
var idx = -1;
while (++idx < length) {
result[idx] = collection[idx];
}
return result;
}
/**
* returns whether list is empty or not
*/
function isEmpty(array) {
return !array || !array.length;
}
/**
* cluster elements by predicate function.
*
* @param {Array} array - array
* @param {Function} fn - predicate function for cluster rule
* @param {Array[]}
*/
function clusterBy(array, fn) {
if (!array.length) {
return [];
}
var aTail = tail(array);
return aTail.reduce(function (memo, v) {
var aLast = last(memo);
if (fn(last(aLast), v)) {
aLast[aLast.length] = v;
}
else {
memo[memo.length] = [v];
}
return memo;
}, [[head(array)]]);
}
/**
* returns a copy of the array with all false values removed
*
* @param {Array} array - array
* @param {Function} fn - predicate function for cluster rule
*/
function compact(array) {
var aResult = [];
for (var idx = 0, len = array.length; idx < len; idx++) {
if (array[idx]) {
aResult.push(array[idx]);
}
}
return aResult;
}
/**
* produces a duplicate-free version of the array
*
* @param {Array} array
*/
function unique(array) {
var results = [];
for (var idx = 0, len = array.length; idx < len; idx++) {
if (!contains(results, array[idx])) {
results.push(array[idx]);
}
}
return results;
}
/**
* returns next item.
* @param {Array} array
*/
function next(array, item) {
if (array && array.length && item) {
var idx = array.indexOf(item);
return idx === -1 ? null : array[idx + 1];
}
return null;
}
/**
* returns prev item.
* @param {Array} array
*/
function prev(array, item) {
if (array && array.length && item) {
var idx = array.indexOf(item);
return idx === -1 ? null : array[idx - 1];
}
return null;
}
/**
* @class core.list
*
* list utils
*
* @singleton
* @alternateClassName list
*/
var lists = {
head: head,
last: last,
initial: initial,
tail: tail,
prev: prev,
next: next,
find: find,
contains: contains,
all: all,
sum: sum,
from: from,
isEmpty: isEmpty,
clusterBy: clusterBy,
compact: compact,
unique: unique
};
var NBSP_CHAR = String.fromCharCode(160);
var ZERO_WIDTH_NBSP_CHAR = '\ufeff';
/**
* @method isEditable
*
* returns whether node is `note-editable` or not.
*
* @param {Node} node
* @return {Boolean}
*/
function isEditable(node) {
return node && $$1(node).hasClass('note-editable');
}
/**
* @method isControlSizing
*
* returns whether node is `note-control-sizing` or not.
*
* @param {Node} node
* @return {Boolean}
*/
function isControlSizing(node) {
return node && $$1(node).hasClass('note-control-sizing');
}
/**
* @method makePredByNodeName
*
* returns predicate which judge whether nodeName is same
*
* @param {String} nodeName
* @return {Function}
*/
function makePredByNodeName(nodeName) {
nodeName = nodeName.toUpperCase();
return function (node) {
return node && node.nodeName.toUpperCase() === nodeName;
};
}
/**
* @method isText
*
*
*
* @param {Node} node
* @return {Boolean} true if node's type is text(3)
*/
function isText(node) {
return node && node.nodeType === 3;
}
/**
* @method isElement
*
*
*
* @param {Node} node
* @return {Boolean} true if node's type is element(1)
*/
function isElement(node) {
return node && node.nodeType === 1;
}
/**
* ex) br, col, embed, hr, img, input, ...
* @see http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
*/
function isVoid(node) {
return node && /^BR|^IMG|^HR|^IFRAME|^BUTTON|^INPUT|^AUDIO|^VIDEO|^EMBED/.test(node.nodeName.toUpperCase());
}
function isPara(node) {
if (isEditable(node)) {
return false;
}
// Chrome(v31.0), FF(v25.0.1) use DIV for paragraph
return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase());
}
function isHeading(node) {
return node && /^H[1-7]/.test(node.nodeName.toUpperCase());
}
var isPre = makePredByNodeName('PRE');
var isLi = makePredByNodeName('LI');
function isPurePara(node) {
return isPara(node) && !isLi(node);
}
var isTable = makePredByNodeName('TABLE');
var isData = makePredByNodeName('DATA');
function isInline(node) {
return !isBodyContainer(node) &&
!isList(node) &&
!isHr(node) &&
!isPara(node) &&
!isTable(node) &&
!isBlockquote(node) &&
!isData(node);
}
function isList(node) {
return node && /^UL|^OL/.test(node.nodeName.toUpperCase());
}
var isHr = makePredByNodeName('HR');
function isCell(node) {
return node && /^TD|^TH/.test(node.nodeName.toUpperCase());
}
var isBlockquote = makePredByNodeName('BLOCKQUOTE');
function isBodyContainer(node) {
return isCell(node) || isBlockquote(node) || isEditable(node);
}
var isAnchor = makePredByNodeName('A');
function isParaInline(node) {
return isInline(node) && !!ancestor(node, isPara);
}
function isBodyInline(node) {
return isInline(node) && !ancestor(node, isPara);
}
var isBody = makePredByNodeName('BODY');
/**
* returns whether nodeB is closest sibling of nodeA
*
* @param {Node} nodeA
* @param {Node} nodeB
* @return {Boolean}
*/
function isClosestSibling(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[]}
*/
function withClosestSiblings(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
* - [workaround] IE11 and other browser works with bogus br
*/
var blankHTML = env.isMSIE && env.browserVersion < 11 ? ' ' : '<br>';
/**
* @method nodeLength
*
* returns #text's text size or element's childNodes size
*
* @param {Node} node
*/
function nodeLength(node) {
if (isText(node)) {
return node.nodeValue.length;
}
if (node) {
return node.childNodes.length;
}
return 0;
}
/**
* returns whether node is empty or not.
*
* @param {Node} node
* @return {Boolean}
*/
function isEmpty$1(node) {
var len = nodeLength(node);
if (len === 0) {
return true;
}
else if (!isText(node) && len === 1 && node.innerHTML === blankHTML) {
// ex) <p><br></p>, <span><br></span>
return true;
}
else if (lists.all(node.childNodes, isText) && node.innerHTML === '') {
// ex) <p></p>, <span></span>
return true;
}
return false;
}
/**
* padding blankHTML if node is empty (for cursor position)
*/
function paddingBlankHTML(node) {
if (!isVoid(node) && !nodeLength(node)) {
node.innerHTML = blankHTML;
}
}
/**
* find nearest ancestor predicate hit
*
* @param {Node} node
* @param {Function} pred - predicate function
*/
function ancestor(node, pred) {
while (node) {
if (pred(node)) {
return node;
}
if (isEditable(node)) {
break;
}
node = node.parentNode;
}
return null;
}
/**
* find nearest ancestor only single child blood line and predicate hit
*
* @param {Node} node
* @param {Function} pred - predicate function
*/
function singleChildAncestor(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).
*
* @param {Node} node
* @param {Function} [optional] pred - predicate function
*/
function listAncestor(node, pred) {
pred = pred || func.fail;
var ancestors = [];
ancestor(node, function (el) {
if (!isEditable(el)) {
ancestors.push(el);
}
return pred(el);
});
return ancestors;
}
/**
* find farthest ancestor predicate hit
*/
function lastAncestor(node, pred) {
var ancestors = listAncestor(node);
return lists.last(ancestors.filter(pred));
}
/**
* returns common ancestor node between two nodes.
*
* @param {Node} nodeA
* @param {Node} nodeB
*/
function commonAncestor(nodeA, nodeB) {
var ancestors = listAncestor(nodeA);
for (var n = nodeB; n; n = n.parentNode) {
if (ancestors.indexOf(n) > -1)
return n;
}
return null; // difference document area
}
/**
* listing all previous siblings (until predicate hit).
*
* @param {Node} node
* @param {Function} [optional] pred - predicate function
*/
function listPrev(node, pred) {
pred = pred || func.fail;
var nodes = [];
while (node) {
if (pred(node)) {
break;
}
nodes.push(node);
node = node.previousSibling;
}
return nodes;
}
/**
* listing next siblings (until predicate hit).
*
* @param {Node} node
* @param {Function} [pred] - predicate function
*/
function listNext(node, pred) {
pred = pred || func.fail;
var nodes = [];
while (node) {
if (pred(node)) {
break;
}
nodes.push(node);
node = node.nextSibling;
}
return nodes;
}
/**
* listing descendant nodes
*
* @param {Node} node
* @param {Function} [pred] - predicate function
*/
function listDescendant(node, pred) {
var descendants = [];
pred = pred || func.ok;
// start DFS(depth first search) with node
(function fnWalk(current) {
if (node !== current && pred(current)) {
descendants.push(current);
}
for (var idx = 0, len = current.childNodes.length; idx < len; idx++) {
fnWalk(current.childNodes[idx]);
}
})(node);
return descendants;
}
/**
* wrap node with new tag.
*
* @param {Node} node
* @param {Node} tagName of wrapper
* @return {Node} - wrapper
*/
function wrap(node, wrapperName) {
var parent = node.parentNode;
var wrapper = $$1('<' + wrapperName + '>')[0];
parent.insertBefore(wrapper, node);
wrapper.appendChild(node);
return wrapper;
}
/**
* insert node after preceding
*
* @param {Node} node
* @param {Node} preceding - predicate function
*/
function insertAfter(node, preceding) {
var next = preceding.nextSibling;
var parent = preceding.parentNode;
if (next) {
parent.insertBefore(node, next);
}
else {
parent.appendChild(node);
}
return node;
}
/**
* append elements.
*
* @param {Node} node
* @param {Collection} aChild
*/
function appendChildNodes(node, aChild) {
$$1.each(aChild, function (idx, child) {
node.appendChild(child);
});
return node;
}
/**
* returns whether boundaryPoint is left edge or not.
*
* @param {BoundaryPoint} point
* @return {Boolean}
*/
function isLeftEdgePoint(point) {
return point.offset === 0;
}
/**
* returns whether boundaryPoint is right edge or not.
*
* @param {BoundaryPoint} point
* @return {Boolean}
*/
function isRightEdgePoint(point) {
return point.offset === nodeLength(point.node);
}
/**
* returns whether boundaryPoint is edge or not.
*
* @param {BoundaryPoint} point
* @return {Boolean}
*/
function isEdgePoint(point) {
return isLeftEdgePoint(point) || isRightEdgePoint(point);
}
/**
* returns whether node is left edge of ancestor or not.
*
* @param {Node} node
* @param {Node} ancestor
* @return {Boolean}
*/
function isLeftEdgeOf(node, ancestor) {
while (node && node !== ancestor) {
if (position(node) !== 0) {
return false;
}
node = node.parentNode;
}
return true;
}
/**
* returns whether node is right edge of ancestor or not.
*
* @param {Node} node
* @param {Node} ancestor
* @return {Boolean}
*/
function isRightEdgeOf(node, ancestor) {
if (!ancestor) {
return false;
}
while (node && node !== ancestor) {
if (position(node) !== nodeLength(node.parentNode) - 1) {
return false;
}
node = node.parentNode;
}
return true;
}
/**
* returns whether point is left edge of ancestor or not.
* @param {BoundaryPoint} point
* @param {Node} ancestor
* @return {Boolean}
*/
function isLeftEdgePointOf(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}
*/
function isRightEdgePointOf(point, ancestor) {
return isRightEdgePoint(point) && isRightEdgeOf(point.node, ancestor);
}
/**
* returns offset from parent.
*
* @param {Node} node
*/
function position(node) {
var offset = 0;
while ((node = node.previousSibling)) {
offset += 1;
}
return offset;
}
function hasChildren(node) {
return !!(node && node.childNodes && node.childNodes.length);
}
/**
* returns previous boundaryPoint
*
* @param {BoundaryPoint} point
* @param {Boolean} isSkipInnerOffset
* @return {BoundaryPoint}
*/
function prevPoint(point, isSkipInnerOffset) {
var node;
var offset;
if (point.offset === 0) {
if (isEditable(point.node)) {
return null;
}
node = point.node.parentNode;
offset = position(point.node);
}
else if (hasChildren(point.node)) {
node = point.node.childNodes[point.offset - 1];
offset = nodeLength(node);
}
else {
node = point.node;
offset = isSkipInnerOffset ? 0 : point.offset - 1;
}
return {
node: node,
offset: offset
};
}
/**
* returns next boundaryPoint
*
* @param {BoundaryPoint} point
* @param {Boolean} isSkipInnerOffset
* @return {BoundaryPoint}
*/
function nextPoint(point, isSkipInnerOffset) {
var node, offset;
if (nodeLength(point.node) === point.offset) {
if (isEditable(point.node)) {
return null;
}
node = point.node.parentNode;
offset = position(point.node) + 1;
}
else if (hasChildren(point.node)) {
node = point.node.childNodes[point.offset];
offset = 0;
}
else {
node = point.node;
offset = isSkipInnerOffset ? nodeLength(point.node) : point.offset + 1;
}
return {
node: node,
offset: offset
};
}
/**
* returns whether pointA and pointB is same or not.
*
* @param {BoundaryPoint} pointA
* @param {BoundaryPoint} pointB
* @return {Boolean}
*/
function isSamePoint(pointA, pointB) {
return pointA.node === pointB.node && pointA.offset === pointB.offset;
}
/**
* returns whether point is visible (can set cursor) or not.
*
* @param {BoundaryPoint} point
* @return {Boolean}
*/
function isVisiblePoint(point) {
if (isText(point.node) || !hasChildren(point.node) || isEmpty$1(point.node)) {
return true;
}
var leftNode = point.node.childNodes[point.offset - 1];
var rightNode = point.node.childNodes[point.offset];
if ((!leftNode || isVoid(leftNode)) && (!rightNode || isVoid(rightNode))) {
return true;
}
return false;
}
/**
* @method prevPointUtil
*
* @param {BoundaryPoint} point
* @param {Function} pred
* @return {BoundaryPoint}
*/
function prevPointUntil(point, pred) {
while (point) {
if (pred(point)) {
return point;
}
point = prevPoint(point);
}
return null;
}
/**
* @method nextPointUntil
*
* @param {BoundaryPoint} point
* @param {Function} pred
* @return {BoundaryPoint}
*/
function nextPointUntil(point, pred) {
while (point) {
if (pred(point)) {
return point;
}
point = nextPoint(point);
}
return null;
}
/**
* returns whether point has character or not.
*
* @param {Point} point
* @return {Boolean}
*/
function isCharPoint(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} endPoint
* @param {Function} handler
* @param {Boolean} isSkipInnerOffset
*/
function walkPoint(startPoint, endPoint, handler, isSkipInnerOffset) {
var point = startPoint;
while (point) {
handler(point);
if (isSamePoint(point, endPoint)) {
break;
}
var isSkipOffset = isSkipInnerOffset &&
startPoint.node !== point.node &&
endPoint.node !== point.node;
point = nextPoint(point, isSkipOffset);
}
}
/**
* @method makeOffsetPath
*
* return offsetPath(array of offset) from ancestor
*
* @param {Node} ancestor - ancestor node
* @param {Node} node
*/
function makeOffsetPath(ancestor, node) {
var ancestors = listAncestor(node, func.eq(ancestor));
return ancestors.map(position).reverse();
}
/**
* @method fromOffsetPath
*
* return element from offsetPath(array of offset)
*
* @param {Node} ancestor - ancestor node
* @param {array} offsets - offsetPath
*/
function fromOffsetPath(ancestor, offsets) {
var current = ancestor;
for (var i = 0, len = offsets.length; i < len; i++) {
if (current.childNodes.length <= offsets[i]) {
current = current.childNodes[current.childNodes.length - 1];
}
else {
current = current.childNodes[offsets[i]];
}
}
return current;
}
/**
* @method splitNode
*
* split element or #text
*
* @param {BoundaryPoint} point
* @param {Object} [options]
* @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false
* @param {Boolean} [options.isNotSplitEdgePoint] - default: false
* @param {Boolean} [options.isDiscardEmptySplits] - default: false
* @return {Node} right node of boundaryPoint
*/
function splitNode(point, options) {
var isSkipPaddingBlankHTML = options && options.isSkipPaddingBlankHTML;
var isNotSplitEdgePoint = options && options.isNotSplitEdgePoint;
var isDiscardEmptySplits = options && options.isDiscardEmptySplits;
if (isDiscardEmptySplits) {
isSkipPaddingBlankHTML = true;
}
// edge case
if (isEdgePoint(point) && (isText(point.node) || isNotSplitEdgePoint)) {
if (isLeftEdgePoint(point)) {
return point.node;
}
else if (isRightEdgePoint(point)) {
return point.node.nextSibling;
}
}
// split #text
if (isText(point.node)) {
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);
}
if (isDiscardEmptySplits) {
if (isEmpty$1(point.node)) {
remove(point.node);
}
if (isEmpty$1(clone)) {
remove(clone);
return point.node.nextSibling;
}
}
return clone;
}
}
/**
* @method splitTree
*
* split tree by point
*
* @param {Node} root - split root
* @param {BoundaryPoint} point
* @param {Object} [options]
* @param {Boolean} [options.isSkipPaddingBlankHTML] - default: false
* @param {Boolean} [options.isNotSplitEdgePoint] - default: false
* @return {Node} right node of boundaryPoint
*/
function splitTree(root, point, options) {
// ex) [#text, <span>, <p>]
var ancestors = listAncestor(point.node, func.eq(root));
if (!ancestors.length) {
return null;
}
else if (ancestors.length === 1) {
return splitNode(point, options);
}
return ancestors.reduce(function (node, parent) {
if (node === point.node) {
node = splitNode(point, options);
}
return splitNode({
node: parent,
offset: node ? position(node) : nodeLength(parent)
}, options);
});
}
/**
* split point
*
* @param {Point} point
* @param {Boolean} isInline
* @return {Object}
*/
function splitPoint(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 = lists.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
};
}
function create(nodeName) {
return document.createElement(nodeName);
}
function createText(text) {
return document.createTextNode(text);
}
/**
* @method remove
*
* remove node, (isRemoveChild: remove child or not)
*
* @param {Node} node
* @param {Boolean} isRemoveChild
*/
function remove(node, isRemoveChild) {
if (!node || !node.parentNode) {
return;
}
if (node.removeNode) {
return node.removeNode(isRemoveChild