mirror of
https://github.com/codex-team/editor.js
synced 2026-03-18 08:29:52 +01:00
Caret module: initial
This commit is contained in:
parent
106209d91b
commit
c2acc25825
9 changed files with 811 additions and 52 deletions
|
|
@ -199,6 +199,15 @@ var Util = function () {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns basic keycodes as constants
|
||||
* @return {{}}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'sequence',
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Object} ChainData
|
||||
* @property {Object} data - data that will be passed to the success or fallback
|
||||
|
|
@ -214,9 +223,6 @@ var Util = function () {
|
|||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'sequence',
|
||||
value: function sequence(chains) {
|
||||
var success = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
|
||||
var fallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
|
||||
|
|
@ -315,6 +321,57 @@ var Util = function () {
|
|||
|
||||
return Promise.resolve(object) === object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed element is contenteditable
|
||||
* @param element
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'isContentEditable',
|
||||
value: function isContentEditable(element) {
|
||||
|
||||
return element.contentEditable === 'true';
|
||||
}
|
||||
}, {
|
||||
key: 'keyCodes',
|
||||
get: function get() {
|
||||
|
||||
return {
|
||||
BACKSPACE: 8,
|
||||
TAB: 9,
|
||||
ENTER: 13,
|
||||
SHIFT: 16,
|
||||
CTRL: 17,
|
||||
ALT: 18,
|
||||
ESC: 27,
|
||||
SPACE: 32,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
RIGHT: 39,
|
||||
DELETE: 46,
|
||||
META: 91
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns basic nodetypes as contants
|
||||
* @return {{TAG: number, TEXT: number, COMMENT: number, DOCUMENT_FRAGMENT: number}}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'nodeTypes',
|
||||
get: function get() {
|
||||
|
||||
return {
|
||||
TAG: 1,
|
||||
TEXT: 3,
|
||||
COMMENT: 8,
|
||||
DOCUMENT_FRAGMENT: 11
|
||||
};
|
||||
}
|
||||
}]);
|
||||
|
||||
return Util;
|
||||
|
|
@ -345,7 +402,7 @@ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr
|
|||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
/**
|
||||
* DOM manupulations helper
|
||||
* DOM manipulations helper
|
||||
*/
|
||||
var Dom = function () {
|
||||
function Dom() {
|
||||
|
|
@ -388,6 +445,20 @@ var Dom = function () {
|
|||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates text Node with content
|
||||
*
|
||||
* @param {String} content - text content
|
||||
* @return {Text}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'text',
|
||||
value: function text(content) {
|
||||
|
||||
return document.createTextNode(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append one or several elements to the parent
|
||||
*
|
||||
|
|
@ -451,6 +522,52 @@ var Dom = function () {
|
|||
return el.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for deepest node
|
||||
*
|
||||
* @param {Element} node - start Node
|
||||
* @param {Boolean} atLast - find last text node
|
||||
* @return {*}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'getDeepestTextNode',
|
||||
value: function getDeepestTextNode(node) {
|
||||
var atLast = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||
|
||||
|
||||
if (node.childNodes.length === 0) {
|
||||
|
||||
/**
|
||||
* We need to return empty text node
|
||||
* But caret will not be placed in empty textNode, so we need textNode with zero-width char
|
||||
*/
|
||||
if (this.isElement(node)) {
|
||||
|
||||
/** and it is not native input */
|
||||
if (!this.isNativeInput(node)) {
|
||||
|
||||
var emptyTextNode = this.text('\u200B');
|
||||
|
||||
node.appendChild(emptyTextNode);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
var childsLength = node.childNodes.length,
|
||||
last = childsLength - 1;
|
||||
|
||||
if (atLast) {
|
||||
|
||||
return this.getDeepestTextNode(node.childNodes[last], atLast);
|
||||
} else {
|
||||
|
||||
return this.getDeepestTextNode(node.childNodes[0], false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if object is DOM node
|
||||
*
|
||||
|
|
@ -464,6 +581,96 @@ var Dom = function () {
|
|||
|
||||
return node && (typeof node === 'undefined' ? 'undefined' : _typeof(node)) === 'object' && node.nodeType && node.nodeType === Node.ELEMENT_NODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks target if it is native input
|
||||
* @param {Element|*} target - HTML element or string
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'isNativeInput',
|
||||
value: function isNativeInput(target) {
|
||||
|
||||
var nativeInputs = ['INPUT', 'TEXTAREA'];
|
||||
|
||||
return nativeInputs.indexOf(target.tagName) !== -1;
|
||||
}
|
||||
}, {
|
||||
key: 'isEmpty',
|
||||
value: function isEmpty(node) {
|
||||
var _this = this;
|
||||
|
||||
var treeWalker = [],
|
||||
stack = [];
|
||||
|
||||
treeWalker.push(node);
|
||||
|
||||
while (treeWalker.length > 0) {
|
||||
|
||||
if (node && node.childNodes.length === 0) {
|
||||
|
||||
stack.push(node);
|
||||
}
|
||||
|
||||
while (node && node.nextSibling) {
|
||||
|
||||
node = node.nextSibling;
|
||||
|
||||
if (!node) continue;
|
||||
|
||||
if (node.childNodes.length === 0) {
|
||||
|
||||
stack.push(node);
|
||||
}
|
||||
|
||||
treeWalker.push(node);
|
||||
}
|
||||
|
||||
node = treeWalker.shift();
|
||||
|
||||
if (!node) continue;
|
||||
|
||||
node = node.firstChild;
|
||||
treeWalker.push(node);
|
||||
}
|
||||
|
||||
var isEmpty = true;
|
||||
|
||||
stack.forEach(function (node) {
|
||||
|
||||
if (_this.isElement(node)) {
|
||||
|
||||
if (_this.isNativeInput(node)) {
|
||||
|
||||
node = node.value;
|
||||
|
||||
if (node.trim()) {
|
||||
|
||||
isEmpty = false;
|
||||
}
|
||||
} else {
|
||||
|
||||
node = node.textContent.replace('\u200B', '');
|
||||
|
||||
if (node.trim()) {
|
||||
|
||||
isEmpty = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
node = node.textContent.replace('\u200B', '');
|
||||
|
||||
if (node.trim()) {
|
||||
|
||||
isEmpty = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return isEmpty;
|
||||
}
|
||||
}]);
|
||||
|
||||
return Dom;
|
||||
|
|
@ -1263,7 +1470,7 @@ webpackContext.id = 6;
|
|||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
/* WEBPACK VAR INJECTION */(function(Module, $, _) {
|
||||
/* WEBPACK VAR INJECTION */(function(Module, _, $) {
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
|
|
@ -1382,6 +1589,8 @@ var BlockManager = function (_Module) {
|
|||
var toolInstance = this.Editor.Tools.construct(toolName, data),
|
||||
block = new _block2.default(toolName, toolInstance);
|
||||
|
||||
this.bindEvents(block);
|
||||
|
||||
/**
|
||||
* Apply callback before inserting html
|
||||
*/
|
||||
|
|
@ -1390,6 +1599,73 @@ var BlockManager = function (_Module) {
|
|||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind Events
|
||||
* @param {Object} block
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'bindEvents',
|
||||
value: function bindEvents(block) {
|
||||
var _this3 = this;
|
||||
|
||||
/** contentNode click handler */
|
||||
block.wrapper.addEventListener('click', function (event) {
|
||||
return _this3.wrapperClicked(event);
|
||||
}, false);
|
||||
|
||||
/** keydown on block */
|
||||
block.pluginsContent.addEventListener('keydown', function (event) {
|
||||
return _this3.keyDownOnBlock(event);
|
||||
}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight clicked block
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'wrapperClicked',
|
||||
value: function wrapperClicked(event) {
|
||||
|
||||
this.setCurrentBlockByChildNode(event.target);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'keyDownOnBlock',
|
||||
value: function keyDownOnBlock(event) {
|
||||
|
||||
switch (event.keyCode) {
|
||||
|
||||
case _.keyCodes.ENTER:
|
||||
this.enterPressedOnPluginsContent(event);
|
||||
break;
|
||||
case _.keyCodes.DOWN:
|
||||
case _.keyCodes.RIGHT:
|
||||
this.blockRightOrDownArrowPressed(event);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'blockRightOrDownArrowPressed',
|
||||
value: function blockRightOrDownArrowPressed(event) {
|
||||
|
||||
console.log(this.getNextBlock());
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new block into _blocks
|
||||
*
|
||||
|
|
@ -1406,6 +1682,8 @@ var BlockManager = function (_Module) {
|
|||
var block = this.composeBlock(toolName, data);
|
||||
|
||||
this._blocks[++this.currentBlockIndex] = block;
|
||||
|
||||
this.Editor.Caret.set(block.pluginsContent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1426,6 +1704,42 @@ var BlockManager = function (_Module) {
|
|||
this._blocks.insert(this.currentBlockIndex, block, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'getLastBlock',
|
||||
value: function getLastBlock() {
|
||||
|
||||
return this._blocks[this._blocks.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param index
|
||||
* @return {*}
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'getBlockByIndex',
|
||||
value: function getBlockByIndex(index) {
|
||||
|
||||
return this._blocks[index];
|
||||
}
|
||||
}, {
|
||||
key: 'getNextBlock',
|
||||
value: function getNextBlock() {
|
||||
|
||||
if (this.currentBlockIndex + 1 > this._blocks.length - 1) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._blocks[this.currentBlockIndex + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Block instance by html element
|
||||
*
|
||||
|
|
@ -1554,6 +1868,13 @@ var BlockManager = function (_Module) {
|
|||
return BlockManager;
|
||||
}(Module);
|
||||
|
||||
BlockManager.displayName = 'BlockManager';
|
||||
exports.default = BlockManager;
|
||||
|
||||
var BlockMethods = function BlockMethods() {
|
||||
_classCallCheck(this, BlockMethods);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class Blocks
|
||||
* @classdesc Class to work with Block instances array
|
||||
|
|
@ -1565,8 +1886,7 @@ var BlockManager = function (_Module) {
|
|||
*/
|
||||
|
||||
|
||||
BlockManager.displayName = 'BlockManager';
|
||||
exports.default = BlockManager;
|
||||
BlockMethods.displayName = 'BlockMethods';
|
||||
|
||||
var Blocks = function () {
|
||||
|
||||
|
|
@ -1787,14 +2107,14 @@ var Blocks = function () {
|
|||
|
||||
Blocks.displayName = 'Blocks';
|
||||
module.exports = exports['default'];
|
||||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0), __webpack_require__(2), __webpack_require__(1)))
|
||||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0), __webpack_require__(1), __webpack_require__(2)))
|
||||
|
||||
/***/ }),
|
||||
/* 8 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
/* WEBPACK VAR INJECTION */(function(Module) {
|
||||
/* WEBPACK VAR INJECTION */(function(Module, $) {
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
|
|
@ -1829,14 +2149,52 @@ var Caret = function (_Module) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set Caret to the last Block
|
||||
*
|
||||
* If last block is not empty, append another empty block
|
||||
* Creates Document Range and sets caret to the element.
|
||||
* @param {Element} element - target node.
|
||||
* @param {Number} offset - offset
|
||||
*/
|
||||
|
||||
|
||||
_createClass(Caret, [{
|
||||
key: 'setToTheLastBlock',
|
||||
key: "set",
|
||||
value: function set(element) {
|
||||
var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
||||
|
||||
|
||||
/** If Element is INPUT */
|
||||
if ($.isNativeInput(element)) {
|
||||
|
||||
element.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
var nodeToSet = $.getDeepestTextNode(element, true);
|
||||
|
||||
/** if found deepest node is native input */
|
||||
if ($.isNativeInput(nodeToSet)) {
|
||||
|
||||
nodeToSet.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
var range = document.createRange(),
|
||||
selection = window.getSelection();
|
||||
|
||||
range.setStart(nodeToSet, offset);
|
||||
range.setEnd(nodeToSet, offset);
|
||||
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
}, {
|
||||
key: "setToTheLastBlock",
|
||||
|
||||
|
||||
/**
|
||||
* Set Caret to the last Block
|
||||
*
|
||||
* If last block is not empty, append another empty block
|
||||
*/
|
||||
value: function setToTheLastBlock() {
|
||||
|
||||
var blocks = this.Editor.BlockManager.blocks,
|
||||
|
|
@ -1902,31 +2260,31 @@ var Caret = function (_Module) {
|
|||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Set caret to the passed Node
|
||||
* @param {Element} node - content-editable Element
|
||||
*/
|
||||
// /**
|
||||
// * Set caret to the passed Node
|
||||
// * @param {Element} node - content-editable Element
|
||||
// */
|
||||
// set(node) {
|
||||
//
|
||||
// /**
|
||||
// * @todo add working with Selection
|
||||
// * tmp: work with textContent
|
||||
// */
|
||||
//
|
||||
// node.textContent += '|';
|
||||
//
|
||||
// }
|
||||
|
||||
}, {
|
||||
key: 'set',
|
||||
value: function set(node) {
|
||||
|
||||
/**
|
||||
* @todo add working with Selection
|
||||
* tmp: work with textContent
|
||||
*/
|
||||
|
||||
node.textContent += '|';
|
||||
}
|
||||
}]);
|
||||
|
||||
return Caret;
|
||||
}(Module);
|
||||
|
||||
Caret.displayName = 'Caret';
|
||||
Caret.displayName = "Caret";
|
||||
exports.default = Caret;
|
||||
module.exports = exports['default'];
|
||||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0)))
|
||||
module.exports = exports["default"];
|
||||
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(0), __webpack_require__(2)))
|
||||
|
||||
/***/ }),
|
||||
/* 9 */
|
||||
|
|
@ -3987,6 +4345,13 @@ var UI = function (_Module) {
|
|||
|
||||
var clickedNode = event.target;
|
||||
|
||||
console.log('click', clickedNode);
|
||||
if (clickedNode.classList.contains(this.CSS.editorZone)) {
|
||||
|
||||
this.clickedOnRedactorZone(event);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select clicked Block as Current
|
||||
*/
|
||||
|
|
@ -4106,6 +4471,24 @@ var UI = function (_Module) {
|
|||
this.Editor.Toolbar.plusButton.show();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'clickedOnRedactorZone',
|
||||
value: function clickedOnRedactorZone(event) {
|
||||
|
||||
var lastBlock = this.Editor.BlockManager.getLastBlock(),
|
||||
pluginsContent = lastBlock.pluginsContent;
|
||||
|
||||
/**
|
||||
* If last block has text content, then insert new Block after
|
||||
*/
|
||||
if (!$.isEmpty(pluginsContent)) {
|
||||
|
||||
this.Editor.BlockManager.insert(this.config.initialBlock, {});
|
||||
} else {
|
||||
|
||||
this.Editor.Caret.set(pluginsContent);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: 'CSS',
|
||||
get: function get() {
|
||||
|
|
@ -4350,7 +4733,7 @@ exports = module.exports = __webpack_require__(19)(undefined);
|
|||
|
||||
|
||||
// module
|
||||
exports.push([module.i, ":root {\n\n /**\n * Toolbar buttons\n */\n\n /**\n * Block content width\n */\n\n /**\n * Toolbar Plus Button and Toolbox buttons height and width\n */\n\n}\n/**\n* Editor wrapper\n*/\n.codex-editor {\n position: relative;\n border: 1px solid #ccc;\n padding: 10px;\n box-sizing: border-box;\n}\n.codex-editor .hide {\n display: none;\n }\n.codex-editor__redactor {\n padding-bottom: 300px;\n }\n.ce-toolbar {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n opacity: 0;\n visibility: hidden;\n transition: opacity 100ms ease;\n will-change: opacity, transform;\n}\n.ce-toolbar--opened {\n opacity: 1;\n visibility: visible;\n }\n.ce-toolbar__content {\n max-width: 650px;\n margin: 0 auto;\n position: relative;\n }\n.ce-toolbar__plus {\n position: absolute;\n left: calc(-34px - 10px);\n display: inline-block;\n background-color: #eff2f5;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n border-radius: 50%\n }\n.ce-toolbar__plus::after {\n content: '+';\n font-size: 26px;\n display: block;\n margin-top: -2px;\n margin-right: -2px;\n\n}\n.ce-toolbar__plus--hidden {\n display: none;\n\n}\n.ce-toolbox {\n visibility: hidden;\n transition: opacity 100ms ease;\n will-change: opacity;\n}\n.ce-toolbox--opened {\n opacity: 1;\n visibility: visible;\n }\n.ce-toolbox__button {\n display: inline-block;\n list-style: none;\n margin: 0;\n background: #eff2f5;\n width: 34px;\n height: 34px;\n border-radius: 30px;\n overflow: hidden;\n text-align: center;\n line-height: 34px\n }\n.ce-toolbox__button::before {\n content: attr(title);\n font-size: 22px;\n font-weight: 500;\n letter-spacing: 1em;\n -webkit-font-feature-settings: \"smcp\", \"c2sc\";\n font-feature-settings: \"smcp\", \"c2sc\";\n font-variant-caps: all-small-caps;\n padding-left: 11.5px;\n margin-top: -1px;\n display: inline-block;\n\n}\n.ce-block {\n border: 1px dotted #ccc;\n margin: 2px 0;\n}\n.ce-block--selected {\n background-color: #eff2f5;\n }\n.ce-block__content {\n max-width: 650px;\n margin: 0 auto;\n }\n", ""]);
|
||||
exports.push([module.i, ":root {\n\n /**\n * Toolbar buttons\n */\n\n /**\n * Block content width\n */\n\n /**\n * Toolbar Plus Button and Toolbox buttons height and width\n */\n\n}\n/**\n* Editor wrapper\n*/\n.codex-editor {\n position: relative;\n border: 1px solid #ccc;\n padding: 10px;\n box-sizing: border-box;\n}\n.codex-editor .hide {\n display: none;\n }\n.codex-editor__redactor {\n padding-bottom: 300px;\n }\n.ce-toolbar {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n /*opacity: 0;*/\n /*visibility: hidden;*/\n transition: opacity 100ms ease;\n will-change: opacity, transform;\n display: none;\n}\n.ce-toolbar--opened {\n display: block;\n /*opacity: 1;*/\n /*visibility: visible;*/\n }\n.ce-toolbar__content {\n max-width: 650px;\n margin: 0 auto;\n position: relative;\n }\n.ce-toolbar__plus {\n position: absolute;\n left: calc(-34px - 10px);\n display: inline-block;\n background-color: #eff2f5;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n border-radius: 50%\n }\n.ce-toolbar__plus::after {\n content: '+';\n font-size: 26px;\n display: block;\n margin-top: -2px;\n margin-right: -2px;\n\n}\n.ce-toolbar__plus--hidden {\n display: none;\n\n}\n.ce-toolbox {\n visibility: hidden;\n transition: opacity 100ms ease;\n will-change: opacity;\n}\n.ce-toolbox--opened {\n opacity: 1;\n visibility: visible;\n }\n.ce-toolbox__button {\n display: inline-block;\n list-style: none;\n margin: 0;\n background: #eff2f5;\n width: 34px;\n height: 34px;\n border-radius: 30px;\n overflow: hidden;\n text-align: center;\n line-height: 34px\n }\n.ce-toolbox__button::before {\n content: attr(title);\n font-size: 22px;\n font-weight: 500;\n letter-spacing: 1em;\n -webkit-font-feature-settings: \"smcp\", \"c2sc\";\n font-feature-settings: \"smcp\", \"c2sc\";\n font-variant-caps: all-small-caps;\n padding-left: 11.5px;\n margin-top: -1px;\n display: inline-block;\n\n}\n.ce-block {\n border: 1px dotted #ccc;\n margin: 2px 0;\n}\n.ce-block--selected {\n background-color: #eff2f5;\n }\n.ce-block__content {\n max-width: 650px;\n margin: 0 auto;\n }\n", ""]);
|
||||
|
||||
// exports
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -82,7 +82,7 @@
|
|||
{
|
||||
type : 'text',
|
||||
data : {
|
||||
text : 'Пишите нам на team@ifmo.su'
|
||||
text : '<span><textarea></textarea></span><p><b></b></p><strong></strong><i><span></span></i>'
|
||||
}
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* DOM manupulations helper
|
||||
* DOM manipulations helper
|
||||
*/
|
||||
export default class Dom {
|
||||
|
||||
|
|
@ -35,6 +35,18 @@ export default class Dom {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates text Node with content
|
||||
*
|
||||
* @param {String} content - text content
|
||||
* @return {Text}
|
||||
*/
|
||||
static text(content) {
|
||||
|
||||
return document.createTextNode(content);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Append one or several elements to the parent
|
||||
*
|
||||
|
|
@ -86,6 +98,54 @@ export default class Dom {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for deepest node
|
||||
*
|
||||
* @param {Element} node - start Node
|
||||
* @param {Boolean} atLast - find last text node
|
||||
* @return {*}
|
||||
*/
|
||||
static getDeepestTextNode(node, atLast = false) {
|
||||
|
||||
if (node.childNodes.length === 0) {
|
||||
|
||||
/**
|
||||
* We need to return empty text node
|
||||
* But caret will not be placed in empty textNode, so we need textNode with zero-width char
|
||||
*/
|
||||
if (this.isElement(node)) {
|
||||
|
||||
/** and it is not native input */
|
||||
if (!this.isNativeInput(node)) {
|
||||
|
||||
let emptyTextNode = this.text('\u200B');
|
||||
|
||||
node.appendChild(emptyTextNode);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
let childsLength = node.childNodes.length,
|
||||
last = childsLength - 1;
|
||||
|
||||
if (atLast) {
|
||||
|
||||
return this.getDeepestTextNode(node.childNodes[last], atLast);
|
||||
|
||||
} else {
|
||||
|
||||
return this.getDeepestTextNode(node.childNodes[0], false);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if object is DOM node
|
||||
*
|
||||
|
|
@ -98,4 +158,106 @@ export default class Dom {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks target if it is native input
|
||||
* @param {Element|*} target - HTML element or string
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isNativeInput(target) {
|
||||
|
||||
let nativeInputs = [
|
||||
'INPUT',
|
||||
'TEXTAREA'
|
||||
];
|
||||
|
||||
return nativeInputs.indexOf(target.tagName) !== -1;
|
||||
|
||||
}
|
||||
|
||||
static isEmpty(node) {
|
||||
|
||||
let treeWalker = [],
|
||||
stack = [];
|
||||
|
||||
treeWalker.push(node);
|
||||
|
||||
while ( treeWalker.length > 0 ) {
|
||||
|
||||
if (node && node.childNodes.length === 0) {
|
||||
|
||||
stack.push(node);
|
||||
|
||||
}
|
||||
|
||||
while ( node && node.nextSibling ) {
|
||||
|
||||
node = node.nextSibling;
|
||||
|
||||
if (!node) continue;
|
||||
|
||||
if (node.childNodes.length === 0) {
|
||||
|
||||
stack.push(node);
|
||||
|
||||
}
|
||||
|
||||
treeWalker.push(node);
|
||||
|
||||
}
|
||||
|
||||
node = treeWalker.shift();
|
||||
|
||||
if (!node) continue;
|
||||
|
||||
node = node.firstChild;
|
||||
treeWalker.push(node);
|
||||
|
||||
}
|
||||
|
||||
let isEmpty = true;
|
||||
|
||||
stack.forEach( (node) => {
|
||||
|
||||
if ( this.isElement(node) ) {
|
||||
|
||||
if ( this.isNativeInput(node) ) {
|
||||
|
||||
node = node.value;
|
||||
|
||||
if ( node.trim() ) {
|
||||
|
||||
isEmpty = false;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
node = node.textContent.replace('\u200B', '');
|
||||
|
||||
if ( node.trim() ) {
|
||||
|
||||
isEmpty = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
node = node.textContent.replace('\u200B', '');
|
||||
|
||||
if ( node.trim() ) {
|
||||
|
||||
isEmpty = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return isEmpty;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -90,6 +90,8 @@ export default class BlockManager extends Module {
|
|||
let toolInstance = this.Editor.Tools.construct(toolName, data),
|
||||
block = new Block(toolName, toolInstance);
|
||||
|
||||
this.bindEvents(block);
|
||||
|
||||
/**
|
||||
* Apply callback before inserting html
|
||||
*/
|
||||
|
|
@ -99,6 +101,60 @@ export default class BlockManager extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind Events
|
||||
* @param {Object} block
|
||||
*/
|
||||
bindEvents(block) {
|
||||
|
||||
/** contentNode click handler */
|
||||
block.wrapper.addEventListener('click', (event) => this.wrapperClicked(event), false);
|
||||
|
||||
/** keydown on block */
|
||||
block.pluginsContent.addEventListener('keydown', (event) => this.keyDownOnBlock(event), false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight clicked block
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
wrapperClicked(event) {
|
||||
|
||||
this.setCurrentBlockByChildNode(event.target);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
keyDownOnBlock(event) {
|
||||
|
||||
switch(event.keyCode) {
|
||||
|
||||
case _.keyCodes.ENTER:
|
||||
this.enterPressedOnPluginsContent(event);
|
||||
break;
|
||||
case _.keyCodes.DOWN:
|
||||
case _.keyCodes.RIGHT:
|
||||
this.blockRightOrDownArrowPressed(event);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
blockRightOrDownArrowPressed(event) {
|
||||
|
||||
console.log(this.getNextBlock());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new block into _blocks
|
||||
*
|
||||
|
|
@ -112,6 +168,8 @@ export default class BlockManager extends Module {
|
|||
|
||||
this._blocks[++this.currentBlockIndex] = block;
|
||||
|
||||
this.Editor.Caret.set(block.pluginsContent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -128,6 +186,39 @@ export default class BlockManager extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {*}
|
||||
*/
|
||||
getLastBlock() {
|
||||
|
||||
return this._blocks[this._blocks.length - 1];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param index
|
||||
* @return {*}
|
||||
*/
|
||||
getBlockByIndex(index) {
|
||||
|
||||
return this._blocks[index];
|
||||
|
||||
}
|
||||
|
||||
getNextBlock() {
|
||||
|
||||
if (this.currentBlockIndex + 1 > this._blocks.length - 1) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
return this._blocks[this.currentBlockIndex + 1];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Block instance by html element
|
||||
*
|
||||
|
|
@ -244,6 +335,12 @@ export default class BlockManager extends Module {
|
|||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BlockMethods {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,6 +15,42 @@ export default class Caret extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Document Range and sets caret to the element.
|
||||
* @param {Element} element - target node.
|
||||
* @param {Number} offset - offset
|
||||
*/
|
||||
set( element, offset = 0) {
|
||||
|
||||
/** If Element is INPUT */
|
||||
if ($.isNativeInput(element)) {
|
||||
|
||||
element.focus();
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
let nodeToSet = $.getDeepestTextNode(element, true);
|
||||
|
||||
/** if found deepest node is native input */
|
||||
if ($.isNativeInput(nodeToSet)) {
|
||||
|
||||
nodeToSet.focus();
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
let range = document.createRange(),
|
||||
selection = window.getSelection();
|
||||
|
||||
range.setStart(nodeToSet, offset);
|
||||
range.setEnd(nodeToSet, offset);
|
||||
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Set Caret to the last Block
|
||||
*
|
||||
|
|
@ -90,20 +126,20 @@ export default class Caret extends Module {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set caret to the passed Node
|
||||
* @param {Element} node - content-editable Element
|
||||
*/
|
||||
set(node) {
|
||||
|
||||
/**
|
||||
* @todo add working with Selection
|
||||
* tmp: work with textContent
|
||||
*/
|
||||
|
||||
node.textContent += '|';
|
||||
|
||||
}
|
||||
// /**
|
||||
// * Set caret to the passed Node
|
||||
// * @param {Element} node - content-editable Element
|
||||
// */
|
||||
// set(node) {
|
||||
//
|
||||
// /**
|
||||
// * @todo add working with Selection
|
||||
// * tmp: work with textContent
|
||||
// */
|
||||
//
|
||||
// node.textContent += '|';
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -224,6 +224,14 @@ export default class UI extends Module {
|
|||
|
||||
let clickedNode = event.target;
|
||||
|
||||
console.log('click', clickedNode);
|
||||
if ( clickedNode.classList.contains(this.CSS.editorZone) ) {
|
||||
|
||||
this.clickedOnRedactorZone(event);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Select clicked Block as Current
|
||||
*/
|
||||
|
|
@ -352,6 +360,26 @@ export default class UI extends Module {
|
|||
|
||||
}
|
||||
|
||||
clickedOnRedactorZone(event) {
|
||||
|
||||
let lastBlock = this.Editor.BlockManager.getLastBlock(),
|
||||
pluginsContent = lastBlock.pluginsContent;
|
||||
|
||||
/**
|
||||
* If last block has text content, then insert new Block after
|
||||
*/
|
||||
if (!$.isEmpty(pluginsContent)) {
|
||||
|
||||
this.Editor.BlockManager.insert(this.config.initialBlock, {});
|
||||
|
||||
} else {
|
||||
|
||||
this.Editor.Caret.set(pluginsContent);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// /**
|
||||
|
|
|
|||
|
|
@ -40,6 +40,46 @@ export default class Util {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns basic keycodes as constants
|
||||
* @return {{}}
|
||||
*/
|
||||
static get keyCodes() {
|
||||
|
||||
return {
|
||||
BACKSPACE: 8,
|
||||
TAB: 9,
|
||||
ENTER: 13,
|
||||
SHIFT: 16,
|
||||
CTRL: 17,
|
||||
ALT: 18,
|
||||
ESC: 27,
|
||||
SPACE: 32,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
RIGHT: 39,
|
||||
DELETE: 46,
|
||||
META: 91
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns basic nodetypes as contants
|
||||
* @return {{TAG: number, TEXT: number, COMMENT: number, DOCUMENT_FRAGMENT: number}}
|
||||
*/
|
||||
static get nodeTypes() {
|
||||
|
||||
return {
|
||||
TAG : 1,
|
||||
TEXT : 3,
|
||||
COMMENT : 8,
|
||||
DOCUMENT_FRAGMENT: 11
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ChainData
|
||||
* @property {Object} data - data that will be passed to the success or fallback
|
||||
|
|
@ -157,4 +197,15 @@ export default class Util {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed element is contenteditable
|
||||
* @param element
|
||||
* @return {boolean}
|
||||
*/
|
||||
static isContentEditable(element) {
|
||||
|
||||
return element.contentEditable === 'true';
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -3,14 +3,16 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
/*opacity: 0;*/
|
||||
/*visibility: hidden;*/
|
||||
transition: opacity 100ms ease;
|
||||
will-change: opacity, transform;
|
||||
display: none;
|
||||
|
||||
&--opened {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
display: block;
|
||||
/*opacity: 1;*/
|
||||
/*visibility: visible;*/
|
||||
}
|
||||
|
||||
&__content {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue