Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve paste behaviour #346

Merged
merged 10 commits into from
Jul 28, 2018
12,253 changes: 7,998 additions & 4,255 deletions build/codex-editor.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/codex-editor.js.map

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ There are few options available by CodeX Editor.
| Name | Type | Default Value | Description |
| -- | -- | -- | -- |
| `inlineToolbar` | _Boolean/Array_ | `false` | Pass `true` to enable the Inline Toolbar with all Tools, or pass an array with specified Tools list |
| `disallowPaste` | _Boolean_ | `false` | Pass `true` if you want to prevent any paste into your Tool
| `config` | _Object_ | `null` | User's configuration for Plugin.

### Paste handling
Expand Down
3 changes: 0 additions & 3 deletions example/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@
<script src="plugins/text/text.js?v=100"></script>
<link rel="stylesheet" href="plugins/text/text.css">

<script src="plugins/header/header.js?v=100"></script>
<link rel="stylesheet" href="plugins/header/header.css">

<script src="https://cdn.rawgit.com/codex-editor/header/25d39a1e/dist/bundle.js"></script>

<script src="plugins/simple-image/simple-image.js?v=100"></script>
Expand Down
68 changes: 55 additions & 13 deletions example/plugins/list/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ class List {
* @param {object} api - CodeX Editor API
*/
constructor(listData = {}, config = {}, api = {}) {

/**
* HTML nodes
* @private
Expand Down Expand Up @@ -89,8 +88,6 @@ class List {

this.api = api;
this.data = listData;


}

/**
Expand All @@ -99,14 +96,14 @@ class List {
* @public
*/
render() {
var style = this._data.style === 'ordered' ? this.CSS.wrapperOrdered : this.CSS.wrapperUnordered;
const style = this._data.style === 'ordered' ? this.CSS.wrapperOrdered : this.CSS.wrapperUnordered;

this._elements.wrapper = this._make('ul', [ this.CSS.baseBlock, this.CSS.wrapper, style ], {
this._elements.wrapper = this._make('ul', [this.CSS.baseBlock, this.CSS.wrapper, style], {
contentEditable: true
});

// fill with data
if (this._data.items.length){
if (this._data.items.length) {
this._data.items.forEach( item => {
this._elements.wrapper.appendChild(this._make('li', [this.CSS.item, this.CSS.input], {
innerHTML: item
Expand All @@ -130,12 +127,11 @@ class List {
this.backspace(event);
break;
case A:
if (cmdPressed){
if (cmdPressed) {
this.selectItem(event);
}
break;
}

}, false);

return this._elements.wrapper;
Expand Down Expand Up @@ -166,6 +162,7 @@ class List {

// clear other buttons
const buttons = itemEl.parentNode.querySelectorAll('.' + this.CSS.settingsButton);

Array.from(buttons).forEach( button => button.classList.remove(this.CSS.settingsButtonActive));

// mark active
Expand All @@ -182,6 +179,16 @@ class List {
return wrapper;
}

/**
* List Tool on paste configuration
* @public
*/
static get onPaste() {
return {
tags: ['OL', 'UL', 'LI'],
handler: List.pasteHandler
};
}

/**
* Toggles List style
Expand Down Expand Up @@ -236,7 +243,8 @@ class List {

for (let i = 0; i < items.length; i++) {
const value = items[i].innerHTML.replace('<br>', ' ').trim();
if (value){

if (value) {
this._data.items.push(items[i].innerHTML);
}
}
Expand Down Expand Up @@ -279,7 +287,7 @@ class List {
/**
* Save the last one.
*/
if (items.length < 2){
if (items.length < 2) {
return;
}

Expand All @@ -288,7 +296,6 @@ class List {

/** Prevent Default li generation if item is empty */
if (currentNode === lastItem && !lastItem.innerHTML.replace('<br>', ' ').trim()) {

/** Insert New Block and set caret */
this.api.blocks.insertNewBlock();
event.preventDefault();
Expand All @@ -300,11 +307,11 @@ class List {
* Handle backspace
* @param {KeyboardEvent} event
*/
backspace(event){
backspace(event) {
const items = this._elements.wrapper.querySelectorAll('.' + this.CSS.item),
firstItem = items[0];

if (!firstItem){
if (!firstItem) {
return;
}

Expand Down Expand Up @@ -333,4 +340,39 @@ class List {
selection.removeAllRanges();
selection.addRange(range);
}

/**
* Handle UL, OL and LI tags paste and returns List data
*
* @param {HTMLUListElement|HTMLOListElement|HTMLLIElement} element
* @returns {ListData}
*/
static pasteHandler(element) {
const {tagName: tag} = element;
let type;

switch(tag) {
case 'OL':
type = 'ordered';
break;
case 'UL':
case 'LI':
type = 'unordered';
}

const data = {
type,
items: []
};

if (tag === 'LI') {
data.items = [ element.innerHTML ];
} else {
const items = Array.from(element.querySelectorAll('LI'));

data.items = items.map(li => li.innerHTML).filter(item => !!item.trim());
}

return data;
}
}
24 changes: 12 additions & 12 deletions example/plugins/text/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
* @version 2.0.1
*/

/**
* @typedef {Object} TextData
* @description Tool's input and output data format
* @property {String} text — Paragraph's content. Can include HTML tags: <a><b><i>
*/
/**
* @typedef {Object} TextData
* @description Tool's input and output data format
* @property {String} text — Paragraph's content. Can include HTML tags: <a><b><i>
*/
class Text {
/**
* Should this tools be displayed at the Editor's Toolbox
Expand Down Expand Up @@ -148,15 +148,15 @@ class Text {
this._element.innerHTML = this._data.text || '';
}

/**
* Used by Codex Editor paste handling API.
* Provides configuration to handle DIV and P tags.
*
* @returns {{handler: (function(HTMLElement): {text: string}), tags: string[]}}
*/
/**
* Used by Codex Editor paste handling API.
* Provides configuration to handle DIV and P tags.
*
* @returns {{handler: (function(HTMLElement): {text: string}), tags: string[]}}
*/
static get onPaste() {
return {
tags: ['P', 'DIV'],
tags: [ 'P' ],
handler: (content) => {
return {
text: content.innerHTML
Expand Down
48 changes: 21 additions & 27 deletions src/components/modules/paste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,31 +201,18 @@ export default class Paste extends Module {

event.preventDefault();

const block = BlockManager.getBlock(event.target),
toolSettings = Tools.getToolSettings(block.name);

/** If paste is dissalowed in block do nothing */
if (toolSettings && toolSettings[Tools.apiSettings.IS_PASTE_DISALLOWED]) {
return;
}

const htmlData = event.clipboardData.getData('text/html'),
plainData = event.clipboardData.getData('text/plain');

/** Add all block tags and tags can be substituted to sanitizer configuration */
const blockTags = $.blockElements.reduce((result, tag) => {
result[tag.toLowerCase()] = {};

return result;
}, {});
/** Add all tags that can be substituted to sanitizer configuration */
const toolsTags = Object.keys(this.toolsTags).reduce((result, tag) => {
result[tag.toLowerCase()] = {};

return result;
}, {});

const customConfig = {tags: Object.assign({}, blockTags, toolsTags, Sanitizer.defaultConfig.tags)},
cleanData = Sanitizer.clean(htmlData, customConfig);
const customConfig = {tags: Object.assign({}, toolsTags, Sanitizer.defaultConfig.tags)};
const cleanData = Sanitizer.clean(htmlData, customConfig);

let dataToInsert = [];

Expand Down Expand Up @@ -269,7 +256,7 @@ export default class Paste extends Module {
if (blockData) {
this.splitBlock();

if (BlockManager.currentBlock.isEmpty) {
if (BlockManager.currentBlock && BlockManager.currentBlock.isEmpty) {
BlockManager.replace(blockData.tool, blockData.data);
} else {
BlockManager.insert(blockData.tool, blockData.data);
Expand Down Expand Up @@ -318,7 +305,7 @@ export default class Paste extends Module {
{BlockManager, Caret} = this.Editor,
{currentBlock} = BlockManager;

if (canReplaceCurrentBlock && currentBlock.isEmpty) {
if (canReplaceCurrentBlock && currentBlock && currentBlock.isEmpty) {
BlockManager.replace(data.tool, blockData);
return;
}
Expand All @@ -334,6 +321,10 @@ export default class Paste extends Module {
private splitBlock() {
const {BlockManager, Caret} = this.Editor;

if (!BlockManager.currentBlock) {
return;
}

/** If we paste into middle of the current block:
* 1. Split
* 2. Navigate to the first part
Expand Down Expand Up @@ -368,22 +359,29 @@ export default class Paste extends Module {
case Node.DOCUMENT_FRAGMENT_NODE:
content = $.make('div');
content.appendChild(node);
content.innerHTML = Sanitizer.clean(content.innerHTML);
break;

/** If node is an element, then there might be a substitution */
case Node.ELEMENT_NODE:
content = node as HTMLElement;
isBlock = true;
content.innerHTML = Sanitizer.clean(content.innerHTML);

if (this.toolsTags[content.tagName]) {
tool = this.toolsTags[content.tagName].tool;
}
break;
}

const handler = Tools.blockTools[tool].onPaste.handler;
const {handler, tags} = Tools.blockTools[tool].onPaste;

const toolTags = tags.reduce((result, tag) => {
result[tag.toLowerCase()] = {};

return result;
}, {});
const customConfig = {tags: Object.assign({}, toolTags, Sanitizer.defaultConfig.tags)};

content.innerHTML = Sanitizer.clean(content.innerHTML, customConfig);

return {content, isBlock, handler, tool};
})
Expand Down Expand Up @@ -453,17 +451,13 @@ export default class Paste extends Module {
/** Append inline elements to previous fragment */
if (
!$.blockElements.includes(element.tagName.toLowerCase()) &&
!tags.includes(element.tagName.toLowerCase())
!tags.includes(element.tagName)
) {
destNode.appendChild(element);
return [...nodes, destNode];
}

if (
(
tags.includes(element.tagName.toLowerCase()) &&
Array.from(element.children).every(({tagName}) => !tags.includes(tagName.toLowerCase()))
) || (
if (tags.includes(element.tagName) || (
$.blockElements.includes(element.tagName.toLowerCase()) &&
Array.from(element.children).every(
({tagName}) => !$.blockElements.includes(tagName.toLowerCase()),
Expand Down