mirror of
https://github.com/codex-team/editor.js
synced 2024-06-16 12:45:29 +02:00
Merge pull request #186 from codex-team/split-pasted-data
Now pasted text splits into paragraphs
This commit is contained in:
commit
02ab4b50d2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
26
example.html
26
example.html
|
@ -56,9 +56,6 @@
|
|||
<script src="plugins/attaches/attaches.js"></script>
|
||||
<link rel="stylesheet" href="plugins/attaches/attaches.css">
|
||||
|
||||
<script src="plugins/personality/personality.js"></script>
|
||||
<link rel="stylesheet" href="plugins/personality/personality.css">
|
||||
|
||||
<script>
|
||||
codex.editor.start({
|
||||
holderId : "codex-editor",
|
||||
|
@ -222,21 +219,6 @@
|
|||
maxSize: 50000,
|
||||
}
|
||||
},
|
||||
personality: {
|
||||
type : 'personality',
|
||||
displayInToolbox : true,
|
||||
iconClassname : 'cdx-personality-icon',
|
||||
prepare : cdxEditorPersonality.prepare,
|
||||
render : cdxEditorPersonality.render,
|
||||
save : cdxEditorPersonality.save,
|
||||
validate : cdxEditorPersonality.validate,
|
||||
destroy : cdxEditorPersonality.destroy,
|
||||
enableLineBreaks : true,
|
||||
showInlineToolbar: true,
|
||||
config: {
|
||||
uploadURL: '/uploadPhoto',
|
||||
}
|
||||
}
|
||||
},
|
||||
data : {
|
||||
items: [
|
||||
|
@ -252,14 +234,6 @@
|
|||
text : 'Пишите нам на team@ifmo.su'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'personality',
|
||||
data : {
|
||||
name : 'Красюк Светлана Ивановна',
|
||||
cite : 'Заместитель директора по учебно-воспитательной работе (начальная школа)',
|
||||
url : 'http://new.school332.ru/user/2'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'list',
|
||||
data : {
|
||||
|
|
|
@ -71,23 +71,8 @@ module.exports = (function (callbacks) {
|
|||
*/
|
||||
event.preventDefault();
|
||||
|
||||
var nativeInputs = editor.content.currentNode.querySelectorAll('textarea, input'),
|
||||
nativeInputsAreEmpty = true,
|
||||
textContentIsEmpty = !editor.content.currentNode.textContent.trim();
|
||||
|
||||
Array.prototype.map.call(nativeInputs, function (input) {
|
||||
|
||||
if (input.type == 'textarea' || input.type == 'text') {
|
||||
|
||||
nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var blockIsEmpty = textContentIsEmpty && nativeInputsAreEmpty;
|
||||
|
||||
if (!blockIsEmpty) {
|
||||
if (!editor.core.isBlockEmpty(editor.content.currentNode)) {
|
||||
|
||||
return;
|
||||
|
||||
|
@ -801,7 +786,7 @@ module.exports = (function (callbacks) {
|
|||
selectionLength,
|
||||
firstLevelBlocksCount;
|
||||
|
||||
if (isNativeInput_(event.target)) {
|
||||
if (editor.core.isNativeInput(event.target)) {
|
||||
|
||||
/** If input value is empty - remove block */
|
||||
if (event.target.value.trim() == '') {
|
||||
|
@ -895,104 +880,6 @@ module.exports = (function (callbacks) {
|
|||
|
||||
};
|
||||
|
||||
/**
|
||||
* This method prevents default behaviour.
|
||||
*
|
||||
* @param {Object} event
|
||||
* @protected
|
||||
*
|
||||
* @description We get from clipboard pasted data, sanitize, make a fragment that contains of this sanitized nodes.
|
||||
* Firstly, we need to memorize the caret position. We can do that by getting the range of selection.
|
||||
* After all, we insert clear fragment into caret placed position. Then, we should move the caret to the last node
|
||||
*/
|
||||
callbacks.blockPasteCallback = function (event) {
|
||||
|
||||
/** If area is input or textarea then allow default behaviour */
|
||||
if ( isNativeInput_(event.target) ) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/** Prevent default behaviour */
|
||||
event.preventDefault();
|
||||
|
||||
var editableParent = editor.content.getEditableParent(event.target),
|
||||
currentNode = editor.content.currentNode;
|
||||
|
||||
/** Allow paste when event target placed in Editable element */
|
||||
if (!editableParent) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/** get html pasted data - dirty data */
|
||||
var htmlData = event.clipboardData.getData('text/html'),
|
||||
plainData = event.clipboardData.getData('text/plain');
|
||||
|
||||
/** Temporary DIV that is used to work with childs as arrays item */
|
||||
var div = editor.draw.node('DIV', '', {}),
|
||||
cleanData,
|
||||
fragment;
|
||||
|
||||
/** Create fragment, that we paste to range after proccesing */
|
||||
fragment = document.createDocumentFragment();
|
||||
|
||||
if ( htmlData.trim() != '' ) {
|
||||
|
||||
cleanData = editor.sanitizer.clean(htmlData);
|
||||
div.innerHTML = cleanData;
|
||||
|
||||
} else {
|
||||
|
||||
div.innerText = plainData.toString();
|
||||
|
||||
}
|
||||
|
||||
var node, lastNode;
|
||||
|
||||
/**
|
||||
* and fill in fragment
|
||||
*/
|
||||
while (( node = div.firstChild) ) {
|
||||
|
||||
lastNode = fragment.appendChild(node);
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (editor.tools[currentNode.dataset.tool].allowRenderOnPaste) {
|
||||
|
||||
if (editor.paste.pasted(event)) return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* work with selection and range
|
||||
*/
|
||||
var selection, range;
|
||||
|
||||
selection = window.getSelection();
|
||||
|
||||
range = selection.getRangeAt(0);
|
||||
range.deleteContents();
|
||||
|
||||
range.insertNode(fragment);
|
||||
|
||||
/** Preserve the selection */
|
||||
if (lastNode) {
|
||||
|
||||
range = range.cloneRange();
|
||||
range.setStartAfter(lastNode);
|
||||
range.collapse(true);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* used by UI module
|
||||
* Clicks on block settings button
|
||||
|
@ -1019,21 +906,6 @@ module.exports = (function (callbacks) {
|
|||
|
||||
};
|
||||
|
||||
/**
|
||||
* Check block
|
||||
* @param target
|
||||
* @private
|
||||
*
|
||||
* @description Checks target is it native input
|
||||
*/
|
||||
var isNativeInput_ = function (target) {
|
||||
|
||||
var nativeInputAreas = ['INPUT', 'TEXTAREA'];
|
||||
|
||||
return (nativeInputAreas.indexOf(target.tagName) != -1);
|
||||
|
||||
};
|
||||
|
||||
return callbacks;
|
||||
|
||||
})({});
|
|
@ -268,6 +268,38 @@ module.exports = (function (caret) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts node at the caret location
|
||||
* @param {HTMLElement|DocumentFragment} node
|
||||
*/
|
||||
caret.insertNode = function (node) {
|
||||
|
||||
var selection, range,
|
||||
lastNode = node;
|
||||
|
||||
if (node.nodeType == editor.core.nodeTypes.DOCUMENT_FRAGMENT) {
|
||||
|
||||
lastNode = node.lastChild;
|
||||
|
||||
}
|
||||
|
||||
selection = window.getSelection();
|
||||
|
||||
range = selection.getRangeAt(0);
|
||||
range.deleteContents();
|
||||
|
||||
range.insertNode(node);
|
||||
|
||||
range.setStartAfter(lastNode);
|
||||
range.collapse(true);
|
||||
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
|
||||
|
||||
};
|
||||
|
||||
return caret;
|
||||
|
||||
})({});
|
|
@ -120,7 +120,7 @@ module.exports = (function (content) {
|
|||
|
||||
}
|
||||
|
||||
this.currentNode = this.getFirstLevelBlock(targetNode);
|
||||
content.currentNode = content.getFirstLevelBlock(targetNode);
|
||||
|
||||
};
|
||||
|
||||
|
@ -639,10 +639,17 @@ module.exports = (function (content) {
|
|||
/**
|
||||
* @public
|
||||
*
|
||||
* @param [String] htmlString - html content as string
|
||||
* @param {string} htmlData - html content as string
|
||||
* @param {string} plainData - plain text
|
||||
* @return {string} - html content as string
|
||||
*/
|
||||
content.wrapTextWithParagraphs = function (htmlString) {
|
||||
content.wrapTextWithParagraphs = function (htmlData, plainData) {
|
||||
|
||||
if (!htmlData.trim()) {
|
||||
|
||||
return wrapPlainTextWithParagraphs(plainData);
|
||||
|
||||
}
|
||||
|
||||
var wrapper = document.createElement('DIV'),
|
||||
newWrapper = document.createElement('DIV'),
|
||||
|
@ -656,7 +663,7 @@ module.exports = (function (content) {
|
|||
* Make HTML Element to Wrap Text
|
||||
* It allows us to work with input data as HTML content
|
||||
*/
|
||||
wrapper.innerHTML = htmlString;
|
||||
wrapper.innerHTML = htmlData;
|
||||
paragraph = document.createElement('P');
|
||||
|
||||
for (i = 0; i < wrapper.childNodes.length; i++) {
|
||||
|
@ -706,6 +713,17 @@ module.exports = (function (content) {
|
|||
|
||||
};
|
||||
|
||||
/**
|
||||
* Splits strings on new line and wraps paragraphs with <p> tag
|
||||
* @param plainText
|
||||
* @returns {string}
|
||||
*/
|
||||
var wrapPlainTextWithParagraphs = function (plainText) {
|
||||
|
||||
return '<p>' + plainText.split('\n\n').join('</p><p>') + '</p>';
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds closest Contenteditable parent from Element
|
||||
* @param {Element} node element looking from
|
||||
|
|
|
@ -112,7 +112,8 @@ module.exports = (function (core) {
|
|||
core.nodeTypes = {
|
||||
TAG : 1,
|
||||
TEXT : 3,
|
||||
COMMENT : 8
|
||||
COMMENT : 8,
|
||||
DOCUMENT_FRAGMENT: 11
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -338,6 +339,49 @@ module.exports = (function (core) {
|
|||
|
||||
};
|
||||
|
||||
/**
|
||||
* Check block
|
||||
* @param target
|
||||
* @description Checks target is it native input
|
||||
*/
|
||||
core.isNativeInput = function (target) {
|
||||
|
||||
var nativeInputAreas = ['INPUT', 'TEXTAREA'];
|
||||
|
||||
return nativeInputAreas.indexOf(target.tagName) != -1;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if block is empty
|
||||
* We should check block textContent, child native inputs and some exceptions like IMG and IFRAME
|
||||
*
|
||||
* @param block
|
||||
* @returns {boolean}
|
||||
*/
|
||||
core.isBlockEmpty = function (block) {
|
||||
|
||||
const EXCEPTION_TAGS = ['IMG', 'IFRAME'];
|
||||
|
||||
var nativeInputs = block.querySelectorAll('textarea, input'),
|
||||
nativeInputsAreEmpty = true,
|
||||
textContentIsEmpty = !block.textContent.trim();
|
||||
|
||||
Array.prototype.forEach.call(nativeInputs, function (input) {
|
||||
|
||||
if (input.type == 'textarea' || input.type == 'text') {
|
||||
|
||||
nativeInputsAreEmpty = nativeInputsAreEmpty && !input.value.trim();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return textContentIsEmpty && nativeInputsAreEmpty && !EXCEPTION_TAGS.includes(block.tagName);
|
||||
|
||||
};
|
||||
|
||||
|
||||
return core;
|
||||
|
||||
})({});
|
||||
|
|
164
modules/paste.js
164
modules/paste.js
|
@ -107,6 +107,170 @@ module.exports = function (paste) {
|
|||
|
||||
};
|
||||
|
||||
/**
|
||||
* This method prevents default behaviour.
|
||||
*
|
||||
* @param {Object} event
|
||||
* @protected
|
||||
*
|
||||
* @description We get from clipboard pasted data, sanitize, make a fragment that contains of this sanitized nodes.
|
||||
* Firstly, we need to memorize the caret position. We can do that by getting the range of selection.
|
||||
* After all, we insert clear fragment into caret placed position. Then, we should move the caret to the last node
|
||||
*/
|
||||
paste.blockPasteCallback = function (event) {
|
||||
|
||||
|
||||
if (!needsToHandlePasteEvent(event.target)) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/** Prevent default behaviour */
|
||||
event.preventDefault();
|
||||
|
||||
/** get html pasted data - dirty data */
|
||||
var htmlData = event.clipboardData.getData('text/html'),
|
||||
plainData = event.clipboardData.getData('text/plain');
|
||||
|
||||
/** Temporary DIV that is used to work with text's paragraphs as DOM-elements*/
|
||||
var paragraphs = editor.draw.node('DIV', '', {}),
|
||||
cleanData,
|
||||
wrappedData;
|
||||
|
||||
/** Create fragment, that we paste to range after proccesing */
|
||||
cleanData = editor.sanitizer.clean(htmlData);
|
||||
|
||||
/**
|
||||
* We wrap pasted text with <p> tags to split it logically
|
||||
* @type {string}
|
||||
*/
|
||||
wrappedData = editor.content.wrapTextWithParagraphs(cleanData, plainData);
|
||||
paragraphs.innerHTML = wrappedData;
|
||||
|
||||
/**
|
||||
* If there only one paragraph, just insert in at the caret location
|
||||
*/
|
||||
if (paragraphs.childNodes.length == 1) {
|
||||
|
||||
emulateUserAgentBehaviour(paragraphs.firstChild);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
insertPastedParagraphs(paragraphs.childNodes);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if we should handle paste event on block
|
||||
* @param block
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
var needsToHandlePasteEvent = function (block) {
|
||||
|
||||
/** If area is input or textarea then allow default behaviour */
|
||||
if ( editor.core.isNativeInput(block) ) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
var editableParent = editor.content.getEditableParent(block);
|
||||
|
||||
/** Allow paste when event target placed in Editable element */
|
||||
if (!editableParent) {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts new initial plugin blocks with data in paragraphs
|
||||
*
|
||||
* @param {Array} paragraphs - array of paragraphs (<p></p>) whit content, that should be inserted
|
||||
*/
|
||||
var insertPastedParagraphs = function (paragraphs) {
|
||||
|
||||
var NEW_BLOCK_TYPE = editor.settings.initialBlockPlugin;
|
||||
|
||||
|
||||
/**
|
||||
* If there was no data in working node, remove it
|
||||
*/
|
||||
if (editor.core.isBlockEmpty(editor.content.currentNode)) {
|
||||
|
||||
editor.content.currentNode.remove();
|
||||
editor.caret.setToPreviousBlock(editor.caret.inputIndex);
|
||||
|
||||
}
|
||||
|
||||
paragraphs.forEach(function (paragraph) {
|
||||
|
||||
/** Don't allow empty paragraphs */
|
||||
if (editor.core.isBlockEmpty(paragraph)) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
editor.content.insertBlock({
|
||||
type : NEW_BLOCK_TYPE,
|
||||
block : editor.tools[NEW_BLOCK_TYPE].render({
|
||||
text : paragraph.innerHTML
|
||||
})
|
||||
});
|
||||
|
||||
editor.caret.inputIndex++;
|
||||
|
||||
});
|
||||
|
||||
editor.caret.setToPreviousBlock(editor.caret.getCurrentInputIndex() + 1);
|
||||
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts node content at the caret position
|
||||
*
|
||||
* @param {Node} node - DOM node (could be DocumentFragment), that should be inserted at the caret location
|
||||
*/
|
||||
var emulateUserAgentBehaviour = function (node) {
|
||||
|
||||
var newNode;
|
||||
|
||||
if (node.childElementCount) {
|
||||
|
||||
newNode = document.createDocumentFragment();
|
||||
|
||||
node.childNodes.forEach(function (current) {
|
||||
|
||||
if (!editor.core.isDomNode(current) && current.data.trim() === '') {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
newNode.appendChild(current.cloneNode(true));
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
newNode = document.createTextNode(node.textContent);
|
||||
|
||||
}
|
||||
|
||||
editor.caret.insertNode(newNode);
|
||||
|
||||
};
|
||||
|
||||
|
||||
return paste;
|
||||
|
||||
}({});
|
|
@ -361,7 +361,7 @@ module.exports = (function (ui) {
|
|||
* @example editor.callback.blockPasteViaSanitize(event), the second method.
|
||||
*
|
||||
*/
|
||||
editor.listeners.add(block, 'paste', editor.callback.blockPasteCallback, false);
|
||||
editor.listeners.add(block, 'paste', editor.paste.blockPasteCallback, false);
|
||||
|
||||
editor.listeners.add(block, 'mouseup', editor.toolbar.inline.show, false);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "codex.editor",
|
||||
"version": "1.6.3",
|
||||
"version": "1.6.4",
|
||||
"description": "Codex Editor. Native JS, based on API and Open Source",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
|
Loading…
Reference in a new issue