Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

lilium-text

Package Overview
Dependencies
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lilium-text - npm Package Compare versions

Comparing version 1.0.9 to 1.3.0

assets/screenshot.png

479

build/liliumtext.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc