@btag/react-trumbowyg
Version:
React wrapper for Trumbowyg
1,475 lines (1,247 loc) • 223 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["react-trumbowyg"] = factory();
else
root["react-trumbowyg"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = __webpack_require__(11);
var _react2 = _interopRequireDefault(_react);
var _propTypes = __webpack_require__(8);
var _propTypes2 = _interopRequireDefault(_propTypes);
var _btTrumbowyg = __webpack_require__(6);
var _btTrumbowyg2 = _interopRequireDefault(_btTrumbowyg);
var _icons = __webpack_require__(12);
var _icons2 = _interopRequireDefault(_icons);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var trumbowygIconsId = 'trumbowyg-icons';
var Trumbowyg = function (_Component) {
_inherits(Trumbowyg, _Component);
function Trumbowyg(props) {
_classCallCheck(this, Trumbowyg);
return _possibleConstructorReturn(this, (Trumbowyg.__proto__ || Object.getPrototypeOf(Trumbowyg)).call(this, props));
}
_createClass(Trumbowyg, [{
key: 'componentDidMount',
value: function componentDidMount() {
var _props = this.props,
id = _props.id,
lang = _props.lang,
buttons = _props.buttons,
semantic = _props.semantic,
resetCss = _props.resetCss,
removeformatPasted = _props.removeformatPasted,
autogrow = _props.autogrow,
data = _props.data,
disabled = _props.disabled,
onFocus = _props.onFocus,
onBlur = _props.onBlur,
onInit = _props.onInit,
onChange = _props.onChange,
onResize = _props.onResize,
onPaste = _props.onPaste,
onOpenFullScreen = _props.onOpenFullScreen,
onCloseFullScreen = _props.onCloseFullScreen,
onClose = _props.onClose,
shouldUseSvgIcons = _props.shouldUseSvgIcons,
shouldInjectSvgIcons = _props.shouldInjectSvgIcons,
svgIconsPath = _props.svgIconsPath,
btnsDef = _props.btnsDef,
plugins = _props.plugins;
if (shouldInjectSvgIcons && $('#' + trumbowygIconsId).length === 0) {
$('body').prepend('<div id="' + trumbowygIconsId + '">' + _icons2.default + '</div>');
}
var trumbowygInstance = $('#' + id).trumbowyg({
lang: lang,
btns: buttons,
btnsDef: btnsDef,
semantic: semantic,
resetCss: resetCss,
removeformatPasted: removeformatPasted,
autogrow: autogrow,
plugins: plugins,
svgPath: shouldUseSvgIcons ? shouldInjectSvgIcons ? '' : svgIconsPath : false
});
if (onFocus) {
trumbowygInstance.on('tbwfocus', onFocus);
}
if (onBlur) {
trumbowygInstance.on('tbwblur', onBlur);
}
if (onInit) {
trumbowygInstance.on('tbwinit', onInit);
}
if (onChange) {
trumbowygInstance.on('tbwchange', onChange);
}
if (onResize) {
trumbowygInstance.on('tbwresize', onResize);
}
if (onPaste) {
trumbowygInstance.on('tbwpaste', onPaste);
}
if (onOpenFullScreen) {
trumbowygInstance.on('tbwopenfullscreen', onOpenFullScreen);
}
if (onCloseFullScreen) {
trumbowygInstance.on('tbwclosefullscreen', onCloseFullScreen);
}
if (onClose) {
trumbowygInstance.on('tbwclose', onClose);
}
$('#' + id).trumbowyg('html', data);
$('#' + id).trumbowyg(disabled === true ? 'disable' : 'enable');
}
}, {
key: 'shouldComponentUpdate',
value: function shouldComponentUpdate(nextProps, nextState) {
return nextProps.data !== this.props.data || nextProps.disabled !== this.props.disabled;
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
$('#' + this.props.id).trumbowyg('html', this.props.data);
$('#' + this.props.id).trumbowyg(this.props.disabled === true ? 'disable' : 'enable');
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
$('#' + this.props.id).trumbowyg('destroy');
}
}, {
key: 'render',
value: function render() {
return _react2.default.createElement('div', {
id: '' + this.props.id,
role: 'textbox',
'aria-multiline': 'true',
'aria-label': this.props.ariaLabel,
placeholder: this.props.placeholder });
}
}]);
return Trumbowyg;
}(_react.Component);
Trumbowyg.defaultProps = {
buttons: [['viewHTML'], ['formatting'], ['strong', 'em', 'underline'], ['link'], ['insertImage'], ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], ['unorderedList', 'orderedList'], ['horizontalRule'], ['removeformat']],
semantic: true,
resetCss: false,
removeformatPasted: false,
autogrow: false,
disabled: false,
shouldUseSvgIcons: true,
shouldInjectSvgIcons: true,
plugins: {},
ariaLabel: "Real Text Editor"
};
Trumbowyg.propTypes = {
id: _propTypes2.default.string.isRequired,
data: _propTypes2.default.string.isRequired,
placeholder: _propTypes2.default.string,
buttons: _propTypes2.default.array,
semantic: _propTypes2.default.bool,
resetCss: _propTypes2.default.bool,
removeformatPasted: _propTypes2.default.bool,
autogrow: _propTypes2.default.bool,
disabled: _propTypes2.default.bool,
btnsDef: _propTypes2.default.object,
//event handlers
onFocus: _propTypes2.default.func,
onBlur: _propTypes2.default.func,
onInit: _propTypes2.default.func,
onChange: _propTypes2.default.func,
onResize: _propTypes2.default.func,
onPaste: _propTypes2.default.func,
onOpenFullScreen: _propTypes2.default.func,
onCloseFullScreen: _propTypes2.default.func,
onClose: _propTypes2.default.func,
shouldUseSvgIcons: _propTypes2.default.bool.isRequired,
svgIconsPath: _propTypes2.default.string,
shouldInjectSvgIcons: _propTypes2.default.bool.isRequired,
plugins: _propTypes2.default.object,
ariaLabel: _propTypes2.default.string
};
exports.default = Trumbowyg;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
'use strict';
/* eslint-disable no-unused-vars */
var getOwnPropertySymbols = Object.getOwnPropertySymbols;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var propIsEnumerable = Object.prototype.propertyIsEnumerable;
function toObject(val) {
if (val === null || val === undefined) {
throw new TypeError('Object.assign cannot be called with null or undefined');
}
return Object(val);
}
function shouldUseNative() {
try {
if (!Object.assign) {
return false;
}
// Detect buggy property enumeration order in older V8 versions.
// https://bugs.chromium.org/p/v8/issues/detail?id=4118
var test1 = new String('abc'); // eslint-disable-line no-new-wrappers
test1[5] = 'de';
if (Object.getOwnPropertyNames(test1)[0] === '5') {
return false;
}
// https://bugs.chromium.org/p/v8/issues/detail?id=3056
var test2 = {};
for (var i = 0; i < 10; i++) {
test2['_' + String.fromCharCode(i)] = i;
}
var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
return test2[n];
});
if (order2.join('') !== '0123456789') {
return false;
}
// https://bugs.chromium.org/p/v8/issues/detail?id=3056
var test3 = {};
'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
test3[letter] = letter;
});
if (Object.keys(Object.assign({}, test3)).join('') !==
'abcdefghijklmnopqrst') {
return false;
}
return true;
} catch (err) {
// We don't expect any of the above to throw, but better to be safe.
return false;
}
}
module.exports = shouldUseNative() ? Object.assign : function (target, source) {
var from;
var to = toObject(target);
var symbols;
for (var s = 1; s < arguments.length; s++) {
from = Object(arguments[s]);
for (var key in from) {
if (hasOwnProperty.call(from, key)) {
to[key] = from[key];
}
}
if (getOwnPropertySymbols) {
symbols = getOwnPropertySymbols(from);
for (var i = 0; i < symbols.length; i++) {
if (propIsEnumerable.call(from, symbols[i])) {
to[symbols[i]] = from[symbols[i]];
}
}
}
}
return to;
};
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var printWarning = function() {};
if (true) {
var ReactPropTypesSecret = __webpack_require__(3);
var loggedTypeFailures = {};
var has = __webpack_require__(4);
printWarning = function(text) {
var message = 'Warning: ' + text;
if (typeof console !== 'undefined') {
console.error(message);
}
try {
// --- Welcome to debugging React ---
// This error was thrown as a convenience so that you can use this stack
// to find the callsite that caused this warning to fire.
throw new Error(message);
} catch (x) { /**/ }
};
}
/**
* Assert that the values match with the type specs.
* Error messages are memorized and will only be shown once.
*
* @param {object} typeSpecs Map of name to a ReactPropType
* @param {object} values Runtime values that need to be type-checked
* @param {string} location e.g. "prop", "context", "child context"
* @param {string} componentName Name of the component for error messages.
* @param {?Function} getStack Returns the component stack.
* @private
*/
function checkPropTypes(typeSpecs, values, location, componentName, getStack) {
if (true) {
for (var typeSpecName in typeSpecs) {
if (has(typeSpecs, typeSpecName)) {
var error;
// Prop type validation may throw. In case they do, we don't want to
// fail the render phase where it didn't fail before. So we log it.
// After these have been cleaned up, we'll let them throw.
try {
// This is intentionally an invariant that gets caught. It's the same
// behavior as without this statement except with a better message.
if (typeof typeSpecs[typeSpecName] !== 'function') {
var err = Error(
(componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' +
'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.' +
'This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.'
);
err.name = 'Invariant Violation';
throw err;
}
error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);
} catch (ex) {
error = ex;
}
if (error && !(error instanceof Error)) {
printWarning(
(componentName || 'React class') + ': type specification of ' +
location + ' `' + typeSpecName + '` is invalid; the type checker ' +
'function must return `null` or an `Error` but returned a ' + typeof error + '. ' +
'You may have forgotten to pass an argument to the type checker ' +
'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +
'shape all require an argument).'
);
}
if (error instanceof Error && !(error.message in loggedTypeFailures)) {
// Only monitor this failure once because there tends to be a lot of the
// same error.
loggedTypeFailures[error.message] = true;
var stack = getStack ? getStack() : '';
printWarning(
'Failed ' + location + ' type: ' + error.message + (stack != null ? stack : '')
);
}
}
}
}
}
/**
* Resets warning cache when testing.
*
* @private
*/
checkPropTypes.resetWarningCache = function() {
if (true) {
loggedTypeFailures = {};
}
}
module.exports = checkPropTypes;
/***/ }),
/* 3 */
/***/ (function(module, exports) {
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';
module.exports = ReactPropTypesSecret;
/***/ }),
/* 4 */
/***/ (function(module, exports) {
module.exports = Function.call.bind(Object.prototype.hasOwnProperty);
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
'use strict';
if (false) {
module.exports = require('./cjs/react-is.production.min.js');
} else {
module.exports = __webpack_require__(9);
}
/***/ }),
/* 6 */
/***/ (function(module, exports) {
/**
* bt-trumbowyg v2.25.2 - Bananatag Version of Trumbowyg
* Trumbowyg core file
* ------------------------
* @link http://alex-d.github.io/Trumbowyg
* @license MIT
* @author Alexandre Demode (Alex-D)
* Twitter : @AlexandreDemode
* Website : alex-d.fr
*/
jQuery.trumbowyg = {
langs: {
en: {
viewHTML: 'View HTML',
undo: 'Undo',
redo: 'Redo',
formatting: 'Formatting',
p: 'Paragraph',
blockquote: 'Quote',
code: 'Code',
header: 'Header',
bold: 'Bold',
italic: 'Italic',
strikethrough: 'Strikethrough',
underline: 'Underline',
strong: 'Strong',
em: 'Emphasis',
del: 'Deleted',
superscript: 'Superscript',
subscript: 'Subscript',
unorderedList: 'Unordered list',
orderedList: 'Ordered list',
insertFirstName: "Insert {First Name}",
insertLastName: "Insert {Last Name}",
insertCustom: "Insert custom variable",
insertImage: 'Insert Image',
link: 'Link',
createLink: 'Insert link',
unlink: 'Remove link',
justifyLeft: 'Align Left',
justifyCenter: 'Align Center',
justifyRight: 'Align Right',
justifyFull: 'Align Justify',
horizontalRule: 'Insert horizontal rule',
removeformat: 'Remove format',
fullscreen: 'Fullscreen',
close: 'Close',
submit: 'Confirm',
reset: 'Cancel',
required: 'Required',
description: 'Description',
title: 'Title',
text: 'Text',
target: 'Target',
width: 'Width'
}
},
// Plugins
plugins: {},
// SVG Path globally
svgPath: null,
svgAbsoluteUseHref: false,
hideButtonTexts: null
};
// Makes default options read-only
Object.defineProperty(jQuery.trumbowyg, 'defaultOptions', {
value: {
lang: 'en',
fixedBtnPane: false,
fixedFullWidth: false,
autogrow: false,
autogrowOnEnter: false,
imageWidthModalEdit: false,
hideButtonTexts: null,
prefix: 'trumbowyg-',
tagClasses: {},
semantic: true,
semanticKeepAttributes: false,
resetCss: false,
removeformatPasted: false,
tabToIndent: false,
tagsToRemove: [],
tagsToKeep: ['hr', 'img', 'embed', 'iframe', 'input'],
btns: [
['viewHTML'],
['undo', 'redo'], // Only supported in Blink browsers
['formatting'],
['strong', 'em', 'del'],
['superscript', 'subscript'],
['link'],
['insertImage'],
['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
['unorderedList', 'orderedList'],
['horizontalRule'],
['removeformat'],
['fullscreen']
],
// For custom button definitions
btnsDef: {},
changeActiveDropdownIcon: false,
inlineElementsSelector: 'a,abbr,acronym,b,caption,cite,code,col,dfn,dir,dt,dd,em,font,hr,i,kbd,li,q,span,strikeout,strong,sub,sup,u',
pasteHandlers: [],
// imgDblClickHandler: default is defined in constructor
plugins: {},
urlProtocol: false,
minimalLinks: false,
defaultLinkTarget: undefined,
svgPath: null
},
writable: false,
enumerable: true,
configurable: false
});
(function (navigator, window, document, $) {
'use strict';
var CONFIRM_EVENT = 'tbwconfirm',
CANCEL_EVENT = 'tbwcancel';
$.fn.trumbowyg = function (options, params) {
var trumbowygDataName = 'trumbowyg';
if (options === Object(options) || !options) {
return this.each(function () {
if (!$(this).data(trumbowygDataName)) {
$(this).data(trumbowygDataName, new Trumbowyg(this, options));
}
});
}
if (this.length === 1) {
try {
var t = $(this).data(trumbowygDataName);
switch (options) {
// Exec command
case 'execCmd':
return t.execCmd(params.cmd, params.param, params.forceCss, params.skipTrumbowyg);
// Modal box
case 'openModal':
return t.openModal(params.title, params.content);
case 'closeModal':
return t.closeModal();
case 'openModalInsert':
return t.openModalInsert(params.title, params.fields, params.callback);
// Range
case 'saveRange':
return t.saveRange();
case 'getRange':
return t.range;
case 'getRangeText':
return t.getRangeText();
case 'restoreRange':
return t.restoreRange();
// Enable/disable
case 'enable':
return t.setDisabled(false);
case 'disable':
return t.setDisabled(true);
// Toggle
case 'toggle':
return t.toggle();
// Destroy
case 'destroy':
return t.destroy();
// Empty
case 'empty':
return t.empty();
// HTML
case 'html':
return t.html(params);
}
} catch (c) {
}
}
return false;
};
// @param: editorElem is the DOM element
var Trumbowyg = function (editorElem, options) {
var t = this,
trumbowygIconsId = 'trumbowyg-icons',
$trumbowyg = $.trumbowyg;
// Get the document of the element. It use to makes the plugin
// compatible on iframes.
t.doc = editorElem.ownerDocument || document;
// jQuery object of the editor
t.$ta = $(editorElem); // $ta : Textarea
t.$c = $(editorElem); // $c : creator
options = options || {};
// Localization management
if (options.lang != null || $trumbowyg.langs[options.lang] != null) {
t.lang = $.extend(true, {}, $trumbowyg.langs.en, $trumbowyg.langs[options.lang]);
} else {
t.lang = $trumbowyg.langs.en;
}
t.hideButtonTexts = $trumbowyg.hideButtonTexts != null ? $trumbowyg.hideButtonTexts : options.hideButtonTexts;
// SVG path
var svgPathOption = $trumbowyg.svgPath != null ? $trumbowyg.svgPath : options.svgPath;
t.hasSvg = svgPathOption !== false;
if (svgPathOption !== false && ($trumbowyg.svgAbsoluteUseHref || $('#' + trumbowygIconsId, t.doc).length === 0)) {
if (svgPathOption == null) {
// Hack to get svgPathOption based on trumbowyg.js path
var $scriptElements = $('script[src]');
$scriptElements.each(function (i, scriptElement) {
var source = scriptElement.src;
var matches = source.match('trumbowyg(\.min)?\.js');
if (matches != null) {
svgPathOption = source.substring(0, source.indexOf(matches[0])) + 'ui/icons.svg';
}
});
}
// Do not merge with previous if block: svgPathOption can be redefined in it.
// Here we are checking that we find a match
if (svgPathOption == null) {
console.warn('You must define svgPath: https://goo.gl/CfTY9U'); // jshint ignore:line
} else if (!$trumbowyg.svgAbsoluteUseHref) {
var div = t.doc.createElement('div');
div.id = trumbowygIconsId;
t.doc.body.insertBefore(div, t.doc.body.childNodes[0]);
$.ajax({
async: true,
type: 'GET',
contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
dataType: 'xml',
crossDomain: true,
url: svgPathOption,
data: null,
beforeSend: null,
complete: null,
success: function (data) {
div.innerHTML = new XMLSerializer().serializeToString(data.documentElement);
}
});
}
}
var baseHref = !!t.doc.querySelector('base') ? window.location.href.split(/[?#]/)[0] : '';
t.svgPath = $trumbowyg.svgAbsoluteUseHref ? svgPathOption : baseHref;
/**
* When the button is associated to a empty object
* fn and title attributes are defined from the button key value
*
* For example
* foo: {}
* is equivalent to :
* foo: {
* fn: 'foo',
* title: this.lang.foo
* }
*/
var h = t.lang.header, // Header translation
isBlinkFunction = function () {
return (window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window;
};
t.btnsDef = {
viewHTML: {
fn: 'toggle',
class: 'trumbowyg-not-disable',
},
undo: {
isSupported: isBlinkFunction,
key: 'Z'
},
redo: {
isSupported: isBlinkFunction,
key: 'Y'
},
p: {
fn: 'formatBlock'
},
blockquote: {
fn: 'formatBlock'
},
h1: {
fn: 'formatBlock',
title: h + ' 1'
},
h2: {
fn: 'formatBlock',
title: h + ' 2'
},
h3: {
fn: 'formatBlock',
title: h + ' 3'
},
h4: {
fn: 'formatBlock',
title: h + ' 4'
},
h5: {
fn: 'formatBlock',
title: h + ' 5'
},
h6: {
fn: 'formatBlock',
title: h + ' 6'
},
subscript: {
tag: 'sub'
},
superscript: {
tag: 'sup'
},
insertFirstName: {},
insertLastName: {},
insertCustom: {
dropdown: ['insertFirstName', 'insertLastName', 'insertCustom'],
ico: 'custom'
},
bold: {
key: 'B',
tag: 'b'
},
italic: {
key: 'I',
tag: 'i'
},
underline: {
tag: 'u'
},
strikethrough: {
tag: 'strike'
},
strong: {
fn: 'bold',
key: 'B'
},
em: {
fn: 'italic',
key: 'I'
},
del: {
fn: 'strikethrough'
},
createLink: {
key: 'K',
tag: 'a'
},
unlink: {},
insertImage: {},
justifyLeft: {
tag: 'left',
forceCss: true
},
justifyCenter: {
tag: 'center',
forceCss: true
},
justifyRight: {
tag: 'right',
forceCss: true
},
justifyFull: {
tag: 'justify',
forceCss: true
},
unorderedList: {
fn: 'insertUnorderedList',
tag: 'ul'
},
orderedList: {
fn: 'insertOrderedList',
tag: 'ol'
},
horizontalRule: {
fn: 'insertHorizontalRule'
},
removeformat: {},
fullscreen: {
class: 'trumbowyg-not-disable'
},
close: {
fn: 'destroy',
class: 'trumbowyg-not-disable'
},
// Dropdowns
formatting: {
dropdown: ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4'],
ico: 'p'
},
link: {
dropdown: ['createLink', 'unlink']
}
};
// Default Options
t.o = $.extend(true, {}, $trumbowyg.defaultOptions, options);
if (!t.o.hasOwnProperty('imgDblClickHandler')) {
t.o.imgDblClickHandler = t.getDefaultImgDblClickHandler();
}
t.urlPrefix = t.setupUrlPrefix();
t.disabled = t.o.disabled || (editorElem.nodeName === 'TEXTAREA' && editorElem.disabled);
if (options.btns) {
t.o.btns = options.btns;
} else if (!t.o.semantic) {
t.o.btns[3] = ['bold', 'italic', 'underline', 'strikethrough'];
}
$.each(t.o.btnsDef, function (btnName, btnDef) {
t.addBtnDef(btnName, btnDef);
});
// put this here in the event it would be merged in with options
t.eventNamespace = 'trumbowyg-event';
// Keyboard shortcuts are load in this array
t.keys = [];
// Tag to button dynamically hydrated
t.tagToButton = {};
t.tagHandlers = [];
// Admit multiple paste handlers
t.pasteHandlers = [].concat(t.o.pasteHandlers);
// Check if browser is IE
t.isIE = navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') !== -1;
// Check if we are on macOs
t.isMac = navigator.platform.toUpperCase().indexOf('MAC') !== -1;
t.init();
};
Trumbowyg.prototype = {
DEFAULT_SEMANTIC_MAP: {
'b': 'strong',
'i': 'em',
's': 'del',
'strike': 'del',
'div': 'p'
},
init: function () {
var t = this;
t.height = t.$ta.height();
t.initPlugins();
try {
// Disable image resize, try-catch for old IE
t.doc.execCommand('enableObjectResizing', false, false);
t.doc.execCommand('defaultParagraphSeparator', false, 'div');
} catch (e) {
}
t.buildEditor();
t.buildBtnPane();
t.fixedBtnPaneEvents();
t.buildOverlay();
setTimeout(function () {
if (t.disabled) {
t.setDisabled(true);
}
t.$c.trigger('tbwinit');
});
},
addBtnDef: function (btnName, btnDef) {
this.btnsDef[btnName] = $.extend(btnDef, this.btnsDef[btnName] || {});
},
setupUrlPrefix: function () {
var protocol = this.o.urlProtocol;
if (!protocol) {
return;
}
if (typeof (protocol) !== 'string') {
return 'https://';
}
return protocol.replace('://', '') + '://';
},
buildEditor: function () {
var t = this,
prefix = t.o.prefix,
html = '';
t.$box = $('<div/>', {
class: prefix + 'box ' + prefix + 'editor-visible ' + prefix + t.o.lang + ' trumbowyg'
});
// $ta = Textarea
// $ed = Editor
t.isTextarea = t.$ta.is('textarea');
if (t.isTextarea) {
html = t.$ta.val();
t.$ed = $('<div/>');
t.$box
.insertAfter(t.$ta)
.append(t.$ed, t.$ta);
} else {
t.$ed = t.$ta;
html = t.$ed.html();
t.$ta = $('<textarea/>', {
name: t.$ta.attr('id'),
height: t.height
}).val(html);
t.$box
.insertAfter(t.$ed)
.append(t.$ta, t.$ed);
t.syncCode();
}
t.$ta
.addClass(prefix + 'textarea')
.attr('tabindex', -1)
;
t.$ed
.addClass(prefix + 'editor')
.attr({
contenteditable: true,
dir: t.lang._dir || 'ltr'
})
.html(html)
;
if (t.o.tabindex) {
t.$ed.attr('tabindex', t.o.tabindex);
}
if (t.$c.is('[placeholder]')) {
t.$ed.attr('placeholder', t.$c.attr('placeholder'));
}
if (t.$c.is('[spellcheck]')) {
t.$ed.attr('spellcheck', t.$c.attr('spellcheck'));
}
if (t.o.resetCss) {
t.$ed.addClass(prefix + 'reset-css');
}
if (!t.o.autogrow) {
t.$ta.add(t.$ed).css({
height: t.height
});
}
t.semanticCode();
if (t.o.autogrowOnEnter) {
t.$ed.addClass(prefix + 'autogrow-on-enter');
}
var ctrl = false,
composition = false,
debounceButtonPaneStatus;
t.$ed
.on('dblclick', 'img', t.o.imgDblClickHandler)
.on('keydown', function (e) {
// append flags to differentiate Chrome spans
var keyCode = e.which;
if (keyCode === 8 || keyCode === 13 || keyCode === 46) {
t.toggleSpan(true);
}
if ((e.ctrlKey || e.metaKey) && !e.altKey) {
ctrl = true;
var key = t.keys[String.fromCharCode(e.which).toUpperCase()];
try {
t.execCmd(key.fn, key.param);
return false;
} catch (c) {
}
} else {
if (t.o.tabToIndent && e.key === 'Tab') {
try {
if (e.shiftKey) {
t.execCmd('outdent', true, null);
} else {
t.execCmd('indent', true, null);
}
return false;
} catch (c) {
}
}
}
})
.on('compositionstart compositionupdate', function () {
composition = true;
})
.on('keyup compositionend', function (e) {
if (e.type === 'compositionend') {
composition = false;
} else if (composition) {
return;
}
var keyCode = e.which;
if (keyCode >= 37 && keyCode <= 40) {
return;
}
// remove Chrome generated span tags
if (keyCode === 8 || keyCode === 13 || keyCode === 46) {
t.toggleSpan();
}
if ((e.ctrlKey || e.metaKey) && (keyCode === 89 || keyCode === 90)) {
t.semanticCode(false, true);
t.$c.trigger('tbwchange');
} else if (!ctrl && keyCode !== 17) {
var compositionEndIE = t.isIE ? e.type === 'compositionend' : true;
t.semanticCode(false, compositionEndIE && keyCode === 13);
t.$c.trigger('tbwchange');
} else if (typeof e.which === 'undefined') {
t.semanticCode(false, false, true);
}
setTimeout(function () {
ctrl = false;
}, 50);
})
.on('mouseup keydown keyup', function (e) {
if ((!e.ctrlKey && !e.metaKey) || e.altKey) {
setTimeout(function () { // "hold on" to the ctrl key for 50ms
ctrl = false;
}, 50);
}
clearTimeout(debounceButtonPaneStatus);
debounceButtonPaneStatus = setTimeout(function () {
t.updateButtonPaneStatus();
}, 50);
})
.on('focus blur', function (e) {
if (e.type === 'blur') {
t.clearButtonPaneStatus();
}
t.$c.trigger('tbw' + e.type);
if (t.o.autogrowOnEnter) {
if (t.autogrowOnEnterDontClose) {
return;
}
if (e.type === 'focus') {
t.autogrowOnEnterWasFocused = true;
t.autogrowEditorOnEnter();
} else if (!t.o.autogrow) {
t.$ed.css({height: t.$ed.css('min-height')});
t.$c.trigger('tbwresize');
}
}
})
.on('cut drop', function () {
setTimeout(function () {
t.semanticCode(false, true);
t.$c.trigger('tbwchange');
}, 0);
})
.on('paste', function (e) {
if (t.o.removeformatPasted) {
e.preventDefault();
if (window.getSelection && window.getSelection().deleteFromDocument) {
window.getSelection().deleteFromDocument();
}
try {
// IE
var text = window.clipboardData.getData('Text');
try {
// <= IE10
t.doc.selection.createRange().pasteHTML(text);
} catch (c) {
// IE 11
t.doc.getSelection().getRangeAt(0).insertNode(t.doc.createTextNode(text));
}
t.$c.trigger('tbwchange', e);
} catch (d) {
// Not IE
t.execCmd('insertText', (e.originalEvent || e).clipboardData.getData('text/plain'));
}
}
// Call pasteHandlers
$.each(t.pasteHandlers, function (i, pasteHandler) {
pasteHandler(e);
});
setTimeout(function () {
t.semanticCode(false, true);
t.$c.trigger('tbwpaste', e);
t.$c.trigger('tbwchange');
}, 0);
});
t.$ta
.on('keyup', function () {
t.$c.trigger('tbwchange');
})
.on('paste', function () {
setTimeout(function () {
t.$c.trigger('tbwchange');
}, 0);
});
$(t.doc.body).on('keydown.' + t.eventNamespace, function (e) {
if (e.which === 27 && $('.' + prefix + 'modal-box').length >= 1) {
t.closeModal();
return false;
}
});
},
//autogrow when entering logic
autogrowEditorOnEnter: function () {
var t = this;
t.$ed.removeClass('autogrow-on-enter');
var oldHeight = t.$ed[0].clientHeight;
t.$ed.height('auto');
var totalHeight = t.$ed[0].scrollHeight;
t.$ed.addClass('autogrow-on-enter');
if (oldHeight !== totalHeight) {
t.$ed.height(oldHeight);
setTimeout(function () {
t.$ed.css({height: totalHeight});
t.$c.trigger('tbwresize');
}, 0);
}
},
// Build button pane, use o.btns option
buildBtnPane: function () {
var t = this,
prefix = t.o.prefix;
var $btnPane = t.$btnPane = $('<div/>', {
class: prefix + 'button-pane'
});
$.each(t.o.btns, function (i, btnGrp) {
if (!$.isArray(btnGrp)) {
btnGrp = [btnGrp];
}
var $btnGroup = $('<div/>', {
class: prefix + 'button-group ' + ((btnGrp.indexOf('fullscreen') >= 0) ? prefix + 'right' : '')
});
$.each(btnGrp, function (i, btn) {
try { // Prevent buildBtn error
if (t.isSupportedBtn(btn)) { // It's a supported button
$btnGroup.append(t.buildBtn(btn));
}
} catch (c) {
}
});
if ($btnGroup.html().trim().length > 0) {
$btnPane.append($btnGroup);
}
});
t.$box.prepend($btnPane);
},
cleanSpans: function(t) {
const spans = t.$ed[0].getElementsByTagName('span');
const stylesCheck = [
'fontWeight',
'fontSize',
'fontFamily',
'color',
'textColor',
'lineHeight',
'backgroundColor'
]
for (const span of spans) {
for (let i = 0; i < stylesCheck.length; i++) {
const name = stylesCheck[i];
const attr = span.style[name];
// chrome does this when it is detecting styles from member site itself
// cannot turn on resetCss as they use important which overrides styles.
if (attr === 'initial') {
span.style[name] = "";
}
}
}
},
// Build a button and his action
buildBtn: function (btnName) { // btnName is name of the button
var t = this,
prefix = t.o.prefix,
btn = t.btnsDef[btnName],
isDropdown = btn.dropdown,
hasIcon = btn.hasIcon != null ? btn.hasIcon : true,
textDef = t.lang[btnName] || btnName,
$btn = $('<button/>', {
type: 'button',
class: prefix + btnName + '-button ' + (btn.class || '') + (!hasIcon ? ' ' + prefix + 'textual-button' : ''),
html: t.hasSvg && hasIcon ?
'<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' :
t.hideButtonTexts ? '' : (btn.text || btn.title || t.lang[btnName] || btnName),
title: (btn.title || btn.text || textDef) + (btn.key ? ' (' + (t.isMac ? 'Cmd' : 'Ctrl') + ' + ' + btn.key + ')' : ''),
tabindex: 0,
mousedown: function () {
if (!isDropdown || $('.' + btnName + '-' + prefix + 'dropdown', t.$box).is(':hidden')) {
$('body', t.doc).trigger('mousedown');
}
if ((t.$btnPane.hasClass(prefix + 'disable') || t.$box.hasClass(prefix + 'disabled')) &&
!$(this).hasClass(prefix + 'active') &&
!$(this).hasClass(prefix + 'not-disable')) {
return false;
}
t.execCmd((isDropdown ? 'dropdown' : false) || btn.fn || btnName, btn.param || btnName, btn.forceCss);
t.cleanSpans(t);
t.syncCode();
return false;
},
keydown: function(e) {
if(e.originalEvent.code === 'Enter' || e.originalEvent.code === 'Space') {
e.preventDefault();
if(isDropdown) {
setTimeout(function() { $(`.${prefix}dropdown-${btnName}`)[0].focus(); }, 0);
}
if(!isDropdown || $('.'+btnName+'-'+prefix + 'dropdown', t.$box).is(':hidden'))
$('body', t.doc).trigger('mousedown');
// Making html viewer accessible
if (btn.fn === 'toggle') {
t.toggle();
return false;
}
if(t.$btnPane.hasClass(prefix + 'disable') && !$(this).hasClass(prefix + 'active') && !$(this).parent().hasClass(prefix + 'not-disable'))
return false;
t.execCmd((isDropdown ? 'dropdown' : false) || btn.func || btnName,
btn.param || btnName);
t.cleanSpans(t);
t.syncCode();
return false;
}
}
});
if (isDropdown) {
$btn.addClass(prefix + 'open-dropdown');
$btn.attr('aria-controls', `${btnName}-${prefix}dropdown`);
var dropdownPrefix = prefix + 'dropdown',
dropdownOptions = { // the dropdown
class: dropdownPrefix + '-' + btnName + ' ' + dropdownPrefix + ' ' + prefix + 'fixed-top ' + (btn.dropdownClass || ''),
id: btnName + '-' + dropdownPrefix,
tabindex: 0
};
dropdownOptions['data-' + dropdownPrefix] = btnName;
var $dropdown = $('<div/>', dropdownOptions);
$.each(isDropdown, function (i, def) {
if (t.btnsDef[def] && t.isSupportedBtn(def)) {
$dropdown.append(t.buildSubBtn(def));
}
});
t.$box.append($dropdown.hide());
} else if (btn.key) {
t.keys[btn.key] = {
fn: btn.fn || btnName,
param: btn.param || btnName
};
}
if (!isDropdown) {
t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName;
}
return $btn;
},
// Build a button for dropdown menu
// @param n : name of the subbutton
buildSubBtn: function (btnName) {
var t = this,
prefix = t.o.prefix,
btn = t.btnsDef[btnName],
hasIcon = btn.hasIcon != null ? btn.hasIcon : true;
if (btn.key) {
t.keys[btn.key] = {
fn: btn.fn || btnName,
param: btn.param || btnName
};
}
t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName;
return $('<button/>', {
type: 'button',
class: prefix + btnName + '-dropdown-button ' + (btn.class || '') + (btn.ico ? ' ' + prefix + btn.ico + '-button' : ''),
html: t.hasSvg