diff --git a/CHANGELOG.md b/CHANGELOG.md index f0cfa8e69..437bd9504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ * 文档修改 * 3.7.0 * `preview` 静态方法添加 `mode` 配置 + * 大纲 DOM 结构和 class 变更 ### v3.6.6 / 2020-11-23 diff --git a/demo/render.html b/demo/render.html index b33b214c9..b30faccaf 100644 --- a/demo/render.html +++ b/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 @@
-
+
diff --git a/src/assets/scss/_content.scss b/src/assets/scss/_content.scss index 5eba17130..8435a9eb7 100644 --- a/src/assets/scss/_content.scss +++ b/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; - } - } } diff --git a/src/assets/scss/_reset.scss b/src/assets/scss/_reset.scss index cb541d113..6c01e0e98 100644 --- a/src/assets/scss/_reset.scss +++ b/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); + } + } + } } diff --git a/src/ts/markdown/outlineRender.ts b/src/ts/markdown/outlineRender.ts index a88344b71..9a64292aa 100644 --- a/src/ts/markdown/outlineRender.ts +++ b/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("", ""); } }); - if (tocHTML !== "") { - const tempElement = document.createElement("div"); - if (vditor) { - if (vditor.currentMode === "wysiwyg") { - tempElement.innerHTML = vditor.lute.SpinVditorDOM("

[ToC]

" + tocHTML); - } else if (vditor.currentMode === "ir") { - tempElement.innerHTML = vditor.lute.SpinVditorIRDOM("

[ToC]

" + 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("

[ToC]

" + tocHTML); + } else if (vditor.currentMode === "ir" && !vditor.preview.element.contains(contentElement)) { + tempElement.innerHTML = vditor.lute.SpinVditorIRDOM("

[ToC]

" + tocHTML); } else { - const lute = Lute.New(); - lute.SetToC(true); - tempElement.innerHTML = lute.HTML2VditorDOM("

[ToC]

" + tocHTML); + tempElement.innerHTML = vditor.lute.HTML2VditorDOM("

[ToC]

" + 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("

[ToC]

" + tocHTML); + } + tempElement.firstElementChild.querySelectorAll("li > span[data-target-id]").forEach((item, index) => { + if (item.nextElementSibling && item.nextElementSibling.tagName === "UL") { + item.insertAdjacentHTML("afterbegin", "") + } else { + item.insertAdjacentHTML("afterbegin", "") } - 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; }; diff --git a/src/ts/markdown/previewRender.ts b/src/ts/markdown/previewRender.ts index 07dc5795a..64728fa41 100644 --- a/src/ts/markdown/previewRender.ts +++ b/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; + } + }) }; diff --git a/src/ts/preview/index.ts b/src/ts/preview/index.ts index 03f37d681..de4275c17 100644 --- a/src/ts/preview/index.ts +++ b/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") { diff --git a/src/ts/util/toc.ts b/src/ts/util/toc.ts index 869f71036..fddb63fc4 100644 --- a/src/ts/util/toc.ts +++ b/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; diff --git a/src/ts/wysiwyg/input.ts b/src/ts/wysiwyg/input.ts index 111b01ff7..209df868c 100644 --- a/src/ts/wysiwyg/input.ts +++ b/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(" { vditor.options.comment.adjustTop(vditor.wysiwyg.getComments(vditor, true)); } } - + renderToc(vditor); afterRenderEvent(vditor, { enableAddUndoStack: true, enableHint: true,