700 lines
22 KiB
JavaScript
700 lines
22 KiB
JavaScript
|
|
|||
|
// also edit class name in style.css when changing this.
|
|||
|
const resizableImageClass = "resizable";
|
|||
|
|
|||
|
const EditorDefaultHtml = "<p></p>";
|
|||
|
|
|||
|
|
|||
|
var editor = {
|
|||
|
|
|||
|
_textField: document.getElementById('editor'),
|
|||
|
|
|||
|
_htmlSetByApplication: null,
|
|||
|
|
|||
|
_currentSelection: {
|
|||
|
"startContainer": 0,
|
|||
|
"startOffset": 0,
|
|||
|
"endContainer": 0,
|
|||
|
"endOffset": 0
|
|||
|
},
|
|||
|
|
|||
|
_useWindowLocationForEditorStateChangedCallback: false,
|
|||
|
|
|||
|
_imageMinWidth: 100,
|
|||
|
_imageMinHeight: 50,
|
|||
|
|
|||
|
_isImageResizingEnabled: true,
|
|||
|
|
|||
|
|
|||
|
init: function() {
|
|||
|
document.addEventListener("selectionchange", function() {
|
|||
|
editor._backupRange();
|
|||
|
editor._handleTextEntered(); // in newly selected area different commands may be activated / deactivated
|
|||
|
});
|
|||
|
|
|||
|
this._textField.addEventListener("keydown", function(e) {
|
|||
|
var BACKSPACE = 8;
|
|||
|
var M = 77;
|
|||
|
|
|||
|
if(e.which == BACKSPACE) {
|
|||
|
if(editor._textField.innerText.length == 1) { // prevent that first paragraph gets deleted
|
|||
|
e.preventDefault();
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
else if(e.which == M && e.ctrlKey) { // TODO: what is Ctrl + M actually good for?
|
|||
|
e.preventDefault(); // but be aware in this way also (JavaFX) application won't be able to use Ctrl + M
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
this._textField.addEventListener("keyup", function(e) {
|
|||
|
if(e.altKey || e.ctrlKey) { // some key combinations activate commands like CTRL + B setBold() -> update editor state so that UI is aware of this
|
|||
|
editor._updateEditorState();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
this._textField.addEventListener("paste", function(e) { editor._handlePaste(e); });
|
|||
|
|
|||
|
this._ensureEditorInsertsParagraphWhenPressingEnter();
|
|||
|
this._initDragImageToResize();
|
|||
|
this._updateEditorState();
|
|||
|
},
|
|||
|
|
|||
|
_ensureEditorInsertsParagraphWhenPressingEnter: function() {
|
|||
|
// see https://stackoverflow.com/a/36373967
|
|||
|
this._executeCommand("DefaultParagraphSeparator", "p");
|
|||
|
|
|||
|
this._textField.innerHTML = ""; // clear previous content
|
|||
|
|
|||
|
var newElement = document.createElement("p");
|
|||
|
newElement.innerHTML = "​";
|
|||
|
this._textField.appendChild(newElement);
|
|||
|
|
|||
|
var selection=document.getSelection();
|
|||
|
var range=document.createRange();
|
|||
|
range.setStart(newElement.firstChild, 1);
|
|||
|
selection.removeAllRanges();
|
|||
|
selection.addRange(range);
|
|||
|
},
|
|||
|
|
|||
|
_initDragImageToResize: function() {
|
|||
|
var angle = 0;
|
|||
|
|
|||
|
interact.addDocument(window.document, {
|
|||
|
events: { passive: false },
|
|||
|
});
|
|||
|
|
|||
|
interact('img.' + resizableImageClass)
|
|||
|
.draggable({
|
|||
|
onmove: window.dragMoveListener,
|
|||
|
restrict: {
|
|||
|
restriction: 'parent',
|
|||
|
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
|
|||
|
},
|
|||
|
})
|
|||
|
.resizable({
|
|||
|
// resize from right or bottom
|
|||
|
edges: { top: true, left: true, right: true, bottom: true},
|
|||
|
|
|||
|
// keep the edges inside the parent
|
|||
|
restrictEdges: {
|
|||
|
outer: 'parent',
|
|||
|
endOnly: true,
|
|||
|
},
|
|||
|
|
|||
|
// minimum size
|
|||
|
restrictSize: {
|
|||
|
min: { width: this._imageMinWidth, height: this._imageMinHeight },
|
|||
|
},
|
|||
|
|
|||
|
inertia: true,
|
|||
|
preserveAspectRatio: true,
|
|||
|
})
|
|||
|
.gesturable({
|
|||
|
onmove: function (event) {
|
|||
|
|
|||
|
var target = event.target;
|
|||
|
|
|||
|
angle += event.da;
|
|||
|
|
|||
|
if(Math.abs(90 - (angle % 360)) < 10){ angle = 90;}
|
|||
|
if(Math.abs(180 - (angle % 360)) < 10){ angle = 180;}
|
|||
|
if(Math.abs(270 - (angle % 360)) < 10){ angle = 270;}
|
|||
|
if(Math.abs(angle % 360) < 10){ angle = 0;}
|
|||
|
|
|||
|
target.style.webkitTransform =
|
|||
|
target.style.transform =
|
|||
|
'rotate(' + angle + 'deg)';
|
|||
|
|
|||
|
}
|
|||
|
})
|
|||
|
.on('resizemove', function (event) {
|
|||
|
|
|||
|
var target = event.target,
|
|||
|
x = (parseFloat(target.getAttribute('data-x')) || 0),
|
|||
|
y = (parseFloat(target.getAttribute('data-y')) || 0);
|
|||
|
|
|||
|
// update the element's style
|
|||
|
target.style.width = event.rect.width + 'px';
|
|||
|
target.style.height = event.rect.height + 'px';
|
|||
|
|
|||
|
target.width = event.rect.width + 'px';
|
|||
|
target.height = event.rect.height + 'px';
|
|||
|
|
|||
|
target.setAttribute('data-x', x);
|
|||
|
target.setAttribute('data-y', y);
|
|||
|
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
_handleTextEntered: function() {
|
|||
|
if(this._getHtml() == "<p><br></p>") { // SwiftKey, when deleting all entered text, inserts a pure "<br>" therefore check for <p>​</p> doesn't work anymore
|
|||
|
this._ensureEditorInsertsParagraphWhenPressingEnter();
|
|||
|
}
|
|||
|
|
|||
|
this._updateEditorState();
|
|||
|
},
|
|||
|
|
|||
|
_handlePaste: function(event) {
|
|||
|
var clipboardData = event.clipboardData || window.clipboardData;
|
|||
|
var pastedData = clipboardData.getData('text/html') || clipboardData.getData('text').replace(/(?:\r\n|\r|\n)/g, '<br />'); // replace new lines // TODO: may use 'text/plain' instead of 'text'
|
|||
|
|
|||
|
this._waitTillPastedDataInserted(event, pastedData);
|
|||
|
},
|
|||
|
|
|||
|
_waitTillPastedDataInserted: function(event, pastedData) {
|
|||
|
var previousHtml = this._getHtml();
|
|||
|
|
|||
|
setTimeout(function () { // on paste event inserted text is not inserted yet -> wait for till text has been inserted
|
|||
|
editor._waitTillPastedTextInserted(previousHtml, 10, pastedData); // max 10 tries, after that we give up to prevent endless loops
|
|||
|
}, 100);
|
|||
|
},
|
|||
|
|
|||
|
_waitTillPastedTextInserted: function(previousHtml, iteration, pastedData) {
|
|||
|
var hasBeenInserted = this._getHtml() != previousHtml;
|
|||
|
|
|||
|
if(hasBeenInserted || ! iteration) {
|
|||
|
// there seems to be a bug (on Linux only?) when pasting data e.g. from Firefox: then only '' gets inserted
|
|||
|
if((this._getHtml().indexOf('ÿþ<') !== -1 || this._getHtml().indexOf('ÿþ<<br>') !== -1) && previousHtml.indexOf('ÿþ<') === -1) {
|
|||
|
this._textField.innerHTML = this._getHtml().replace('ÿþ<', pastedData).replace('ÿþ<<br>', pastedData);
|
|||
|
// TODO: set caret to end of pasted data
|
|||
|
}
|
|||
|
|
|||
|
this._updateEditorState();
|
|||
|
}
|
|||
|
else {
|
|||
|
setTimeout(function () { // wait for till pasted data has been inserted
|
|||
|
editor._waitTillPastedTextInserted(pastedText, iteration - 1);
|
|||
|
}, 100);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
_getHtml: function() {
|
|||
|
return this._textField.innerHTML;
|
|||
|
},
|
|||
|
|
|||
|
_getHtmlWithoutInternalModifications: function() {
|
|||
|
var clonedHtml = this._textField.cloneNode(true);
|
|||
|
|
|||
|
this._removeResizeImageClasses(clonedHtml);
|
|||
|
|
|||
|
return clonedHtml.innerHTML;
|
|||
|
},
|
|||
|
|
|||
|
getEncodedHtml: function() {
|
|||
|
return encodeURIComponent(this._getHtmlWithoutInternalModifications());
|
|||
|
},
|
|||
|
|
|||
|
setHtml: function(html, baseUrl) {
|
|||
|
if(baseUrl) {
|
|||
|
this._setBaseUrl(baseUrl);
|
|||
|
}
|
|||
|
|
|||
|
if(html.length != 0) {
|
|||
|
var decodedHtml = this._decodeHtml(html);
|
|||
|
this._textField.innerHTML = decodedHtml;
|
|||
|
|
|||
|
this._htmlSetByApplication = decodedHtml;
|
|||
|
|
|||
|
if(this._isImageResizingEnabled) {
|
|||
|
this.makeImagesResizeable();
|
|||
|
}
|
|||
|
}
|
|||
|
else {
|
|||
|
this._ensureEditorInsertsParagraphWhenPressingEnter();
|
|||
|
|
|||
|
this._htmlSetByApplication = null;
|
|||
|
}
|
|||
|
|
|||
|
this.didHtmlChange = false;
|
|||
|
},
|
|||
|
|
|||
|
_decodeHtml: function(html) {
|
|||
|
return decodeURIComponent(html.replace(/\+/g, '%20'));
|
|||
|
},
|
|||
|
|
|||
|
_setBaseUrl: function(baseUrl) {
|
|||
|
var baseElements = document.head.getElementsByTagName('base');
|
|||
|
var baseElement = null;
|
|||
|
if(baseElements.length > 0) {
|
|||
|
baseElement = baseElements[0];
|
|||
|
}
|
|||
|
else {
|
|||
|
var baseElement = document.createElement('base');
|
|||
|
document.head.appendChild(baseElement); // don't know why but append() is not available
|
|||
|
}
|
|||
|
|
|||
|
baseElement.setAttribute('href', baseUrl);
|
|||
|
baseElement.setAttribute('target', '_blank');
|
|||
|
},
|
|||
|
|
|||
|
useWindowLocationForEditorStateChangedCallback: function() {
|
|||
|
this._useWindowLocationForEditorStateChangedCallback = true;
|
|||
|
},
|
|||
|
|
|||
|
makeImagesResizeable: function() {
|
|||
|
this._isImageResizingEnabled = true;
|
|||
|
|
|||
|
var images = document.getElementsByTagName("img");
|
|||
|
|
|||
|
for(var i = 0; i < images.length; i++) {
|
|||
|
this._addClass(images[i], resizableImageClass);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
disableImageResizing: function() {
|
|||
|
this._isImageResizingEnabled = false;
|
|||
|
|
|||
|
this._removeResizeImageClasses(document);
|
|||
|
},
|
|||
|
|
|||
|
_removeResizeImageClasses: function(document) {
|
|||
|
var images = document.getElementsByTagName("img");
|
|||
|
|
|||
|
for(var i = 0; i < images.length; i++) {
|
|||
|
this._removeClass(images[i], resizableImageClass);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_hasClass: function(element, className) {
|
|||
|
return !!element.className.match(new RegExp('(\\s|^)' + className +'(\\s|$)'));
|
|||
|
},
|
|||
|
|
|||
|
_addClass: function(element, className) {
|
|||
|
if (this._hasClass(element, className) == false) {
|
|||
|
element.className += " " + className;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_removeClass: function(element, className) {
|
|||
|
if (this._hasClass(element, className)) {
|
|||
|
element.classList.remove(className);
|
|||
|
|
|||
|
var classAttributeValue = element.getAttribute('class');
|
|||
|
if (!!! classAttributeValue) { // remove class attribute if no class is left to restore original html
|
|||
|
element.removeAttribute('class');
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
/* Text Commands */
|
|||
|
|
|||
|
undo: function() {
|
|||
|
this._executeCommand('undo', null);
|
|||
|
},
|
|||
|
|
|||
|
redo: function() {
|
|||
|
this._executeCommand('redo', null);
|
|||
|
},
|
|||
|
|
|||
|
setBold: function() {
|
|||
|
this._executeCommand('bold', null);
|
|||
|
},
|
|||
|
|
|||
|
setItalic: function() {
|
|||
|
this._executeCommand('italic', null);
|
|||
|
},
|
|||
|
|
|||
|
setUnderline: function() {
|
|||
|
this._executeCommand('underline', null);
|
|||
|
},
|
|||
|
|
|||
|
setSubscript: function() {
|
|||
|
this._executeCommand('subscript', null);
|
|||
|
},
|
|||
|
|
|||
|
setSuperscript: function() {
|
|||
|
this._executeCommand('superscript', null);
|
|||
|
},
|
|||
|
|
|||
|
setStrikeThrough: function() {
|
|||
|
this._executeCommand('strikeThrough', null);
|
|||
|
},
|
|||
|
|
|||
|
setTextColor: function(color) {
|
|||
|
this._executeStyleCommand('foreColor', color);
|
|||
|
},
|
|||
|
|
|||
|
setTextBackgroundColor: function(color) {
|
|||
|
if(color == 'rgba(0, 0, 0, 0)') { // resetting backColor does not work with any color value (whether #00000000 nor rgba(0, 0, 0, 0)), we have to pass 'inherit'. Thanks to https://stackoverflow.com/a/7071465 for pointing this out to me
|
|||
|
this._executeStyleCommand('backColor', 'inherit');
|
|||
|
}
|
|||
|
else {
|
|||
|
this._executeStyleCommand('backColor', color);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
setFontName: function(fontName) {
|
|||
|
this._executeCommand("fontName", fontName);
|
|||
|
},
|
|||
|
|
|||
|
setFontSize: function(fontSize) {
|
|||
|
this._executeCommand("fontSize", fontSize);
|
|||
|
},
|
|||
|
|
|||
|
setHeading: function(heading) {
|
|||
|
this._executeCommand('formatBlock', '<h'+heading+'>');
|
|||
|
},
|
|||
|
|
|||
|
setFormattingToParagraph: function() {
|
|||
|
this._executeCommand('formatBlock', '<p>');
|
|||
|
},
|
|||
|
|
|||
|
setPreformat: function() {
|
|||
|
this._executeCommand('formatBlock', '<pre>');
|
|||
|
},
|
|||
|
|
|||
|
setBlockQuote: function() {
|
|||
|
this._executeCommand('formatBlock', '<blockquote>');
|
|||
|
},
|
|||
|
|
|||
|
removeFormat: function() {
|
|||
|
this._executeCommand('removeFormat', null);
|
|||
|
},
|
|||
|
|
|||
|
setJustifyLeft: function() {
|
|||
|
this._executeCommand('justifyLeft', null);
|
|||
|
},
|
|||
|
|
|||
|
setJustifyCenter: function() {
|
|||
|
this._executeCommand('justifyCenter', null);
|
|||
|
},
|
|||
|
|
|||
|
setJustifyRight: function() {
|
|||
|
this._executeCommand('justifyRight', null);
|
|||
|
},
|
|||
|
|
|||
|
setJustifyFull: function() {
|
|||
|
this._executeCommand('justifyFull', null);
|
|||
|
},
|
|||
|
|
|||
|
setIndent: function() {
|
|||
|
this._executeCommand('indent', null);
|
|||
|
},
|
|||
|
|
|||
|
setOutdent: function() {
|
|||
|
this._executeCommand('outdent', null);
|
|||
|
},
|
|||
|
|
|||
|
insertBulletList: function() {
|
|||
|
this._executeCommand('insertUnorderedList', null);
|
|||
|
},
|
|||
|
|
|||
|
insertNumberedList: function() {
|
|||
|
this._executeCommand('insertOrderedList', null);
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
/* Insert elements */
|
|||
|
|
|||
|
insertLink: function(url, title) {
|
|||
|
this._restoreRange();
|
|||
|
var sel = document.getSelection();
|
|||
|
|
|||
|
if (sel.toString().length == 0) {
|
|||
|
this._insertHtml("<a href='"+url+"'>"+title+"</a>");
|
|||
|
}
|
|||
|
else if (sel.rangeCount) {
|
|||
|
var el = document.createElement("a");
|
|||
|
el.setAttribute("href", url);
|
|||
|
el.setAttribute("title", title);
|
|||
|
|
|||
|
var range = sel.getRangeAt(0).cloneRange();
|
|||
|
range.surroundContents(el);
|
|||
|
sel.removeAllRanges();
|
|||
|
sel.addRange(range);
|
|||
|
|
|||
|
this._updateEditorState();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
insertImage: function(url, alt, width, height, rotation) {
|
|||
|
var imageElement = document.createElement('img');
|
|||
|
|
|||
|
imageElement.setAttribute('src', url);
|
|||
|
|
|||
|
if(alt) {
|
|||
|
imageElement.setAttribute('alt', alt);
|
|||
|
}
|
|||
|
|
|||
|
if(width) {
|
|||
|
imageElement.setAttribute('width', width);
|
|||
|
}
|
|||
|
|
|||
|
if(height) {
|
|||
|
imageElement.setAttribute('height', height);
|
|||
|
}
|
|||
|
|
|||
|
if(this._isImageResizingEnabled) {
|
|||
|
imageElement.setAttribute('class', resizableImageClass);
|
|||
|
}
|
|||
|
|
|||
|
if(rotation) {
|
|||
|
this._setImageRotation(imageElement, rotation);
|
|||
|
}
|
|||
|
|
|||
|
this._insertHtml(imageElement.outerHTML);
|
|||
|
},
|
|||
|
|
|||
|
_setImageRotation: function(imageElement, rotation) {
|
|||
|
if(rotation == 90) {
|
|||
|
this._addClass(imageElement, 'rotate90deg');
|
|||
|
}
|
|||
|
else if(rotation == 180) {
|
|||
|
this._addClass(imageElement, 'rotate180deg');
|
|||
|
}
|
|||
|
else if(rotation == 270) {
|
|||
|
this._addClass(imageElement, 'rotate270deg');
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
insertCheckbox: function(text) {
|
|||
|
var editor = this;
|
|||
|
|
|||
|
var html = '<input type="checkbox" name="'+ text +'" value="'+ text +'" onclick="editor._checkboxClicked(this)"/> ';
|
|||
|
this._insertHtml(html);
|
|||
|
},
|
|||
|
|
|||
|
_checkboxClicked: function(clickedCheckbox) {
|
|||
|
// incredible, checked attribute doesn't get set in html, see issue https://github.com/dankito/RichTextEditor/issues/24
|
|||
|
if (clickedCheckbox.checked) {
|
|||
|
clickedCheckbox.setAttribute('checked', 'checked');
|
|||
|
}
|
|||
|
else {
|
|||
|
clickedCheckbox.removeAttribute('checked');
|
|||
|
}
|
|||
|
|
|||
|
this._updateEditorState();
|
|||
|
},
|
|||
|
|
|||
|
insertHtml: function(encodedHtml) {
|
|||
|
var html = this._decodeHtml(encodedHtml);
|
|||
|
this._insertHtml(html);
|
|||
|
},
|
|||
|
|
|||
|
_insertHtml: function(html) {
|
|||
|
this._backupRange();
|
|||
|
this._restoreRange();
|
|||
|
|
|||
|
document.execCommand('insertHTML', false, html);
|
|||
|
|
|||
|
if(this._isImageResizingEnabled) {
|
|||
|
this.makeImagesResizeable();
|
|||
|
}
|
|||
|
|
|||
|
this._updateEditorState();
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
/* Editor default settings */
|
|||
|
|
|||
|
setBaseTextColor: function(color) {
|
|||
|
this._textField.style.color = color;
|
|||
|
},
|
|||
|
|
|||
|
setBaseFontFamily: function(fontFamily) {
|
|||
|
this._textField.style.fontFamily = fontFamily;
|
|||
|
},
|
|||
|
|
|||
|
setBaseFontSize: function(size) {
|
|||
|
this._textField.style.fontSize = size;
|
|||
|
},
|
|||
|
|
|||
|
setPadding: function(left, top, right, bottom) {
|
|||
|
this._textField.style.paddingLeft = left;
|
|||
|
this._textField.style.paddingTop = top;
|
|||
|
this._textField.style.paddingRight = right;
|
|||
|
this._textField.style.paddingBottom = bottom;
|
|||
|
},
|
|||
|
|
|||
|
// TODO: is this one ever user?
|
|||
|
setBackgroundColor: function(color) {
|
|||
|
document.body.style.backgroundColor = color;
|
|||
|
},
|
|||
|
|
|||
|
setBackgroundImage: function(image) {
|
|||
|
this._textField.style.backgroundImage = image;
|
|||
|
},
|
|||
|
|
|||
|
setWidth: function(size) {
|
|||
|
this._textField.style.minWidth = size; // TODO: why did i use minWidth here but height (not minHeight) below?
|
|||
|
},
|
|||
|
|
|||
|
setHeight: function(size) {
|
|||
|
this._textField.style.height = size;
|
|||
|
},
|
|||
|
|
|||
|
setTextAlign: function(align) {
|
|||
|
this._textField.style.textAlign = align;
|
|||
|
},
|
|||
|
|
|||
|
setVerticalAlign: function(align) {
|
|||
|
this._textField.style.verticalAlign = align;
|
|||
|
},
|
|||
|
|
|||
|
setPlaceholder: function(placeholder) {
|
|||
|
this._textField.setAttribute("placeholder", placeholder);
|
|||
|
},
|
|||
|
|
|||
|
setInputEnabled: function(inputEnabled) {
|
|||
|
this._textField.contentEditable = String(inputEnabled);
|
|||
|
|
|||
|
if(inputEnabled) { // TODO: may interferes with _isImageResizingEnabled
|
|||
|
this.makeImagesResizeable();
|
|||
|
}
|
|||
|
else {
|
|||
|
this.disableImageResizing();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
focus: function() {
|
|||
|
var range = document.createRange();
|
|||
|
range.selectNodeContents(this._textField);
|
|||
|
range.collapse(false);
|
|||
|
var selection = window.getSelection();
|
|||
|
selection.removeAllRanges();
|
|||
|
selection.addRange(range);
|
|||
|
this._textField.focus();
|
|||
|
},
|
|||
|
|
|||
|
blurFocus: function() {
|
|||
|
this._textField.blur();
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
_executeStyleCommand: function(command, parameter) {
|
|||
|
this._executeCommand("styleWithCSS", null, true);
|
|||
|
this._executeCommand(command, parameter);
|
|||
|
this._executeCommand("styleWithCSS", null, false);
|
|||
|
},
|
|||
|
|
|||
|
_executeCommand: function(command, parameter) {
|
|||
|
document.execCommand(command, false, parameter);
|
|||
|
|
|||
|
this._updateEditorState();
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
_updateEditorState: function() {
|
|||
|
var html = this._getHtmlWithoutInternalModifications();
|
|||
|
var didHtmlChange = (this._htmlSetByApplication != null && this._htmlSetByApplication != html) || // html set by application changed
|
|||
|
(this._htmlSetByApplication == null && html != EditorDefaultHtml); // or if html not set by application: default html changed
|
|||
|
|
|||
|
if (typeof editorCallback !== 'undefined') { // in most applications like in the JavaFX app changing window.location.href doesn't work -> tell them via callback that editor state changed
|
|||
|
editorCallback.updateEditorState(didHtmlChange) // these applications determine editor state manually
|
|||
|
}
|
|||
|
else if (this._useWindowLocationForEditorStateChangedCallback) { // Android can handle changes to windows.location -> communicate editor changes via a self defined protocol name
|
|||
|
var commandStates = this._determineCommandStates();
|
|||
|
|
|||
|
var editorState = {
|
|||
|
'didHtmlChange': didHtmlChange,
|
|||
|
'html': html, // TODO: remove in upcoming versions
|
|||
|
'commandStates': commandStates
|
|||
|
};
|
|||
|
|
|||
|
window.location.href = "editor-state-changed-callback://" + encodeURIComponent(JSON.stringify(editorState));
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_determineCommandStates: function() {
|
|||
|
var commandStates = {};
|
|||
|
|
|||
|
this._determineStateForCommand('undo', commandStates);
|
|||
|
this._determineStateForCommand('redo', commandStates);
|
|||
|
|
|||
|
this._determineStateForCommand('bold', commandStates);
|
|||
|
this._determineStateForCommand('italic', commandStates);
|
|||
|
this._determineStateForCommand('underline', commandStates);
|
|||
|
this._determineStateForCommand('subscript', commandStates);
|
|||
|
this._determineStateForCommand('superscript', commandStates);
|
|||
|
this._determineStateForCommand('strikeThrough', commandStates);
|
|||
|
|
|||
|
this._determineStateForCommand('foreColor', commandStates);
|
|||
|
this._determineStateForCommand('backColor', commandStates);
|
|||
|
|
|||
|
this._determineStateForCommand('fontName', commandStates);
|
|||
|
this._determineStateForCommand('fontSize', commandStates);
|
|||
|
|
|||
|
this._determineStateForCommand('formatBlock', commandStates);
|
|||
|
this._determineStateForCommand('removeFormat', commandStates);
|
|||
|
|
|||
|
this._determineStateForCommand('justifyLeft', commandStates);
|
|||
|
this._determineStateForCommand('justifyCenter', commandStates);
|
|||
|
this._determineStateForCommand('justifyRight', commandStates);
|
|||
|
this._determineStateForCommand('justifyFull', commandStates);
|
|||
|
|
|||
|
this._determineStateForCommand('indent', commandStates);
|
|||
|
this._determineStateForCommand('outdent', commandStates);
|
|||
|
|
|||
|
this._determineStateForCommand('insertUnorderedList', commandStates);
|
|||
|
this._determineStateForCommand('insertOrderedList', commandStates);
|
|||
|
this._determineStateForCommand('insertHorizontalRule', commandStates);
|
|||
|
this._determineStateForCommand('insertHTML', commandStates);
|
|||
|
|
|||
|
return commandStates;
|
|||
|
},
|
|||
|
|
|||
|
_determineStateForCommand: function(command, commandStates) {
|
|||
|
commandStates[command.toUpperCase()] = {
|
|||
|
'executable': document.queryCommandEnabled(command),
|
|||
|
'value': document.queryCommandValue(command)
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
|
|||
|
_backupRange: function(){
|
|||
|
var selection = window.getSelection();
|
|||
|
if(selection.rangeCount > 0) {
|
|||
|
var range = selection.getRangeAt(0);
|
|||
|
|
|||
|
this._currentSelection = {
|
|||
|
"startContainer": range.startContainer,
|
|||
|
"startOffset": range.startOffset,
|
|||
|
"endContainer": range.endContainer,
|
|||
|
"endOffset": range.endOffset
|
|||
|
};
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
_restoreRange: function(){
|
|||
|
var selection = window.getSelection();
|
|||
|
selection.removeAllRanges();
|
|||
|
|
|||
|
var range = document.createRange();
|
|||
|
range.setStart(this._currentSelection.startContainer, this._currentSelection.startOffset);
|
|||
|
range.setEnd(this._currentSelection.endContainer, this._currentSelection.endOffset);
|
|||
|
|
|||
|
selection.addRange(range);
|
|||
|
},
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
editor.init();
|