Created
July 24, 2014 21:59
-
-
Save swang/74c8de3a6c858f4fe5bb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function MediumEditor(elements, options) { | |
'use strict'; | |
return this.init(elements, options); | |
} | |
if (typeof module === 'object') { | |
module.exports = MediumEditor; | |
} | |
(function (window, document) { | |
'use strict'; | |
function extend(b, a) { | |
var prop; | |
if (b === undefined) { | |
return a; | |
} | |
for (prop in a) { | |
if (a.hasOwnProperty(prop) && b.hasOwnProperty(prop) === false) { | |
b[prop] = a[prop]; | |
} | |
} | |
return b; | |
} | |
// http://stackoverflow.com/questions/5605401/insert-link-in-contenteditable-element | |
// by Tim Down | |
function saveSelection() { | |
var i, | |
len, | |
ranges, | |
sel = window.getSelection(); | |
console.log("saveSelection") | |
if (sel.getRangeAt && sel.rangeCount) { | |
ranges = []; | |
for (i = 0, len = sel.rangeCount; i < len; i += 1) { | |
ranges.push(sel.getRangeAt(i)); | |
} | |
return ranges; | |
} | |
return null; | |
} | |
function restoreSelection(savedSel) { | |
var i, | |
len, | |
sel = window.getSelection(); | |
console.log("restoreSelection") | |
if (savedSel) { | |
sel.removeAllRanges(); | |
for (i = 0, len = savedSel.length; i < len; i += 1) { | |
sel.addRange(savedSel[i]); | |
} | |
} | |
} | |
// http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi | |
// by You | |
function getSelectionStart(docEle) { | |
// var node = document.getSelection().anchorNode, | |
// var node = document.activeElement.ownerDocument.getSelection().anchorNode, | |
var node = docEle.getSelection().anchorNode, | |
startNode = (node && node.nodeType === 3 ? node.parentNode : node); | |
console.log("getSelectionStart", docEle, node, startNode, document.getSelection().anchorNode) | |
return startNode; | |
} | |
// http://stackoverflow.com/questions/4176923/html-of-selected-text | |
// by Tim Down | |
function getSelectionHtml() { | |
var i, | |
html = '', | |
sel, | |
len, | |
container, | |
realWin = document.activeElement.contentWindow, | |
realDoc = document.activeElement.ownerDocument | |
if (realWin.getSelection !== undefined) { | |
sel = realWin.getSelection(); | |
if (sel.rangeCount) { | |
container = realDoc.createElement('div'); | |
for (i = 0, len = sel.rangeCount; i < len; i += 1) { | |
container.appendChild(sel.getRangeAt(i).cloneContents()); | |
} | |
html = container.innerHTML; | |
} | |
} else if (realDoc.selection !== undefined) { | |
if (realDoc.selection.type === 'Text') { | |
html = realDoc.selection.createRange().htmlText; | |
} | |
} | |
return html; | |
} | |
// https://github.com/jashkenas/underscore | |
function isElement(obj) { | |
return !!(obj && obj.nodeType === 1); | |
} | |
MediumEditor.prototype = { | |
defaults: { | |
allowMultiParagraphSelection: true, | |
anchorInputPlaceholder: 'Paste or type a link', | |
anchorPreviewHideDelay: 500, | |
buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote'], | |
buttonLabels: false, | |
checkLinkFormat: false, | |
cleanPastedHTML: false, | |
delay: 0, | |
diffLeft: 0, | |
diffTop: -10, | |
disableReturn: false, | |
disableDoubleReturn: false, | |
disableToolbar: false, | |
disableEditing: false, | |
elementsContainer: false, | |
firstHeader: 'h3', | |
forcePlainText: true, | |
placeholder: 'Type your text', | |
secondHeader: 'h4', | |
targetBlank: false, | |
extensions: {}, | |
activeButtonClass: 'medium-editor-button-active', | |
firstButtonClass: 'medium-editor-button-first', | |
lastButtonClass: 'medium-editor-button-last' | |
}, | |
// http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562 | |
// by rg89 | |
isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))), | |
init: function (elements, options) { | |
console.log(elements, options) | |
this.setElementSelection(elements); | |
console.log("this.elements", this.elements, this.elements.length) | |
if (this.elements.length === 0) { | |
return; | |
} | |
this.parentElements = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre']; | |
this.id = (options.elementsContainer ? options.elementsContainer.ownerDocument : document).querySelectorAll('.medium-editor-toolbar').length + 1; | |
this.options = extend(options, this.defaults); | |
return this.setup(); | |
}, | |
setup: function () { | |
this.isActive = true; | |
this.initElements() | |
.bindSelect() | |
.bindPaste() | |
.setPlaceholders() | |
.bindWindowActions(); | |
}, | |
initElements: function () { | |
this.updateElementList(); | |
var i, | |
addToolbar = false; | |
for (i = 0; i < this.elements.length; i += 1) { | |
if (!this.options.disableEditing && !this.elements[i].getAttribute('data-disable-editing')) { | |
this.elements[i].setAttribute('contentEditable', true); | |
} | |
if (!this.elements[i].getAttribute('data-placeholder')) { | |
this.elements[i].setAttribute('data-placeholder', this.options.placeholder); | |
} | |
this.elements[i].setAttribute('data-medium-element', true); | |
this.bindParagraphCreation(i).bindReturn(i).bindTab(i); | |
if (!this.options.disableToolbar && !this.elements[i].getAttribute('data-disable-toolbar')) { | |
addToolbar = true; | |
} | |
} | |
// Init toolbar | |
if (addToolbar) { | |
if (!this.options.elementsContainer) { | |
this.options.elementsContainer = document.body; | |
} | |
this.initToolbar() | |
.bindButtons() | |
.bindAnchorForm() | |
.bindAnchorPreview(); | |
} | |
return this; | |
}, | |
setElementSelection: function (selector) { | |
this.elementSelection = selector; | |
console.log("selector" ,selector) | |
this.updateElementList(); | |
}, | |
updateElementList: function () { | |
// this.elements = typeof this.elementSelection === 'string' ? (document.activeElement && document.activeElement.contentWindow.document || document).querySelectorAll(this.elementSelection) : this.elementSelection; | |
this.elements = typeof this.elementSelection === 'string' ? (document.querySelectorAll(this.elementSelection)) : this.elementSelection; | |
console.log(this.elements) | |
if (this.elements.nodeType === 1) { | |
this.elements = [this.elements]; | |
} | |
}, | |
serialize: function () { | |
var i, | |
elementid, | |
content = {}; | |
for (i = 0; i < this.elements.length; i += 1) { | |
elementid = (this.elements[i].id !== '') ? this.elements[i].id : 'element-' + i; | |
content[elementid] = { | |
value: this.elements[i].innerHTML.trim() | |
}; | |
} | |
return content; | |
}, | |
/** | |
* Helper function to call a method with a number of parameters on all registered extensions. | |
* The function assures that the function exists before calling. | |
* | |
* @param {string} funcName name of the function to call | |
* @param [args] arguments passed into funcName | |
*/ | |
callExtensions: function (funcName) { | |
if (arguments.length < 1) { | |
return; | |
} | |
var args = Array.prototype.slice.call(arguments, 1), | |
ext, | |
name; | |
for (name in this.options.extensions) { | |
if (this.options.extensions.hasOwnProperty(name)) { | |
ext = this.options.extensions[name]; | |
if (ext[funcName] !== undefined) { | |
ext[funcName].apply(ext, args); | |
} | |
} | |
} | |
}, | |
bindParagraphCreation: function (index) { | |
var self = this; | |
var realDocu = this.options.elementsContainer.ownerDocument; | |
this.elements[index].addEventListener('keypress', function (e) { | |
var node = getSelectionStart(realDocu), | |
tagName; | |
if (e.which === 32) { | |
tagName = node.tagName.toLowerCase(); | |
if (tagName === 'a') { | |
realDocu.execCommand('unlink', false, null); | |
} | |
} | |
}); | |
this.elements[index].addEventListener('keyup', function (e) { | |
var node = getSelectionStart(realDocu), | |
tagName; | |
if (node && node.getAttribute('data-medium-element') && node.children.length === 0 && !(self.options.disableReturn || node.getAttribute('data-disable-return'))) { | |
realDocu.execCommand('formatBlock', false, 'p'); | |
} | |
if (e.which === 13) { | |
node = getSelectionStart(self.options.elementsContainer.ownerDocument); | |
tagName = node.tagName.toLowerCase(); | |
if (!(self.options.disableReturn || this.getAttribute('data-disable-return')) && | |
tagName !== 'li' && !self.isListItemChild(node)) { | |
if (!e.shiftKey) { | |
realDocu.execCommand('formatBlock', false, 'p'); | |
} | |
if (tagName === 'a') { | |
realDocu.execCommand('unlink', false, null); | |
} | |
} | |
} | |
}); | |
return this; | |
}, | |
isListItemChild: function (node) { | |
var parentNode = node.parentNode, | |
tagName = parentNode.tagName.toLowerCase(); | |
while (this.parentElements.indexOf(tagName) === -1 && tagName !== 'div') { | |
if (tagName === 'li') { | |
return true; | |
} | |
parentNode = parentNode.parentNode; | |
if (parentNode && parentNode.tagName) { | |
tagName = parentNode.tagName.toLowerCase(); | |
} else { | |
return false; | |
} | |
} | |
return false; | |
}, | |
bindReturn: function (index) { | |
var self = this; | |
this.elements[index].addEventListener('keypress', function (e) { | |
if (e.which === 13) { | |
if (self.options.disableReturn || this.getAttribute('data-disable-return')) { | |
e.preventDefault(); | |
} else if (self.options.disableDoubleReturn || this.getAttribute('data-disable-double-return')) { | |
var node = getSelectionStart(self.options.elementsContainer.ownerDocument); | |
if (node && node.innerText === '\n') { | |
e.preventDefault(); | |
} | |
} | |
} | |
}); | |
return this; | |
}, | |
bindTab: function (index) { | |
var self = this; | |
var realDocu = this.options.elementsContainer.ownerDocument; | |
this.elements[index].addEventListener('keydown', function (e) { | |
if (e.which === 9) { | |
// Override tab only for pre nodes | |
var tag = getSelectionStart(realDocu).tagName.toLowerCase(); | |
if (tag === 'pre') { | |
e.preventDefault(); | |
realDocu.execCommand('insertHtml', null, ' '); | |
} | |
} | |
}); | |
return this; | |
}, | |
buttonTemplate: function (btnType) { | |
var buttonLabels = this.getButtonLabels(this.options.buttonLabels), | |
buttonTemplates = { | |
'bold': '<button class="medium-editor-action medium-editor-action-bold" data-action="bold" data-element="b">' + buttonLabels.bold + '</button>', | |
'italic': '<button class="medium-editor-action medium-editor-action-italic" data-action="italic" data-element="i">' + buttonLabels.italic + '</button>', | |
'underline': '<button class="medium-editor-action medium-editor-action-underline" data-action="underline" data-element="u">' + buttonLabels.underline + '</button>', | |
'strikethrough': '<button class="medium-editor-action medium-editor-action-strikethrough" data-action="strikethrough" data-element="strike"><strike>A</strike></button>', | |
'superscript': '<button class="medium-editor-action medium-editor-action-superscript" data-action="superscript" data-element="sup">' + buttonLabels.superscript + '</button>', | |
'subscript': '<button class="medium-editor-action medium-editor-action-subscript" data-action="subscript" data-element="sub">' + buttonLabels.subscript + '</button>', | |
'anchor': '<button class="medium-editor-action medium-editor-action-anchor" data-action="anchor" data-element="a">' + buttonLabels.anchor + '</button>', | |
'image': '<button class="medium-editor-action medium-editor-action-image" data-action="image" data-element="img">' + buttonLabels.image + '</button>', | |
'header1': '<button class="medium-editor-action medium-editor-action-header1" data-action="append-' + this.options.firstHeader + '" data-element="' + this.options.firstHeader + '">' + buttonLabels.header1 + '</button>', | |
'header2': '<button class="medium-editor-action medium-editor-action-header2" data-action="append-' + this.options.secondHeader + '" data-element="' + this.options.secondHeader + '">' + buttonLabels.header2 + '</button>', | |
'quote': '<button class="medium-editor-action medium-editor-action-quote" data-action="append-blockquote" data-element="blockquote">' + buttonLabels.quote + '</button>', | |
'orderedlist': '<button class="medium-editor-action medium-editor-action-orderedlist" data-action="insertorderedlist" data-element="ol">' + buttonLabels.orderedlist + '</button>', | |
'unorderedlist': '<button class="medium-editor-action medium-editor-action-unorderedlist" data-action="insertunorderedlist" data-element="ul">' + buttonLabels.unorderedlist + '</button>', | |
'pre': '<button class="medium-editor-action medium-editor-action-pre" data-action="append-pre" data-element="pre">' + buttonLabels.pre + '</button>', | |
'indent': '<button class="medium-editor-action medium-editor-action-indent" data-action="indent" data-element="ul">' + buttonLabels.indent + '</button>', | |
'outdent': '<button class="medium-editor-action medium-editor-action-outdent" data-action="outdent" data-element="ul">' + buttonLabels.outdent + '</button>' | |
}; | |
return buttonTemplates[btnType] || false; | |
}, | |
// TODO: break method | |
getButtonLabels: function (buttonLabelType) { | |
var customButtonLabels, | |
attrname, | |
buttonLabels = { | |
'bold': '<b>B</b>', | |
'italic': '<b><i>I</i></b>', | |
'underline': '<b><u>U</u></b>', | |
'superscript': '<b>x<sup>1</sup></b>', | |
'subscript': '<b>x<sub>1</sub></b>', | |
'anchor': '<b>#</b>', | |
'image': '<b>image</b>', | |
'header1': '<b>H1</b>', | |
'header2': '<b>H2</b>', | |
'quote': '<b>“</b>', | |
'orderedlist': '<b>1.</b>', | |
'unorderedlist': '<b>•</b>', | |
'pre': '<b>0101</b>', | |
'indent': '<b>→</b>', | |
'outdent': '<b>←</b>' | |
}; | |
if (buttonLabelType === 'fontawesome') { | |
customButtonLabels = { | |
'bold': '<i class="fa fa-bold"></i>', | |
'italic': '<i class="fa fa-italic"></i>', | |
'underline': '<i class="fa fa-underline"></i>', | |
'superscript': '<i class="fa fa-superscript"></i>', | |
'subscript': '<i class="fa fa-subscript"></i>', | |
'anchor': '<i class="fa fa-link"></i>', | |
'image': '<i class="fa fa-picture-o"></i>', | |
'quote': '<i class="fa fa-quote-right"></i>', | |
'orderedlist': '<i class="fa fa-list-ol"></i>', | |
'unorderedlist': '<i class="fa fa-list-ul"></i>', | |
'pre': '<i class="fa fa-code fa-lg"></i>', | |
'indent': '<i class="fa fa-indent"></i>', | |
'outdent': '<i class="fa fa-outdent"></i>' | |
}; | |
} else if (typeof buttonLabelType === 'object') { | |
customButtonLabels = buttonLabelType; | |
} | |
if (typeof customButtonLabels === 'object') { | |
for (attrname in customButtonLabels) { | |
if (customButtonLabels.hasOwnProperty(attrname)) { | |
buttonLabels[attrname] = customButtonLabels[attrname]; | |
} | |
} | |
} | |
return buttonLabels; | |
}, | |
initToolbar: function () { | |
if (this.toolbar) { | |
return this; | |
} | |
this.toolbar = this.createToolbar(); | |
this.keepToolbarAlive = false; | |
this.anchorForm = this.toolbar.querySelector('.medium-editor-toolbar-form-anchor'); | |
this.anchorInput = this.anchorForm.querySelector('input'); | |
this.toolbarActions = this.toolbar.querySelector('.medium-editor-toolbar-actions'); | |
this.anchorPreview = this.createAnchorPreview(); | |
console.log("initToolbar", this) | |
return this; | |
}, | |
createToolbar: function () { | |
console.log("createToolbar: this.options: ", this.options) | |
var toolbar = this.options.elementsContainer.ownerDocument.createElement('div'); | |
toolbar.id = 'medium-editor-toolbar-' + this.id; | |
toolbar.className = 'medium-editor-toolbar'; | |
toolbar.appendChild(this.toolbarButtons()); | |
toolbar.appendChild(this.toolbarFormAnchor()); | |
console.log(this.options.elementsContainer); | |
this.options.elementsContainer.appendChild(toolbar); | |
return toolbar; | |
}, | |
//TODO: actionTemplate | |
toolbarButtons: function () { | |
var btns = this.options.buttons, | |
ul = document.createElement('ul'), | |
li, | |
i, | |
btn, | |
ext; | |
ul.id = 'medium-editor-toolbar-actions'; | |
ul.className = 'medium-editor-toolbar-actions clearfix'; | |
for (i = 0; i < btns.length; i += 1) { | |
if (this.options.extensions.hasOwnProperty(btns[i])) { | |
ext = this.options.extensions[btns[i]]; | |
btn = ext.getButton !== undefined ? ext.getButton() : null; | |
} else { | |
btn = this.buttonTemplate(btns[i]); | |
} | |
if (btn) { | |
li = document.createElement('li'); | |
if (isElement(btn)) { | |
li.appendChild(btn); | |
} else { | |
li.innerHTML = btn; | |
} | |
ul.appendChild(li); | |
} | |
} | |
return ul; | |
}, | |
toolbarFormAnchor: function () { | |
var anchor = document.createElement('div'), | |
input = document.createElement('input'), | |
a = document.createElement('a'); | |
a.setAttribute('href', '#'); | |
a.innerHTML = '×'; | |
input.setAttribute('type', 'text'); | |
input.setAttribute('placeholder', this.options.anchorInputPlaceholder); | |
anchor.className = 'medium-editor-toolbar-form-anchor'; | |
anchor.id = 'medium-editor-toolbar-form-anchor'; | |
anchor.appendChild(input); | |
anchor.appendChild(a); | |
return anchor; | |
}, | |
bindSelect: function () { | |
var self = this, | |
timer = '', | |
i; | |
this.checkSelectionWrapper = function (e) { | |
// Do not close the toolbar when bluring the editable area and clicking into the anchor form | |
if (e && self.clickingIntoArchorForm(e)) { | |
return false; | |
} | |
clearTimeout(timer); | |
timer = setTimeout(function () { | |
console.log("checkSelectionTimer") | |
self.checkSelection(); | |
}, self.options.delay); | |
}; | |
// console.log("t.o.eC ", this.options.elementsContainer.ownerDocument); | |
(this.options.elementsContainer.ownerDocument.documentElement || document.documentElement).addEventListener('mouseup', this.checkSelectionWrapper); | |
// function getDocuEle() { | |
// var docuEle; | |
// if (document.activeElement) { | |
// console.log("doc.activeEle.ownerDoc.docEle") | |
// docuEle = document.activeElement.ownerDocument.documentElement | |
// } | |
// else { | |
// console.log("doc.docEle") | |
// docuEle = document.documentElement; | |
// } | |
// return docuEle | |
// } | |
// for (var i = 0; i < window.frames.length; i++) { | |
// if (window.frames[i]getDocuEle().addEventListener('mouseup', this.checkSelectionWrapper); | |
// } | |
for (i = 0; i < this.elements.length; i += 1) { | |
this.elements[i].addEventListener('keyup', this.checkSelectionWrapper); | |
this.elements[i].addEventListener('blur', this.checkSelectionWrapper); | |
} | |
return this; | |
}, | |
checkSelection: function () { | |
var newSelection, | |
selectionElement; | |
console.log("checkSelection") | |
if (this.keepToolbarAlive !== true && !this.options.disableToolbar) { | |
// newSelection = window.getSelection(); | |
newSelection = (document.activeElement.contentWindow || window).getSelection(); | |
console.log("newSelection: ", newSelection.toString(), document.activeElement.contentWindow, window.getSelection()) | |
if (newSelection.toString().trim() === '' || | |
(this.options.allowMultiParagraphSelection === false && this.hasMultiParagraphs())) { | |
this.hideToolbarActions(); | |
} else { | |
selectionElement = this.getSelectionElement(); | |
if (!selectionElement || selectionElement.getAttribute('data-disable-toolbar')) { | |
this.hideToolbarActions(); | |
} else { | |
this.checkSelectionElement(newSelection, selectionElement); | |
} | |
} | |
} | |
return this; | |
}, | |
clickingIntoArchorForm: function (e) { | |
var self = this; | |
if (e.type && e.type.toLowerCase() === 'blur' && e.relatedTarget && e.relatedTarget === self.anchorInput) { | |
return true; | |
} | |
return false; | |
}, | |
hasMultiParagraphs: function () { | |
var selectionHtml = getSelectionHtml().replace(/<[\S]+><\/[\S]+>/gim, ''), | |
hasMultiParagraphs = selectionHtml.match(/<(p|h[0-6]|blockquote)>([\s\S]*?)<\/(p|h[0-6]|blockquote)>/g); | |
return (hasMultiParagraphs ? hasMultiParagraphs.length : 0); | |
}, | |
checkSelectionElement: function (newSelection, selectionElement) { | |
var i; | |
console.log("checkSelectionElement", newSelection, selectionElement) | |
this.selection = newSelection; | |
this.selectionRange = this.selection.getRangeAt(0); | |
for (i = 0; i < this.elements.length; i += 1) { | |
if (this.elements[i] === selectionElement) { | |
this.setToolbarButtonStates() | |
.setToolbarPosition() | |
.showToolbarActions(); | |
return; | |
} | |
} | |
this.hideToolbarActions(); | |
}, | |
getSelectionElement: function () { | |
console.log("getSeleEle.0") | |
var selection = (document.activeElement.contentWindow ? document.activeElement.contentWindow.window : window).getSelection(), | |
range, current, parent, | |
result, | |
getMediumElement = function (e) { | |
var localParent = e; | |
try { | |
while (!localParent.getAttribute('data-medium-element')) { | |
localParent = localParent.parentNode; | |
} | |
} catch (errb) { | |
return false; | |
} | |
return localParent; | |
}; | |
console.log("getSeleEle") | |
// First try on current node | |
try { | |
range = selection.getRangeAt(0); | |
current = range.commonAncestorContainer; | |
parent = current.parentNode; | |
if (current.getAttribute('data-medium-element')) { | |
result = current; | |
} else { | |
result = getMediumElement(parent); | |
} | |
// If not search in the parent nodes. | |
} catch (err) { | |
result = getMediumElement(parent); | |
} | |
return result; | |
}, | |
setToolbarPosition: function () { | |
var buttonHeight = 50, | |
selection = (document.activeElement && document.activeElement.contentWindow ? document.activeElement.contentWindow : window).getSelection(), | |
range = selection.getRangeAt(0), | |
boundary = range.getBoundingClientRect(), | |
defaultLeft = (this.options.diffLeft) - (this.toolbar.offsetWidth / 2), | |
middleBoundary = (boundary.left + boundary.right) / 2, | |
halfOffsetWidth = this.toolbar.offsetWidth / 2; | |
// selection = window.getSelection().baseNode, | |
// x_s = console.log(selection), | |
// range = document.createRange(), | |
// range = range.selectNode(selection), | |
// x_s = console.log(range), | |
// boundary = range.getBoundingClientRect(), | |
// defaultLeft = (this.options.diffLeft) - (this.toolbar.offsetWidth / 2), | |
// middleBoundary = (boundary.left + boundary.right) / 2, | |
// halfOffsetWidth = this.toolbar.offsetWidth / 2; | |
console.log("setToolbarPosition", buttonHeight, selection, range, boundary, defaultLeft, middleBoundary, halfOffsetWidth) | |
var realWin = (document.activeElement && document.activeElement.contentWindow ? document.activeElement.contentWindow : window) | |
if (boundary.top < buttonHeight) { | |
console.log("1") | |
this.toolbar.classList.add('medium-toolbar-arrow-over'); | |
this.toolbar.classList.remove('medium-toolbar-arrow-under'); | |
this.toolbar.style.top = buttonHeight + boundary.bottom - this.options.diffTop + realWin.pageYOffset - this.toolbar.offsetHeight + 'px'; | |
} else { | |
console.log("2") | |
this.toolbar.classList.add('medium-toolbar-arrow-under'); | |
this.toolbar.classList.remove('medium-toolbar-arrow-over'); | |
this.toolbar.style.top = boundary.top + this.options.diffTop + realWin.pageYOffset - this.toolbar.offsetHeight + 'px'; | |
} | |
if (middleBoundary < halfOffsetWidth) { | |
console.log("3") | |
this.toolbar.style.left = defaultLeft + halfOffsetWidth + 'px'; | |
} else if ((realWin.innerWidth - middleBoundary) < halfOffsetWidth) { | |
console.log("4") | |
this.toolbar.style.left = realWin.innerWidth + defaultLeft - halfOffsetWidth + 'px'; | |
} else { | |
console.log("5") | |
this.toolbar.style.left = defaultLeft + middleBoundary + 'px'; | |
} | |
this.hideAnchorPreview(); | |
return this; | |
}, | |
setToolbarButtonStates: function () { | |
var buttons = this.toolbarActions.querySelectorAll('button'), | |
i; | |
for (i = 0; i < buttons.length; i += 1) { | |
buttons[i].classList.remove(this.options.activeButtonClass); | |
} | |
this.checkActiveButtons(); | |
return this; | |
}, | |
checkActiveButtons: function () { | |
var elements = Array.prototype.slice.call(this.elements), | |
parentNode = this.getSelectedParentElement(); | |
while (parentNode.tagName !== undefined && this.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) { | |
this.activateButton(parentNode.tagName.toLowerCase()); | |
this.callExtensions('checkState', parentNode); | |
// we can abort the search upwards if we leave the contentEditable element | |
if (elements.indexOf(parentNode) !== -1) { | |
break; | |
} | |
parentNode = parentNode.parentNode; | |
} | |
}, | |
activateButton: function (tag) { | |
var el = this.toolbar.querySelector('[data-element="' + tag + '"]'); | |
if (el !== null && el.className.indexOf(this.options.activeButtonClass) === -1) { | |
el.className += ' ' + this.options.activeButtonClass; | |
} | |
}, | |
bindButtons: function () { | |
console.log("bindButtons: this.toolbar", this.toolbar) | |
var buttons = this.toolbar.querySelectorAll('button'), | |
i, | |
self = this, | |
triggerAction = function (e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
console.log("bindButtons.triggerAction: ", e, self.selection) | |
if (self.selection === undefined) { | |
self.checkSelection(); | |
} | |
if (this.className.indexOf(self.options.activeButtonClass) > -1) { | |
this.classList.remove(self.options.activeButtonClass); | |
} else { | |
this.className += ' ' + self.options.activeButtonClass; | |
} | |
if (this.hasAttribute('data-action')) { | |
self.execAction(this.getAttribute('data-action'), e); | |
} | |
}; | |
for (i = 0; i < buttons.length; i += 1) { | |
console.log(buttons[i]) | |
buttons[i].addEventListener('click', triggerAction); | |
} | |
// console.log("bindButtons") | |
this.setFirstAndLastItems(buttons); | |
return this; | |
}, | |
setFirstAndLastItems: function (buttons) { | |
if (buttons.length > 0) { | |
buttons[0].className += ' ' + this.options.firstButtonClass; | |
buttons[buttons.length - 1].className += ' ' + this.options.lastButtonClass; | |
} | |
return this; | |
}, | |
execAction: function (action, e) { | |
console.log("execAction" , action, e) | |
var realDocu = this.options.elementsContainer.ownerDocument; | |
var realWin = (document.activeElement.contentWindow || window); | |
if (action.indexOf('append-') > -1) { | |
this.execFormatBlock(action.replace('append-', '')); | |
this.setToolbarPosition(); | |
this.setToolbarButtonStates(); | |
} else if (action === 'anchor') { | |
this.triggerAnchorAction(e); | |
} else if (action === 'image') { | |
realDocu.execCommand('insertImage', false, realWin.getSelection()); | |
} else { | |
realDocu.execCommand(action, false, null); | |
this.setToolbarPosition(); | |
} | |
}, | |
// http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox | |
rangeSelectsSingleNode: function (range) { | |
console.log("rangeSelectsSingleNode", range) | |
var startNode = range.startContainer; | |
return startNode === range.endContainer && | |
startNode.hasChildNodes() && | |
range.endOffset === range.startOffset + 1; | |
}, | |
getSelectedParentElement: function () { | |
var selectedParentElement = null, | |
range = this.selectionRange; | |
console.log("getSelectedParentElement", range) | |
if (this.rangeSelectsSingleNode(range)) { | |
selectedParentElement = range.startContainer.childNodes[range.startOffset]; | |
} else if (range.startContainer.nodeType === 3) { | |
selectedParentElement = range.startContainer.parentNode; | |
} else { | |
selectedParentElement = range.startContainer; | |
} | |
return selectedParentElement; | |
}, | |
triggerAnchorAction: function () { | |
var realDocu = this.options.elementsContainer.ownerDocument; | |
var selectedParentElement = this.getSelectedParentElement(); | |
console.log("triggerAnchorAction") | |
if (selectedParentElement.tagName && | |
selectedParentElement.tagName.toLowerCase() === 'a') { | |
realDocu.execCommand('unlink', false, null); | |
} else { | |
if (this.anchorForm.style.display === 'block') { | |
console.log("triggerAnchorAction.style.display=block") | |
this.showToolbarActions(); | |
} else { | |
console.log("triggerAnchorAction.style.display=block") | |
} | |
} | |
return this; | |
}, | |
execFormatBlock: function (el) { | |
var realDocu = this.options.elementsContainer.ownerDocument; | |
var selectionData = this.getSelectionData(this.selection.anchorNode); | |
// FF handles blockquote differently on formatBlock | |
// allowing nesting, we need to use outdent | |
// https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla | |
if (el === 'blockquote' && selectionData.el && | |
selectionData.el.parentNode.tagName.toLowerCase() === 'blockquote') { | |
return realDocu.execCommand('outdent', false, null); | |
} | |
if (selectionData.tagName === el) { | |
el = 'p'; | |
} | |
// When IE we need to add <> to heading elements and | |
// blockquote needs to be called as indent | |
// http://stackoverflow.com/questions/10741831/execcommand-formatblock-headings-in-ie | |
// http://stackoverflow.com/questions/1816223/rich-text-editor-with-blockquote-function/1821777#1821777 | |
if (this.isIE) { | |
if (el === 'blockquote') { | |
return realDocu.execCommand('indent', false, el); | |
} | |
el = '<' + el + '>'; | |
} | |
return realDocu.execCommand('formatBlock', false, el); | |
}, | |
getSelectionData: function (el) { | |
var tagName; | |
console.log("getSelectionData: ", el) | |
if (el && el.tagName) { | |
tagName = el.tagName.toLowerCase(); | |
} | |
while (el && this.parentElements.indexOf(tagName) === -1) { | |
el = el.parentNode; | |
if (el && el.tagName) { | |
tagName = el.tagName.toLowerCase(); | |
} | |
} | |
return { | |
el: el, | |
tagName: tagName | |
}; | |
}, | |
getFirstChild: function (el) { | |
var firstChild = el.firstChild; | |
while (firstChild !== null && firstChild.nodeType !== 1) { | |
firstChild = firstChild.nextSibling; | |
} | |
return firstChild; | |
}, | |
hideToolbarActions: function () { | |
this.keepToolbarAlive = false; | |
if (this.toolbar !== undefined) { | |
this.toolbar.classList.remove('medium-editor-toolbar-active'); | |
} | |
}, | |
showToolbarActions: function () { | |
var self = this, | |
timer; | |
console.log("showToolbarActions") | |
this.anchorForm.style.display = 'none'; | |
this.toolbarActions.style.display = 'block'; | |
this.keepToolbarAlive = false; | |
clearTimeout(timer); | |
timer = setTimeout(function () { | |
if (self.toolbar && !self.toolbar.classList.contains('medium-editor-toolbar-active')) { | |
self.toolbar.classList.add('medium-editor-toolbar-active'); | |
} | |
}, 100); | |
}, | |
saveSelection: function() { | |
this.savedSelection = saveSelection(); | |
}, | |
restoreSelection: function() { | |
restoreSelection(this.savedSelection); | |
}, | |
showAnchorForm: function (link_value) { | |
console.log("showAnchorForm") | |
this.toolbarActions.style.display = 'none'; | |
this.saveSelection(); | |
this.anchorForm.style.display = 'block'; | |
this.keepToolbarAlive = true; | |
this.anchorInput.focus(); | |
this.anchorInput.value = link_value || ''; | |
}, | |
bindAnchorForm: function () { | |
console.log("bindAnchorForm") | |
var linkCancel = this.anchorForm.querySelector('a'), | |
self = this; | |
this.anchorForm.addEventListener('click', function (e) { | |
e.stopPropagation(); | |
}); | |
this.anchorInput.addEventListener('keyup', function (e) { | |
if (e.keyCode === 13) { | |
e.preventDefault(); | |
self.createLink(this); | |
} | |
}); | |
this.anchorInput.addEventListener('click', function (e) { | |
// make sure not to hide form when cliking into the input | |
e.stopPropagation(); | |
self.keepToolbarAlive = true; | |
}); | |
this.anchorInput.addEventListener('blur', function () { | |
self.keepToolbarAlive = false; | |
self.checkSelection(); | |
}); | |
linkCancel.addEventListener('click', function (e) { | |
e.preventDefault(); | |
self.showToolbarActions(); | |
restoreSelection(self.savedSelection); | |
}); | |
return this; | |
}, | |
hideAnchorPreview: function () { | |
this.anchorPreview.classList.remove('medium-editor-anchor-preview-active'); | |
}, | |
// TODO: break method | |
showAnchorPreview: function (anchorEl) { | |
if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) { | |
return true; | |
} | |
var self = this, | |
buttonHeight = 40, | |
boundary = anchorEl.getBoundingClientRect(), | |
middleBoundary = (boundary.left + boundary.right) / 2, | |
halfOffsetWidth, | |
defaultLeft, | |
timer; | |
self.anchorPreview.querySelector('i').textContent = anchorEl.href; | |
halfOffsetWidth = self.anchorPreview.offsetWidth / 2; | |
defaultLeft = self.options.diffLeft - halfOffsetWidth; | |
clearTimeout(timer); | |
timer = setTimeout(function () { | |
if (self.anchorPreview && !self.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) { | |
self.anchorPreview.classList.add('medium-editor-anchor-preview-active'); | |
} | |
}, 100); | |
self.observeAnchorPreview(anchorEl); | |
self.anchorPreview.classList.add('medium-toolbar-arrow-over'); | |
self.anchorPreview.classList.remove('medium-toolbar-arrow-under'); | |
self.anchorPreview.style.top = Math.round(buttonHeight + boundary.bottom - self.options.diffTop + window.pageYOffset - self.anchorPreview.offsetHeight) + 'px'; | |
if (middleBoundary < halfOffsetWidth) { | |
self.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px'; | |
} else if ((window.innerWidth - middleBoundary) < halfOffsetWidth) { | |
self.anchorPreview.style.left = window.innerWidth + defaultLeft - halfOffsetWidth + 'px'; | |
} else { | |
self.anchorPreview.style.left = defaultLeft + middleBoundary + 'px'; | |
} | |
return this; | |
}, | |
// TODO: break method | |
observeAnchorPreview: function (anchorEl) { | |
var self = this, | |
lastOver = (new Date()).getTime(), | |
over = true, | |
stamp = function () { | |
lastOver = (new Date()).getTime(); | |
over = true; | |
}, | |
unstamp = function (e) { | |
if (!e.relatedTarget || !/anchor-preview/.test(e.relatedTarget.className)) { | |
over = false; | |
} | |
}, | |
interval_timer = setInterval(function () { | |
if (over) { | |
return true; | |
} | |
var durr = (new Date()).getTime() - lastOver; | |
if (durr > self.options.anchorPreviewHideDelay) { | |
// hide the preview 1/2 second after mouse leaves the link | |
self.hideAnchorPreview(); | |
// cleanup | |
clearInterval(interval_timer); | |
self.anchorPreview.removeEventListener('mouseover', stamp); | |
self.anchorPreview.removeEventListener('mouseout', unstamp); | |
anchorEl.removeEventListener('mouseover', stamp); | |
anchorEl.removeEventListener('mouseout', unstamp); | |
} | |
}, 200); | |
self.anchorPreview.addEventListener('mouseover', stamp); | |
self.anchorPreview.addEventListener('mouseout', unstamp); | |
anchorEl.addEventListener('mouseover', stamp); | |
anchorEl.addEventListener('mouseout', unstamp); | |
}, | |
createAnchorPreview: function () { | |
var self = this, | |
realDocu = this.options.elementsContainer.ownerDocument, | |
anchorPreview = realDocu.createElement('div'); | |
anchorPreview.id = 'medium-editor-anchor-preview-' + this.id; | |
anchorPreview.className = 'medium-editor-anchor-preview'; | |
anchorPreview.innerHTML = this.anchorPreviewTemplate(); | |
this.options.elementsContainer.appendChild(anchorPreview); | |
anchorPreview.addEventListener('click', function () { | |
self.anchorPreviewClickHandler(); | |
}); | |
return anchorPreview; | |
}, | |
anchorPreviewTemplate: function () { | |
return '<div class="medium-editor-toolbar-anchor-preview" id="medium-editor-toolbar-anchor-preview">' + | |
' <i class="medium-editor-toolbar-anchor-preview-inner"></i>' + | |
'</div>'; | |
}, | |
anchorPreviewClickHandler: function (e) { | |
if (this.activeAnchor) { | |
var self = this, | |
range = document.createRange(), | |
sel = window.getSelection(); | |
range.selectNodeContents(self.activeAnchor); | |
sel.removeAllRanges(); | |
sel.addRange(range); | |
setTimeout(function () { | |
if (self.activeAnchor) { | |
self.showAnchorForm(self.activeAnchor.href); | |
} | |
self.keepToolbarAlive = false; | |
}, 100 + self.options.delay); | |
} | |
this.hideAnchorPreview(); | |
}, | |
editorAnchorObserver: function (e) { | |
var self = this, | |
overAnchor = true, | |
leaveAnchor = function () { | |
// mark the anchor as no longer hovered, and stop listening | |
overAnchor = false; | |
self.activeAnchor.removeEventListener('mouseout', leaveAnchor); | |
}; | |
if (e.target && e.target.tagName.toLowerCase() === 'a') { | |
// Detect empty href attributes | |
// The browser will make href="" or href="#top" | |
// into absolute urls when accessed as e.targed.href, so check the html | |
if (!/href=["']\S+["']/.test(e.target.outerHTML) || /href=["']#\S+["']/.test(e.target.outerHTML)) { | |
return true; | |
} | |
// only show when hovering on anchors | |
if (this.toolbar.classList.contains('medium-editor-toolbar-active')) { | |
// only show when toolbar is not present | |
return true; | |
} | |
this.activeAnchor = e.target; | |
this.activeAnchor.addEventListener('mouseout', leaveAnchor); | |
// show the anchor preview according to the configured delay | |
// if the mouse has not left the anchor tag in that time | |
setTimeout(function () { | |
if (overAnchor) { | |
self.showAnchorPreview(e.target); | |
} | |
}, self.options.delay); | |
} | |
}, | |
bindAnchorPreview: function (index) { | |
var i, self = this; | |
this.editorAnchorObserverWrapper = function (e) { | |
self.editorAnchorObserver(e); | |
}; | |
for (i = 0; i < this.elements.length; i += 1) { | |
this.elements[i].addEventListener('mouseover', this.editorAnchorObserverWrapper); | |
} | |
return this; | |
}, | |
checkLinkFormat: function (value) { | |
var re = /^(https?|ftps?|rtmpt?):\/\/|mailto:/; | |
return (re.test(value) ? '' : 'http://') + value; | |
}, | |
setTargetBlank: function () { | |
var el = getSelectionStart(this.options.elementsContainer.ownerDocument), | |
i; | |
if (el.tagName.toLowerCase() === 'a') { | |
el.target = '_blank'; | |
} else { | |
el = el.getElementsByTagName('a'); | |
for (i = 0; i < el.length; i += 1) { | |
el[i].target = '_blank'; | |
} | |
} | |
}, | |
createLink: function (input) { | |
var realDocu = this.options.elementsContainer.ownerDocument; | |
if (input.value.trim().length === 0) { | |
this.hideToolbarActions(); | |
return; | |
} | |
restoreSelection(this.savedSelection); | |
if (this.options.checkLinkFormat) { | |
input.value = this.checkLinkFormat(input.value); | |
} | |
realDocu.execCommand('createLink', false, input.value); | |
if (this.options.targetBlank) { | |
this.setTargetBlank(); | |
} | |
this.checkSelection(); | |
this.showToolbarActions(); | |
input.value = ''; | |
}, | |
bindWindowActions: function () { | |
var timerResize, | |
self = this; | |
this.windowResizeHandler = function () { | |
clearTimeout(timerResize); | |
timerResize = setTimeout(function () { | |
console.log("bindWindowActions.timerResize") | |
if (self.toolbar && self.toolbar.classList.contains('medium-editor-toolbar-active')) { | |
self.setToolbarPosition(); | |
} | |
}, 100); | |
}; | |
console.log("bindWindowActions") | |
document.addEventListener('resize', this.windowResizeHandler); | |
return this; | |
}, | |
activate: function () { | |
if (this.isActive) { | |
return; | |
} | |
this.setup(); | |
}, | |
// TODO: break method | |
deactivate: function () { | |
var i; | |
if (!this.isActive) { | |
return; | |
} | |
this.isActive = false; | |
if (this.toolbar !== undefined) { | |
this.options.elementsContainer.removeChild(this.anchorPreview); | |
this.options.elementsContainer.removeChild(this.toolbar); | |
delete this.toolbar; | |
delete this.anchorPreview; | |
} | |
document.documentElement.removeEventListener('mouseup', this.checkSelectionWrapper); | |
window.removeEventListener('resize', this.windowResizeHandler); | |
for (i = 0; i < this.elements.length; i += 1) { | |
this.elements[i].removeEventListener('mouseover', this.editorAnchorObserverWrapper); | |
this.elements[i].removeEventListener('keyup', this.checkSelectionWrapper); | |
this.elements[i].removeEventListener('blur', this.checkSelectionWrapper); | |
this.elements[i].removeEventListener('paste', this.pasteWrapper); | |
this.elements[i].removeAttribute('contentEditable'); | |
this.elements[i].removeAttribute('data-medium-element'); | |
} | |
}, | |
htmlEntities: function (str) { | |
// converts special characters (like <) into their escaped/encoded values (like <). | |
// This allows you to show to display the string without the browser reading it as HTML. | |
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); | |
}, | |
bindPaste: function () { | |
var i, self = this; | |
this.pasteWrapper = function (e) { | |
var paragraphs, | |
html = '', | |
p; | |
var realDocu = self.options.elementsContainer.ownerDocument; | |
this.classList.remove('medium-editor-placeholder'); | |
if (!self.options.forcePlainText && !self.options.cleanPastedHTML) { | |
return this; | |
} | |
if (e.clipboardData && e.clipboardData.getData && !e.defaultPrevented) { | |
e.preventDefault(); | |
if (self.options.cleanPastedHTML && e.clipboardData.getData('text/html')) { | |
return self.cleanPaste(e.clipboardData.getData('text/html')); | |
} | |
if (!(self.options.disableReturn || this.getAttribute('data-disable-return'))) { | |
paragraphs = e.clipboardData.getData('text/plain').split(/[\r\n]/g); | |
for (p = 0; p < paragraphs.length; p += 1) { | |
if (paragraphs[p] !== '') { | |
if (navigator.userAgent.match(/firefox/i) && p === 0) { | |
html += self.htmlEntities(paragraphs[p]); | |
} else { | |
html += '<p>' + self.htmlEntities(paragraphs[p]) + '</p>'; | |
} | |
} | |
} | |
realDocu.execCommand('insertHTML', false, html); | |
} else { | |
realDocu.execCommand('insertHTML', false, e.clipboardData.getData('text/plain')); | |
} | |
} | |
}; | |
for (i = 0; i < this.elements.length; i += 1) { | |
this.elements[i].addEventListener('paste', this.pasteWrapper); | |
} | |
return this; | |
}, | |
setPlaceholders: function () { | |
var i, | |
activatePlaceholder = function (el) { | |
if (!(el.querySelector('img')) && | |
!(el.querySelector('blockquote')) && | |
el.textContent.replace(/^\s+|\s+$/g, '') === '') { | |
el.classList.add('medium-editor-placeholder'); | |
} | |
}, | |
placeholderWrapper = function (e) { | |
this.classList.remove('medium-editor-placeholder'); | |
if (e.type !== 'keypress') { | |
activatePlaceholder(this); | |
} | |
}; | |
for (i = 0; i < this.elements.length; i += 1) { | |
activatePlaceholder(this.elements[i]); | |
this.elements[i].addEventListener('blur', placeholderWrapper); | |
this.elements[i].addEventListener('keypress', placeholderWrapper); | |
} | |
return this; | |
}, | |
cleanPaste: function (text) { | |
/*jslint regexp: true*/ | |
/* | |
jslint does not allow character negation, because the negation | |
will not match any unicode characters. In the regexes in this | |
block, negation is used specifically to match the end of an html | |
tag, and in fact unicode characters *should* be allowed. | |
*/ | |
console.log("cleanPaste") | |
var i, elList, workEl, | |
el = this.getSelectionElement(), | |
multiline = /<p|<br|<div/.test(text), | |
realDocu = this.options.elementsContainer.ownerDocument, | |
replacements = [ | |
// replace two bogus tags that begin pastes from google docs | |
[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ""], | |
[new RegExp(/<\/b>(<br[^>]*>)?$/gi), ""], | |
// un-html spaces and newlines inserted by OS X | |
[new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '], | |
[new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'], | |
// replace google docs italics+bold with a span to be replaced once the html is inserted | |
[new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'], | |
// replace google docs italics with a span to be replaced once the html is inserted | |
[new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'], | |
//[replace google docs bolds with a span to be replaced once the html is inserted | |
[new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'], | |
// replace manually entered b/i/a tags with real ones | |
[new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'], | |
// replace manually a tags with real ones, converting smart-quotes from google docs | |
[new RegExp(/<a\s+href=("|”|“|“|”)([^&]+)("|”|“|“|”)>/gi), '<a href="$2">'] | |
]; | |
/*jslint regexp: false*/ | |
for (i = 0; i < replacements.length; i += 1) { | |
text = text.replace(replacements[i][0], replacements[i][1]); | |
} | |
if (multiline) { | |
// double br's aren't converted to p tags, but we want paragraphs. | |
elList = text.split('<br><br>'); | |
this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>'); | |
realDocu.execCommand('insertText', false, "\n"); | |
// block element cleanup | |
elList = el.querySelectorAll('p,div,br'); | |
for (i = 0; i < elList.length; i += 1) { | |
workEl = elList[i]; | |
switch (workEl.tagName.toLowerCase()) { | |
case 'p': | |
case 'div': | |
this.filterCommonBlocks(workEl); | |
break; | |
case 'br': | |
this.filterLineBreak(workEl); | |
break; | |
} | |
} | |
} else { | |
this.pasteHTML(text); | |
} | |
}, | |
pasteHTML: function (html) { | |
var elList, workEl, i, fragmentBody, pasteBlock = document.createDocumentFragment(); | |
var realDocu = this.options.elementsContainer.ownerDocument; | |
pasteBlock.appendChild(document.createElement('body')); | |
fragmentBody = pasteBlock.querySelector('body'); | |
fragmentBody.innerHTML = html; | |
this.cleanupSpans(fragmentBody); | |
elList = fragmentBody.querySelectorAll('*'); | |
for (i = 0; i < elList.length; i += 1) { | |
workEl = elList[i]; | |
// delete ugly attributes | |
workEl.removeAttribute('class'); | |
workEl.removeAttribute('style'); | |
workEl.removeAttribute('dir'); | |
if (workEl.tagName.toLowerCase() === 'meta') { | |
workEl.parentNode.removeChild(workEl); | |
} | |
} | |
realDocu.execCommand('insertHTML', false, fragmentBody.innerHTML.replace(/ /g, ' ')); | |
}, | |
isCommonBlock: function (el) { | |
return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div')); | |
}, | |
filterCommonBlocks: function (el) { | |
if (/^\s*$/.test(el.innerText)) { | |
el.parentNode.removeChild(el); | |
} | |
}, | |
filterLineBreak: function (el) { | |
if (this.isCommonBlock(el.previousElementSibling)) { | |
// remove stray br's following common block elements | |
el.parentNode.removeChild(el); | |
} else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) { | |
// remove br's just inside open or close tags of a div/p | |
el.parentNode.removeChild(el); | |
} else if (el.parentNode.childElementCount === 1) { | |
// and br's that are the only child of a div/p | |
this.removeWithParent(el); | |
} | |
}, | |
// remove an element, including its parent, if it is the only element within its parent | |
removeWithParent: function (el) { | |
if (el && el.parentNode) { | |
if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) { | |
el.parentNode.parentNode.removeChild(el.parentNode); | |
} else { | |
el.parentNode.removeChild(el.parentNode); | |
} | |
} | |
}, | |
cleanupSpans: function (container_el) { | |
var i, | |
el, | |
new_el, | |
spans = container_el.querySelectorAll('.replace-with'); | |
for (i = 0; i < spans.length; i += 1) { | |
el = spans[i]; | |
new_el = document.createElement(el.classList.contains('bold') ? 'b' : 'i'); | |
if (el.classList.contains('bold') && el.classList.contains('italic')) { | |
// add an i tag as well if this has both italics and bold | |
new_el.innerHTML = '<i>' + el.innerHTML + '</i>'; | |
} else { | |
new_el.innerHTML = el.innerHTML; | |
} | |
el.parentNode.replaceChild(new_el, el); | |
} | |
spans = container_el.querySelectorAll('span'); | |
for (i = 0; i < spans.length; i += 1) { | |
el = spans[i]; | |
// remove empty spans, replace others with their contents | |
if (/^\s*$/.test()) { | |
el.parentNode.removeChild(el); | |
} else { | |
el.parentNode.replaceChild(document.createTextNode(el.innerText), el); | |
} | |
} | |
} | |
}; | |
}(window, document)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment