-
-
Save Daenero/3442213dc5093dc10f30711edb529729 to your computer and use it in GitHub Desktop.
// 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}`; } |
It will work with normal java script?
This works fine if I use only UL or OL. There is a major issue if I use UL and OL together.
Have you faced this issue?
I would also like to express my thanks! This was super helpful.
I also made a small change I wanted to mention in case it's useful for someone else.
The ordered lists output I got from
quillDecodeIndent
were using numeric markers for all levels, so to keep it consistent with Quill, which repeats [numeric, lower alpha, lower roman], I added this line just before the outermostforEach
inquillDecodeIndent
:const listTypes = ['1', 'a', 'i'];
and then added the
type
attribute to the<ol>
just after creatingnewList
:const newList = document.createElement(type); if (type === 'ol') { newList.setAttribute('type', listTypes[currentLiLevel % 3]); } lastLiInCurrentLevel.appendChild(newList);
fixed the nested indent issue, thanks
I would also like to express my thanks! This was super helpful.
I also made a small change I wanted to mention in case it's useful for someone else.
The ordered lists output I got from
quillDecodeIndent
were using numeric markers for all levels, so to keep it consistent with Quill, which repeats [numeric, lower alpha, lower roman], I added this line just before the outermostforEach
inquillDecodeIndent
:const listTypes = ['1', 'a', 'i'];
and then added the
type
attribute to the<ol>
just after creatingnewList
:const newList = document.createElement(type); if (type === 'ol') { newList.setAttribute('type', listTypes[currentLiLevel % 3]); } lastLiInCurrentLevel.appendChild(newList);
Thanks, it fixed my problem :)
thank you so much! works like a charm.
jah bless 🙏
How to write this in php please?
How to using this with react-quill?
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.
Thank you :)