Skip to content

Commit 654ed54

Browse files
committed
feat: add reading time
1 parent fc1712f commit 654ed54

File tree

6 files changed

+111
-18
lines changed

6 files changed

+111
-18
lines changed

package-lock.json

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"pinia": "^2.2.7",
4646
"qiniu-js": "^3.4.2",
4747
"radix-vue": "^1.9.10",
48+
"reading-time": "^1.5.0",
4849
"tailwind-merge": "^2.5.5",
4950
"tailwindcss-animate": "^1.0.7",
5051
"tiny-oss": "^0.5.1",

src/components/CodemirrorEditor/EditorHeader/index.vue

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ const formatItems = [
7171
const store = useStore()
7272
const displayStore = useDisplayStore()
7373
74-
const { isDark, isCiteStatus, output, primaryColor } = storeToRefs(store)
74+
const { isDark, isCiteStatus, isCountStatus, output, primaryColor } = storeToRefs(store)
7575
76-
const { toggleDark, editorRefresh, citeStatusChanged } = store
76+
const { toggleDark, editorRefresh, citeStatusChanged, countStatusChanged } = store
7777
7878
const copyMode = useStorage(addPrefix(`copyMode`), `txt`)
7979
const source = ref(``)
@@ -173,6 +173,13 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
173173
>
174174
微信外链转底部引用
175175
</MenubarCheckboxItem>
176+
<MenubarSeparator />
177+
<MenubarCheckboxItem
178+
:checked="isCountStatus"
179+
@click="countStatusChanged()"
180+
>
181+
统计字数和阅读时间
182+
</MenubarCheckboxItem>
176183
</MenubarContent>
177184
</MenubarMenu>
178185
<EditDropdown />
@@ -384,6 +391,31 @@ const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
384391
</Button>
385392
</div>
386393
</div>
394+
<div class="space-y-2">
395+
<h2>统计字数和阅读时间</h2>
396+
<div class="grid grid-cols-5 justify-items-center gap-2">
397+
<Button
398+
class="w-full"
399+
variant="outline"
400+
:class="{
401+
'border-black dark:border-white': store.isCountStatus,
402+
}"
403+
@click="!store.isCountStatus && store.countStatusChanged()"
404+
>
405+
开启
406+
</Button>
407+
<Button
408+
class="w-full"
409+
variant="outline"
410+
:class="{
411+
'border-black dark:border-white': !store.isCountStatus,
412+
}"
413+
@click="store.isCountStatus && store.countStatusChanged()"
414+
>
415+
关闭
416+
</Button>
417+
</div>
418+
</div>
387419
<div class="space-y-2">
388420
<h2>段落首行缩进</h2>
389421
<div class="grid grid-cols-5 justify-items-center gap-2">

