| 1 |
/** |
| 2 |
* Licensed under MIT, https://github.com/sofish/pen |
| 3 |
* |
| 4 |
* Customized and fixed by Elementor team |
| 5 |
*/ |
| 6 |
|
| 7 |
(function(root, doc) { |
| 8 |
|
| 9 |
var InlineEditor, debugMode, selection, utils = {}; |
| 10 |
var slice = Array.prototype.slice; |
| 11 |
|
| 12 |
// allow command list |
| 13 |
var commandsReg = { |
| 14 |
block: /^(?:p|h[1-6]|blockquote|pre)$/, |
| 15 |
inline: /^(?:justify(center|full|left|right)|strikethrough|insert(un)?orderedlist|(in|out)dent)$/, |
| 16 |
biu: /^(bold|italic|underline)$/, |
| 17 |
source: /^(?:createlink|unlink)$/, |
| 18 |
insert: /^(?:inserthorizontalrule|insertimage|insert)$/, |
| 19 |
wrap: /^(?:code)$/ |
| 20 |
}; |
| 21 |
|
| 22 |
var lineBreakReg = /^(?:blockquote|pre|div)$/i, |
| 23 |
effectNodeReg = /(?:[pubia]|strong|em|h[1-6]|blockquote|code|[uo]l|li)/i; |
| 24 |
|
| 25 |
var strReg = { |
| 26 |
whiteSpace: /(^\s+)|(\s+$)/g, |
| 27 |
mailTo: /^(?!mailto:|.+\/|.+#|.+\?)(.*@.*\..+)$/, |
| 28 |
http: /^(?!\w+?:\/\/|mailto:|\/|\.\/|\?|#)(.*)$/ |
| 29 |
}; |
| 30 |
|
| 31 |
var autoLinkReg = { |
| 32 |
url: /((https?|ftp):\/\/|www\.)[^\s<]{3,}/gi, |
| 33 |
prefix: /^(?:https?|ftp):\/\//i, |
| 34 |
notLink: /^(?:img|a|input|audio|video|source|code|pre|script|head|title|style)$/i, |
| 35 |
maxLength: 100 |
| 36 |
}; |
| 37 |
|
| 38 |
var styleBackupDict = { |
| 39 |
bold: { |
| 40 |
styleKey: 'font-weight', |
| 41 |
correctValue: 'normal' |
| 42 |
}, |
| 43 |
italic: { |
| 44 |
styleKey: 'font-style', |
| 45 |
correctValue: 'normal' |
| 46 |
}, |
| 47 |
underline: { |
| 48 |
styleKey: 'text-decoration', |
| 49 |
correctValue: 'none' |
| 50 |
} |
| 51 |
}; |
| 52 |
|
| 53 |
// type detect |
| 54 |
utils.is = function(obj, type) { |
| 55 |
return Object.prototype.toString.call(obj).slice(8, -1) === type; |
| 56 |
}; |
| 57 |
|
| 58 |
utils.forEach = function(obj, iterator, arrayLike) { |
| 59 |
if (!obj) return; |
| 60 |
if (arrayLike == null) arrayLike = utils.is(obj, 'Array'); |
| 61 |
if (arrayLike) { |
| 62 |
for (var i = 0, l = obj.length; i < l; i++) iterator(obj[i], i, obj); |
| 63 |
} else { |
| 64 |
for (var key in obj) { |
| 65 |
if (obj.hasOwnProperty(key)) iterator(obj[key], key, obj); |
| 66 |
} |
| 67 |
} |
| 68 |
}; |
| 69 |
|
| 70 |
// copy props from a obj |
| 71 |
utils.copy = function(defaults, source) { |
| 72 |
utils.forEach(source, function (value, key) { |
| 73 |
defaults[key] = utils.is(value, 'Object') ? utils.copy({}, value) : |
| 74 |
utils.is(value, 'Array') ? utils.copy([], value) : value; |
| 75 |
}); |
| 76 |
return defaults; |
| 77 |
}; |
| 78 |
|
| 79 |
// log |
| 80 |
utils.log = function(message, force) { |
| 81 |
if (debugMode || force) |
| 82 |
console.log('%cPEN DEBUGGER: %c' + message, 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;'); |
| 83 |
}; |
| 84 |
|
| 85 |
utils.delayExec = function (fn) { |
| 86 |
var timer = null; |
| 87 |
return function (delay) { |
| 88 |
clearTimeout(timer); |
| 89 |
timer = setTimeout(function() { |
| 90 |
fn(); |
| 91 |
}, delay || 1); |
| 92 |
}; |
| 93 |
}; |
| 94 |
|
| 95 |
// merge: make it easy to have a fallback |
| 96 |
utils.merge = function(config) { |
| 97 |
|
| 98 |
// default settings |
| 99 |
var defaults = { |
| 100 |
class: 'pen', |
| 101 |
placeholderClass: 'pen-placeholder', |
| 102 |
placeholderAttr: 'data-pen-placeholder', |
| 103 |
debug: false, |
| 104 |
toolbar: null, // custom toolbar |
| 105 |
mode: 'basic', |
| 106 |
ignoreLineBreak: false, |
| 107 |
toolbarIconsPrefix: 'fa fa-', |
| 108 |
toolbarIconsDictionary: {externalLink: 'eicon-editor-external-link'}, |
| 109 |
stay: config.stay || !config.debug, |
| 110 |
stayMsg: 'Are you going to leave here?', |
| 111 |
textarea: '<textarea name="content"></textarea>', |
| 112 |
list: [ |
| 113 |
'blockquote', 'h2', 'h3', 'p', 'code', 'insertOrderedList', 'insertUnorderedList', 'inserthorizontalrule', |
| 114 |
'indent', 'outdent', 'bold', 'italic', 'underline', 'createlink', 'insertimage' |
| 115 |
], |
| 116 |
titles: {}, |
| 117 |
cleanAttrs: ['id', 'class', 'style', 'name'], |
| 118 |
cleanTags: ['script'], |
| 119 |
linksInNewWindow: false |
| 120 |
}; |
| 121 |
|
| 122 |
// user-friendly config |
| 123 |
if (config.nodeType === 1) { |
| 124 |
defaults.editor = config; |
| 125 |
} else if (config.match && config.match(/^#[\S]+$/)) { |
| 126 |
defaults.editor = doc.getElementById(config.slice(1)); |
| 127 |
} else { |
| 128 |
defaults = utils.copy(defaults, config); |
| 129 |
} |
| 130 |
|
| 131 |
return defaults; |
| 132 |
}; |
| 133 |
|
| 134 |
function commandOverall(cmd, val) { |
| 135 |
var message = ' to exec 「' + cmd + '」 command' + (val ? (' with value: ' + val) : ''); |
| 136 |
|
| 137 |
try { |
| 138 |
doc.execCommand(cmd, false, val); |
| 139 |
} catch(err) { |
| 140 |
// TODO: there's an error when insert a image to document, but not a bug |
| 141 |
return utils.log('fail' + message, true); |
| 142 |
} |
| 143 |
|
| 144 |
utils.log('success' + message); |
| 145 |
} |
| 146 |
|
| 147 |
function commandInsert(ctx, name, val) { |
| 148 |
var node = getNode(ctx); |
| 149 |
if (!node) return; |
| 150 |
ctx._range.selectNode(node); |
| 151 |
ctx._range.collapse(false); |
| 152 |
|
| 153 |
// hide menu when a image was inserted |
| 154 |
if(name === 'insertimage' && ctx._menu) toggleNode(ctx._menu, true); |
| 155 |
|
| 156 |
return commandOverall(name, val); |
| 157 |
} |
| 158 |
|
| 159 |
function commandBlock(ctx, name) { |
| 160 |
var effectNodes = getEffectNodes(ctx), |
| 161 |
tagsList = effectNodes.map(function(node) { |
| 162 |
return node.nodeName.toLowerCase(); |
| 163 |
}); |
| 164 |
|
| 165 |
if (tagsList.indexOf(name) !== -1) name = 'p'; |
| 166 |
|
| 167 |
return commandOverall('formatblock', name); |
| 168 |
} |
| 169 |
|
| 170 |
function commandWrap(ctx, tag, value) { |
| 171 |
value = '<' + tag + '>' + (value||selection.toString()) + '</' + tag + '>'; |
| 172 |
return commandOverall('insertHTML', value); |
| 173 |
} |
| 174 |
|
| 175 |
function commandLink(ctx, tag, value) { |
| 176 |
if (ctx.config.linksInNewWindow && 'unlink' !== tag) { |
| 177 |
value = '<a href="' + value + '" target="_blank">' + (selection.toString()) + '</a>'; |
| 178 |
return commandOverall('insertHTML', value); |
| 179 |
} else { |
| 180 |
return commandOverall(tag, value); |
| 181 |
} |
| 182 |
} |
| 183 |
|
| 184 |
function createTool(ctx, name, type, group) { |
| 185 |
var title = ctx.config.titles[name] || '', |
| 186 |
iconElement = document.createElement( 'div' ); |
| 187 |
|
| 188 |
iconElement.classList.add('pen-icon'); |
| 189 |
|
| 190 |
iconElement.setAttribute('title', title); |
| 191 |
|
| 192 |
if ('parent' === type) { |
| 193 |
iconElement.classList.add('pen-group-icon'); |
| 194 |
|
| 195 |
iconElement.setAttribute('data-group-toggle', name); |
| 196 |
} else { |
| 197 |
iconElement.setAttribute('data-action', name); |
| 198 |
} |
| 199 |
|
| 200 |
if('child' === type) { |
| 201 |
iconElement.setAttribute('data-group', group); |
| 202 |
} |
| 203 |
|
| 204 |
var iconDictionary = ctx.config.toolbarIconsDictionary[ name ]; |
| 205 |
|
| 206 |
if ( iconDictionary && iconDictionary.text ) { |
| 207 |
iconElement.textContent = iconDictionary.text; |
| 208 |
} else { |
| 209 |
var iconClass; |
| 210 |
|
| 211 |
if ( iconDictionary && iconDictionary.className ) { |
| 212 |
iconClass = iconDictionary.className; |
| 213 |
} else { |
| 214 |
iconClass = ctx.config.toolbarIconsPrefix + name; |
| 215 |
} |
| 216 |
|
| 217 |
iconElement.innerHTML += '<i class="' + iconClass + '" ></i>'; |
| 218 |
} |
| 219 |
|
| 220 |
return iconElement.outerHTML; |
| 221 |
} |
| 222 |
|
| 223 |
function getMenuTools(ctx) { |
| 224 |
return slice.call(ctx._menu.children); |
| 225 |
} |
| 226 |
|
| 227 |
function activateGroup(ctx, group) { |
| 228 |
var tools = getMenuTools(ctx); |
| 229 |
|
| 230 |
tools.forEach(function(tool) { |
| 231 |
toggleNode(tool, tool.getAttribute('data-group') !== group); |
| 232 |
}); |
| 233 |
|
| 234 |
toggleMenuClose(ctx, ! group); |
| 235 |
|
| 236 |
ctx.refreshMenuPosition(); |
| 237 |
} |
| 238 |
|
| 239 |
function showMainMenu(ctx) { |
| 240 |
activateGroup(ctx, null); |
| 241 |
|
| 242 |
toggleLinkInput(ctx, true); |
| 243 |
|
| 244 |
toggleUnlinkTool(ctx, !ctx._urlInput || ctx._urlInput.value === ''); |
| 245 |
} |
| 246 |
|
| 247 |
function showLinkInput(ctx) { |
| 248 |
var tools = getMenuTools(ctx); |
| 249 |
|
| 250 |
tools.forEach(function(tool) { |
| 251 |
toggleNode(tool, true); |
| 252 |
}); |
| 253 |
|
| 254 |
toggleLinkInput(ctx); |
| 255 |
|
| 256 |
toggleMenuClose(ctx); |
| 257 |
} |
| 258 |
|
| 259 |
function toggleLinkInput(ctx, hide) { |
| 260 |
var linkInput = ctx._menu.querySelector('.pen-input-wrapper'); |
| 261 |
|
| 262 |
if (! linkInput) { |
| 263 |
return; |
| 264 |
} |
| 265 |
|
| 266 |
toggleNode(linkInput, hide); |
| 267 |
} |
| 268 |
|
| 269 |
function toggleUnlinkTool(ctx, hide) { |
| 270 |
var unlinkTool = ctx._menu.querySelector('[data-action="unlink"]'); |
| 271 |
|
| 272 |
if (! unlinkTool) { |
| 273 |
return; |
| 274 |
} |
| 275 |
|
| 276 |
toggleNode(unlinkTool, hide); |
| 277 |
|
| 278 |
ctx.refreshMenuPosition(); |
| 279 |
} |
| 280 |
|
| 281 |
function toggleMenuClose(ctx, hide) { |
| 282 |
var closeButton = ctx._menu.querySelector('[data-action="close"]'); |
| 283 |
|
| 284 |
toggleNode(closeButton, hide); |
| 285 |
|
| 286 |
ctx.refreshMenuPosition(); |
| 287 |
} |
| 288 |
|
| 289 |
function createLinkInput(ctx) { |
| 290 |
var inputWrapper = doc.createElement('div'), |
| 291 |
urlInput = doc.createElement('input'), |
| 292 |
newWindowLabel = doc.createElement('label'), |
| 293 |
newWindowCheckbox = doc.createElement('input'), |
| 294 |
newWindowIcon = doc.createElement('i'); |
| 295 |
|
| 296 |
inputWrapper.className = 'pen-input-wrapper'; |
| 297 |
|
| 298 |
urlInput.className = 'pen-url-input'; |
| 299 |
urlInput.type = 'url'; |
| 300 |
urlInput.placeholder = 'http://'; |
| 301 |
|
| 302 |
newWindowLabel.className = 'pen-icon pen-input-label'; |
| 303 |
|
| 304 |
newWindowCheckbox.className = 'pen-external-url-checkbox'; |
| 305 |
newWindowCheckbox.type = 'checkbox'; |
| 306 |
|
| 307 |
newWindowIcon.className = ctx.config.toolbarIconsDictionary.externalLink.className; |
| 308 |
|
| 309 |
newWindowLabel.appendChild(newWindowCheckbox); |
| 310 |
newWindowLabel.appendChild(newWindowIcon); |
| 311 |
|
| 312 |
inputWrapper.appendChild(urlInput); |
| 313 |
inputWrapper.appendChild(newWindowLabel); |
| 314 |
|
| 315 |
return inputWrapper; |
| 316 |
} |
| 317 |
|
| 318 |
function menuApply(ctx, action, value) { |
| 319 |
ctx.execCommand(action, value); |
| 320 |
|
| 321 |
ctx._range = ctx.getRange(); |
| 322 |
|
| 323 |
ctx.highlight().menu(); |
| 324 |
} |
| 325 |
|
| 326 |
function onToolbarClick(ctx, target) { |
| 327 |
var toolbar = ctx._toolbar || ctx._menu, |
| 328 |
action; |
| 329 |
|
| 330 |
while (!(action = target.getAttribute('data-action'))) { |
| 331 |
if (target.parentNode === toolbar) { |
| 332 |
break; |
| 333 |
} |
| 334 |
|
| 335 |
target = target.parentNode; |
| 336 |
} |
| 337 |
|
| 338 |
var groupToggle = target.getAttribute('data-group-toggle'); |
| 339 |
|
| 340 |
if (groupToggle) { |
| 341 |
activateGroup(ctx, groupToggle); |
| 342 |
} |
| 343 |
|
| 344 |
if (!action) return; |
| 345 |
|
| 346 |
if ('close' === action) { |
| 347 |
showMainMenu(ctx); |
| 348 |
|
| 349 |
return; |
| 350 |
} |
| 351 |
|
| 352 |
if (!/(?:createlink)|(?:insertimage)/.test(action)) return menuApply(ctx, action); |
| 353 |
|
| 354 |
if (!ctx._urlInput) return; |
| 355 |
|
| 356 |
// create link |
| 357 |
var input = ctx._urlInput; |
| 358 |
if (toolbar === ctx._menu) showLinkInput(ctx); |
| 359 |
else { |
| 360 |
ctx._inputActive = true; |
| 361 |
ctx.menu(); |
| 362 |
} |
| 363 |
if (ctx._menu.style.display === 'none') return; |
| 364 |
|
| 365 |
setTimeout(function() { input.focus(); }, 10); |
| 366 |
|
| 367 |
var createLink = function() { |
| 368 |
var inputValue = input.value; |
| 369 |
|
| 370 |
if (inputValue) { |
| 371 |
ctx.config.linksInNewWindow = ctx._externalUrlCheckbox.checked; |
| 372 |
|
| 373 |
inputValue = input.value |
| 374 |
.replace(strReg.whiteSpace, '') |
| 375 |
.replace(strReg.mailTo, 'mailto:$1') |
| 376 |
.replace(strReg.http, 'http://$1'); |
| 377 |
} else { |
| 378 |
action = 'unlink'; |
| 379 |
} |
| 380 |
|
| 381 |
menuApply(ctx, action, inputValue); |
| 382 |
}; |
| 383 |
|
| 384 |
input.onkeypress = function(e) { |
| 385 |
if (e.which === 13) { |
| 386 |
e.preventDefault(); |
| 387 |
|
| 388 |
createLink() |
| 389 |
} |
| 390 |
}; |
| 391 |
|
| 392 |
ctx._externalUrlCheckbox.onchange = createLink; |
| 393 |
} |
| 394 |
|
| 395 |
function initToolbar(ctx) { |
| 396 |
var icons = '', inputStr = createLinkInput(ctx).outerHTML; |
| 397 |
|
| 398 |
ctx._toolbar = ctx.config.toolbar; |
| 399 |
|
| 400 |
if (!ctx._toolbar) { |
| 401 |
var toolList = ctx.config.list; |
| 402 |
|
| 403 |
if (! Object.values(toolList).length) { |
| 404 |
return; |
| 405 |
} |
| 406 |
|
| 407 |
utils.forEach(toolList, function (name, key) { |
| 408 |
if (Array.isArray(name)) { |
| 409 |
var children = name; |
| 410 |
|
| 411 |
name = key; |
| 412 |
|
| 413 |
icons += createTool(ctx, name, 'parent'); |
| 414 |
|
| 415 |
utils.forEach(children, function(childName) { |
| 416 |
icons += createTool(ctx, childName, 'child', name); |
| 417 |
}, true); |
| 418 |
} else { |
| 419 |
icons += createTool(ctx, name); |
| 420 |
} |
| 421 |
}); |
| 422 |
|
| 423 |
var toolListValues = Object.values(toolList); |
| 424 |
|
| 425 |
if (toolListValues.indexOf('createlink') >= 0 || toolListValues.indexOf('insertimage') >= 0) |
| 426 |
icons += inputStr; |
| 427 |
|
| 428 |
icons += createTool(ctx, 'close'); |
| 429 |
} else if (ctx._toolbar.querySelectorAll('[data-action=createlink]').length || |
| 430 |
ctx._toolbar.querySelectorAll('[data-action=insertimage]').length) { |
| 431 |
icons += inputStr; |
| 432 |
} |
| 433 |
|
| 434 |
if (icons) { |
| 435 |
ctx._menu = doc.createElement('div'); |
| 436 |
ctx._menu.setAttribute('class', ctx.config.class + '-menu pen-menu'); |
| 437 |
ctx._menu.innerHTML = icons; |
| 438 |
ctx._urlInput = ctx._menu.querySelector('.pen-url-input'); |
| 439 |
ctx._externalUrlCheckbox = ctx._menu.querySelector('.pen-external-url-checkbox'); |
| 440 |
toggleNode(ctx._menu, true); |
| 441 |
doc.body.appendChild(ctx._menu); |
| 442 |
} |
| 443 |
} |
| 444 |
|
| 445 |
function initEvents(ctx) { |
| 446 |
var toolbar = ctx._toolbar || ctx._menu, editor = ctx.config.editor; |
| 447 |
|
| 448 |
var toggleMenu = utils.delayExec(function() { |
| 449 |
if (toolbar) { |
| 450 |
ctx.highlight().menu(); |
| 451 |
} |
| 452 |
}); |
| 453 |
|
| 454 |
var outsideClick = function() {}; |
| 455 |
|
| 456 |
function updateStatus(delay) { |
| 457 |
ctx._range = ctx.getRange(); |
| 458 |
toggleMenu(delay); |
| 459 |
} |
| 460 |
|
| 461 |
if (ctx._menu) { |
| 462 |
var setpos = function() { |
| 463 |
if (ctx._menu.style.display === 'flex') ctx.menu(); |
| 464 |
}; |
| 465 |
|
| 466 |
// change menu offset when window resize / scroll |
| 467 |
addListener(ctx, root, 'resize', setpos); |
| 468 |
addListener(ctx, root, 'scroll', setpos); |
| 469 |
|
| 470 |
// toggle toolbar on mouse select |
| 471 |
var selecting = false; |
| 472 |
addListener(ctx, editor, 'mousedown', function() { |
| 473 |
selecting = true; |
| 474 |
}); |
| 475 |
|
| 476 |
addListener(ctx, editor, 'mouseleave', function() { |
| 477 |
if (selecting) updateStatus(800); |
| 478 |
selecting = false; |
| 479 |
}); |
| 480 |
|
| 481 |
addListener(ctx, editor, 'mouseup', function() { |
| 482 |
if (selecting) updateStatus(200); |
| 483 |
selecting = false; |
| 484 |
}); |
| 485 |
|
| 486 |
// Hide menu when focusing outside of editor |
| 487 |
outsideClick = function(e) { |
| 488 |
if (ctx._menu && !containsNode(editor, e.target) && !containsNode(ctx._menu, e.target)) { |
| 489 |
removeListener(ctx, doc, 'click', outsideClick); |
| 490 |
toggleMenu(100); |
| 491 |
} |
| 492 |
}; |
| 493 |
} else { |
| 494 |
addListener(ctx, editor, 'click', function() { |
| 495 |
updateStatus(0); |
| 496 |
}); |
| 497 |
} |
| 498 |
|
| 499 |
addListener(ctx, editor, 'keyup', function(e) { |
| 500 |
checkPlaceholder(ctx); |
| 501 |
|
| 502 |
if (ctx.isEmpty()) { |
| 503 |
if (ctx.config.mode === 'advanced') { |
| 504 |
handleEmptyContent(ctx); |
| 505 |
} |
| 506 |
|
| 507 |
return; |
| 508 |
} |
| 509 |
|
| 510 |
if (isCaretAtEnd(ctx) && !isCaretAtStart(ctx) && ctx.config.mode !== 'advanced') { |
| 511 |
editor.innerHTML = editor.innerHTML.replace( /\u200b/, '' ); |
| 512 |
|
| 513 |
addEmptyCharAtEnd(ctx); |
| 514 |
} |
| 515 |
|
| 516 |
// toggle toolbar on key select |
| 517 |
if (e.which !== 13 || e.shiftKey) return updateStatus(400); |
| 518 |
var node = getNode(ctx, true); |
| 519 |
if (!node || !node.nextSibling || !lineBreakReg.test(node.nodeName)) return; |
| 520 |
if (node.nodeName !== node.nextSibling.nodeName) return; |
| 521 |
// hack for webkit, make 'enter' behavior like as firefox. |
| 522 |
if (node.lastChild.nodeName !== 'BR') node.appendChild(doc.createElement('br')); |
| 523 |
utils.forEach(node.nextSibling.childNodes, function(child) { |
| 524 |
if (child) node.appendChild(child); |
| 525 |
}, true); |
| 526 |
node.parentNode.removeChild(node.nextSibling); |
| 527 |
focusNode(ctx, node.lastChild, ctx.getRange()); |
| 528 |
}); |
| 529 |
|
| 530 |
// check line break |
| 531 |
addListener(ctx, editor, 'keydown', function(e) { |
| 532 |
editor.classList.remove(ctx.config.placeholderClass); |
| 533 |
|
| 534 |
if (e.which !== 13 || e.shiftKey) return; |
| 535 |
|
| 536 |
if ( ctx.config.ignoreLineBreak ) { |
| 537 |
e.preventDefault(); |
| 538 |
|
| 539 |
return; |
| 540 |
} |
| 541 |
|
| 542 |
var node = getNode(ctx, true); |
| 543 |
|
| 544 |
if(!node || !lineBreakReg.test(node.nodeName)) { |
| 545 |
if (ctx.config.mode === 'basic') { |
| 546 |
e.preventDefault(); |
| 547 |
|
| 548 |
commandOverall('insertHTML', '<br>'); |
| 549 |
} |
| 550 |
|
| 551 |
return; |
| 552 |
} |
| 553 |
|
| 554 |
if (!node) { |
| 555 |
return; |
| 556 |
} |
| 557 |
|
| 558 |
var lastChild = node.lastChild; |
| 559 |
if (!lastChild || !lastChild.previousSibling) return; |
| 560 |
if (lastChild.previousSibling.textContent || lastChild.textContent) return; |
| 561 |
// quit block mode for 2 'enter' |
| 562 |
e.preventDefault(); |
| 563 |
var p = doc.createElement('p'); |
| 564 |
p.innerHTML = '<br>'; |
| 565 |
node.removeChild(lastChild); |
| 566 |
if (!node.nextSibling) node.parentNode.appendChild(p); |
| 567 |
else node.parentNode.insertBefore(p, node.nextSibling); |
| 568 |
focusNode(ctx, p, ctx.getRange()); |
| 569 |
}); |
| 570 |
|
| 571 |
if (toolbar) { |
| 572 |
addListener(ctx, toolbar, 'click', function(e) { |
| 573 |
onToolbarClick(ctx, e.target); |
| 574 |
}); |
| 575 |
} |
| 576 |
|
| 577 |
addListener(ctx, editor, 'focus', function() { |
| 578 |
if (ctx.isEmpty() && ctx.config.mode === 'advanced') handleEmptyContent(ctx); |
| 579 |
addListener(ctx, doc, 'click', outsideClick); |
| 580 |
}); |
| 581 |
|
| 582 |
addListener(ctx, editor, 'blur', function() { |
| 583 |
checkPlaceholder(ctx); |
| 584 |
ctx.checkContentChange(); |
| 585 |
}); |
| 586 |
|
| 587 |
// listen for paste and clear style |
| 588 |
addListener(ctx, editor, 'paste', function() { |
| 589 |
setTimeout(function() { |
| 590 |
ctx.cleanContent(); |
| 591 |
}); |
| 592 |
}); |
| 593 |
} |
| 594 |
|
| 595 |
function addListener(ctx, target, type, listener) { |
| 596 |
if (ctx._events.hasOwnProperty(type)) { |
| 597 |
ctx._events[type].push(listener); |
| 598 |
} else { |
| 599 |
ctx._eventTargets = ctx._eventTargets || []; |
| 600 |
ctx._eventsCache = ctx._eventsCache || []; |
| 601 |
var index = ctx._eventTargets.indexOf(target); |
| 602 |
if (index < 0) index = ctx._eventTargets.push(target) - 1; |
| 603 |
ctx._eventsCache[index] = ctx._eventsCache[index] || {}; |
| 604 |
ctx._eventsCache[index][type] = ctx._eventsCache[index][type] || []; |
| 605 |
ctx._eventsCache[index][type].push(listener); |
| 606 |
|
| 607 |
target.addEventListener(type, listener, false); |
| 608 |
} |
| 609 |
return ctx; |
| 610 |
} |
| 611 |
|
| 612 |
// trigger local events |
| 613 |
function triggerListener(ctx, type) { |
| 614 |
if (!ctx._events.hasOwnProperty(type)) return; |
| 615 |
var args = slice.call(arguments, 2); |
| 616 |
utils.forEach(ctx._events[type], function (listener) { |
| 617 |
listener.apply(ctx, args); |
| 618 |
}); |
| 619 |
} |
| 620 |
|
| 621 |
function removeListener(ctx, target, type, listener) { |
| 622 |
var events = ctx._events[type]; |
| 623 |
if (!events) { |
| 624 |
var _index = ctx._eventTargets.indexOf(target); |
| 625 |
if (_index >= 0) events = ctx._eventsCache[_index][type]; |
| 626 |
} |
| 627 |
if (!events) return ctx; |
| 628 |
var index = events.indexOf(listener); |
| 629 |
if (index >= 0) events.splice(index, 1); |
| 630 |
target.removeEventListener(type, listener, false); |
| 631 |
return ctx; |
| 632 |
} |
| 633 |
|
| 634 |
function removeAllListeners(ctx) { |
| 635 |
utils.forEach(this._events, function (events) { |
| 636 |
events.length = 0; |
| 637 |
}, false); |
| 638 |
if (!ctx._eventsCache) return ctx; |
| 639 |
utils.forEach(ctx._eventsCache, function (events, index) { |
| 640 |
var target = ctx._eventTargets[index]; |
| 641 |
utils.forEach(events, function (listeners, type) { |
| 642 |
utils.forEach(listeners, function (listener) { |
| 643 |
target.removeEventListener(type, listener, false); |
| 644 |
}, true); |
| 645 |
}, false); |
| 646 |
}, true); |
| 647 |
ctx._eventTargets = []; |
| 648 |
ctx._eventsCache = []; |
| 649 |
return ctx; |
| 650 |
} |
| 651 |
|
| 652 |
function checkPlaceholder(ctx) { |
| 653 |
ctx.config.editor.classList[ctx.isEmpty() ? 'add' : 'remove'](ctx.config.placeholderClass); |
| 654 |
} |
| 655 |
|
| 656 |
function trim(str) { |
| 657 |
return (str || '').trim().replace(/\u200b/g, ''); |
| 658 |
} |
| 659 |
|
| 660 |
// node.contains is not implemented in IE10/IE11 |
| 661 |
function containsNode(parent, child) { |
| 662 |
if (parent === child) return true; |
| 663 |
child = child.parentNode; |
| 664 |
while (child) { |
| 665 |
if (child === parent) return true; |
| 666 |
child = child.parentNode; |
| 667 |
} |
| 668 |
return false; |
| 669 |
} |
| 670 |
|
| 671 |
function getNode(ctx, byRoot) { |
| 672 |
var node, |
| 673 |
root = ctx.config.editor; |
| 674 |
|
| 675 |
ctx._range = ctx._range || ctx.getRange(); |
| 676 |
|
| 677 |
node = ctx._range.commonAncestorContainer; |
| 678 |
|
| 679 |
// Fix selection detection for Firefox |
| 680 |
if (node.hasChildNodes() && ctx._range.startOffset + 1 === ctx._range.endOffset) { |
| 681 |
node = node.childNodes[ctx._range.startOffset]; |
| 682 |
} |
| 683 |
|
| 684 |
if (!node || node === root) return null; |
| 685 |
|
| 686 |
while (node && (node.nodeType !== 1) && (node.parentNode !== root)) node = node.parentNode; |
| 687 |
|
| 688 |
while (node && byRoot && (node.parentNode !== root)) node = node.parentNode; |
| 689 |
|
| 690 |
return containsNode(root, node) ? node : null; |
| 691 |
} |
| 692 |
|
| 693 |
function getEffectNodes(ctx) { |
| 694 |
return getNodeParents(ctx).filter(function(node) { |
| 695 |
return node.nodeName.match(effectNodeReg); |
| 696 |
}); |
| 697 |
} |
| 698 |
|
| 699 |
function getNodeParents(ctx) { |
| 700 |
var nodes = [], |
| 701 |
el = getNode(ctx); |
| 702 |
|
| 703 |
while (el && el !== ctx.config.editor) { |
| 704 |
if (el.nodeType === Node.ELEMENT_NODE) { |
| 705 |
nodes.push(el); |
| 706 |
} |
| 707 |
|
| 708 |
el = el.parentNode; |
| 709 |
} |
| 710 |
|
| 711 |
return nodes; |
| 712 |
} |
| 713 |
|
| 714 |
function handleEmptyContent(ctx) { |
| 715 |
var range = ctx._range = ctx.getRange(); |
| 716 |
|
| 717 |
ctx.config.editor.innerHTML = ''; |
| 718 |
|
| 719 |
var p = doc.createElement('p'); |
| 720 |
|
| 721 |
p.innerHTML = '<br>'; |
| 722 |
|
| 723 |
range.insertNode(p); |
| 724 |
|
| 725 |
focusNode(ctx, p.childNodes[0], range); |
| 726 |
} |
| 727 |
|
| 728 |
function addEmptyCharAtEnd(ctx) { |
| 729 |
var range = ctx.getRange(), |
| 730 |
emptyCharNode = doc.createTextNode('\u200b'); |
| 731 |
|
| 732 |
range.selectNodeContents(ctx.config.editor); |
| 733 |
range.collapse(false); |
| 734 |
range.insertNode(emptyCharNode); |
| 735 |
|
| 736 |
focusNode(ctx, emptyCharNode, range); |
| 737 |
} |
| 738 |
|
| 739 |
function isCaretAtEnd(ctx) { |
| 740 |
var range = ctx.getRange(), |
| 741 |
clonedRange = range.cloneRange(); |
| 742 |
|
| 743 |
clonedRange.selectNodeContents(ctx.config.editor); |
| 744 |
clonedRange.setStart(range.endContainer, range.endOffset); |
| 745 |
|
| 746 |
return clonedRange.toString() === ''; |
| 747 |
} |
| 748 |
|
| 749 |
function isCaretAtStart(ctx) { |
| 750 |
var range = ctx.getRange(), |
| 751 |
clonedRange = range.cloneRange(); |
| 752 |
|
| 753 |
clonedRange.selectNodeContents(ctx.config.editor); |
| 754 |
clonedRange.setEnd(range.startContainer, range.startOffset); |
| 755 |
|
| 756 |
return clonedRange.toString() === ''; |
| 757 |
} |
| 758 |
|
| 759 |
function focusNode(ctx, node, range) { |
| 760 |
range.setStartAfter(node); |
| 761 |
range.setEndBefore(node); |
| 762 |
range.collapse(false); |
| 763 |
ctx.setRange(range); |
| 764 |
} |
| 765 |
|
| 766 |
function autoLink(node) { |
| 767 |
if (node.nodeType === 1) { |
| 768 |
if (autoLinkReg.notLink.test(node.tagName)) return; |
| 769 |
utils.forEach(node.childNodes, function (child) { |
| 770 |
autoLink(child); |
| 771 |
}, true); |
| 772 |
} else if (node.nodeType === 3) { |
| 773 |
var result = urlToLink(node.nodeValue || ''); |
| 774 |
if (!result.links) return; |
| 775 |
var frag = doc.createDocumentFragment(), |
| 776 |
div = doc.createElement('div'); |
| 777 |
div.innerHTML = result.text; |
| 778 |
while (div.childNodes.length) frag.appendChild(div.childNodes[0]); |
| 779 |
node.parentNode.replaceChild(frag, node); |
| 780 |
} |
| 781 |
} |
| 782 |
|
| 783 |
function urlToLink(str) { |
| 784 |
var count = 0; |
| 785 |
str = str.replace(autoLinkReg.url, function(url) { |
| 786 |
var realUrl = url, displayUrl = url; |
| 787 |
count++; |
| 788 |
if (url.length > autoLinkReg.maxLength) displayUrl = url.slice(0, autoLinkReg.maxLength) + '...'; |
| 789 |
// Add http prefix if necessary |
| 790 |
if (!autoLinkReg.prefix.test(realUrl)) realUrl = 'http://' + realUrl; |
| 791 |
return '<a href="' + realUrl + '">' + displayUrl + '</a>'; |
| 792 |
}); |
| 793 |
return {links: count, text: str}; |
| 794 |
} |
| 795 |
|
| 796 |
function toggleNode(node, hide) { |
| 797 |
node.style.display = hide ? 'none' : 'flex'; |
| 798 |
} |
| 799 |
|
| 800 |
InlineEditor = function(config) { |
| 801 |
|
| 802 |
if (!config) throw new Error('Can\'t find config'); |
| 803 |
|
| 804 |
debugMode = config.debug; |
| 805 |
|
| 806 |
// merge user config |
| 807 |
var defaults = utils.merge(config); |
| 808 |
|
| 809 |
var editor = defaults.editor; |
| 810 |
|
| 811 |
if (!editor || editor.nodeType !== 1) throw new Error('Can\'t find editor'); |
| 812 |
|
| 813 |
// set default class |
| 814 |
editor.classList.add.apply(editor.classList, defaults.class.split(' ')); |
| 815 |
|
| 816 |
// set contenteditable |
| 817 |
editor.setAttribute('contenteditable', 'true'); |
| 818 |
|
| 819 |
// assign config |
| 820 |
this.config = defaults; |
| 821 |
|
| 822 |
// set placeholder |
| 823 |
if (defaults.placeholder) editor.setAttribute(this.config.placeholderAttr, defaults.placeholder); |
| 824 |
checkPlaceholder(this); |
| 825 |
|
| 826 |
// save the selection obj |
| 827 |
this.selection = selection; |
| 828 |
|
| 829 |
// define local events |
| 830 |
this._events = {change: []}; |
| 831 |
|
| 832 |
// enable toolbar |
| 833 |
initToolbar(this); |
| 834 |
|
| 835 |
// init events |
| 836 |
initEvents(this); |
| 837 |
|
| 838 |
// to check content change |
| 839 |
this._prevContent = this.getContent(); |
| 840 |
|
| 841 |
// enable markdown covert |
| 842 |
if (this.markdown) this.markdown.init(this); |
| 843 |
|
| 844 |
// stay on the page |
| 845 |
if (this.config.stay) this.stay(this.config); |
| 846 |
|
| 847 |
if(this.config.input) { |
| 848 |
this.addOnSubmitListener(this.config.input); |
| 849 |
} |
| 850 |
|
| 851 |
if (this.config.mode === 'advanced') { |
| 852 |
this.getRange().selectNodeContents(editor); |
| 853 |
|
| 854 |
this.setRange(); |
| 855 |
} else { |
| 856 |
addEmptyCharAtEnd(this); |
| 857 |
} |
| 858 |
}; |
| 859 |
|
| 860 |
InlineEditor.prototype.on = function(type, listener) { |
| 861 |
addListener(this, this.config.editor, type, listener); |
| 862 |
return this; |
| 863 |
}; |
| 864 |
|
| 865 |
InlineEditor.prototype.addOnSubmitListener = function(inputElement) { |
| 866 |
var form = inputElement.form; |
| 867 |
var me = this; |
| 868 |
form.addEventListener("submit", function() { |
| 869 |
inputElement.value = me.config.saveAsMarkdown ? me.toMd(me.config.editor.innerHTML) : me.config.editor.innerHTML; |
| 870 |
}); |
| 871 |
}; |
| 872 |
|
| 873 |
InlineEditor.prototype.isEmpty = function(node) { |
| 874 |
node = node || this.config.editor; |
| 875 |
return !(node.querySelector('img')) && !(node.querySelector('blockquote')) && |
| 876 |
!(node.querySelector('li')) && !trim(node.textContent); |
| 877 |
}; |
| 878 |
|
| 879 |
InlineEditor.prototype.getContent = function() { |
| 880 |
return this.isEmpty() ? '' : trim(this.config.editor.innerHTML); |
| 881 |
}; |
| 882 |
|
| 883 |
InlineEditor.prototype.setContent = function(html) { |
| 884 |
this.config.editor.innerHTML = html; |
| 885 |
this.cleanContent(); |
| 886 |
return this; |
| 887 |
}; |
| 888 |
|
| 889 |
InlineEditor.prototype.checkContentChange = function () { |
| 890 |
var prevContent = this._prevContent, currentContent = this.getContent(); |
| 891 |
if (prevContent === currentContent) return; |
| 892 |
this._prevContent = currentContent; |
| 893 |
triggerListener(this, 'change', currentContent, prevContent); |
| 894 |
}; |
| 895 |
|
| 896 |
InlineEditor.prototype.getRange = function() { |
| 897 |
var editor = this.config.editor, range = selection.rangeCount && selection.getRangeAt(0); |
| 898 |
if (!range) range = doc.createRange(); |
| 899 |
if (!containsNode(editor, range.commonAncestorContainer)) { |
| 900 |
range.selectNodeContents(editor); |
| 901 |
range.collapse(false); |
| 902 |
} |
| 903 |
return range; |
| 904 |
}; |
| 905 |
|
| 906 |
InlineEditor.prototype.setRange = function(range) { |
| 907 |
range = range || this._range; |
| 908 |
|
| 909 |
if (!range) { |
| 910 |
range = this.getRange(); |
| 911 |
range.collapse(false); // set to end |
| 912 |
} |
| 913 |
try { |
| 914 |
selection.removeAllRanges(); |
| 915 |
selection.addRange(range); |
| 916 |
} catch (e) {/* IE throws error sometimes*/} |
| 917 |
return this; |
| 918 |
}; |
| 919 |
|
| 920 |
InlineEditor.prototype.focus = function(focusStart) { |
| 921 |
if (!focusStart) this.setRange(); |
| 922 |
this.config.editor.focus(); |
| 923 |
return this; |
| 924 |
}; |
| 925 |
|
| 926 |
InlineEditor.prototype.execCommand = function(name, value) { |
| 927 |
name = name.toLowerCase(); |
| 928 |
this.setRange(); |
| 929 |
|
| 930 |
if (commandsReg.block.test(name)) { |
| 931 |
commandBlock(this, name); |
| 932 |
} else if (commandsReg.inline.test(name)) { |
| 933 |
commandOverall(name, value); |
| 934 |
} else if (commandsReg.biu.test(name)) { |
| 935 |
// Temporarily removing all override style rules |
| 936 |
// to make sure the command will be executed correctly |
| 937 |
var styleBackup = styleBackupDict[ name ]; |
| 938 |
|
| 939 |
styleBackup.backupValue = this.config.editor.style[ styleBackup.styleKey ]; |
| 940 |
|
| 941 |
this.config.editor.style[ styleBackup.styleKey ] = styleBackup.correctValue; |
| 942 |
|
| 943 |
commandOverall(name, value); |
| 944 |
|
| 945 |
this.config.editor.style[ styleBackup.styleKey ] = styleBackup.backupValue; |
| 946 |
} else if (commandsReg.source.test(name)) { |
| 947 |
commandLink(this, name, value); |
| 948 |
} else if (commandsReg.insert.test(name)) { |
| 949 |
commandInsert(this, name, value); |
| 950 |
} else if (commandsReg.wrap.test(name)) { |
| 951 |
commandWrap(this, name, value); |
| 952 |
} else { |
| 953 |
utils.log('can not find command function for name: ' + name + (value ? (', value: ' + value) : ''), true); |
| 954 |
} |
| 955 |
|
| 956 |
if (name === 'indent') this.checkContentChange(); |
| 957 |
}; |
| 958 |
|
| 959 |
// remove attrs and tags |
| 960 |
// pen.cleanContent({cleanAttrs: ['style'], cleanTags: ['id']}) |
| 961 |
InlineEditor.prototype.cleanContent = function(options) { |
| 962 |
var editor = this.config.editor; |
| 963 |
|
| 964 |
if (!options) options = this.config; |
| 965 |
utils.forEach(options.cleanAttrs, function (attr) { |
| 966 |
utils.forEach(editor.querySelectorAll('[' + attr + ']'), function(item) { |
| 967 |
item.removeAttribute(attr); |
| 968 |
}, true); |
| 969 |
}, true); |
| 970 |
utils.forEach(options.cleanTags, function (tag) { |
| 971 |
utils.forEach(editor.querySelectorAll(tag), function(item) { |
| 972 |
item.parentNode.removeChild(item); |
| 973 |
}, true); |
| 974 |
}, true); |
| 975 |
|
| 976 |
checkPlaceholder(this); |
| 977 |
this.checkContentChange(); |
| 978 |
return this; |
| 979 |
}; |
| 980 |
|
| 981 |
// auto link content, return content |
| 982 |
InlineEditor.prototype.autoLink = function() { |
| 983 |
autoLink(this.config.editor); |
| 984 |
return this.getContent(); |
| 985 |
}; |
| 986 |
|
| 987 |
// highlight menu |
| 988 |
InlineEditor.prototype.highlight = function() { |
| 989 |
var toolbar = this._toolbar || this._menu, |
| 990 |
node = getNode(this); |
| 991 |
|
| 992 |
// remove all highlights |
| 993 |
utils.forEach(toolbar.querySelectorAll('.active'), function(el) { |
| 994 |
el.classList.remove('active'); |
| 995 |
}, true); |
| 996 |
|
| 997 |
if (!node) return this; |
| 998 |
|
| 999 |
var nodeParents = getNodeParents(this), |
| 1000 |
urlInput = this._urlInput, |
| 1001 |
externalUrlCheckbox = this._externalUrlCheckbox, |
| 1002 |
highlight; |
| 1003 |
|
| 1004 |
if (urlInput && toolbar === this._menu) { |
| 1005 |
// reset url inputs |
| 1006 |
urlInput.value = ''; |
| 1007 |
|
| 1008 |
this._externalUrlCheckbox.checked = false; |
| 1009 |
} |
| 1010 |
|
| 1011 |
highlight = function(str) { |
| 1012 |
if (!str) return; |
| 1013 |
var el = toolbar.querySelector('[data-action=' + str + ']'); |
| 1014 |
return el && el.classList.add('active'); |
| 1015 |
}; |
| 1016 |
|
| 1017 |
utils.forEach(nodeParents, function(item) { |
| 1018 |
var tag = item.nodeName.toLowerCase(), |
| 1019 |
align = item.style.textAlign, |
| 1020 |
textDecoration = item.style.textDecoration; |
| 1021 |
|
| 1022 |
if (align) { |
| 1023 |
if ('justify' === align) { |
| 1024 |
align = 'full'; |
| 1025 |
} |
| 1026 |
|
| 1027 |
highlight('justify' + align[0].toUpperCase() + align.slice(1)); |
| 1028 |
} |
| 1029 |
|
| 1030 |
if ('underline' === textDecoration) { |
| 1031 |
highlight('underline'); |
| 1032 |
} |
| 1033 |
|
| 1034 |
if (! tag.match(effectNodeReg)) { |
| 1035 |
return; |
| 1036 |
} |
| 1037 |
|
| 1038 |
switch(tag) { |
| 1039 |
case 'a': |
| 1040 |
urlInput.value = item.getAttribute('href'); |
| 1041 |
|
| 1042 |
externalUrlCheckbox.checked = item.getAttribute('target') === '_blank'; |
| 1043 |
|
| 1044 |
tag = 'createlink'; |
| 1045 |
|
| 1046 |
break; |
| 1047 |
case 'img': |
| 1048 |
urlInput.value = item.getAttribute('src'); |
| 1049 |
|
| 1050 |
tag = 'insertimage'; |
| 1051 |
|
| 1052 |
break; |
| 1053 |
case 'i': |
| 1054 |
case 'em': |
| 1055 |
tag = 'italic'; |
| 1056 |
|
| 1057 |
break; |
| 1058 |
case 'u': |
| 1059 |
tag = 'underline'; |
| 1060 |
|
| 1061 |
break; |
| 1062 |
case 'b': |
| 1063 |
case 'strong': |
| 1064 |
tag = 'bold'; |
| 1065 |
|
| 1066 |
break; |
| 1067 |
case 'strike': |
| 1068 |
tag = 'strikethrough'; |
| 1069 |
|
| 1070 |
break; |
| 1071 |
case 'ul': |
| 1072 |
tag = 'insertUnorderedList'; |
| 1073 |
break; |
| 1074 |
|
| 1075 |
case 'ol': |
| 1076 |
tag = 'insertOrderedList'; |
| 1077 |
|
| 1078 |
break; |
| 1079 |
case 'li': |
| 1080 |
tag = 'indent'; |
| 1081 |
|
| 1082 |
break; |
| 1083 |
} |
| 1084 |
|
| 1085 |
highlight(tag); |
| 1086 |
}, true); |
| 1087 |
|
| 1088 |
return this; |
| 1089 |
}; |
| 1090 |
|
| 1091 |
// show menu |
| 1092 |
InlineEditor.prototype.menu = function() { |
| 1093 |
if (!this._menu) return this; |
| 1094 |
|
| 1095 |
if (selection.isCollapsed) { |
| 1096 |
this._menu.style.display = 'none'; //hide menu |
| 1097 |
this._inputActive = false; |
| 1098 |
return this; |
| 1099 |
} |
| 1100 |
|
| 1101 |
if (this._toolbar) { |
| 1102 |
if (!this._urlInput || !this._inputActive) return this; |
| 1103 |
} |
| 1104 |
|
| 1105 |
showMainMenu(this); |
| 1106 |
}; |
| 1107 |
|
| 1108 |
InlineEditor.prototype.refreshMenuPosition = function() { |
| 1109 |
var offset = this._range.getBoundingClientRect() |
| 1110 |
, menuPadding = 10 |
| 1111 |
, top = offset.top - menuPadding |
| 1112 |
, left = offset.left + (offset.width / 2) |
| 1113 |
, menu = this._menu |
| 1114 |
, menuOffset = {x: 0, y: 0} |
| 1115 |
, stylesheet = this._stylesheet; |
| 1116 |
|
| 1117 |
// fixes some browser double click visual discontinuity |
| 1118 |
// if the offset has no width or height it should not be used |
| 1119 |
if (offset.width === 0 && offset.height === 0) return this; |
| 1120 |
|
| 1121 |
// store the stylesheet used for positioning the menu horizontally |
| 1122 |
if (this._stylesheet === undefined) { |
| 1123 |
var style = document.createElement("style"); |
| 1124 |
document.head.appendChild(style); |
| 1125 |
this._stylesheet = stylesheet = style.sheet; |
| 1126 |
} |
| 1127 |
// display it to caculate its width & height |
| 1128 |
menu.style.display = 'flex'; |
| 1129 |
|
| 1130 |
menuOffset.x = left - (menu.clientWidth / 2); |
| 1131 |
menuOffset.y = top - menu.clientHeight; |
| 1132 |
|
| 1133 |
// check to see if menu has over-extended its bounding box. if it has, |
| 1134 |
// 1) apply a new class if overflowed on top; |
| 1135 |
// 2) apply a new rule if overflowed on the left |
| 1136 |
if (stylesheet.cssRules.length > 0) { |
| 1137 |
stylesheet.deleteRule(0); |
| 1138 |
} |
| 1139 |
if (menuOffset.x < 0) { |
| 1140 |
menuOffset.x = 0; |
| 1141 |
stylesheet.insertRule('.pen-menu:after {left: ' + left + 'px;}', 0); |
| 1142 |
} else { |
| 1143 |
stylesheet.insertRule('.pen-menu:after {left: 50%; }', 0); |
| 1144 |
} |
| 1145 |
if (menuOffset.y < 0) { |
| 1146 |
menu.classList.add('pen-menu-below'); |
| 1147 |
menuOffset.y = offset.top + offset.height + menuPadding; |
| 1148 |
} else { |
| 1149 |
menu.classList.remove('pen-menu-below'); |
| 1150 |
} |
| 1151 |
|
| 1152 |
menu.style.top = menuOffset.y + 'px'; |
| 1153 |
menu.style.left = menuOffset.x + 'px'; |
| 1154 |
|
| 1155 |
return this; |
| 1156 |
}; |
| 1157 |
|
| 1158 |
InlineEditor.prototype.stay = function(config) { |
| 1159 |
var ctx = this; |
| 1160 |
if (!window.onbeforeunload) { |
| 1161 |
window.onbeforeunload = function() { |
| 1162 |
if (!ctx._isDestroyed) return config.stayMsg; |
| 1163 |
}; |
| 1164 |
} |
| 1165 |
}; |
| 1166 |
|
| 1167 |
InlineEditor.prototype.destroy = function() { |
| 1168 |
var config = this.config; |
| 1169 |
|
| 1170 |
removeAllListeners(this); |
| 1171 |
|
| 1172 |
config.editor.classList.remove.apply(config.editor.classList, config.class.split(' ').concat(config.placeholderClass)); |
| 1173 |
|
| 1174 |
config.editor.removeAttribute('contenteditable'); |
| 1175 |
|
| 1176 |
config.editor.removeAttribute(config.placeholderAttr); |
| 1177 |
|
| 1178 |
try { |
| 1179 |
selection.removeAllRanges(); |
| 1180 |
if (this._menu) this._menu.parentNode.removeChild(this._menu); |
| 1181 |
} catch (e) {/* IE throws error sometimes*/} |
| 1182 |
|
| 1183 |
this._isDestroyed = true; |
| 1184 |
|
| 1185 |
return this; |
| 1186 |
}; |
| 1187 |
|
| 1188 |
InlineEditor.prototype.rebuild = function() { |
| 1189 |
initToolbar(this); |
| 1190 |
|
| 1191 |
initEvents(this); |
| 1192 |
|
| 1193 |
return this; |
| 1194 |
}; |
| 1195 |
|
| 1196 |
// a fallback for old browers |
| 1197 |
root.ElementorInlineEditor = function(config) { |
| 1198 |
if (!config) return utils.log('can\'t find config', true); |
| 1199 |
|
| 1200 |
var defaults = utils.merge(config) |
| 1201 |
, klass = defaults.editor.getAttribute('class'); |
| 1202 |
|
| 1203 |
klass = klass ? klass.replace(/\bpen\b/g, '') + ' pen-textarea ' + defaults.class : 'pen pen-textarea'; |
| 1204 |
defaults.editor.setAttribute('class', klass); |
| 1205 |
defaults.editor.innerHTML = defaults.textarea; |
| 1206 |
return defaults.editor; |
| 1207 |
}; |
| 1208 |
|
| 1209 |
// export content as markdown |
| 1210 |
var regs = { |
| 1211 |
a: [/<a\b[^>]*href=["']([^"]+|[^']+)\b[^>]*>(.*?)<\/a>/ig, '[$2]($1)'], |
| 1212 |
img: [/<img\b[^>]*src=["']([^\"+|[^']+)[^>]*>/ig, ''], |
| 1213 |
b: [/<b\b[^>]*>(.*?)<\/b>/ig, '**$1**'], |
| 1214 |
i: [/<i\b[^>]*>(.*?)<\/i>/ig, '***$1***'], |
| 1215 |
h: [/<h([1-6])\b[^>]*>(.*?)<\/h\1>/ig, function(a, b, c) { |
| 1216 |
return '\n' + ('######'.slice(0, b)) + ' ' + c + '\n'; |
| 1217 |
}], |
| 1218 |
li: [/<(li)\b[^>]*>(.*?)<\/\1>/ig, '* $2\n'], |
| 1219 |
blockquote: [/<(blockquote)\b[^>]*>(.*?)<\/\1>/ig, '\n> $2\n'], |
| 1220 |
pre: [/<pre\b[^>]*>(.*?)<\/pre>/ig, '\n```\n$1\n```\n'], |
| 1221 |
code: [/<code\b[^>]*>(.*?)<\/code>/ig, '\n`\n$1\n`\n'], |
| 1222 |
p: [/<p\b[^>]*>(.*?)<\/p>/ig, '\n$1\n'], |
| 1223 |
hr: [/<hr\b[^>]*>/ig, '\n---\n'] |
| 1224 |
}; |
| 1225 |
|
| 1226 |
InlineEditor.prototype.toMd = function() { |
| 1227 |
var html = this.getContent() |
| 1228 |
.replace(/\n+/g, '') // remove line break |
| 1229 |
.replace(/<([uo])l\b[^>]*>(.*?)<\/\1l>/ig, '$2'); // remove ul/ol |
| 1230 |
|
| 1231 |
for(var p in regs) { |
| 1232 |
if (regs.hasOwnProperty(p)) |
| 1233 |
html = html.replace.apply(html, regs[p]); |
| 1234 |
} |
| 1235 |
return html.replace(/\*{5}/g, '**'); |
| 1236 |
}; |
| 1237 |
|
| 1238 |
// make it accessible |
| 1239 |
if (doc.getSelection) { |
| 1240 |
selection = doc.getSelection(); |
| 1241 |
root.ElementorInlineEditor = InlineEditor; |
| 1242 |
} |
| 1243 |
|
| 1244 |
}(window, document)); |
| 1245 |
|