mirror of
https://gitee.com/blossom-editor/blossom.git
synced 2025-12-06 16:58:26 +08:00
refactor: 目录的构造方式
This commit is contained in:
parent
71c7e8619a
commit
6f39fe9a5f
@ -69,12 +69,10 @@
|
|||||||
<div :class="['bl-preview-toc-absolute', tocsExpand ? 'is-expand-open' : 'is-expand-close']" ref="TocRef">
|
<div :class="['bl-preview-toc-absolute', tocsExpand ? 'is-expand-open' : 'is-expand-close']" ref="TocRef">
|
||||||
<div class="toc-title" ref="TocTitleRef">
|
<div class="toc-title" ref="TocTitleRef">
|
||||||
目录
|
目录
|
||||||
<span v-show="tocsExpand" style="font-size: 10px">({{ isMacOS() ? 'Cmd' : 'Alt' }}+2 可隐藏)</span>
|
<span v-show="tocsExpand" style="font-size: 10px">({{ keymaps.hideToc }} 可隐藏)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="toc-content" v-show="tocsExpand">
|
<div class="toc-content" v-show="tocsExpand">
|
||||||
<div v-for="toc in articleToc" :key="toc.index" :class="[toc.clazz]" @click="toScroll(toc.level, toc.content)">
|
<div v-for="toc in articleToc" :key="toc.id" :class="[toc.clazz]" @click="toScroll(toc.id)" v-html="toc.content"></div>
|
||||||
<span v-html="toc.content"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="img-title">
|
<div class="img-title">
|
||||||
引用图片
|
引用图片
|
||||||
@ -171,7 +169,8 @@ import Notify from '@renderer/scripts/notify'
|
|||||||
import { useDraggable } from '@renderer/scripts/draggable'
|
import { useDraggable } from '@renderer/scripts/draggable'
|
||||||
import type { shortcutFunc } from '@renderer/scripts/shortcut-register'
|
import type { shortcutFunc } from '@renderer/scripts/shortcut-register'
|
||||||
import { treeToInfo, provideKeyDocInfo, provideKeyCurArticleInfo } from '@renderer/views/doc/doc'
|
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 { beforeUpload, onError, picCacheWrapper, picCacheRefresh, uploadForm, uploadDate } from '@renderer/views/picture/scripts/picture'
|
||||||
import { useResize } from './scripts/editor-preview-resize'
|
import { useResize } from './scripts/editor-preview-resize'
|
||||||
// codemirror
|
// codemirror
|
||||||
@ -189,6 +188,8 @@ import marked, {
|
|||||||
} from './scripts/markedjs'
|
} from './scripts/markedjs'
|
||||||
import { EPScroll } from './scripts/editor-preview-scroll'
|
import { EPScroll } from './scripts/editor-preview-scroll'
|
||||||
import { useArticleHtmlEvent } from './scripts/article-html-event'
|
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 PictureViewerInfo = defineAsyncComponent(() => import('@renderer/views/picture/PictureViewerInfo.vue'))
|
||||||
// const EditorTools = defineAsyncComponent(() => import('./EditorTools.vue'))
|
// const EditorTools = defineAsyncComponent(() => import('./EditorTools.vue'))
|
||||||
@ -463,7 +464,7 @@ const saveCurArticleContent = async (auto: boolean = false) => {
|
|||||||
name: curArticle.value!.name,
|
name: curArticle.value!.name,
|
||||||
markdown: cmw.getDocString(),
|
markdown: cmw.getDocString(),
|
||||||
html: PreviewRef.value.innerHTML,
|
html: PreviewRef.value.innerHTML,
|
||||||
toc: JSON.stringify(articleToc.value),
|
// toc: JSON.stringify(articleToc.value),
|
||||||
references: articleImg.value.concat(articleLink.value)
|
references: articleImg.value.concat(articleLink.value)
|
||||||
}
|
}
|
||||||
await articleUpdContentApi(data)
|
await articleUpdContentApi(data)
|
||||||
@ -614,7 +615,7 @@ const setNewState = (md: string): void => {
|
|||||||
|
|
||||||
//#region ----------------------------------------< marked/preview >-------------------------------
|
//#region ----------------------------------------< marked/preview >-------------------------------
|
||||||
const renderInterval = ref(0) // 解析用时
|
const renderInterval = ref(0) // 解析用时
|
||||||
const articleHtml = ref('') // 解析后的 html 内容
|
const articleHtml = shallowRef('') // 解析后的 html 内容
|
||||||
let immediateParse = false // 是否立即渲染, 文档初次加载时立即渲染, 内容变更时防抖渲染
|
let immediateParse = false // 是否立即渲染, 文档初次加载时立即渲染, 内容变更时防抖渲染
|
||||||
/**
|
/**
|
||||||
* 自定义渲染
|
* 自定义渲染
|
||||||
@ -633,7 +634,6 @@ const renderer = {
|
|||||||
return renderCode(code, language, _isEscaped)
|
return renderCode(code, language, _isEscaped)
|
||||||
},
|
},
|
||||||
heading(text: any, level: number): string {
|
heading(text: any, level: number): string {
|
||||||
articleToc.value.push({ level: level, clazz: 'toc-' + level, index: articleToc.value.length, content: text })
|
|
||||||
return renderHeading(text, level)
|
return renderHeading(text, level)
|
||||||
},
|
},
|
||||||
image(href: string | null, _title: string | null, text: string): string {
|
image(href: string | null, _title: string | null, text: string): string {
|
||||||
@ -671,6 +671,7 @@ const parse = () => {
|
|||||||
articleHtml.value = content
|
articleHtml.value = content
|
||||||
renderInterval.value = Date.now() - begin
|
renderInterval.value = Date.now() - begin
|
||||||
articleParseing = false
|
articleParseing = false
|
||||||
|
nextTick(() => parseToc())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -693,9 +694,9 @@ useResize(EditorRef, PreviewRef, ResizeDividerRef)
|
|||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region ----------------------------------------< TOC >------------------------------------------
|
//#region ----------------------------------------< TOC >------------------------------------------
|
||||||
const articleToc = ref<any[]>([])
|
const articleToc = shallowRef<Toc[]>([])
|
||||||
const articleImg = ref<ArticleReference[]>([]) // 文章对图片引用
|
const articleImg = shallowRef<ArticleReference[]>([]) // 文章对图片引用
|
||||||
const articleLink = ref<ArticleReference[]>([]) // 文章对链接的引用
|
const articleLink = shallowRef<ArticleReference[]>([]) // 文章对链接的引用
|
||||||
const TocRef = ref()
|
const TocRef = ref()
|
||||||
const TocTitleRef = ref()
|
const TocTitleRef = ref()
|
||||||
/**
|
/**
|
||||||
@ -703,18 +704,20 @@ const TocTitleRef = ref()
|
|||||||
* @param level 标题级别
|
* @param level 标题级别
|
||||||
* @param content 标题内容
|
* @param content 标题内容
|
||||||
*/
|
*/
|
||||||
const toScroll = (level: number, content: string) => {
|
const toScroll = (id: string) => {
|
||||||
let id = level + '-' + content
|
|
||||||
let elm: HTMLElement = document.getElementById(id) as HTMLElement
|
let elm: HTMLElement = document.getElementById(id) as HTMLElement
|
||||||
;(elm.parentNode as Element).scrollTop = elm.offsetTop
|
;(elm.parentNode as Element).scrollTop = elm.offsetTop
|
||||||
}
|
}
|
||||||
// 清空当前目录内容
|
// 清空当前目录内容
|
||||||
const clearTocAndImg = () => {
|
const clearTocAndImg = () => {
|
||||||
articleToc.value = []
|
|
||||||
articleImg.value = []
|
articleImg.value = []
|
||||||
articleLink.value = []
|
articleLink.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseToc = async () => {
|
||||||
|
parseTocAsync(PreviewRef.value).then((tocs) => (articleToc.value = tocs))
|
||||||
|
}
|
||||||
|
|
||||||
useDraggable(TocRef, TocTitleRef)
|
useDraggable(TocRef, TocTitleRef)
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
@ -4,60 +4,62 @@
|
|||||||
<div class="bl-preview-toc-block">
|
<div class="bl-preview-toc-block">
|
||||||
<div class="toc-subtitle">《{{ article?.name }}》</div>
|
<div class="toc-subtitle">《{{ article?.name }}》</div>
|
||||||
<div class="toc-subtitle">
|
<div class="toc-subtitle">
|
||||||
<span class="iconbl bl-pen-line"></span> {{ article?.words }} 字 |
|
<span class="iconbl bl-pen-line"></span> {{ article?.words }} 字 | <span class="iconbl bl-read-line"></span> {{ article?.uv }} |
|
||||||
<span class="iconbl bl-read-line"></span> {{ article?.uv }} |
|
|
||||||
<span class="iconbl bl-like-line"></span> {{ article?.likes }}
|
<span class="iconbl bl-like-line"></span> {{ article?.likes }}
|
||||||
</div>
|
</div>
|
||||||
<div class="toc-subtitle">
|
<div class="toc-subtitle"><span class="iconbl bl-a-clock3-line"></span> 公开 {{ article?.openTime }}</div>
|
||||||
<span class="iconbl bl-a-clock3-line"></span> 公开 {{ article?.openTime }}
|
<div class="toc-subtitle"><span class="iconbl bl-a-clock3-line"></span> 修改 {{ article?.updTime }}</div>
|
||||||
</div>
|
|
||||||
<div class="toc-subtitle">
|
|
||||||
<span class="iconbl bl-a-clock3-line"></span> 修改 {{ article?.updTime }}
|
|
||||||
</div>
|
|
||||||
<div class="toc-title">目录</div>
|
<div class="toc-title">目录</div>
|
||||||
<div class="toc-content">
|
<div class="toc-content">
|
||||||
<div v-for="toc in tocList" :key="toc.index" :class="[toc.clazz]" @click="toScroll(toc.level, toc.content)">
|
<div v-for="toc in tocs" :key="toc.id" :class="[toc.clazz]" @click="toScroll(toc.id)">
|
||||||
{{ toc.content }}
|
{{ toc.content }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview bl-preview" :style="editorStyle" v-html="article?.html"></div>
|
<div class="preview bl-preview" :style="editorStyle" v-html="article?.html" ref="WindowPreviewRef"></div>
|
||||||
<el-backtop target=".preview" :right="50" :bottom="50">
|
<el-backtop target=".preview" :right="50" :bottom="50">
|
||||||
<div class="iconbl bl-send-line backtop"></div>
|
<div class="iconbl bl-send-line backtop"></div>
|
||||||
</el-backtop>
|
</el-backtop>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from "pinia"
|
import { storeToRefs } from 'pinia'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, nextTick } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { articleInfoApi } from '@renderer/api/blossom'
|
import { articleInfoApi } from '@renderer/api/blossom'
|
||||||
import { useConfigStore } from '@renderer/stores/config'
|
import { useConfigStore } from '@renderer/stores/config'
|
||||||
|
import { parseTocAsync } from './scripts/article'
|
||||||
|
import type { Toc } from './scripts/article'
|
||||||
|
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const { editorStyle } = storeToRefs(configStore)
|
const { editorStyle } = storeToRefs(configStore)
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute()
|
||||||
const article = ref<DocInfo>()
|
const article = ref<DocInfo>()
|
||||||
const tocList = ref<any>([])
|
const tocs = ref<Toc[]>([])
|
||||||
|
const WindowPreviewRef = ref()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跳转至指定ID位置,ID为 标题级别-标题内容
|
* 跳转至指定ID位置,ID为 标题级别-标题内容
|
||||||
* @param level 标题级别
|
* @param level 标题级别
|
||||||
* @param content 标题内容
|
* @param content 标题内容
|
||||||
*/
|
*/
|
||||||
const toScroll = (level: number, content: string) => {
|
const toScroll = (id: string) => {
|
||||||
let id = level + '-' + content
|
|
||||||
let elm: HTMLElement = document.getElementById(id) as HTMLElement
|
let elm: HTMLElement = document.getElementById(id) as HTMLElement
|
||||||
(elm.parentNode as Element).scrollTop = elm.offsetTop
|
;(elm.parentNode as Element).scrollTop = elm.offsetTop - 40
|
||||||
}
|
}
|
||||||
|
|
||||||
const initPreview = (articleId: string) => {
|
const initPreview = (articleId: string) => {
|
||||||
articleInfoApi({ id: articleId, showToc: true, showMarkdown: false, showHtml: true }).then(resp => {
|
articleInfoApi({ id: articleId, showToc: false, showMarkdown: false, showHtml: true }).then((resp) => {
|
||||||
article.value = resp.data
|
article.value = resp.data
|
||||||
tocList.value = JSON.parse(resp.data.toc)
|
nextTick(() => initToc())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const initToc = () => {
|
||||||
|
parseTocAsync(WindowPreviewRef.value).then((toc) => {
|
||||||
|
tocs.value = toc
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +67,7 @@ onMounted(() => {
|
|||||||
initPreview(route.query.articleId as string)
|
initPreview(route.query.articleId as string)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang=scss>
|
<style scoped lang="scss">
|
||||||
@import './styles/bl-preview-toc.scss';
|
@import './styles/bl-preview-toc.scss';
|
||||||
@import './styles/article-backtop.scss';
|
@import './styles/article-backtop.scss';
|
||||||
|
|
||||||
@ -88,10 +90,7 @@ onMounted(() => {
|
|||||||
:deep(.katex > *) {
|
:deep(.katex > *) {
|
||||||
font-size: 1.2em !important;
|
font-size: 1.2em !important;
|
||||||
font-family: 'KaTeX_Size1', sans-serif !important;
|
font-family: 'KaTeX_Size1', sans-serif !important;
|
||||||
// font-size: 1.3em !important;
|
|
||||||
// font-family: 'KaTeX_Math', sans-serif !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -227,7 +227,7 @@
|
|||||||
<!-- 其他工具 -->
|
<!-- 其他工具 -->
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
content="查看快捷键"
|
content="快捷键说明"
|
||||||
popper-class="is-small"
|
popper-class="is-small"
|
||||||
effect="light"
|
effect="light"
|
||||||
placement="top"
|
placement="top"
|
||||||
|
|||||||
@ -46,3 +46,52 @@ export interface ArticleReference {
|
|||||||
*/
|
*/
|
||||||
type: 10 | 11 | 12 | 21
|
type: 10 | 11 | 12 | 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目录结构
|
||||||
|
*/
|
||||||
|
export interface Toc {
|
||||||
|
content: string
|
||||||
|
clazz: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析文章中的标题, 返回目录对象集合
|
||||||
|
*
|
||||||
|
* @param ele
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const parseTocAsync = async (ele: HTMLElement): Promise<Toc[]> => {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -103,15 +103,26 @@ export const tokenizerCodespan = (src: string): any => {
|
|||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region ----------------------------------------< renderer >--------------------------------------
|
//#region ----------------------------------------< renderer >--------------------------------------
|
||||||
|
const domParser = new DOMParser()
|
||||||
/**
|
/**
|
||||||
* 标题解析为 TOC 集合, 增加锚点跳转
|
* 标题解析为 TOC 集合, 增加锚点跳转
|
||||||
* @param text 标题内容
|
* @param text 标题内容
|
||||||
* @param level 标题级别
|
* @param level 标题级别
|
||||||
*/
|
*/
|
||||||
export const renderHeading = (text: any, level: number) => {
|
export const renderHeading = (text: any, level: number) => {
|
||||||
const realLevel = level
|
let id: string = randomInt(1000000, 9999999).toString()
|
||||||
return `<h${realLevel} id="${realLevel}-${text}">${text}</h${realLevel}>`
|
try {
|
||||||
|
let dom = domParser.parseFromString(text, 'text/html')
|
||||||
|
if (dom) {
|
||||||
|
id += dom.body.innerText
|
||||||
|
} else {
|
||||||
|
id += text
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
id += text
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<h${level} id="${id}">${text}</h${level}>`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -328,25 +339,10 @@ export const renderCode = (code: string, language: string | undefined, _isEscape
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 单行代码块的解析拓展
|
* 单行代码块的解析拓展
|
||||||
* 1. katex `$内部写表达式$`
|
|
||||||
* @param src
|
* @param src
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const renderCodespan = (src: string) => {
|
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 `<div class='bl-preview-analysis-fail-inline'>
|
|
||||||
Katex 语法解析失败! 你可以尝试前往<a href='https://katex.org/#demo' target='_blank'> Katex 官网</a> 来校验你的公式。
|
|
||||||
</div>`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `<code>${src}</code>`
|
return `<code>${src}</code>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
.toc-content {
|
.toc-content {
|
||||||
overflow-y: overlay;
|
overflow-y: overlay;
|
||||||
padding-top: 10px;
|
|
||||||
|
|
||||||
.toc-1,
|
.toc-1,
|
||||||
.toc-2,
|
.toc-2,
|
||||||
@ -38,33 +37,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toc-2 {
|
.toc-2 {
|
||||||
&::before {
|
padding-left: 10px;
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc-3 {
|
.toc-3 {
|
||||||
&::before {
|
padding-left: 20px;
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc-4 {
|
.toc-4 {
|
||||||
&::before {
|
padding-left: 30px;
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc-5 {
|
.toc-5 {
|
||||||
&::before {
|
padding-left: 40px;
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toc-6 {
|
.toc-6 {
|
||||||
&::before {
|
padding-left: 50px;
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,7 +113,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="toc-title">目录</div>
|
<div class="toc-title">目录</div>
|
||||||
<div class="toc-content">
|
<div class="toc-content">
|
||||||
<div v-for="toc in tocList" :key="toc.index" :class="[toc.clazz]" @click="toScroll(toc.level, toc.content)">
|
<div v-for="toc in tocList" :key="toc.id" :class="[toc.clazz]" @click="toScroll(toc.id)">
|
||||||
{{ toc.content }}
|
{{ toc.content }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -185,7 +185,7 @@ const article = ref<DocInfo>({
|
|||||||
html: `<div style="color:#E3E3E3;width:100%;height:300px;display:flex;justify-content: center;
|
html: `<div style="color:#E3E3E3;width:100%;height:300px;display:flex;justify-content: center;
|
||||||
align-items: center;font-size:25px;">请在左侧菜单选择文章</div>`
|
align-items: center;font-size:25px;">请在左侧菜单选择文章</div>`
|
||||||
})
|
})
|
||||||
const tocList = ref<any>([])
|
const tocList = ref<Toc[]>([])
|
||||||
const defaultOpeneds = ref<string[]>([])
|
const defaultOpeneds = ref<string[]>([])
|
||||||
const PreviewRef = ref()
|
const PreviewRef = ref()
|
||||||
|
|
||||||
@ -217,6 +217,10 @@ const getDocTree = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文章信息
|
||||||
|
* @param tree
|
||||||
|
*/
|
||||||
const clickCurDoc = async (tree: DocTree) => {
|
const clickCurDoc = async (tree: DocTree) => {
|
||||||
// 如果选中的是文章, 则查询文章详情, 用于在编辑器中显示以及注入
|
// 如果选中的是文章, 则查询文章详情, 用于在编辑器中显示以及注入
|
||||||
if (tree.ty == 3) {
|
if (tree.ty == 3) {
|
||||||
@ -224,6 +228,7 @@ const clickCurDoc = async (tree: DocTree) => {
|
|||||||
window.history.replaceState('', '', '#/articles?articleId=' + tree.i)
|
window.history.replaceState('', '', '#/articles?articleId=' + tree.i)
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
PreviewRef.value.scrollTo({ top: 0 })
|
PreviewRef.value.scrollTo({ top: 0 })
|
||||||
|
parseTocAsync(PreviewRef.value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,19 +245,56 @@ const getCurEditArticle = async (id: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const then = (resp: any) => {
|
const then = (resp: any) => {
|
||||||
if (isNull(resp.data)) return
|
if (isNull(resp.data)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
article.value = resp.data
|
article.value = resp.data
|
||||||
tocList.value = JSON.parse(resp.data.toc)
|
|
||||||
}
|
}
|
||||||
if (userStore.isLogin) {
|
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 {
|
} 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)
|
let elm = document.getElementById(id)
|
||||||
elm?.scrollIntoView(true)
|
elm?.scrollIntoView(true)
|
||||||
}
|
}
|
||||||
@ -349,6 +391,9 @@ const closeAll = () => {
|
|||||||
maskStyle.value = { display: 'none' }
|
maskStyle.value = { display: 'none' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
const onresize = () => {
|
const onresize = () => {
|
||||||
let width = document.body.clientWidth
|
let width = document.body.clientWidth
|
||||||
if (width < 1100) {
|
if (width < 1100) {
|
||||||
@ -552,10 +597,6 @@ const onresize = () => {
|
|||||||
.toc-5,
|
.toc-5,
|
||||||
.toc-6 {
|
.toc-6 {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
// overflow: hidden;
|
|
||||||
// white-space: nowrap;
|
|
||||||
// text-overflow: ellipsis;
|
|
||||||
// white-space: pre;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -564,7 +605,6 @@ const onresize = () => {
|
|||||||
|
|
||||||
.toc-1 {
|
.toc-1 {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
border-top: 2px solid #eeeeee;
|
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
|
|
||||||
|
|||||||
9
blossom-web/src/views/article/index.d.ts
vendored
9
blossom-web/src/views/article/index.d.ts
vendored
@ -53,3 +53,12 @@ declare type DocType = 1 | 2 | 3
|
|||||||
declare interface Window {
|
declare interface Window {
|
||||||
onHtmlEventDispatch: any
|
onHtmlEventDispatch: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目录结构
|
||||||
|
*/
|
||||||
|
declare interface Toc {
|
||||||
|
content: string
|
||||||
|
clazz: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user