- 
      
- 
        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}`; } | 
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
quillDecodeIndentwere 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 outermostforEachinquillDecodeIndent:const listTypes = ['1', 'a', 'i'];and then added the
typeattribute 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.
fixed the nested indent issue, thanks