Created
November 13, 2020 14:29
-
-
Save Daenero/3442213dc5093dc10f30711edb529729 to your computer and use it in GitHub Desktop.
Fix for Quill.js issue with list indent (use of css class instead of pure HTML)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://github.com/quilljs/quill/issues/979 | |
interface NestedElement { | |
content: string; | |
indent: number; | |
classes: string; | |
} | |
export function quillDecodeIndent(text: string) { | |
if (!text || text.length === 0) { | |
return text; | |
} | |
const tempEl = window.document.createElement('div'); | |
tempEl.setAttribute('style', 'display: none;'); | |
tempEl.innerHTML = text; | |
['ul', 'ol'].forEach((type) => { | |
// Grab each list, and work on it in turn | |
Array.from(tempEl.querySelectorAll(type)).forEach((outerListEl) => { | |
const listChildren = Array.from(outerListEl.children).filter((el) => el.tagName === 'LI'); | |
let lastLiLevel = 0; | |
const parentElementsStack: Element[] = []; | |
const root = document.createElement(type); | |
parentElementsStack.push(root); | |
listChildren.forEach((e, i) => { | |
const currentLiLevel = getQuillListLevel(e); | |
e.className = e.className.replace(getIndentClass(currentLiLevel), ''); | |
const difference = currentLiLevel - lastLiLevel; | |
lastLiLevel = currentLiLevel; | |
if (difference > 0) { | |
let currentDiff = difference; | |
while (currentDiff > 0) { | |
let lastLiInCurrentLevel = seekLastElement(parentElementsStack).lastElementChild; | |
if (!lastLiInCurrentLevel) { | |
lastLiInCurrentLevel = document.createElement('li'); | |
encode_addChildToCurrentParent(parentElementsStack, lastLiInCurrentLevel); | |
} | |
const newList = document.createElement(type); | |
lastLiInCurrentLevel.appendChild(newList); | |
parentElementsStack.push(newList); | |
currentDiff--; | |
} | |
} | |
if (difference < 0) { | |
let currentDiff = difference; | |
while (currentDiff < 0) { | |
parentElementsStack.pop(); | |
currentDiff++; | |
} | |
} | |
encode_addChildToCurrentParent(parentElementsStack, e); | |
}); | |
outerListEl.innerHTML = root.innerHTML; | |
}); | |
}); | |
const newContent = tempEl.innerHTML; | |
tempEl.remove(); | |
return newContent; | |
} | |
export function quillEncodeIndent(text: string) { | |
if (!text || text.length === 0) { | |
return text; | |
} | |
const tempEl = window.document.createElement('div'); | |
tempEl.setAttribute('style', 'display: none;'); | |
tempEl.innerHTML = text; | |
['ul', 'ol'].forEach((type) => { | |
Array.from(tempEl.querySelectorAll(type)).forEach((outerListEl) => { | |
const listResult = Array.from(outerListEl.children) | |
.filter(e => e.tagName === 'LI') | |
.map(e => encode_UnwindElement(type.toUpperCase(), e, 0)) | |
.reduce((prev, c) => [...prev, ...c], []) // flatten list | |
.map(e => encode_GetLi(e)) | |
.reduce((prev, c) => `${prev}${c}`, ''); // merge to one string | |
outerListEl.innerHTML = listResult; | |
}); | |
}); | |
const newContent = tempEl.innerHTML; | |
tempEl.remove(); | |
return newContent; | |
} | |
function encode_UnwindElement(listType: string, li: Element, level: number): NestedElement[] { | |
const childElements = Array.from(li.children) | |
.filter(innerElement => innerElement.tagName === listType) | |
.map(innerList => | |
Array.from(li.removeChild(innerList).children) | |
.map(nestedListElement => encode_UnwindElement(listType, innerList.removeChild(nestedListElement), level + 1)) | |
.reduce((prev, c) => [...prev, ...c], [])) | |
.reduce((prev, c) => [...prev, ...c], []); | |
const current: NestedElement = { | |
classes: li.className, | |
content: li.innerHTML, | |
indent: level | |
}; | |
return [current, ...childElements]; | |
} | |
function encode_GetLi(e: NestedElement) { | |
let cl = ''; | |
if (e.indent > 0) { | |
cl += `${getIndentClass(e.indent)}`; | |
} | |
if (e.classes.length > 0) { | |
cl += ` ${e.classes}`; | |
} | |
return `<li${cl.length > 0 ? ` class="${cl}"` : ''}>${e.content}</li>`; | |
} | |
function seekLastElement(list: Element[]): Element { | |
return list[list.length - 1]; | |
} | |
function encode_addChildToCurrentParent(parentStack: Element[], child: Element): void { | |
const currentParent = seekLastElement(parentStack); | |
currentParent.appendChild(child); | |
} | |
function getQuillListLevel(el: Element) { | |
const className = el.className || '0'; | |
return +className.replace(/[^\d]/g, ''); | |
} | |
function getIndentClass(level: number) { return `ql-indent-${level}`; } |
This answer worked for me! I ended up adding the following clipboard matcher as well:
// Processes any <li> element inserted into Quill via a delta.
// Ensures "ql-indent-*" classes are correctly converted into a delta
// with the appropriate number of indents for the Quill editor.
const nestedListHandler = (node, delta) => {
const indentClass = Array.from(node.classList).find(className => className.startsWith("ql-indent-"));
if (indentClass) {
const indentLevel = indentClass.replace("ql-indent-", "");
const indentLevelInt = parseInt(indentLevel) + 1;
let newOps = [];
let hasIndented = false;
delta.ops.forEach(op => {
newOps.push(op);
if (!hasIndented) {
newOps.unshift({
retain: op.insert.length,
attributes: {indent: indentLevelInt}
});
hasIndented = true;
}
});
delta.ops = newOps;
}
return delta;
};
new Quill(domElement, {
modules: {
history: {
delay: 1000,
maxStack: 10,
userOnly: false
},
toolbar: [
["bold", "italic", "underline"],
[{align: ""}],
[{"list": "ordered"}, {"list": "bullet"}],
],
clipboard: {
matchVisual: true,
matchers: [
["li", nestedListHandler], // Handle any li elements being converted to deltas
]
}
},
placeholder: placeholder,
theme: "snow",
formats: ["bold", "italic", "underline", "list", "align"]
});
This works in combination with the original answer to further support for nested lists in Quill's deltas.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to using this with react-quill?