Browse Source

:sparkles: fix #497

Vanessa 5 years ago
parent
commit
38f007a2b6

+ 1 - 0
CHANGELOG.md

@@ -94,6 +94,7 @@
 * 文档修改
   * 3.7.0
     * `preview` 静态方法添加 `mode` 配置
+    * 大纲 DOM 结构和 class 变更
 
 ### v3.6.6 / 2020-11-23
 

+ 6 - 25
demo/render.html

@@ -71,39 +71,20 @@
             overflow: auto;
             font-size: 12px;
             border-left: 1px solid var(--border-color);
+            border-right: 0;
             --border-color: #eee;
-            --color: #616161;
-            --hover-color: #4285f4;
+            --toolbar-icon-hover-color: #4285f4;
+            --textarea-text-color: #616161;
             --hover-background-color: #f6f8fa;
         }
 
         #outline.dark {
             --border-color: #d1d5da;
-            --color: #a6aab0;
-            --hover-color: #fff;
+            --toolbar-icon-hover-color: #fff;
+            --textarea-text-color: #a6aab0;
             --hover-background-color: #444d56;
         }
 
-        #outline ul {
-            list-style: none !important;
-            padding-left: 1em;
-            margin: 0;
-        }
-
-        #outline li > span {
-            display: block;
-            padding: 5px 10px;
-            cursor: pointer;
-            white-space: nowrap;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            color: var(--color);
-        }
-
-        #outline li > span:hover {
-            color: var(--hover-color);
-        }
-
         .vditor-reset ul[data-style="*"] {
             list-style-type: disc
         }
@@ -179,6 +160,6 @@
 <div id="previewWrap">
     <div id="preview" class="preview"></div>
 </div>
-<div id="outline"></div>
+<div id="outline" class="vditor-outline"></div>
 </body>
 </html>

+ 0 - 39
src/assets/scss/_content.scss

@@ -224,43 +224,4 @@
       }
     }
   }
-
-  &-outline {
-    width: 250px;
-    border-right: 1px solid var(--border-color);
-    background-color: var(--panel-background-color);
-    display: none;
-    overflow: auto;
-
-    &::-webkit-scrollbar {
-      display: none;
-    }
-
-    ul {
-      list-style: none !important;
-      padding-left: 1em;
-      margin: 0;
-    }
-
-    li > span {
-      display: block;
-      padding: 5px 10px;
-      cursor: pointer;
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      color: var(--textarea-text-color);
-
-      &:hover {
-        color: var(--toolbar-icon-hover-color);
-      }
-    }
-
-    &__title {
-      border-bottom: 1px dashed var(--border-color);
-      padding: 5px 10px;
-      color: var(--toolbar-icon-color);
-      font-size: 12px;
-    }
-  }
 }

+ 64 - 0
src/assets/scss/_reset.scss

@@ -498,13 +498,77 @@
     user-select: text;
     color: $blurColor;
 
+    .vditor-outline__action {
+      display: none;
+    }
+
     ul {
       list-style: none !important;
       padding-left: 1em;
     }
 
+    & > ul {
+      padding-left: 0;
+    }
+
     span {
       cursor: pointer;
     }
   }
+
+  &-outline {
+    width: 250px;
+    border-right: 1px solid var(--border-color);
+    background-color: var(--panel-background-color);
+    display: none;
+    overflow: auto;
+
+    &::-webkit-scrollbar {
+      display: none;
+    }
+
+    ul {
+      list-style: none !important;
+      padding-left: 1em;
+      margin: 0;
+    }
+
+    &__content > ul {
+      padding-left: 0;
+    }
+
+    li > span {
+      display: flex;
+      align-items: center;
+      padding: 5px 10px;
+      cursor: pointer;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      color: var(--textarea-text-color);
+
+      &:hover {
+        color: var(--toolbar-icon-hover-color);
+      }
+    }
+
+    &__title {
+      border-bottom: 1px dashed var(--border-color);
+      padding: 5px 10px;
+      color: var(--toolbar-icon-color);
+      font-size: 12px;
+    }
+
+    &__action {
+      transition: $transition;
+      height: 10px;
+      width: 10px;
+      fill: currentColor;
+      margin-right: 5px;
+
+      &--close {
+        transform: rotate(-90deg);
+      }
+    }
+  }
 }

