mirror of
https://github.com/doocs/md.git
synced 2025-01-22 20:04:39 +08:00
feat: add reading time (#509)
This commit is contained in:
parent
fc1712f948
commit
de2cc52e15
10
package-lock.json
generated
10
package-lock.json
generated
@ -33,6 +33,7 @@
|
|||||||
"pinia": "^2.2.7",
|
"pinia": "^2.2.7",
|
||||||
"qiniu-js": "^3.4.2",
|
"qiniu-js": "^3.4.2",
|
||||||
"radix-vue": "^1.9.10",
|
"radix-vue": "^1.9.10",
|
||||||
|
"reading-time": "^1.5.0",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tiny-oss": "^0.5.1",
|
"tiny-oss": "^0.5.1",
|
||||||
@ -12496,9 +12497,10 @@
|
|||||||
},
|
},
|
||||||
"node_modules/mdast-util-to-string": {
|
"node_modules/mdast-util-to-string": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
|
||||||
"integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
|
"integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mdast": "^4.0.0"
|
"@types/mdast": "^4.0.0"
|
||||||
},
|
},
|
||||||
@ -15945,6 +15947,12 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reading-time": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/rechoir": {
|
"node_modules/rechoir": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.6.2.tgz",
|
"resolved": "https://registry.npmmirror.com/rechoir/-/rechoir-0.6.2.tgz",
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
"pinia": "^2.2.7",
|
"pinia": "^2.2.7",
|
||||||
"qiniu-js": "^3.4.2",
|
"qiniu-js": "^3.4.2",
|
||||||
"radix-vue": "^1.9.10",
|
"radix-vue": "^1.9.10",
|
||||||
|
"reading-time": "^1.5.0",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tiny-oss": "^0.5.1",
|
"tiny-oss": "^0.5.1",
|
||||||
|
@ -71,9 +71,9 @@ const formatItems = [
|
|||||||
const store = useStore()
|
const store = useStore()
|
||||||
const displayStore = useDisplayStore()
|
const displayStore = useDisplayStore()
|
||||||
|
|
||||||
const { isDark, isCiteStatus, output, primaryColor } = storeToRefs(store)
|
const { isDark, isCiteStatus, isCountStatus, output, primaryColor } = storeToRefs(store)
|
||||||
|
|
||||||
const { toggleDark, editorRefresh, citeStatusChanged } = store
|
const { toggleDark, editorRefresh, citeStatusChanged, countStatusChanged } = store
|
||||||
|
|
||||||
const copyMode = useStorage(addPrefix(`copyMode`), `txt`)
|
const copyMode = useStorage(addPrefix(`copyMode`), `txt`)
|
||||||
const source = ref(``)
|
const source = ref(``)
|
||||||
@ -173,6 +173,13 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
|||||||
>
|
>
|
||||||
微信外链转底部引用
|
微信外链转底部引用
|
||||||
</MenubarCheckboxItem>
|
</MenubarCheckboxItem>
|
||||||
|
<MenubarSeparator />
|
||||||
|
<MenubarCheckboxItem
|
||||||
|
:checked="isCountStatus"
|
||||||
|
@click="countStatusChanged()"
|
||||||
|
>
|
||||||
|
统计字数和阅读时间
|
||||||
|
</MenubarCheckboxItem>
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
<EditDropdown />
|
<EditDropdown />
|
||||||
@ -384,6 +391,31 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<h2>统计字数和阅读时间</h2>
|
||||||
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
<Button
|
||||||
|
class="w-full"
|
||||||
|
variant="outline"
|
||||||
|
:class="{
|
||||||
|
'border-black dark:border-white': store.isCountStatus,
|
||||||
|
}"
|
||||||
|
@click="!store.isCountStatus && store.countStatusChanged()"
|
||||||
|
>
|
||||||
|
开启
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="w-full"
|
||||||
|
variant="outline"
|
||||||
|
:class="{
|
||||||
|
'border-black dark:border-white': !store.isCountStatus,
|
||||||
|
}"
|
||||||
|
@click="store.isCountStatus && store.countStatusChanged()"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<h2>段落首行缩进</h2>
|
<h2>段落首行缩进</h2>
|
||||||
<div class="grid grid-cols-5 justify-items-center gap-2">
|
<div class="grid grid-cols-5 justify-items-center gap-2">
|
||||||
|
@ -84,6 +84,7 @@ const defaultTheme: Theme = {
|
|||||||
'border-radius': `6px`,
|
'border-radius': `6px`,
|
||||||
'color': `rgba(0,0,0,0.5)`,
|
'color': `rgba(0,0,0,0.5)`,
|
||||||
'background': `var(--blockquote-background)`,
|
'background': `var(--blockquote-background)`,
|
||||||
|
'margin-bottom': `1em`,
|
||||||
},
|
},
|
||||||
|
|
||||||
// 引用内容
|
// 引用内容
|
||||||
@ -334,6 +335,7 @@ const graceTheme = toMerged(defaultTheme, {
|
|||||||
'border-radius': `6px`,
|
'border-radius': `6px`,
|
||||||
'color': `rgba(0,0,0,0.6)`,
|
'color': `rgba(0,0,0,0.6)`,
|
||||||
'box-shadow': `0 4px 6px rgba(0,0,0,0.05)`,
|
'box-shadow': `0 4px 6px rgba(0,0,0,0.05)`,
|
||||||
|
'margin-bottom': `1em`,
|
||||||
},
|
},
|
||||||
|
|
||||||
'blockquote_p': {
|
'blockquote_p': {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
import type { ReadTimeResults } from 'reading-time'
|
||||||
import DEFAULT_CONTENT from '@/assets/example/markdown.md?raw'
|
import DEFAULT_CONTENT from '@/assets/example/markdown.md?raw'
|
||||||
import DEFAULT_CSS_CONTENT from '@/assets/example/theme-css.txt?raw'
|
import DEFAULT_CSS_CONTENT from '@/assets/example/theme-css.txt?raw'
|
||||||
import { altKey, codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, shiftKey, themeMap, themeOptions } from '@/config'
|
import { altKey, codeBlockThemeOptions, colorOptions, fontFamilyOptions, fontSizeOptions, legendOptions, shiftKey, themeMap, themeOptions } from '@/config'
|
||||||
import { addPrefix, css2json, customCssWithTemplate, customizeTheme, downloadMD, exportHTML, formatDoc } from '@/utils'
|
import { addPrefix, css2json, customCssWithTemplate, customizeTheme, downloadMD, exportHTML, formatDoc } from '@/utils'
|
||||||
import { initRenderer } from '@/utils/renderer'
|
|
||||||
|
|
||||||
|
import { initRenderer } from '@/utils/renderer'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
|
|
||||||
@ -24,6 +25,10 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
const isCiteStatus = useStorage(`isCiteStatus`, false)
|
const isCiteStatus = useStorage(`isCiteStatus`, false)
|
||||||
const toggleCiteStatus = useToggle(isCiteStatus)
|
const toggleCiteStatus = useToggle(isCiteStatus)
|
||||||
|
|
||||||
|
// 是否统计字数和阅读时间
|
||||||
|
const isCountStatus = useStorage(`isCountStatus`, false)
|
||||||
|
const toggleCountStatus = useToggle(isCountStatus)
|
||||||
|
|
||||||
// 是否开启段落首行缩进
|
// 是否开启段落首行缩进
|
||||||
const isUseIndent = useStorage(addPrefix(`use_indent`), false)
|
const isUseIndent = useStorage(addPrefix(`use_indent`), false)
|
||||||
const toggleUseIndent = useToggle(isUseIndent)
|
const toggleUseIndent = useToggle(isUseIndent)
|
||||||
@ -174,14 +179,23 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
isUseIndent: isUseIndent.value,
|
isUseIndent: isUseIndent.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const readingTime = ref<ReadTimeResults | null>(null)
|
||||||
|
|
||||||
// 更新编辑器
|
// 更新编辑器
|
||||||
const editorRefresh = () => {
|
const editorRefresh = () => {
|
||||||
codeThemeChange()
|
codeThemeChange()
|
||||||
renderer.reset({ citeStatus: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value })
|
renderer.reset({ citeStatus: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value, countStatus: isCountStatus.value })
|
||||||
|
|
||||||
const { markdownContent } = renderer.parseFrontMatterAndContent(editor.value!.getValue())
|
const { markdownContent, readingTime: readingTimeResult } = renderer.parseFrontMatterAndContent(editor.value!.getValue())
|
||||||
|
console.log(`Reading time result:`, readingTimeResult)
|
||||||
|
readingTime.value = readingTimeResult
|
||||||
let outputTemp = marked.parse(markdownContent) as string
|
let outputTemp = marked.parse(markdownContent) as string
|
||||||
|
|
||||||
|
console.log(readingTime.value)
|
||||||
|
|
||||||
|
// 阅读时间及字数统计
|
||||||
|
outputTemp = renderer.buildReadingTime(readingTimeResult) + outputTemp
|
||||||
|
|
||||||
// 去除第一行的 margin-top
|
// 去除第一行的 margin-top
|
||||||
outputTemp = outputTemp.replace(/(style=".*?)"/, `$1;margin-top: 0"`)
|
outputTemp = outputTemp.replace(/(style=".*?)"/, `$1;margin-top: 0"`)
|
||||||
// 引用脚注
|
// 引用脚注
|
||||||
@ -275,6 +289,7 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
const resetStyle = () => {
|
const resetStyle = () => {
|
||||||
isCiteStatus.value = false
|
isCiteStatus.value = false
|
||||||
isMacCodeBlock.value = true
|
isMacCodeBlock.value = true
|
||||||
|
isCountStatus.value = false
|
||||||
|
|
||||||
theme.value = themeOptions[0].value
|
theme.value = themeOptions[0].value
|
||||||
fontFamily.value = fontFamilyOptions[0].value
|
fontFamily.value = fontFamilyOptions[0].value
|
||||||
@ -366,6 +381,10 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
toggleCiteStatus()
|
toggleCiteStatus()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const countStatusChanged = withAfterRefresh(() => {
|
||||||
|
toggleCountStatus()
|
||||||
|
})
|
||||||
|
|
||||||
const useIndentChanged = withAfterRefresh(() => {
|
const useIndentChanged = withAfterRefresh(() => {
|
||||||
toggleUseIndent()
|
toggleUseIndent()
|
||||||
})
|
})
|
||||||
@ -427,6 +446,9 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
isUseIndent,
|
isUseIndent,
|
||||||
useIndentChanged,
|
useIndentChanged,
|
||||||
|
|
||||||
|
isCountStatus,
|
||||||
|
countStatusChanged,
|
||||||
|
|
||||||
output,
|
output,
|
||||||
editor,
|
editor,
|
||||||
cssEditor,
|
cssEditor,
|
||||||
@ -436,6 +458,7 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
primaryColor,
|
primaryColor,
|
||||||
codeBlockTheme,
|
codeBlockTheme,
|
||||||
legend,
|
legend,
|
||||||
|
readingTime,
|
||||||
|
|
||||||
editorRefresh,
|
editorRefresh,
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ export interface IOpts {
|
|||||||
isUseIndent: boolean
|
isUseIndent: boolean
|
||||||
legend?: string
|
legend?: string
|
||||||
citeStatus?: boolean
|
citeStatus?: boolean
|
||||||
|
countStatus?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ThemeStyles = Record<Block | Inline, ExtendedProperties>
|
export type ThemeStyles = Record<Block | Inline, ExtendedProperties>
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
import type { ExtendedProperties, IOpts, ThemeStyles } from '@/types'
|
import type { ExtendedProperties, IOpts, ThemeStyles } from '@/types'
|
||||||
import type { PropertiesHyphen } from 'csstype'
|
import type { PropertiesHyphen } from 'csstype'
|
||||||
import type { Renderer, RendererObject, Tokens } from 'marked'
|
import type { Renderer, RendererObject, Tokens } from 'marked'
|
||||||
|
import type { ReadTimeResults } from 'reading-time'
|
||||||
import { cloneDeep, toMerged } from 'es-toolkit'
|
import { cloneDeep, toMerged } from 'es-toolkit'
|
||||||
import frontMatter from 'front-matter'
|
import frontMatter from 'front-matter'
|
||||||
|
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import mermaid from 'mermaid'
|
import mermaid from 'mermaid'
|
||||||
|
import readingTime from 'reading-time'
|
||||||
|
|
||||||
import { getStyleString } from '.'
|
import { getStyleString } from '.'
|
||||||
import markedAlert from './MDAlert'
|
import markedAlert from './MDAlert'
|
||||||
|
|
||||||
import { MDKatex } from './MDKatex'
|
import { MDKatex } from './MDKatex'
|
||||||
|
|
||||||
marked.setOptions({
|
marked.setOptions({
|
||||||
@ -109,6 +113,36 @@ const macCodeSvg = `
|
|||||||
</svg>
|
</svg>
|
||||||
`.trim()
|
`.trim()
|
||||||
|
|
||||||
|
interface ParseResult {
|
||||||
|
yamlData: Record<string, any>
|
||||||
|
markdownContent: string
|
||||||
|
readingTime: ReadTimeResults
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFrontMatterAndContent(markdownText: string): ParseResult {
|
||||||
|
try {
|
||||||
|
const parsed = frontMatter(markdownText)
|
||||||
|
const yamlData = parsed.attributes
|
||||||
|
const markdownContent = parsed.body
|
||||||
|
|
||||||
|
const readingTimeResult = readingTime(markdownContent)
|
||||||
|
|
||||||
|
return {
|
||||||
|
yamlData: yamlData as Record<string, any>,
|
||||||
|
markdownContent,
|
||||||
|
readingTime: readingTimeResult,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(`Error parsing front-matter:`, error)
|
||||||
|
return {
|
||||||
|
yamlData: {},
|
||||||
|
markdownContent: markdownText,
|
||||||
|
readingTime: readingTime(markdownText),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function initRenderer(opts: IOpts) {
|
export function initRenderer(opts: IOpts) {
|
||||||
const footnotes: [number, string, string][] = []
|
const footnotes: [number, string, string][] = []
|
||||||
let footnoteIndex: number = 0
|
let footnoteIndex: number = 0
|
||||||
@ -121,19 +155,6 @@ export function initRenderer(opts: IOpts) {
|
|||||||
return getStyles(styleMapping, tag, addition)
|
return getStyles(styleMapping, tag, addition)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseFrontMatterAndContent(markdownText: string) {
|
|
||||||
try {
|
|
||||||
const parsed = frontMatter(markdownText)
|
|
||||||
const yamlData = parsed.attributes
|
|
||||||
const markdownContent = parsed.body
|
|
||||||
return { yamlData, markdownContent }
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error(`Error parsing front-matter:`, error)
|
|
||||||
return { yamlData: {}, markdownContent: markdownText }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function styledContent(styleLabel: string, content: string, tagName?: string): string {
|
function styledContent(styleLabel: string, content: string, tagName?: string): string {
|
||||||
const tag = tagName ?? styleLabel
|
const tag = tagName ?? styleLabel
|
||||||
return `<${tag} ${styles(styleLabel)}>${content}</${tag}>`
|
return `<${tag} ${styles(styleLabel)}>${content}</${tag}>`
|
||||||
@ -156,6 +177,20 @@ export function initRenderer(opts: IOpts) {
|
|||||||
marked.use(markedAlert({ styles: styleMapping }))
|
marked.use(markedAlert({ styles: styleMapping }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildReadingTime(readingTime: ReadTimeResults): string {
|
||||||
|
if (!opts.countStatus) {
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
if (!readingTime.words) {
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<blockquote ${styles(`blockquote`)}>
|
||||||
|
<p ${styles(`blockquote_p`)}>字数 ${readingTime?.words},阅读大约需 ${Math.ceil(readingTime?.minutes)} 分钟</p>
|
||||||
|
</blockquote>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
const buildFootnotes = () => {
|
const buildFootnotes = () => {
|
||||||
if (!footnotes.length) {
|
if (!footnotes.length) {
|
||||||
return ``
|
return ``
|
||||||
@ -305,6 +340,7 @@ export function initRenderer(opts: IOpts) {
|
|||||||
setOptions,
|
setOptions,
|
||||||
reset,
|
reset,
|
||||||
parseFrontMatterAndContent,
|
parseFrontMatterAndContent,
|
||||||
|
buildReadingTime,
|
||||||
createContainer(content: string) {
|
createContainer(content: string) {
|
||||||
return styledContent(`container`, content, `section`)
|
return styledContent(`container`, content, `section`)
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,7 @@ import CodeMirror from 'codemirror'
|
|||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const displayStore = useDisplayStore()
|
const displayStore = useDisplayStore()
|
||||||
const { isDark, output, editor } = storeToRefs(store)
|
const { isDark, output, editor, readingTime } = storeToRefs(store)
|
||||||
const { isShowCssEditor } = storeToRefs(displayStore)
|
const { isShowCssEditor } = storeToRefs(displayStore)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -363,29 +363,18 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="container" class="container flex flex-col">
|
<div ref="container" class="container flex flex-col">
|
||||||
<EditorHeader
|
<EditorHeader @add-format="addFormat" @format-content="formatContent" @start-copy="startCopy" @end-copy="endCopy" />
|
||||||
@add-format="addFormat"
|
|
||||||
@format-content="formatContent"
|
|
||||||
@start-copy="startCopy"
|
|
||||||
@end-copy="endCopy"
|
|
||||||
/>
|
|
||||||
<main class="container-main flex-1">
|
<main class="container-main flex-1">
|
||||||
<div class="container-main-section h-full flex border-1">
|
<div class="container-main-section h-full flex border-1">
|
||||||
<PostSlider />
|
<PostSlider />
|
||||||
<div
|
<div
|
||||||
ref="codeMirrorWrapper"
|
ref="codeMirrorWrapper" class="codeMirror-wrapper flex-1 border-r-1" :class="{
|
||||||
class="codeMirror-wrapper flex-1 border-r-1"
|
|
||||||
:class="{
|
|
||||||
'order-1': !store.isEditOnLeft,
|
'order-1': !store.isEditOnLeft,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<textarea
|
<textarea id="editor" type="textarea" placeholder="Your markdown text here." />
|
||||||
id="editor"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="Your markdown text here."
|
|
||||||
/>
|
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent class="w-64">
|
<ContextMenuContent class="w-64">
|
||||||
<ContextMenuItem inset @click="toggleShowUploadImgDialog()">
|
<ContextMenuItem inset @click="toggleShowUploadImgDialog()">
|
||||||
@ -414,12 +403,7 @@ onMounted(() => {
|
|||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div id="preview" ref="preview" :span="isShowCssEditor ? 8 : 12" class="preview-wrapper flex-1 p-5">
|
||||||
id="preview"
|
|
||||||
ref="preview"
|
|
||||||
:span="isShowCssEditor ? 8 : 12"
|
|
||||||
class="preview-wrapper flex-1 p-5"
|
|
||||||
>
|
|
||||||
<div id="output-wrapper" :class="{ output_night: !backLight }">
|
<div id="output-wrapper" :class="{ output_night: !backLight }">
|
||||||
<div class="preview border shadow-xl">
|
<div class="preview border shadow-xl">
|
||||||
<section id="output" v-html="output" />
|
<section id="output" v-html="output" />
|
||||||
@ -434,11 +418,11 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<CssEditor class="flex-1" />
|
<CssEditor class="flex-1" />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
<footer class="flex flex-1 justify-end pr-5 text-[12px]">
|
||||||
|
字数 {{ readingTime?.words }}, 阅读大约需 {{ Math.ceil(readingTime?.minutes ?? 0) }} 分钟
|
||||||
|
</footer>
|
||||||
|
|
||||||
<UploadImgDialog
|
<UploadImgDialog @upload-image="uploadImage" />
|
||||||
@upload-image="uploadImage"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InsertFormDialog />
|
<InsertFormDialog />
|
||||||
|
|
||||||
@ -460,6 +444,7 @@ onMounted(() => {
|
|||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user