src/stores/index.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export const useStore = defineStore(`store`, () => {
2424
const isCiteStatus = useStorage(`isCiteStatus`, false)
2525
const toggleCiteStatus = useToggle(isCiteStatus)
2626

27+
// 是否统计字数和阅读时间
28+
const isCountStatus = useStorage(`isCountStatus`, false)
29+
const toggleCountStatus = useToggle(isCountStatus)
30+
2731
// 是否开启段落首行缩进
2832
const isUseIndent = useStorage(addPrefix(`use_indent`), false)
2933
const toggleUseIndent = useToggle(isUseIndent)
@@ -177,11 +181,14 @@ export const useStore = defineStore(`store`, () => {
177181
// 更新编辑器
178182
const editorRefresh = () => {
179183
codeThemeChange()
180-
renderer.reset({ citeStatus: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value })
184+
renderer.reset({ citeStatus: isCiteStatus.value, legend: legend.value, isUseIndent: isUseIndent.value, countStatus: isCountStatus.value })
181185

182-
const { markdownContent } = renderer.parseFrontMatterAndContent(editor.value!.getValue())
186+
const { markdownContent, readingTime } = renderer.parseFrontMatterAndContent(editor.value!.getValue())
183187
let outputTemp = marked.parse(markdownContent) as string
184188

189+
// 阅读时间及字数统计
190+
outputTemp = renderer.buildReadingTime(readingTime) + outputTemp
191+
185192
// 去除第一行的 margin-top
186193
outputTemp = outputTemp.replace(/(style=".*?)"/, `$1;margin-top: 0"`)
187194
// 引用脚注
@@ -275,6 +282,7 @@ export const useStore = defineStore(`store`, () => {
275282
const resetStyle = () => {
276283
isCiteStatus.value = false
277284
isMacCodeBlock.value = true
285+
isCountStatus.value = false
278286

279287
theme.value = themeOptions[0].value
280288
fontFamily.value = fontFamilyOptions[0].value
@@ -366,6 +374,10 @@ export const useStore = defineStore(`store`, () => {
366374
toggleCiteStatus()
367375
})
368376

377+
const countStatusChanged = withAfterRefresh(() => {
378+
toggleCountStatus()
379+
})
380+
369381
const useIndentChanged = withAfterRefresh(() => {
370382
toggleUseIndent()
371383
})
@@ -427,6 +439,9 @@ export const useStore = defineStore(`store`, () => {
427439
isUseIndent,
428440
useIndentChanged,
429441

442+
isCountStatus,
443+
countStatusChanged,
444+
430445
output,
431446
editor,
432447
cssEditor,

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface IOpts {
2626
isUseIndent: boolean
2727
legend?: string
2828
citeStatus?: boolean
29+
countStatus?: boolean
2930
}
3031

3132
export type ThemeStyles = Record<Block | Inline, ExtendedProperties>

src/utils/renderer.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import type { ExtendedProperties, IOpts, ThemeStyles } from '@/types'
22
import type { PropertiesHyphen } from 'csstype'
33
import type { Renderer, RendererObject, Tokens } from 'marked'
4+
import type { ReadTimeResults } from 'reading-time'
45
import { cloneDeep, toMerged } from 'es-toolkit'
56
import frontMatter from 'front-matter'
67

78
import hljs from 'highlight.js'
89
import { marked } from 'marked'
910
import mermaid from 'mermaid'
11+
import readingTime from 'reading-time'
12+
1013
import { getStyleString } from '.'
1114
import markedAlert from './MDAlert'
15+
1216
import { MDKatex } from './MDKatex'
1317

1418
marked.setOptions({
@@ -109,6 +113,36 @@ const macCodeSvg = `
109113
</svg>
110114
`.trim()
111115

116+
interface ParseResult {
117+
yamlData: Record<string, any>
118+
markdownContent: string
119+
readingTime: ReadTimeResults
120+
}
121+
122+
function parseFrontMatterAndContent(markdownText: string): ParseResult {
123+
try {
124+
const parsed = frontMatter(markdownText)
125+
const yamlData = parsed.attributes
126+
const markdownContent = parsed.body
127+
128+
const readingTimeResult = readingTime(markdownContent)
129+
130+
return {
131+
yamlData: yamlData as Record<string, any>,
132+
markdownContent,
133+
readingTime: readingTimeResult,
134+
}
135+
}
136+
catch (error) {
137+
console.error(`Error parsing front-matter:`, error)
138+
return {
139+
yamlData: {},
140+
markdownContent: markdownText,
141+
readingTime: readingTime(markdownText),
142+
}
143+
}
144+
}
145+
112146
export function initRenderer(opts: IOpts) {
113147
const footnotes: [number, string, string][] = []
114148
let footnoteIndex: number = 0
@@ -121,19 +155,6 @@ export function initRenderer(opts: IOpts) {
121155
return getStyles(styleMapping, tag, addition)
122156
}
123157

124-
function parseFrontMatterAndContent(markdownText: string) {
125-
try {
126-
const parsed = frontMatter(markdownText)
127-
const yamlData = parsed.attributes
128-
const markdownContent = parsed.body
129-
return { yamlData, markdownContent }
130-
}
131-
catch (error) {
132-
console.error(`Error parsing front-matter:`, error)
133-
return { yamlData: {}, markdownContent: markdownText }
134-
}
135-
}
136-
137158
function styledContent(styleLabel: string, content: string, tagName?: string): string {
138159
const tag = tagName ?? styleLabel
139160
return `<${tag} ${styles(styleLabel)}>${content}</${tag}>`
@@ -156,6 +177,20 @@ export function initRenderer(opts: IOpts) {
156177
marked.use(markedAlert({ styles: styleMapping }))
157178
}
158179

180+
function buildReadingTime(readingTime: ReadTimeResults): string {
181+
if (!opts.countStatus) {
182+
return ``
183+
}
184+
if (!readingTime.words) {
185+
return ``
186+
}
187+
return `
188+
<blockquote ${styles(`blockquote`)}>
189+
<p ${styles(`blockquote_p`)}>约 ${readingTime.words} 字,需 ${readingTime.text.replace(`read`, `阅读。`)}</p>
190+
</blockquote>
191+
`
192+
}
193+
159194
const buildFootnotes = () => {
160195
if (!footnotes.length) {
161196
return ``
@@ -305,6 +340,7 @@ export function initRenderer(opts: IOpts) {
305340
setOptions,
306341
reset,
307342
parseFrontMatterAndContent,
343+
buildReadingTime,
308344
createContainer(content: string) {
309345
return styledContent(`container`, content, `section`)
310346
},

0 commit comments

Comments
 (0)