Skip to content

Commit

Permalink
fix: paste wysiwyg list (#1230)
Browse files Browse the repository at this point in the history
* fix: paste the list data to consider current depth

* chore: add test case and fix broken test

* chore: apply code review
  • Loading branch information
js87zz committed Oct 19, 2020
1 parent d3b7bf0 commit 90143c4
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 4 deletions.
120 changes: 119 additions & 1 deletion apps/editor/src/js/wwPasteContentHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,11 @@ class WwPasteContentHelper {
}
}

return newFragment;
return this._getResolvePastedListDepthToCurrentDepth(
rangeInfo.startContainer,
node,
newFragment
);
}

/**
Expand Down Expand Up @@ -495,6 +499,120 @@ class WwPasteContentHelper {
addClass(table, tableManager.getTableIDClassName());
});
}

/**
* get the list resolved the depth to current list depth
* @param {HTMLElement} currentEl - current list element
* @param {HTMLElement} orgPastedNode - original pasted data
* @param {DocumentFragment} fragment - preprocessed data
* @returns {HTMLElement} resolved element
* @private
*/
_getResolvePastedListDepthToCurrentDepth(currentEl, orgPastedNode, fragment) {
const currentListDepth = this._getListDepth(currentEl);
let continuousDepth = this._getContinuousDepth(orgPastedNode);

fragment = this._getRemovedUnnecessaryListWrapper(fragment, orgPastedNode);

// If the depth of the pasted data is greater than current depth, get child element for resolving the depth.
// For example, If 2-depth list is pasted to 1-depth list element, 2-depth list should be changed to 1-depth.
while (currentListDepth < continuousDepth) {
if (fragment.firstChild.tagName !== 'UL' && fragment.firstChild.tagName !== 'OL') {
break;
}

const childNodes = toArray(fragment.childNodes);

fragment = fragment.firstChild;
/* eslint-disable no-loop-func */
childNodes
.filter(node => node !== fragment)
.forEach(node => {
fragment.insertAdjacentElement('beforeend', node);
});
/* eslint-enable no-loop-func */
continuousDepth -= 1;
}

// If the depth of the pasted data is less than current depth, wrap the list element for resolving the depth.
// For example, If 1-depth list is pasted to 2-depth list element, 1-depth list should be changed to 2-depth.
while (currentListDepth && currentListDepth > continuousDepth) {
const rootList = fragment.firstChild.parentElement;
const list = document.createElement(rootList.tagName);

list.appendChild(rootList);
fragment = list;
continuousDepth += 1;
}

if (currentListDepth && !currentEl.textContent) {
domUtils.remove(currentEl);
}
return fragment;
}

/**
* get the depth of the list item element
* @param {HTMLElement} el - target element
* @returns {number} depth
* @private
*/
_getListDepth(el) {
let depth = 0;

while (el) {
if (el.tagName === 'UL' || el.tagName === 'OL') {
depth += 1;
}
el = el.parentNode;
}
return depth;
}

/**
* get the continuous depth of the list.
* the continuous depth of below example is 2
* <ul>
* <li>
* <ul>
* <li>...</li>
* <ul>...</ul>
* </ul>
* </li>
* </ul>
*
* @param {HTMLElement} el - target element
* @returns {number} depth
* @private
*/
_getContinuousDepth(el) {
let depth = 0;

while (el && (el.tagName === 'UL' || el.tagName === 'OL')) {
depth += 1;

if (el.childNodes.length > 1) {
break;
}

el = el.firstChild;
}
return depth;
}

/**
* get the element which is removed unnecessay list wrapper element
* @param {HTMLElement} el - target element
* @param {HTMLElement} orgEl - target element
* @returns {HTMLElement} el
* @private
*/
_getRemovedUnnecessaryListWrapper(el, orgEl) {
while (el.querySelectorAll('ul,ol').length > orgEl.querySelectorAll('ul,ol').length) {
el = el.firstChild;
}
return el;
}
}

export default WwPasteContentHelper;
94 changes: 93 additions & 1 deletion apps/editor/test/unit/integration/clipboard.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function pasteClipboardEvent(text, html, fileType) {
}

describe('Clipboard', () => {
let editor, se;
let editor, se, wwe;

// We can't simulate browser paste. skip IE & Edge browsers
if (browser.msie || browser.edge) {
Expand All @@ -75,6 +75,7 @@ describe('Clipboard', () => {
height: '300px',
initialEditType: 'wysiwyg'
});
wwe = editor.wwEditor;
se = editor.wwEditor.editor;
setTimeout(done, 0);
});
Expand Down Expand Up @@ -285,5 +286,96 @@ describe('Clipboard', () => {
expect(spy).toHaveBeenCalled();
});
});

describe('list', () => {
it('should decrease the pasted list depth to match current list depth', () => {
const html = source`
<ul>
<li>list1</li>
<li>list2</li>
</ul>
`;

const inputHtml = source`
<ul>
<ul>
<li>text</li>
</ul>
</ul>
`;
const outputHtml = oneLineTrim`
<ul>
<li>l<br></li>
<li>text<br></li>
<li>ist1<br></li>
<li>list2<br></li>
</ul>
<div><br></div>
`;

se.setHTML(html);

const range = se.getSelection().cloneRange();

range.setStart(wwe.getBody().querySelectorAll('li')[0].childNodes[0], 1);
range.collapse(true);

se.setSelection(range);
se.fireEvent('paste', pasteClipboardEvent(null, inputHtml));

expect(se.getHTML()).toBe(outputHtml);
});
});

it('should increase the pasted list depth to match current list depth', () => {
const html = source`
<ul>
<li>text1</li>
<li>
<ul>
<li>text2</li>
</ul>
</li>
</ul>
`;
const inputHtml = source`
<ul>
<li>list1</li>
</ul>
`;

const outputHtml = oneLineTrim`
<ul>
<li>text1<br></li>
<li>
<ul>
<li>t<br></li>
</ul>
</li>
<ul>
<li>list1<br></li>
</ul>
<li>
<div><br></div>
<ul>
<li>ext2<br></li>
</ul>
</li>
</ul>
<div><br></div>
`;

se.setHTML(html);

const range = se.getSelection().cloneRange();

range.setStart(wwe.getBody().querySelectorAll('ul > li > ul > li')[0].childNodes[0], 1);
range.collapse(true);

se.setSelection(range);
se.fireEvent('paste', pasteClipboardEvent(null, inputHtml));

expect(se.getHTML()).toBe(outputHtml);
});
});
});
4 changes: 2 additions & 2 deletions apps/editor/test/unit/wwPasteContentHelper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ describe('WwPasteContentHelper', () => {

expect(element.childNodes.length).toEqual(1);
expect(element.childNodes[0].tagName).toEqual('UL');
expect(element.childNodes[0].childNodes.length).toEqual(2);
expect(element.childNodes[0].childNodes[0].childNodes.length).toEqual(2);
});

it('if content have complete list and has format li then make depth based on current selection', () => {
Expand Down Expand Up @@ -420,7 +420,7 @@ describe('WwPasteContentHelper', () => {

expect(element.childNodes.length).toEqual(1);
expect(element.childNodes[0].tagName).toEqual('UL');
expect($(element.childNodes[0]).find('li > ul > li > ul > li').length).toEqual(2);
expect($(element.childNodes[0]).find('li > div').length).toEqual(2);
});

it('if content have orphan list and hasnt format li then wrap list parent based on rangeInfo', () => {
Expand Down

0 comments on commit 90143c4

Please sign in to comment.