+ 58 - 30
src/ts/markdown/outlineRender.ts

@@ -3,42 +3,68 @@ import {mathRender} from "./mathRender";
 
 export const outlineRender = (contentElement: HTMLElement, targetElement: Element, vditor?: IVditor) => {
     let tocHTML = "";
-    const ids: string[] = []
+    const ids: string[] = [];
     Array.from(contentElement.children).forEach((item: HTMLElement, index: number) => {
         if (hasClosestByHeadings(item)) {
-            const lastIndex = item.id.lastIndexOf("_");
-            item.id = item.id.substring(0, lastIndex === -1 ? undefined : lastIndex) + "_" + index;
+            if (vditor) {
+                const lastIndex = item.id.lastIndexOf("_");
+                item.id = item.id.substring(0, lastIndex === -1 ? undefined : lastIndex) + "_" + index;
+            }
             ids.push(item.id);
             tocHTML += item.outerHTML.replace("<wbr>", "");
         }
     });
-    if (tocHTML !== "") {
-        const tempElement = document.createElement("div");
-        if (vditor) {
-            if (vditor.currentMode === "wysiwyg") {
-                tempElement.innerHTML = vditor.lute.SpinVditorDOM("<p>[ToC]</p>" + tocHTML);
-            } else if (vditor.currentMode === "ir") {
-                tempElement.innerHTML = vditor.lute.SpinVditorIRDOM("<p>[ToC]</p>" + tocHTML);
-            }
+    if (tocHTML === "") {
+        return ""
+    }
+    const tempElement = document.createElement("div");
+    if (vditor) {
+        if (vditor.currentMode === "wysiwyg" && !vditor.preview.element.contains(contentElement)) {
+            tempElement.innerHTML = vditor.lute.SpinVditorDOM("<p>[ToC]</p>" + tocHTML);
+        } else if (vditor.currentMode === "ir" && !vditor.preview.element.contains(contentElement)) {
+            tempElement.innerHTML = vditor.lute.SpinVditorIRDOM("<p>[ToC]</p>" + tocHTML);
         } else {
-            const lute = Lute.New();
-            lute.SetToC(true);
-            tempElement.innerHTML = lute.HTML2VditorDOM("<p>[ToC]</p>" + tocHTML);
+            tempElement.innerHTML = vditor.lute.HTML2VditorDOM("<p>[ToC]</p>" + tocHTML);
         }
-        tempElement.firstElementChild.querySelectorAll("li > span[data-target-id]").forEach((item, index) => {
-            item.setAttribute("data-target-id", ids[index]);
-        });
-        tocHTML = tempElement.firstElementChild.innerHTML;
-        targetElement.innerHTML = tocHTML;
-        if (vditor) {
-            mathRender(targetElement as HTMLElement, {
-                cdn: vditor.options.cdn,
-                math: vditor.options.preview.math,
-            });
+    } else {
+        const lute = Lute.New();
+        lute.SetToC(true);
+        tempElement.innerHTML = lute.HTML2VditorDOM("<p>[ToC]</p>" + tocHTML);
+    }
+    tempElement.firstElementChild.querySelectorAll("li > span[data-target-id]").forEach((item, index) => {
+        if (item.nextElementSibling && item.nextElementSibling.tagName === "UL") {
+            item.insertAdjacentHTML("afterbegin", "<svg class='vditor-outline__action'><use xlink:href='#vditor-icon-down'></use></svg>")
+        } else {
+            item.insertAdjacentHTML("afterbegin", "<svg class='vditor-outline__action'></svg>")
         }
-        targetElement.querySelectorAll("li > span").forEach((item) => {
-            item.addEventListener("click", (event: Event & { target: HTMLElement }) => {
-                const idElement = document.getElementById(item.getAttribute("data-target-id"));
+        item.setAttribute("data-target-id", ids[index]);
+    });
+    tocHTML = tempElement.firstElementChild.innerHTML;
+    targetElement.innerHTML = tocHTML;
+    if (vditor) {
+        mathRender(targetElement as HTMLElement, {
+            cdn: vditor.options.cdn,
+            math: vditor.options.preview.math,
+        });
+    }
+    targetElement.firstElementChild.addEventListener("click", (event: Event) => {
+        let target = event.target as HTMLElement;
+        while (target && !target.isEqualNode(targetElement)) {
+            if (target.classList.contains("vditor-outline__action")) {
+                if (target.classList.contains("vditor-outline__action--close")) {
+                    target.classList.remove("vditor-outline__action--close");
+                    target.parentElement.nextElementSibling.setAttribute("style", "display:block");
+                } else {
+                    target.classList.add("vditor-outline__action--close");
+                    target.parentElement.nextElementSibling.setAttribute("style", "display:none");
+                }
+                event.preventDefault();
+                event.stopPropagation();
+                break;
+            } else if (target.getAttribute("data-target-id")) {
+                event.preventDefault();
+                event.stopPropagation();
+                const idElement = document.getElementById(target.getAttribute("data-target-id"));
                 if (!idElement) {
                     return;
                 }
@@ -62,8 +88,10 @@ export const outlineRender = (contentElement: HTMLElement, targetElement: Elemen
                 } else {
                     window.scrollTo(window.scrollX, idElement.offsetTop);
                 }
-            });
-        });
-    }
+                break;
+            }
+            target = target.parentElement;
+        }
+    });
     return tocHTML;
 };

+ 11 - 0
src/ts/markdown/previewRender.ts

@@ -16,6 +16,7 @@ import {mermaidRender} from "./mermaidRender";
 import {mindmapRender} from "./mindmapRender";
 import {setLute} from "./setLute";
 import {speechRender} from "./speechRender";
+import {hasClosestByClassName, hasClosestByMatchTag} from "../util/hasClosest";
 
 const mergeOptions = (options?: IPreviewOptions) => {
     const defaultOption: IPreviewOptions = {
@@ -111,4 +112,14 @@ export const previewRender = async (previewElement: HTMLDivElement, markdown: st
     if (mergedOptions.icon) {
         addScript(`${mergedOptions.cdn}/dist/js/icons/${mergedOptions.icon}.js`, "vditorIconScript");
     }
+    previewElement.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
+        const spanElement = hasClosestByMatchTag(event.target, "SPAN");
+        if (spanElement && hasClosestByClassName(spanElement, "vditor-toc")) {
+            const headingElement = previewElement.querySelector("#" + spanElement.getAttribute("data-target-id")) as HTMLElement;
+            if (headingElement) {
+                window.scrollTo(window.scrollX, headingElement.offsetTop);
+            }
+            return;
+        }
+    })
 };

+ 28 - 4
src/ts/preview/index.ts

@@ -13,6 +13,7 @@ import {mindmapRender} from "../markdown/mindmapRender";
 import {getEventName} from "../util/compatibility";
 import {hasClosestByTag} from "../util/hasClosestByHeadings";
 import {setSelectionFocus} from "../util/selection";
+import {hasClosestByClassName, hasClosestByMatchTag} from "../util/hasClosest";
 
 export class Preview {
     public element: HTMLElement;
@@ -36,6 +37,16 @@ export class Preview {
             this.copyToX(vditor, tempElement);
             event.preventDefault();
         });
+        previewElement.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
+            const spanElement = hasClosestByMatchTag(event.target, "SPAN");
+            if (spanElement && hasClosestByClassName(spanElement, "vditor-toc")) {
+                const headingElement = previewElement.querySelector("#" + spanElement.getAttribute("data-target-id")) as HTMLElement;
+                if (headingElement) {
+                    this.element.scrollTop = headingElement.offsetTop;
+                }
+                return;
+            }
+        })
 
         const actions = vditor.options.preview.actions;
         const actionElement = document.createElement("div");
