lilium-text
Advanced tools
Comparing version 1.0.9 to 1.3.0
@@ -44,6 +44,17 @@ "use strict"; | ||
el.addEventListener('click', function (ev) { | ||
el.addEventListener('mousedown', function (ev) { | ||
ev.preventDefault(); | ||
ev.stopPropagation(); | ||
return false; | ||
}); | ||
el.addEventListener('mouseup', function (ev) { | ||
ev.preventDefault(); | ||
ev.stopPropagation(); | ||
editor.log('Executed command ' + _this.webName + (_this.param ? " with parameter '" + _this.param + "'" : '')); | ||
editor.fire('command', _this.webName); | ||
_this.execute(ev, _this, editor); | ||
return false; | ||
}); | ||
@@ -58,2 +69,55 @@ | ||
var LiliumTextBrowserCompat = function () { | ||
function LiliumTextBrowserCompat() { | ||
_classCallCheck(this, LiliumTextBrowserCompat); | ||
} | ||
_createClass(LiliumTextBrowserCompat, null, [{ | ||
key: "runCommandOrFallback", | ||
value: function runCommandOrFallback(command, arg, cb) { | ||
document.execCommand(command, false, arg) || cb(command, arg); | ||
} | ||
}, { | ||
key: "nodeToCommand", | ||
value: function nodeToCommand(nodetype) { | ||
switch (nodetype) { | ||
case "strong": | ||
case "b": | ||
return "bold"; | ||
case "italic": | ||
case "em": | ||
return "italic"; | ||
case "underline": | ||
case "u": | ||
return "underline"; | ||
case "strike": | ||
case "s": | ||
return "strikeThrough"; | ||
// I hate not having a default case... | ||
default: | ||
return undefined; | ||
} | ||
} | ||
}, { | ||
key: "getStrategyKind", | ||
value: function getStrategyKind() { | ||
if (window.navigator) { | ||
if (document.execCommand) { | ||
return "exec"; | ||
} else { | ||
return "logic"; | ||
} | ||
} else { | ||
return "old"; | ||
} | ||
} | ||
}]); | ||
return LiliumTextBrowserCompat; | ||
}(); | ||
var LiliumTextWebCommand = function (_LiliumTextCommand) { | ||
@@ -76,2 +140,15 @@ _inherits(LiliumTextWebCommand, _LiliumTextCommand); | ||
_createClass(LiliumTextWebCommand, [{ | ||
key: "highlightNode", | ||
value: function highlightNode(node) { | ||
var selection = this.editor.restoreSelection(); | ||
var range = document.createRange(); | ||
this.editor.log("Highlighting node " + node.nodeName + " from Web Command"); | ||
range.selectNode(node); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
this.editor.storeRange(range); | ||
} | ||
}, { | ||
key: "executeText", | ||
@@ -83,111 +160,162 @@ value: function executeText() { | ||
var nodetype = this.param; | ||
var cmdtype = LiliumTextBrowserCompat.nodeToCommand(nodetype); | ||
if (selection.type == "Caret") { | ||
var context = this.editor.createSelectionContext(selection.focusNode); | ||
var maybeCtxElem = context.find(function (x) { | ||
return x.type == nodetype; | ||
}); | ||
if (maybeCtxElem) { | ||
this.editor.log('Unwrapping element of node type ' + nodetype); | ||
var el = maybeCtxElem.element; | ||
this.editor.unwrap(el); | ||
} | ||
} else if (selection.type == "Range") { | ||
var _ref = selection.anchorNode.compareDocumentPosition(selection.focusNode) & Node.DOCUMENT_POSITION_FOLLOWING ? [selection.anchorNode, selection.focusNode] : [selection.focusNode, selection.anchorNode], | ||
_ref2 = _slicedToArray(_ref, 2), | ||
left = _ref2[0], | ||
right = _ref2[1]; | ||
LiliumTextBrowserCompat.runCommandOrFallback(cmdtype, this.param, function () { | ||
if (selection.type == "Caret") { | ||
var context = _this3.editor.createSelectionContext(selection.focusNode); | ||
var maybeCtxElem = context.find(function (x) { | ||
return x.type == nodetype; | ||
}); | ||
if (maybeCtxElem) { | ||
_this3.editor.log('Unwrapping element of node type ' + nodetype); | ||
_this3.editor.unwrap(maybeCtxElem); | ||
} | ||
} else if (selection.type == "Range") { | ||
/* | ||
* CONTEXT VARIABLES DEFINITION ------ | ||
* | ||
* left, right : Nodes where selection starts and ends. Everything in between is located inside the fragment. | ||
* leftCtx, rightCtx : Array containing nodes from the selected one up to the editor one | ||
* leftExistWrap, rightExistWrap : Wrapped node if node already exists, otherwise undefined | ||
* | ||
* range : current selection range object | ||
* frag : fragment containing everything from inside the selection. Not a copy; actual nodes. | ||
* | ||
**/ | ||
var _ref3 = [this.editor.createSelectionContext(left), this.editor.createSelectionContext(right)], | ||
leftCtx = _ref3[0], | ||
rightCtx = _ref3[1]; | ||
var _ref4 = [leftCtx.find(function (x) { | ||
return x.type == nodetype; | ||
}), rightCtx.find(function (x) { | ||
return x.type == nodetype; | ||
})], | ||
leftExistWrap = _ref4[0], | ||
rightExistWrap = _ref4[1]; | ||
var capNodeType = nodetype.toUpperCase(); | ||
// Fun! :D | ||
var _ref = selection.anchorNode.compareDocumentPosition(selection.focusNode) & Node.DOCUMENT_POSITION_FOLLOWING ? [selection.anchorNode, selection.focusNode] : [selection.focusNode, selection.anchorNode], | ||
_ref2 = _slicedToArray(_ref, 2), | ||
left = _ref2[0], | ||
right = _ref2[1]; | ||
this.editor.log("Long logic with range using node type " + nodetype); | ||
var _ref3 = [_this3.editor.createSelectionContext(left), _this3.editor.createSelectionContext(right)], | ||
leftCtx = _ref3[0], | ||
rightCtx = _ref3[1]; | ||
var _ref4 = [leftCtx.find(function (x) { | ||
return x.nodeName == capNodeType; | ||
}), rightCtx.find(function (x) { | ||
return x.nodeName == capNodeType; | ||
})], | ||
leftExistWrap = _ref4[0], | ||
rightExistWrap = _ref4[1]; | ||
var range = selection.getRangeAt(0); | ||
var frag = range.extractContents(); | ||
// Fun! :D | ||
if (frag.childNodes[0].nodeName == "#text" && !frag.childNodes[0].data.trim()) { | ||
this.editor.log("Removed extra empty text node from fragment"); | ||
range.insertNode(frag.childNodes[0]); | ||
} | ||
_this3.editor.log("Long logic with range using node type " + nodetype); | ||
if (left.parentElement === right.parentElement && !leftExistWrap) { | ||
this.editor.log("Quick range wrap with element of node type " + nodetype); | ||
var newElem = document.createElement(nodetype); | ||
newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(newElem); | ||
} else if (!leftExistWrap != !rightExistWrap) { | ||
// Apparently there is no XOR in Javascript, so here's a syntax monstrosity | ||
// This will not execute the block unless one is truthy and one is falsey | ||
this.editor.log('XOR range wrapper extension of node type ' + nodetype); | ||
var range = selection.getRangeAt(0); | ||
var frag = range.extractContents(); | ||
var _newElem = document.createElement(nodetype); | ||
_newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(_newElem); | ||
/* | ||
if (frag.childNodes[0].nodeName == "#text" && !frag.childNodes[0].data.trim()) { | ||
this.editor.log("Removed extra empty text node from fragment"); | ||
range.insertNode(frag.childNodes[0]); | ||
} | ||
*/ | ||
// Extend existing wrapper | ||
var wrapper = leftExistWrap || rightExistWrap; | ||
Array.prototype.forEach.call(wrapper.element.querySelectorAll(nodetype), function (node) { | ||
_this3.editor.unwrap(node); | ||
}); | ||
} else if (leftExistWrap && rightExistWrap && leftExistWrap.element === rightExistWrap.element) { | ||
// Unwrap both ends, possible solution : while (textnode has next sibling) { insert sibling after wrapper node } | ||
this.editor.log("Placeholder unwrap from two sources with node types : " + nodetype); | ||
var placeholder = document.createElement('liliumtext-placeholder'); | ||
selection.getRangeAt(0).insertNode(placeholder); | ||
var fragWrap = !leftExistWrap && !rightExistWrap && frag.querySelectorAll(nodetype); | ||
var leftEl = leftExistWrap.element; | ||
var clone = leftEl.cloneNode(); | ||
leftEl.parentElement.insertBefore(clone, leftEl); | ||
if (left.parentElement === right.parentElement && !leftExistWrap && !fragWrap.length) { | ||
_this3.editor.log("Quick range wrap with element of node type " + nodetype); | ||
while (leftEl.firstChild != placeholder) { | ||
clone.appendChild(leftEl.firstChild); | ||
} | ||
// Might be worth looking at Range.surroundContents() | ||
var newElem = document.createElement(nodetype); | ||
newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(newElem); | ||
leftEl.parentElement.insertBefore(frag, leftEl); | ||
placeholder.remove(); | ||
_this3.highlightNode(newElem); | ||
} else if (!leftExistWrap != !rightExistWrap) { | ||
// Apparently there is no XOR in Javascript, so here's a syntax monstrosity | ||
// This will not execute the block unless one is truthy and one is falsey | ||
_this3.editor.log('XOR range wrapper extension of node type ' + nodetype); | ||
this.editor.log('Removing empty trailing <' + nodetype + '> element'); | ||
!clone.textContent.trim() && clone.remove(); | ||
!leftEl.textContent.trim() && leftEl.remove(); | ||
} else if (leftExistWrap && rightExistWrap) { | ||
this.editor.log("Merge wrap from two sources with node types : " + nodetype); | ||
// Merge wrap | ||
var leftFrag = frag.firstChild; | ||
var rightFrag = frag.lastChild; | ||
while (leftFrag.nextSibling != rightFrag) { | ||
leftFrag.appendChild(leftFrag.nextSibling); | ||
} | ||
var _newElem = document.createElement(nodetype); | ||
_newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(_newElem); | ||
while (rightFrag.firstChild) { | ||
leftFrag.appendChild(rightFrag.firstChild); | ||
} | ||
// Extend existing wrapper | ||
var wrapper = leftExistWrap || rightExistWrap; | ||
Array.prototype.forEach.call(wrapper.querySelectorAll(nodetype), function (node) { | ||
_this3.editor.unwrap(node); | ||
}); | ||
rightFrag.remove(); | ||
selection.getRangeAt(0).insertNode(frag); | ||
} else if (frag.childNodes.length == 1) { | ||
// Entire element is selected, Unwrap entire element | ||
this.editor.log("Single unwrap of node type : " + nodetype); | ||
var wrap = frag.childNodes[0]; | ||
while (wrap.lastChild) { | ||
selection.getRangeAt(0).insertNode(wrap.lastChild); | ||
_this3.highlightNode(node); | ||
} else if (fragWrap.length != 0) { | ||
// There is an element inside the fragment with requested node name | ||
// Unwrap child element | ||
_this3.editor.log('Fragment child unwrap with node type ' + nodetype); | ||
Array.prototype.forEach.call(fragWrap, function (elem) { | ||
while (elem.firstChild) { | ||
elem.parentNode ? elem.parentNode.insertBefore(elem.firstChild, elem) : frag.insertBefore(elem.firstChild, frag); | ||
} | ||
}); | ||
Array.prototype.forEach.call(fragWrap, function (elem) { | ||
return elem && elem.remove && elem.remove(); | ||
}); | ||
selection.getRangeAt(0).insertNode(frag); | ||
} else if (leftExistWrap && rightExistWrap && leftExistWrap === rightExistWrap) { | ||
// Unwrap both ends, possible solution : while (textnode has next sibling) { insert sibling after wrapper node } | ||
_this3.editor.log("Placeholder unwrap from two sources with node types : " + nodetype); | ||
var placeholder = document.createElement('liliumtext-placeholder'); | ||
selection.getRangeAt(0).insertNode(placeholder); | ||
var leftEl = leftExistWrap; | ||
var clone = leftEl.cloneNode(true); | ||
leftEl.parentNode.insertBefore(clone, leftEl); | ||
var clonePlaceholder = clone.querySelector('liliumtext-placeholder'); | ||
while (clonePlaceholder.nextSibling) { | ||
clonePlaceholder.nextSibling.remove(); | ||
} | ||
while (placeholder.previousSibling) { | ||
placeholder.previousSibling.remove(); | ||
} | ||
leftEl.parentNode.insertBefore(frag, leftEl); | ||
placeholder.remove(); | ||
clonePlaceholder.remove(); | ||
_this3.highlightNode(clone); | ||
} else if (leftExistWrap && rightExistWrap) { | ||
_this3.editor.log("Merge wrap from two sources with node types : " + nodetype); | ||
// Merge wrap | ||
var leftFrag = frag.firstChild; | ||
var rightFrag = frag.lastChild; | ||
while (leftFrag.nextSibling != rightFrag) { | ||
leftFrag.appendChild(leftFrag.nextSibling); | ||
} | ||
while (rightFrag.firstChild) { | ||
leftFrag.appendChild(rightFrag.firstChild); | ||
} | ||
rightFrag.remove(); | ||
selection.getRangeAt(0).insertNode(frag); | ||
} else if (frag.childNodes.length == 1 && frag.childNodes[0].nodeName == nodetype) { | ||
// Entire element is selected, Unwrap entire element | ||
_this3.editor.log("Single unwrap of node type : " + nodetype); | ||
var wrap = frag.childNodes[0]; | ||
while (wrap.lastChild) { | ||
selection.getRangeAt(0).insertNode(wrap.lastChild); | ||
} | ||
} else { | ||
// Create new element, insert before selection | ||
_this3.editor.log("Fragment wrap with node type : " + nodetype); | ||
var _newElem2 = document.createElement(nodetype); | ||
_newElem2.appendChild(frag); | ||
var _range = selection.getRangeAt(0); | ||
_range.insertNode(_newElem2); | ||
_range.selectNode(_newElem2); | ||
selection.removeAllRanges(); | ||
selection.addRange(_range); | ||
} | ||
} else { | ||
// Create new element, insert before selection | ||
this.editor.log("Fragment wrap with node type : " + nodetype); | ||
var _newElem2 = document.createElement(nodetype); | ||
_newElem2.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(_newElem2); | ||
} | ||
} | ||
}); | ||
} | ||
@@ -203,30 +331,36 @@ }, { | ||
value: function executeBlock() { | ||
var _this4 = this; | ||
var selection = this.editor.restoreSelection(); | ||
var range = selection.getRangeAt(0); | ||
var nodetype = this.param; | ||
var context = this.editor.createSelectionContext(selection.focusNode); | ||
var blocktags = this.editor.settings.blockelements; | ||
var cmdnodearg = nodetype.toUpperCase(); | ||
var topLevelTag = context[context.length - 1].element; | ||
LiliumTextBrowserCompat.runCommandOrFallback('formatBlock', cmdnodearg, function () { | ||
var context = _this4.editor.createSelectionContext(selection.focusNode); | ||
var blocktags = _this4.editor.settings.blockelements; | ||
if (topLevelTag.nodeName != nodetype) { | ||
var newNode = document.createElement(nodetype); | ||
topLevelTag.parentElement.insertBefore(newNode, topLevelTag); | ||
var topLevelTag = context && context.length ? context[context.length - 1] : _this4.editor.contentel.children[selection.focusOffset]; | ||
if (topLevelTag.data) { | ||
newNode.appendChild(topLevelTag); | ||
} else { | ||
while (topLevelTag.firstChild) { | ||
newNode.appendChild(topLevelTag.firstChild); | ||
if (topLevelTag.nodeName != nodetype) { | ||
var newNode = document.createElement(nodetype); | ||
topLevelTag.parentElement.insertBefore(newNode, topLevelTag); | ||
if (topLevelTag.data) { | ||
newNode.appendChild(topLevelTag); | ||
} else { | ||
while (topLevelTag.firstChild) { | ||
newNode.appendChild(topLevelTag.firstChild); | ||
} | ||
topLevelTag.remove(); | ||
} | ||
topLevelTag.remove(); | ||
range.setStart(newNode, 0); | ||
range.collapse(true); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
range.setStart(newNode, 1); | ||
range.collapse(true); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
}); | ||
} | ||
@@ -236,14 +370,16 @@ }, { | ||
value: function executeRemove() { | ||
var _this4 = this; | ||
if (this.param) { | ||
var el = this.editor.restoreSelection().focusNode.parentElement; | ||
if (this.param == "a") { | ||
this.editor.restoreSelection(); | ||
document.execCommand('unlink', false); | ||
} else if (this.param) { | ||
var el = this.editor.restoreSelection().focusNode.parentNode; | ||
var context = this.editor.createSelectionContext(el); | ||
var upperNode = this.param.toUpperCase(); | ||
var wrapperCtx = context.find(function (x) { | ||
return x.type == _this4.param; | ||
return x.nodeName == upperNode; | ||
}); | ||
if (wrapperCtx) { | ||
this.editor.log('Unwrapping node ' + this.param); | ||
this.editor.unwrap(wrapperCtx.element); | ||
this.editor.unwrap(wrapperCtx); | ||
} | ||
@@ -264,13 +400,20 @@ } else { | ||
var el = selection.focusNode; | ||
var context = this.editor.createSelectionContext(el); | ||
var topLevelEl = context[context.length - 1].element; | ||
this.editor.contentel.insertBefore(newNode, topLevelEl.nextElementSibling); | ||
if (el == this.editor.contentel) { | ||
this.editor.contentel.insertBefore(newNode, this.editor.contentel.children[selection.focusOffset]); | ||
} else { | ||
var context = this.editor.createSelectionContext(el); | ||
var topLevelEl = context[context.length - 1]; | ||
var range = selection.getRangeAt(0); | ||
range.setStart(newNode.nextElementSibling || topLevelEl.nextElementSibling || newNode, 0); | ||
range.collapse(true); | ||
this.editor.contentel.insertBefore(newNode, topLevelEl.nextElementSibling); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
if (selection.focusOffset == 0) {} else { | ||
var range = selection.getRangeAt(0); | ||
range.setStart(newNode.nextElementSibling || topLevelEl.nextElementSibling || newNode, 0); | ||
range.collapse(true); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
} | ||
} | ||
@@ -610,3 +753,3 @@ }, { | ||
while (elem != this.contentel && elem) { | ||
context.push({ type: elem.nodeName.toLowerCase().replace('#', ''), element: elem }); | ||
context.push(elem); | ||
elem = elem.parentNode; | ||
@@ -651,3 +794,3 @@ } | ||
value: function unwrap(el) { | ||
var par = el.parentElement; | ||
var par = el.parentNode; | ||
while (el.firstChild) { | ||
@@ -732,2 +875,38 @@ par.insertBefore(el.firstChild, el); | ||
}, { | ||
key: "insertBlock", | ||
value: function insertBlock(element) { | ||
var selection = this.restoreSelection(); | ||
var context = this.createSelectionContext(selection.focusNode); | ||
var ctxElem = context[context.length - 1]; | ||
if (selection.focusNode == this.contentel) { | ||
this.contentel.insertBefore(element, this.contentel.children[selection.focusOffset]); | ||
var range = selection.getRangeAt(0).cloneRange(); | ||
range.setEndAfter(element); | ||
range.collapse(false); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} else { | ||
if (ctxElem && ctxElem.nextSibling) { | ||
var curParag = ctxElem; | ||
if (curParag) { | ||
curParag.parentNode.insertBefore(element, selection.focusOffset == 0 ? curParag : curParag.nextSibling); | ||
} | ||
} else { | ||
this.contentel.appendChild(element); | ||
} | ||
if (selection.focusOffset != 0) { | ||
var _range2 = selection.getRangeAt(0).cloneRange(); | ||
_range2.setEndAfter(element); | ||
_range2.collapse(false); | ||
selection.removeAllRanges(); | ||
selection.addRange(_range2); | ||
} | ||
} | ||
} | ||
}, { | ||
key: "_focused", | ||
@@ -747,5 +926,24 @@ value: function _focused() { | ||
var selection = window.getSelection(); | ||
var context = this.createSelectionContext(selection.focusNode); | ||
var element = selection.focusNode.parentElement; | ||
this.fire('clicked', { context: context, event: event, selection: selection, element: element }); | ||
if (selection.focusNode && selection.focusNode.parentElement) { | ||
var context = this.createSelectionContext(selection.focusNode); | ||
var element = selection.focusNode.parentElement; | ||
this.fire('clicked', { context: context, event: event, selection: selection, element: element }); | ||
} else { | ||
this.fire('clicked', { selection: selection, event: event }); | ||
} | ||
if (event.target == this.contentel && this.contentel.offsetHeight - event.offsetY < 60 && editor.contentel.scrollHeight - this.contentel.getBoundingClientRect().height - this.contentel.scrollTop < 30) { | ||
var newParag = document.createElement(this.settings.breaktag); | ||
newParag.appendChild(document.createElement('br')); | ||
this.contentel.appendChild(newParag); | ||
var range = document.createRange(); | ||
range.selectNode(newParag); | ||
range.collapse(true); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
this.contentel.scrollTop = editor.contentel.scrollHeight - this.contentel.getBoundingClientRect().height; | ||
} | ||
} | ||
@@ -792,7 +990,15 @@ }, { | ||
key: "storeRange", | ||
value: function storeRange() { | ||
var tempSelection = window.getSelection(); | ||
value: function storeRange(maybeRange) { | ||
if (maybeRange) { | ||
this._tempRange = maybeRange; | ||
} else { | ||
var tempSelection = window.getSelection(); | ||
if (tempSelection.focusNode) { | ||
this._tempRange = tempSelection.getRangeAt(0).cloneRange(); | ||
if (tempSelection.type == "None" || !tempSelection.focusNode) { | ||
var range = document.createRange(); | ||
range.setStart(this.contentel, 0); | ||
this._tempRange = range; | ||
} else { | ||
this._tempRange = tempSelection.getRangeAt(0).cloneRange(); | ||
} | ||
} | ||
@@ -806,2 +1012,3 @@ | ||
var sel = window.getSelection(); | ||
if (!this.focused) { | ||
@@ -840,3 +1047,3 @@ sel.removeAllRanges(); | ||
var data = e.clipboardData || window.clipboardData; | ||
var eventresult = this.fire('paste', data); | ||
var eventresult = this.fire('paste', { dataTransfer: data, event: e }); | ||
@@ -843,0 +1050,0 @@ if (eventresult && eventresult.includes(false)) { |
@@ -18,6 +18,17 @@ class LiliumTextCommand { | ||
el.addEventListener('click', (ev) => { | ||
el.addEventListener('mousedown', ev => { | ||
ev.preventDefault(); | ||
ev.stopPropagation(); | ||
return false; | ||
}); | ||
el.addEventListener('mouseup', (ev) => { | ||
ev.preventDefault(); | ||
ev.stopPropagation(); | ||
editor.log('Executed command ' + this.webName + (this.param ? (" with parameter '" + this.param + "'") : '')); | ||
editor.fire('command', this.webName); | ||
this.execute(ev, this, editor); | ||
return false; | ||
}); | ||
@@ -29,2 +40,43 @@ | ||
class LiliumTextBrowserCompat { | ||
static runCommandOrFallback(command, arg, cb) { | ||
document.execCommand(command, false, arg) || cb(command, arg); | ||
} | ||
static nodeToCommand(nodetype) { | ||
switch (nodetype) { | ||
case "strong": | ||
case "b": | ||
return "bold"; | ||
case "italic": | ||
case "em": | ||
return "italic"; | ||
case "underline": | ||
case "u": | ||
return "underline"; | ||
case "strike": | ||
case "s": | ||
return "strikeThrough"; | ||
// I hate not having a default case... | ||
default : return undefined; | ||
} | ||
} | ||
static getStrategyKind() { | ||
if (window.navigator) { | ||
if (document.execCommand) { | ||
return "exec"; | ||
} else { | ||
return "logic"; | ||
} | ||
} else { | ||
return "old"; | ||
} | ||
} | ||
} | ||
class LiliumTextWebCommand extends LiliumTextCommand { | ||
@@ -41,101 +93,163 @@ constructor(webName, param, cssClass, imageURL, text) { | ||
highlightNode(node) { | ||
const selection = this.editor.restoreSelection(); | ||
const range = document.createRange(); | ||
this.editor.log("Highlighting node " + node.nodeName + " from Web Command"); | ||
range.selectNode(node); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
this.editor.storeRange(range); | ||
} | ||
executeText() { | ||
const selection = this.editor.restoreSelection(); | ||
const nodetype = this.param; | ||
const cmdtype = LiliumTextBrowserCompat.nodeToCommand(nodetype); | ||
if (selection.type == "Caret") { | ||
const context = this.editor.createSelectionContext(selection.focusNode); | ||
let maybeCtxElem = context.find(x => x.type == nodetype); | ||
if (maybeCtxElem) { | ||
this.editor.log('Unwrapping element of node type ' + nodetype); | ||
const el = maybeCtxElem.element; | ||
this.editor.unwrap(el); | ||
} | ||
} else if (selection.type == "Range") { | ||
const [left, right] = selection.anchorNode.compareDocumentPosition(selection.focusNode) & Node.DOCUMENT_POSITION_FOLLOWING ? | ||
[selection.anchorNode, selection.focusNode] : [selection.focusNode, selection.anchorNode]; | ||
LiliumTextBrowserCompat.runCommandOrFallback(cmdtype, this.param, () => { | ||
if (selection.type == "Caret") { | ||
const context = this.editor.createSelectionContext(selection.focusNode); | ||
let maybeCtxElem = context.find(x => x.type == nodetype); | ||
if (maybeCtxElem) { | ||
this.editor.log('Unwrapping element of node type ' + nodetype); | ||
this.editor.unwrap(maybeCtxElem); | ||
} | ||
} else if (selection.type == "Range") { | ||
/* | ||
* CONTEXT VARIABLES DEFINITION ------ | ||
* | ||
* left, right : Nodes where selection starts and ends. Everything in between is located inside the fragment. | ||
* leftCtx, rightCtx : Array containing nodes from the selected one up to the editor one | ||
* leftExistWrap, rightExistWrap : Wrapped node if node already exists, otherwise undefined | ||
* | ||
* range : current selection range object | ||
* frag : fragment containing everything from inside the selection. Not a copy; actual nodes. | ||
* | ||
**/ | ||
const [leftCtx, rightCtx] = [this.editor.createSelectionContext(left), this.editor.createSelectionContext(right)]; | ||
const [leftExistWrap, rightExistWrap] = [leftCtx.find(x => x.type == nodetype), rightCtx.find(x => x.type == nodetype)]; | ||
// Fun! :D | ||
this.editor.log("Long logic with range using node type " + nodetype); | ||
const range = selection.getRangeAt(0); | ||
const frag = range.extractContents(); | ||
const capNodeType = nodetype.toUpperCase(); | ||
const [left, right] = selection.anchorNode.compareDocumentPosition(selection.focusNode) & Node.DOCUMENT_POSITION_FOLLOWING ? | ||
[selection.anchorNode, selection.focusNode] : [selection.focusNode, selection.anchorNode]; | ||
if (frag.childNodes[0].nodeName == "#text" && !frag.childNodes[0].data.trim()) { | ||
this.editor.log("Removed extra empty text node from fragment"); | ||
range.insertNode(frag.childNodes[0]); | ||
} | ||
const [leftCtx, rightCtx] = [this.editor.createSelectionContext(left), this.editor.createSelectionContext(right)]; | ||
let [leftExistWrap, rightExistWrap] = [leftCtx.find(x => x.nodeName == capNodeType), rightCtx.find(x => x.nodeName == capNodeType)]; | ||
// Fun! :D | ||
this.editor.log("Long logic with range using node type " + nodetype); | ||
const range = selection.getRangeAt(0); | ||
const frag = range.extractContents(); | ||
if (left.parentElement === right.parentElement && !leftExistWrap) { | ||
this.editor.log("Quick range wrap with element of node type " + nodetype); | ||
const newElem = document.createElement(nodetype); | ||
newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(newElem); | ||
} else if (!leftExistWrap != !rightExistWrap) { | ||
// Apparently there is no XOR in Javascript, so here's a syntax monstrosity | ||
// This will not execute the block unless one is truthy and one is falsey | ||
this.editor.log('XOR range wrapper extension of node type ' + nodetype); | ||
/* | ||
if (frag.childNodes[0].nodeName == "#text" && !frag.childNodes[0].data.trim()) { | ||
this.editor.log("Removed extra empty text node from fragment"); | ||
range.insertNode(frag.childNodes[0]); | ||
} | ||
*/ | ||
const newElem = document.createElement(nodetype); | ||
newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(newElem); | ||
let fragWrap = !leftExistWrap && !rightExistWrap && frag.querySelectorAll(nodetype); | ||
// Extend existing wrapper | ||
const wrapper = leftExistWrap || rightExistWrap; | ||
Array.prototype.forEach.call(wrapper.element.querySelectorAll(nodetype), node => { | ||
this.editor.unwrap(node); | ||
}); | ||
} else if (leftExistWrap && rightExistWrap && leftExistWrap.element === rightExistWrap.element) { | ||
// Unwrap both ends, possible solution : while (textnode has next sibling) { insert sibling after wrapper node } | ||
this.editor.log("Placeholder unwrap from two sources with node types : " + nodetype); | ||
const placeholder = document.createElement('liliumtext-placeholder'); | ||
selection.getRangeAt(0).insertNode(placeholder); | ||
if (left.parentElement === right.parentElement && !leftExistWrap && !fragWrap.length) { | ||
this.editor.log("Quick range wrap with element of node type " + nodetype); | ||
const leftEl = leftExistWrap.element; | ||
const clone = leftEl.cloneNode(); | ||
leftEl.parentElement.insertBefore(clone, leftEl); | ||
// Might be worth looking at Range.surroundContents() | ||
const newElem = document.createElement(nodetype); | ||
newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(newElem); | ||
while (leftEl.firstChild != placeholder) { | ||
clone.appendChild(leftEl.firstChild); | ||
} | ||
this.highlightNode(newElem); | ||
} else if (!leftExistWrap != !rightExistWrap) { | ||
// Apparently there is no XOR in Javascript, so here's a syntax monstrosity | ||
// This will not execute the block unless one is truthy and one is falsey | ||
this.editor.log('XOR range wrapper extension of node type ' + nodetype); | ||
leftEl.parentElement.insertBefore(frag, leftEl); | ||
placeholder.remove(); | ||
const newElem = document.createElement(nodetype); | ||
newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(newElem); | ||
this.editor.log('Removing empty trailing <' + nodetype + '> element'); | ||
!clone.textContent.trim() && clone.remove(); | ||
!leftEl.textContent.trim() && leftEl.remove(); | ||
} else if (leftExistWrap && rightExistWrap) { | ||
this.editor.log("Merge wrap from two sources with node types : " + nodetype); | ||
// Merge wrap | ||
const leftFrag = frag.firstChild; | ||
const rightFrag = frag.lastChild; | ||
while (leftFrag.nextSibling != rightFrag) { | ||
leftFrag.appendChild(leftFrag.nextSibling); | ||
} | ||
// Extend existing wrapper | ||
const wrapper = leftExistWrap || rightExistWrap; | ||
Array.prototype.forEach.call(wrapper.querySelectorAll(nodetype), node => { | ||
this.editor.unwrap(node); | ||
}); | ||
while(rightFrag.firstChild) { | ||
leftFrag.appendChild(rightFrag.firstChild); | ||
} | ||
this.highlightNode(node); | ||
} else if (fragWrap.length != 0) { | ||
// There is an element inside the fragment with requested node name | ||
// Unwrap child element | ||
this.editor.log('Fragment child unwrap with node type ' + nodetype); | ||
Array.prototype.forEach.call(fragWrap, elem => { | ||
while (elem.firstChild) { | ||
elem.parentNode ? | ||
elem.parentNode.insertBefore(elem.firstChild, elem) : | ||
frag.insertBefore(elem.firstChild, frag); | ||
} | ||
}); | ||
rightFrag.remove(); | ||
selection.getRangeAt(0).insertNode(frag); | ||
} else if (frag.childNodes.length == 1) { | ||
// Entire element is selected, Unwrap entire element | ||
this.editor.log("Single unwrap of node type : " + nodetype); | ||
const wrap = frag.childNodes[0]; | ||
while (wrap.lastChild) { | ||
selection.getRangeAt(0).insertNode(wrap.lastChild); | ||
Array.prototype.forEach.call(fragWrap, elem => elem && elem.remove && elem.remove()); | ||
selection.getRangeAt(0).insertNode(frag); | ||
} else if (leftExistWrap && rightExistWrap && leftExistWrap === rightExistWrap) { | ||
// Unwrap both ends, possible solution : while (textnode has next sibling) { insert sibling after wrapper node } | ||
this.editor.log("Placeholder unwrap from two sources with node types : " + nodetype); | ||
const placeholder = document.createElement('liliumtext-placeholder'); | ||
selection.getRangeAt(0).insertNode(placeholder); | ||
const leftEl = leftExistWrap; | ||
const clone = leftEl.cloneNode(true); | ||
leftEl.parentNode.insertBefore(clone, leftEl); | ||
const clonePlaceholder = clone.querySelector('liliumtext-placeholder'); | ||
while (clonePlaceholder.nextSibling) { | ||
clonePlaceholder.nextSibling.remove(); | ||
} | ||
while (placeholder.previousSibling) { | ||
placeholder.previousSibling.remove(); | ||
} | ||
leftEl.parentNode.insertBefore(frag, leftEl); | ||
placeholder.remove(); | ||
clonePlaceholder.remove(); | ||
this.highlightNode(clone); | ||
} else if (leftExistWrap && rightExistWrap) { | ||
this.editor.log("Merge wrap from two sources with node types : " + nodetype); | ||
// Merge wrap | ||
const leftFrag = frag.firstChild; | ||
const rightFrag = frag.lastChild; | ||
while (leftFrag.nextSibling != rightFrag) { | ||
leftFrag.appendChild(leftFrag.nextSibling); | ||
} | ||
while(rightFrag.firstChild) { | ||
leftFrag.appendChild(rightFrag.firstChild); | ||
} | ||
rightFrag.remove(); | ||
selection.getRangeAt(0).insertNode(frag); | ||
} else if (frag.childNodes.length == 1 && frag.childNodes[0].nodeName == nodetype) { | ||
// Entire element is selected, Unwrap entire element | ||
this.editor.log("Single unwrap of node type : " + nodetype); | ||
const wrap = frag.childNodes[0]; | ||
while (wrap.lastChild) { | ||
selection.getRangeAt(0).insertNode(wrap.lastChild); | ||
} | ||
} else { | ||
// Create new element, insert before selection | ||
this.editor.log("Fragment wrap with node type : " + nodetype); | ||
const newElem = document.createElement(nodetype); | ||
newElem.appendChild(frag); | ||
const range = selection.getRangeAt(0); | ||
range.insertNode(newElem); | ||
range.selectNode(newElem); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
} else { | ||
// Create new element, insert before selection | ||
this.editor.log("Fragment wrap with node type : " + nodetype); | ||
const newElem = document.createElement(nodetype); | ||
newElem.appendChild(frag); | ||
selection.getRangeAt(0).insertNode(newElem); | ||
} | ||
} | ||
}); | ||
} | ||
@@ -152,38 +266,48 @@ | ||
const nodetype = this.param; | ||
const context = this.editor.createSelectionContext(selection.focusNode); | ||
const blocktags = this.editor.settings.blockelements; | ||
const cmdnodearg = nodetype.toUpperCase(); | ||
const topLevelTag = context[context.length - 1].element; | ||
LiliumTextBrowserCompat.runCommandOrFallback('formatBlock', cmdnodearg, () => { | ||
const context = this.editor.createSelectionContext(selection.focusNode); | ||
const blocktags = this.editor.settings.blockelements; | ||
if (topLevelTag.nodeName != nodetype) { | ||
const newNode = document.createElement(nodetype); | ||
topLevelTag.parentElement.insertBefore(newNode, topLevelTag); | ||
const topLevelTag = context && context.length ? | ||
context[context.length - 1] : | ||
this.editor.contentel.children[selection.focusOffset]; | ||
if (topLevelTag.data) { | ||
newNode.appendChild(topLevelTag); | ||
} else { | ||
while (topLevelTag.firstChild) { | ||
newNode.appendChild(topLevelTag.firstChild); | ||
if (topLevelTag.nodeName != nodetype) { | ||
const newNode = document.createElement(nodetype); | ||
topLevelTag.parentElement.insertBefore(newNode, topLevelTag); | ||
if (topLevelTag.data) { | ||
newNode.appendChild(topLevelTag); | ||
} else { | ||
while (topLevelTag.firstChild) { | ||
newNode.appendChild(topLevelTag.firstChild); | ||
} | ||
topLevelTag.remove(); | ||
} | ||
topLevelTag.remove(); | ||
} | ||
range.setStart(newNode, 1); | ||
range.collapse(true); | ||
range.setStart(newNode, 0); | ||
range.collapse(true); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
}); | ||
} | ||
executeRemove() { | ||
if (this.param) { | ||
const el = this.editor.restoreSelection().focusNode.parentElement; | ||
if (this.param == "a") { | ||
this.editor.restoreSelection(); | ||
document.execCommand('unlink', false); | ||
} else if (this.param) { | ||
const el = this.editor.restoreSelection().focusNode.parentNode; | ||
const context = this.editor.createSelectionContext(el); | ||
const wrapperCtx = context.find(x => x.type == this.param); | ||
const upperNode = this.param.toUpperCase(); | ||
const wrapperCtx = context.find(x => x.nodeName == upperNode); | ||
if (wrapperCtx) { | ||
this.editor.log('Unwrapping node ' + this.param); | ||
this.editor.unwrap(wrapperCtx.element); | ||
this.editor.unwrap(wrapperCtx); | ||
} | ||
@@ -203,13 +327,22 @@ } else { | ||
const el = selection.focusNode; | ||
const context = this.editor.createSelectionContext(el); | ||
const topLevelEl = context[context.length - 1].element; | ||
this.editor.contentel.insertBefore(newNode, topLevelEl.nextElementSibling); | ||
const range = selection.getRangeAt(0); | ||
range.setStart(newNode.nextElementSibling || topLevelEl.nextElementSibling || newNode, 0); | ||
range.collapse(true); | ||
if (el == this.editor.contentel) { | ||
this.editor.contentel.insertBefore(newNode, this.editor.contentel.children[selection.focusOffset]); | ||
} else { | ||
const context = this.editor.createSelectionContext(el); | ||
const topLevelEl = context[context.length - 1]; | ||
this.editor.contentel.insertBefore(newNode, topLevelEl.nextElementSibling); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
if (selection.focusOffset == 0) { | ||
} else { | ||
const range = selection.getRangeAt(0); | ||
range.setStart(newNode.nextElementSibling || topLevelEl.nextElementSibling || newNode, 0); | ||
range.collapse(true); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
} | ||
} | ||
@@ -469,3 +602,3 @@ | ||
while (elem != this.contentel && elem) { | ||
context.push({ type : elem.nodeName.toLowerCase().replace('#', ''), element : elem }); | ||
context.push(elem); | ||
elem = elem.parentNode; | ||
@@ -507,3 +640,3 @@ } | ||
unwrap(el) { | ||
const par = el.parentElement; | ||
const par = el.parentNode; | ||
while(el.firstChild) { | ||
@@ -574,2 +707,37 @@ par.insertBefore(el.firstChild, el); | ||
insertBlock(element) { | ||
const selection = this.restoreSelection(); | ||
const context = this.createSelectionContext(selection.focusNode); | ||
const ctxElem = context[context.length-1]; | ||
if (selection.focusNode == this.contentel) { | ||
this.contentel.insertBefore(element, this.contentel.children[selection.focusOffset]); | ||
const range = selection.getRangeAt(0).cloneRange(); | ||
range.setEndAfter(element); | ||
range.collapse(false); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} else { | ||
if (ctxElem && ctxElem.nextSibling) { | ||
const curParag = ctxElem; | ||
if (curParag) { | ||
curParag.parentNode.insertBefore(element, selection.focusOffset == 0 ? curParag : curParag.nextSibling); | ||
} | ||
} else { | ||
this.contentel.appendChild(element); | ||
} | ||
if (selection.focusOffset != 0) { | ||
const range = selection.getRangeAt(0).cloneRange(); | ||
range.setEndAfter(element); | ||
range.collapse(false); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
} | ||
} | ||
_focused() { | ||
@@ -587,5 +755,27 @@ const eventresult = this.fire('focus'); | ||
const selection = window.getSelection(); | ||
const context = this.createSelectionContext(selection.focusNode); | ||
const element = selection.focusNode.parentElement; | ||
this.fire('clicked', { context, event, selection, element }); | ||
if (selection.focusNode && selection.focusNode.parentElement) { | ||
const context = this.createSelectionContext(selection.focusNode); | ||
const element = selection.focusNode.parentElement; | ||
this.fire('clicked', { context, event, selection, element }); | ||
} else { | ||
this.fire('clicked', { selection, event }); | ||
} | ||
if (event.target == this.contentel && | ||
this.contentel.offsetHeight - event.offsetY < 60 && | ||
editor.contentel.scrollHeight - this.contentel.getBoundingClientRect().height - this.contentel.scrollTop < 30 | ||
) { | ||
var newParag = document.createElement(this.settings.breaktag); | ||
newParag.appendChild(document.createElement('br')); | ||
this.contentel.appendChild(newParag); | ||
var range = document.createRange(); | ||
range.selectNode(newParag); | ||
range.collapse(true); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
this.contentel.scrollTop = editor.contentel.scrollHeight - this.contentel.getBoundingClientRect().height; | ||
} | ||
} | ||
@@ -629,7 +819,15 @@ | ||
storeRange() { | ||
const tempSelection = window.getSelection(); | ||
storeRange(maybeRange) { | ||
if (maybeRange) { | ||
this._tempRange = maybeRange; | ||
} else { | ||
const tempSelection = window.getSelection(); | ||
if (tempSelection.focusNode) { | ||
this._tempRange = tempSelection.getRangeAt(0).cloneRange(); | ||
if (tempSelection.type == "None" || !tempSelection.focusNode) { | ||
const range = document.createRange(); | ||
range.setStart(this.contentel, 0); | ||
this._tempRange = range; | ||
} else { | ||
this._tempRange = tempSelection.getRangeAt(0).cloneRange(); | ||
} | ||
} | ||
@@ -642,2 +840,3 @@ | ||
const sel = window.getSelection(); | ||
if (!this.focused) { | ||
@@ -672,3 +871,3 @@ sel.removeAllRanges(); | ||
const data = e.clipboardData || window.clipboardData; | ||
const eventresult = this.fire('paste', data); | ||
const eventresult = this.fire('paste', { dataTransfer : data, event : e }); | ||
@@ -675,0 +874,0 @@ if (eventresult && eventresult.includes(false)) { |
{ | ||
"name": "lilium-text", | ||
"version": "1.0.9", | ||
"description": "Web rich text editor. Not ready to be used at all.", | ||
"main": "build/liliumtext.js", | ||
"version": "1.3.0", | ||
"description": "Web rich text editor. Ready to be used in a test environment.", | ||
"main": "build/liliumtext.min.js", | ||
"scripts": { | ||
"build": "lessc dev/minim.less build/minim.css && uglifycss build/minim.css > build/minim.min.css && babel dev/liliumtext.js -o build/liliumtext.js", | ||
"build": "lessc dev/minim.less build/minim.css && uglifycss build/minim.css > build/minim.min.css && babel dev/liliumtext.js -o build/liliumtext.js && uglifyjs --compress --mangle --output build/liliumtext.min.js -- build/liliumtext.js", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
@@ -27,4 +27,5 @@ }, | ||
"less": "^3.0.0", | ||
"uglify-js": "^3.3.16", | ||
"uglifycss": "0.0.27" | ||
} | ||
} |
# Lilium Text | ||
A lightweight, dependency-free, extensible text editor. | ||
A *24kb*, dependency-free, extensible text editor. [TRY IT HERE](https://erikdesjardins.com/experiments/lilium-text/dev/test.html). | ||
![First Screenshot](https://erikdesjardins.com/static/git/liliumtext-1.jpg) | ||
![First Screenshot](https://raw.githubusercontent.com/rykdesjardins/lilium-text/master/assets/screenshot.png) | ||
@@ -11,2 +11,38 @@ ## About the project | ||
## Adding it to your project | ||
The project is still in early development and **is not ready to be used in production**. This is why we are using a single branch for now. | ||
If you would like to try it, it is currently published on [npm](https://www.npmjs.com/package/lilium-text), and you should be able to install it using `npm install lilium-text`. | ||
### For dynamic / ES6+ projects | ||
```javascript | ||
import { LiliumText } from 'lilium-text'; | ||
// OR | ||
const LiliumText = require('lilium-text').LiliumText; | ||
``` | ||
### For static projects | ||
If you clone from `npm`, your build will be under `node_modules/build/liliumtext.js`. Depending on how you setup your project, you might want to set a custom rule in your webserver, or simply move the file elsewhere. | ||
```html | ||
<script src="your/assets/directory/liliumtext.js"></script> | ||
``` | ||
### Exported classes | ||
The following classes are exported from the final build. | ||
- **LiliumText** | ||
- LiliumTextCustomCommand | ||
- LiliumTextWebCommand | ||
- LiliumTextCommand | ||
- LiliumTextPlugin | ||
All exported classes except the first one are abstract and are meant to be extended like so : | ||
```javascript | ||
class myPlugin extends LiliumTextPlugin { | ||
constructor(liliumTextInstance) { | ||
super('myPlugin'); | ||
this.instance = liliumTextInstance; | ||
} | ||
} | ||
``` | ||
## Compiling | ||
@@ -75,3 +111,3 @@ Lilium Text uses [Babel](https://babeljs.io/) to compile ES6 files into "browser Javascript". Simply install all required packages once using `npm install`, then run `npm run build`. Compiled files will be located under `/build`. | ||
| focus | The text portion of the editor was focused | | | ||
| paste | The user pasted content into the editor | Object `DataTransfer` | | ||
| paste | The user pasted content into the editor | Object : `{ dataTransfer, event }` | | ||
| code | Toggle between text view and html view | Boolean, true if code view | | ||
@@ -88,4 +124,4 @@ | willrender | Editor is about to render | | | ||
```javascript | ||
editor.bind('someEvent', (thatEvent, eventArgs) => { | ||
editor === thatEvent // true | ||
editor.bind('someEvent', (thatEditor, eventArgs) => { | ||
editor === thatEditor // true | ||
}); | ||
@@ -110,2 +146,17 @@ ``` | ||
### The paste event | ||
Since older browsers do not handle the paste event the same way, the original event is passed as well as the `dataTransfer` object fetched from either the event itself, or `window`. | ||
It is possible to cancel the original paste event and do something else by returning `false`, and calling `event.preventDefault()` like so : | ||
```javascript | ||
editor.bind('paste', (thatEditor, pasteArgs) => { | ||
// Will prevent the browser from pasting the text | ||
pasteArgs.preventDefault(); | ||
/* Execute your logic */ | ||
// Will prevent LiliumText from executing its paste logic | ||
return false; | ||
}); | ||
``` | ||
## Adding commands | ||
@@ -232,1 +283,12 @@ It is possible to add custom commands in the top bar. This can be done by calling the `addCommand` instance method, and by passing a new `LiliumTextCustomCommand` object. The following example takes for granted that the programmer uses FontAwesome. | ||
``` | ||
## Future plans | ||
Since I wanted to explore the latest In-the-Browser Javascript features, the project is currently not really backward compatible. It would be great if it were. | ||
I also plan on making it a [React](https://reactjs.org/) component. | ||
The build is dependency-free (aside from Babel), and it will remain like so. | ||
If Narcity Media is open to it, I'd like to host builds on our CDN and let everyone use it. | ||
Finally, since the editor allows anyone to create their own plugins, I'd like to create a small website where anyone would be able to submit their plugins for a more centralized showcasing. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1438128
26
2127
290
8