diff --git a/blossom-editor/src/renderer/src/views/article/ArticleIndex.vue b/blossom-editor/src/renderer/src/views/article/ArticleIndex.vue index c6d75a2..98cd549 100644 --- a/blossom-editor/src/renderer/src/views/article/ArticleIndex.vue +++ b/blossom-editor/src/renderer/src/views/article/ArticleIndex.vue @@ -69,12 +69,10 @@
目录 - ({{ isMacOS() ? 'Cmd' : 'Alt' }}+2 可隐藏) + ({{ keymaps.hideToc }} 可隐藏)
-
- -
+
引用图片 @@ -171,7 +169,8 @@ import Notify from '@renderer/scripts/notify' import { useDraggable } from '@renderer/scripts/draggable' import type { shortcutFunc } from '@renderer/scripts/shortcut-register' import { treeToInfo, provideKeyDocInfo, provideKeyCurArticleInfo } from '@renderer/views/doc/doc' -import { TempTextareaKey, ArticleReference, DocEditorStyle } from './scripts/article' +import { TempTextareaKey, ArticleReference, DocEditorStyle, parseTocAsync } from './scripts/article' +import type { Toc } from './scripts/article' import { beforeUpload, onError, picCacheWrapper, picCacheRefresh, uploadForm, uploadDate } from '@renderer/views/picture/scripts/picture' import { useResize } from './scripts/editor-preview-resize' // codemirror @@ -189,6 +188,8 @@ import marked, { } from './scripts/markedjs' import { EPScroll } from './scripts/editor-preview-scroll' import { useArticleHtmlEvent } from './scripts/article-html-event' +import { shallowRef } from 'vue' +import { keymaps } from './scripts/editor-tools' const PictureViewerInfo = defineAsyncComponent(() => import('@renderer/views/picture/PictureViewerInfo.vue')) // const EditorTools = defineAsyncComponent(() => import('./EditorTools.vue')) @@ -463,7 +464,7 @@ const saveCurArticleContent = async (auto: boolean = false) => { name: curArticle.value!.name, markdown: cmw.getDocString(), html: PreviewRef.value.innerHTML, - toc: JSON.stringify(articleToc.value), + // toc: JSON.stringify(articleToc.value), references: articleImg.value.concat(articleLink.value) } await articleUpdContentApi(data) @@ -614,7 +615,7 @@ const setNewState = (md: string): void => { //#region ----------------------------------------< marked/preview >------------------------------- const renderInterval = ref(0) // 解析用时 -const articleHtml = ref('') // 解析后的 html 内容 +const articleHtml = shallowRef('') // 解析后的 html 内容 let immediateParse = false // 是否立即渲染, 文档初次加载时立即渲染, 内容变更时防抖渲染 /** * 自定义渲染 @@ -633,7 +634,6 @@ const renderer = { return renderCode(code, language, _isEscaped) }, heading(text: any, level: number): string { - articleToc.value.push({ level: level, clazz: 'toc-' + level, index: articleToc.value.length, content: text }) return renderHeading(text, level) }, image(href: string | null, _title: string | null, text: string): string { @@ -671,6 +671,7 @@ const parse = () => { articleHtml.value = content renderInterval.value = Date.now() - begin articleParseing = false + nextTick(() => parseToc()) }) } @@ -693,9 +694,9 @@ useResize(EditorRef, PreviewRef, ResizeDividerRef) //#endregion //#region ----------------------------------------< TOC >------------------------------------------ -const articleToc = ref([]) -const articleImg = ref([]) // 文章对图片引用 -const articleLink = ref([]) // 文章对链接的引用 +const articleToc = shallowRef([]) +const articleImg = shallowRef([]) // 文章对图片引用 +const articleLink = shallowRef([]) // 文章对链接的引用 const TocRef = ref() const TocTitleRef = ref() /** @@ -703,18 +704,20 @@ const TocTitleRef = ref() * @param level 标题级别 * @param content 标题内容 */ -const toScroll = (level: number, content: string) => { - let id = level + '-' + content +const toScroll = (id: string) => { let elm: HTMLElement = document.getElementById(id) as HTMLElement ;(elm.parentNode as Element).scrollTop = elm.offsetTop } // 清空当前目录内容 const clearTocAndImg = () => { - articleToc.value = [] articleImg.value = [] articleLink.value = [] } +const parseToc = async () => { + parseTocAsync(PreviewRef.value).then((tocs) => (articleToc.value = tocs)) +} + useDraggable(TocRef, TocTitleRef) //#endregion diff --git a/blossom-editor/src/renderer/src/views/article/ArticleViewWindow.vue b/blossom-editor/src/renderer/src/views/article/ArticleViewWindow.vue index b285cd4..1d85251 100644 --- a/blossom-editor/src/renderer/src/views/article/ArticleViewWindow.vue +++ b/blossom-editor/src/renderer/src/views/article/ArticleViewWindow.vue @@ -4,60 +4,62 @@
《{{ article?.name }}》
- {{ article?.words }} 字 | - {{ article?.uv }} | + {{ article?.words }} 字 | {{ article?.uv }} | {{ article?.likes }}
-
- 公开 {{ article?.openTime }} -
-
- 修改 {{ article?.updTime }} -
+
公开 {{ article?.openTime }}
+
修改 {{ article?.updTime }}
目录
-
+
{{ toc.content }}
-
+
- \ No newline at end of file + diff --git a/blossom-editor/src/renderer/src/views/article/EditorTools.vue b/blossom-editor/src/renderer/src/views/article/EditorTools.vue index e1c2679..22c67ff 100644 --- a/blossom-editor/src/renderer/src/views/article/EditorTools.vue +++ b/blossom-editor/src/renderer/src/views/article/EditorTools.vue @@ -227,7 +227,7 @@
=> { + let heads = ele.querySelectorAll('h1, h2, h3, h4, h5, h6') + let tocs: Toc[] = [] + for (let i = 0; i < heads.length; i++) { + let head: Element = heads[i] + let level = 1 + let content = (head as HTMLElement).innerText + let id = head.id + switch (head.localName) { + case 'h1': + level = 1 + break + case 'h2': + level = 2 + break + case 'h3': + level = 3 + break + case 'h4': + level = 4 + break + case 'h5': + level = 5 + break + case 'h6': + level = 6 + break + } + let toc: Toc = { content: content, clazz: 'toc-' + level, id: id } + tocs.push(toc) + } + return tocs +} diff --git a/blossom-editor/src/renderer/src/views/article/scripts/markedjs.ts b/blossom-editor/src/renderer/src/views/article/scripts/markedjs.ts index 3b3e5c8..593958c 100644 --- a/blossom-editor/src/renderer/src/views/article/scripts/markedjs.ts +++ b/blossom-editor/src/renderer/src/views/article/scripts/markedjs.ts @@ -103,15 +103,26 @@ export const tokenizerCodespan = (src: string): any => { //#endregion //#region ----------------------------------------< renderer >-------------------------------------- - +const domParser = new DOMParser() /** * 标题解析为 TOC 集合, 增加锚点跳转 * @param text 标题内容 * @param level 标题级别 */ export const renderHeading = (text: any, level: number) => { - const realLevel = level - return `${text}` + let id: string = randomInt(1000000, 9999999).toString() + try { + let dom = domParser.parseFromString(text, 'text/html') + if (dom) { + id += dom.body.innerText + } else { + id += text + } + } catch { + id += text + } + + return `${text}` } /** @@ -328,25 +339,10 @@ export const renderCode = (code: string, language: string | undefined, _isEscape /** * 单行代码块的解析拓展 - * 1. katex `$内部写表达式$` * @param src * @returns */ export const renderCodespan = (src: string) => { - let arr = src.match(singleDollar) - if (arr != null && arr.length > 0) { - try { - return katex.renderToString(arr[1], { - throwOnError: true, - output: 'html' - }) - } catch (error) { - console.error(error) - return `
- Katex 语法解析失败! 你可以尝试前往 Katex 官网 来校验你的公式。 -
` - } - } return `${src}` } diff --git a/blossom-editor/src/renderer/src/views/article/styles/bl-preview-toc.scss b/blossom-editor/src/renderer/src/views/article/styles/bl-preview-toc.scss index 5025b97..ddbf359 100644 --- a/blossom-editor/src/renderer/src/views/article/styles/bl-preview-toc.scss +++ b/blossom-editor/src/renderer/src/views/article/styles/bl-preview-toc.scss @@ -7,7 +7,6 @@ .toc-content { overflow-y: overlay; - padding-top: 10px; .toc-1, .toc-2, @@ -38,33 +37,23 @@ } .toc-2 { - &::before { - content: ' '; - } + padding-left: 10px; } .toc-3 { - &::before { - content: ' '; - } + padding-left: 20px; } .toc-4 { - &::before { - content: ' '; - } + padding-left: 30px; } .toc-5 { - &::before { - content: ' '; - } + padding-left: 40px; } .toc-6 { - &::before { - content: ' '; - } + padding-left: 50px; } } } diff --git a/blossom-web/src/views/article/Articles.vue b/blossom-web/src/views/article/Articles.vue index 905b12e..91d7da3 100644 --- a/blossom-web/src/views/article/Articles.vue +++ b/blossom-web/src/views/article/Articles.vue @@ -113,7 +113,7 @@
目录
-
+
{{ toc.content }}
@@ -185,7 +185,7 @@ const article = ref({ html: `
请在左侧菜单选择文章
` }) -const tocList = ref([]) +const tocList = ref([]) const defaultOpeneds = ref([]) const PreviewRef = ref() @@ -217,6 +217,10 @@ const getDocTree = () => { } } +/** + * 获取文章信息 + * @param tree + */ const clickCurDoc = async (tree: DocTree) => { // 如果选中的是文章, 则查询文章详情, 用于在编辑器中显示以及注入 if (tree.ty == 3) { @@ -224,6 +228,7 @@ const clickCurDoc = async (tree: DocTree) => { window.history.replaceState('', '', '#/articles?articleId=' + tree.i) nextTick(() => { PreviewRef.value.scrollTo({ top: 0 }) + parseTocAsync(PreviewRef.value) }) } } @@ -240,19 +245,56 @@ const getCurEditArticle = async (id: number) => { } const then = (resp: any) => { - if (isNull(resp.data)) return + if (isNull(resp.data)) { + return + } article.value = resp.data - tocList.value = JSON.parse(resp.data.toc) } if (userStore.isLogin) { - await articleInfoApi({ id: id, showToc: true, showMarkdown: false, showHtml: true }).then((resp) => then(resp)) + await articleInfoApi({ id: id, showToc: false, showMarkdown: false, showHtml: true }).then((resp) => then(resp)) } else { - await articleInfoOpenApi({ id: id, showToc: true, showMarkdown: false, showHtml: true }).then((resp) => then(resp)) + await articleInfoOpenApi({ id: id, showToc: false, showMarkdown: false, showHtml: true }).then((resp) => then(resp)) } } -const toScroll = (level: number, content: string) => { - let id = level + '-' + content +/** + * 解析目录 + */ +const parseTocAsync = async (ele: HTMLElement) => { + let heads = ele.querySelectorAll('h1, h2, h3, h4, h5, h6') + let tocs: Toc[] = [] + for (let i = 0; i < heads.length; i++) { + let head: Element = heads[i] + let level = 1 + let content = (head as HTMLElement).innerText + let id = head.id + switch (head.localName) { + case 'h1': + level = 1 + break + case 'h2': + level = 2 + break + case 'h3': + level = 3 + break + case 'h4': + level = 4 + break + case 'h5': + level = 5 + break + case 'h6': + level = 6 + break + } + let toc: Toc = { content: content, clazz: 'toc-' + level, id: id } + tocs.push(toc) + } + tocList.value = tocs +} + +const toScroll = (id: string) => { let elm = document.getElementById(id) elm?.scrollIntoView(true) } @@ -349,6 +391,9 @@ const closeAll = () => { maskStyle.value = { display: 'none' } } +/** + * + */ const onresize = () => { let width = document.body.clientWidth if (width < 1100) { @@ -552,10 +597,6 @@ const onresize = () => { .toc-5, .toc-6 { cursor: pointer; - // overflow: hidden; - // white-space: nowrap; - // text-overflow: ellipsis; - // white-space: pre; &:hover { font-weight: bold; @@ -564,7 +605,6 @@ const onresize = () => { .toc-1 { font-size: 1.1em; - border-top: 2px solid #eeeeee; margin-top: 5px; padding-top: 5px; diff --git a/blossom-web/src/views/article/index.d.ts b/blossom-web/src/views/article/index.d.ts index b3144e2..986e50b 100644 --- a/blossom-web/src/views/article/index.d.ts +++ b/blossom-web/src/views/article/index.d.ts @@ -53,3 +53,12 @@ declare type DocType = 1 | 2 | 3 declare interface Window { onHtmlEventDispatch: any } + +/** + * 目录结构 + */ +declare interface Toc { + content: string + clazz: string + id: string +}