@@ -189,10 +200,6 @@ export class Preview {
         codeRender(vditor.preview.element.lastElementChild as HTMLElement, vditor.options.lang);
         highlightRender(vditor.options.preview.hljs, vditor.preview.element.lastElementChild as HTMLElement,
             vditor.options.cdn);
-        mathRender(vditor.preview.element.lastElementChild as HTMLElement, {
-            cdn: vditor.options.cdn,
-            math: vditor.options.preview.math,
-        });
         mermaidRender(vditor.preview.element.lastElementChild as HTMLElement, vditor.options.cdn, vditor.options.theme);
         flowchartRender(vditor.preview.element.lastElementChild as HTMLElement, vditor.options.cdn);
         graphvizRender(vditor.preview.element.lastElementChild as HTMLElement, vditor.options.cdn);
@@ -200,6 +207,23 @@ export class Preview {
         mindmapRender(vditor.preview.element.lastElementChild as HTMLElement, vditor.options.cdn, vditor.options.theme);
         abcRender(vditor.preview.element.lastElementChild as HTMLElement, vditor.options.cdn);
         mediaRender(vditor.preview.element.lastElementChild as HTMLElement);
+        // toc render
+        const editorElement = vditor.preview.element;
+        let tocHTML = vditor.outline.render(vditor);
+        if (tocHTML === "") {
+            tocHTML = "[ToC]";
+        }
+        editorElement.querySelectorAll('[data-type="toc-block"]').forEach((item: HTMLElement) => {
+            item.innerHTML = tocHTML;
+            mathRender(item, {
+                cdn: vditor.options.cdn,
+                math: vditor.options.preview.math,
+            });
+        });
+        mathRender(vditor.preview.element.lastElementChild as HTMLElement, {
+            cdn: vditor.options.cdn,
+            math: vditor.options.preview.math,
+        });
     }
 
     private copyToX(vditor: IVditor, copyElement: HTMLElement, type = "mp-wechat") {

+ 3 - 1
src/ts/util/toc.ts

@@ -4,6 +4,9 @@ import {getSelectPosition} from "./selection";
 import {execAfterRender, insertAfterBlock, insertBeforeBlock} from "./fixBrowserBehavior";
 
 export const renderToc = (vditor: IVditor) => {
+    if (vditor.currentMode === "sv") {
+        return;
+    }
     const editorElement = vditor[vditor.currentMode].element;
     let tocHTML = vditor.outline.render(vditor);
     if (tocHTML === "") {
@@ -19,7 +22,6 @@ export const renderToc = (vditor: IVditor) => {
 };
 
 export const clickToc = (event: MouseEvent & { target: HTMLElement }, vditor: IVditor) => {
-    // TOC 点击
     const spanElement = hasClosestByMatchTag(event.target, "SPAN");
     if (spanElement && hasClosestByClassName(spanElement, "vditor-toc")) {
         const headingElement = vditor[vditor.currentMode].element.querySelector("#" + spanElement.getAttribute("data-target-id")) as HTMLElement;

+ 2 - 7
src/ts/wysiwyg/input.ts

@@ -2,7 +2,7 @@ import {
     getTopList,
     hasClosestBlock, hasClosestByAttribute, hasTopClosestByTag,
 } from "../util/hasClosest";
-import {hasClosestByHeadings, hasClosestByTag} from "../util/hasClosestByHeadings";
+import { hasClosestByTag} from "../util/hasClosestByHeadings";
 import {log} from "../util/log";
 import {processCodeRender} from "../util/processCode";
 import {setRangeByWbr} from "../util/selection";
@@ -187,11 +187,6 @@ export const input = (vditor: IVditor, range: Range, event?: InputEvent) => {
             vditor.wysiwyg.element.insertAdjacentElement("beforeend", allFootnoteElement[0]);
         }
 
-        if (hasClosestByHeadings(blockElement) || html.startsWith("<h") || event?.inputType === "deleteContentBackward"
-            || event?.inputType === "deleteContentForward") {
-            renderToc(vditor);
-        }
-
         // 设置光标
         setRangeByWbr(vditor.wysiwyg.element, range);
 
@@ -206,7 +201,7 @@ export const input = (vditor: IVditor, range: Range, event?: InputEvent) => {
             vditor.options.comment.adjustTop(vditor.wysiwyg.getComments(vditor, true));
         }
     }
-
+    renderToc(vditor);
     afterRenderEvent(vditor, {
         enableAddUndoStack: true,
         enableHint: true,