@sugarat/theme 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/node.d.ts +174 -0
- package/node.js +185 -0
- package/package.json +57 -0
- package/src/components/BlogAlert.vue +74 -0
- package/src/components/BlogApp.vue +103 -0
- package/src/components/BlogArticleAnalyze.vue +154 -0
- package/src/components/BlogComment.vue +131 -0
- package/src/components/BlogFriendLink.vue +94 -0
- package/src/components/BlogHomeBanner.vue +105 -0
- package/src/components/BlogHomeInfo.vue +38 -0
- package/src/components/BlogHomeOverview.vue +97 -0
- package/src/components/BlogHomeTags.vue +123 -0
- package/src/components/BlogHotArticle.vue +177 -0
- package/src/components/BlogImagePreview.vue +52 -0
- package/src/components/BlogItem.vue +112 -0
- package/src/components/BlogList.vue +75 -0
- package/src/components/BlogPopover.vue +218 -0
- package/src/components/BlogRecommendArticle.vue +173 -0
- package/src/components/BlogSearch.vue +198 -0
- package/src/components/BlogSidebar.vue +19 -0
- package/src/composables/config/blog.ts +96 -0
- package/src/composables/config/index.ts +159 -0
- package/src/index.ts +18 -0
- package/src/node.ts +175 -0
- package/src/styles/bg.png +0 -0
- package/src/styles/index.scss +95 -0
- package/src/utils/index.ts +84 -0
- package/types/vue-shim.d.ts +6 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { ElButton } from 'element-plus'
|
|
2
|
+
import { DefaultTheme } from 'vitepress'
|
|
3
|
+
|
|
4
|
+
export namespace BlogPopover {
|
|
5
|
+
export interface Title {
|
|
6
|
+
type: 'title'
|
|
7
|
+
content: string
|
|
8
|
+
style?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Text {
|
|
12
|
+
type: 'text'
|
|
13
|
+
content: string
|
|
14
|
+
style?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Image {
|
|
18
|
+
type: 'image'
|
|
19
|
+
src: string
|
|
20
|
+
style?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Button {
|
|
24
|
+
type: 'button'
|
|
25
|
+
link: string
|
|
26
|
+
content: string
|
|
27
|
+
style?: string
|
|
28
|
+
props?: InstanceType<typeof ElButton>['$props']
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type Value = Title | Text | Image | Button
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export namespace Theme {
|
|
35
|
+
export interface PageMeta {
|
|
36
|
+
title: string
|
|
37
|
+
date: string
|
|
38
|
+
tag?: string[]
|
|
39
|
+
description?: string
|
|
40
|
+
cover?: string
|
|
41
|
+
sticky?: number
|
|
42
|
+
author?: string
|
|
43
|
+
hidden?: boolean
|
|
44
|
+
layout?: string
|
|
45
|
+
// old
|
|
46
|
+
categories: string[]
|
|
47
|
+
tags: string[]
|
|
48
|
+
}
|
|
49
|
+
export interface PageData {
|
|
50
|
+
route: string
|
|
51
|
+
meta: PageMeta
|
|
52
|
+
}
|
|
53
|
+
export interface activeTag {
|
|
54
|
+
label: string
|
|
55
|
+
type: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface GiscusConfig {
|
|
59
|
+
repo: string
|
|
60
|
+
repoId: string
|
|
61
|
+
category: string
|
|
62
|
+
categoryId: string
|
|
63
|
+
mapping?: string
|
|
64
|
+
inputPosition?: 'top' | 'bottom'
|
|
65
|
+
lang?: string
|
|
66
|
+
loading?: 'lazy' | ''
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface HotArticle {
|
|
70
|
+
title?: string
|
|
71
|
+
pageSize?: number
|
|
72
|
+
nextText?: string
|
|
73
|
+
empty?: string | boolean
|
|
74
|
+
}
|
|
75
|
+
export interface RecommendArticle {
|
|
76
|
+
title?: string
|
|
77
|
+
pageSize?: number
|
|
78
|
+
nextText?: string
|
|
79
|
+
empty?: string | boolean
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface HomeBlog {
|
|
83
|
+
name?: string
|
|
84
|
+
motto?: string
|
|
85
|
+
inspiring?: string
|
|
86
|
+
pageSize?: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ArticleConfig {
|
|
90
|
+
readingTime?: boolean
|
|
91
|
+
}
|
|
92
|
+
export interface Alert {
|
|
93
|
+
type: 'success' | 'warning' | 'info' | 'error'
|
|
94
|
+
/**
|
|
95
|
+
* 细粒度的时间控制
|
|
96
|
+
* 默认展示时间,-1 只展示1次,其它数字为每次都展示,一定时间后自动消失,0为不自动消失
|
|
97
|
+
* 配置改变时,会重新触发展示
|
|
98
|
+
*/
|
|
99
|
+
duration: number
|
|
100
|
+
title?: string
|
|
101
|
+
description?: string
|
|
102
|
+
closable?: boolean
|
|
103
|
+
center?: boolean
|
|
104
|
+
closeText?: string
|
|
105
|
+
showIcon?: boolean
|
|
106
|
+
html?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface Popover {
|
|
110
|
+
title: string
|
|
111
|
+
/**
|
|
112
|
+
* 细粒度的时间控制
|
|
113
|
+
* 默认展示时间,-1 只展示1次,其它数字为每次都展示,一定时间后自动消失,0为不自动消失
|
|
114
|
+
* 配置改变时,会重新触发展示
|
|
115
|
+
*/
|
|
116
|
+
duration: number
|
|
117
|
+
body?: BlogPopover.Value[]
|
|
118
|
+
footer?: BlogPopover.Value[]
|
|
119
|
+
/**
|
|
120
|
+
* 手动重新打开
|
|
121
|
+
*/
|
|
122
|
+
reopen?: boolean
|
|
123
|
+
}
|
|
124
|
+
export interface FriendLink {
|
|
125
|
+
nickname: string
|
|
126
|
+
des: string
|
|
127
|
+
url: string
|
|
128
|
+
avatar: string
|
|
129
|
+
}
|
|
130
|
+
export interface BlogConfig {
|
|
131
|
+
pagesData: PageData[]
|
|
132
|
+
srcDir?: string
|
|
133
|
+
author?: string
|
|
134
|
+
hotArticle?: HotArticle
|
|
135
|
+
home?: HomeBlog
|
|
136
|
+
// TODO: 本地全文搜索定制 pagefind || minisearch
|
|
137
|
+
search?: boolean
|
|
138
|
+
/**
|
|
139
|
+
* 配置评论
|
|
140
|
+
* power by https://giscus.app/zh-CN
|
|
141
|
+
*/
|
|
142
|
+
comment?: GiscusConfig | false
|
|
143
|
+
recommend?: RecommendArticle
|
|
144
|
+
article?: ArticleConfig
|
|
145
|
+
/**
|
|
146
|
+
* el-alert
|
|
147
|
+
*/
|
|
148
|
+
alert?: Alert
|
|
149
|
+
popover?: Popover
|
|
150
|
+
friend?: FriendLink[]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface Config extends DefaultTheme.Config {
|
|
154
|
+
blog: BlogConfig
|
|
155
|
+
}
|
|
156
|
+
export interface HomeConfig {
|
|
157
|
+
handleChangeSlogan?: (oldSlogan: string) => string | Promise<string>
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// override style
|
|
2
|
+
import './styles/index.scss'
|
|
3
|
+
|
|
4
|
+
// element-ui
|
|
5
|
+
import 'element-plus/dist/index.css'
|
|
6
|
+
import 'element-plus/theme-chalk/dark/css-vars.css'
|
|
7
|
+
|
|
8
|
+
import { Theme } from 'vitepress'
|
|
9
|
+
import DefaultTheme from 'vitepress/theme'
|
|
10
|
+
import BlogApp from './components/BlogApp.vue'
|
|
11
|
+
import { withConfigProvider } from './composables/config/blog'
|
|
12
|
+
|
|
13
|
+
export const BlogTheme: Theme = {
|
|
14
|
+
...DefaultTheme,
|
|
15
|
+
Layout: withConfigProvider(BlogApp)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export * from './composables/config/index'
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import glob from 'fast-glob'
|
|
2
|
+
import matter from 'gray-matter'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import { execSync, spawn } from 'child_process'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import type { UserConfig } from 'vitepress'
|
|
7
|
+
import { formatDate } from './utils/index'
|
|
8
|
+
import type { Theme } from './composables/config/index'
|
|
9
|
+
|
|
10
|
+
export function getThemeConfig(cfg?: Partial<Theme.BlogConfig>) {
|
|
11
|
+
const srcDir = cfg?.srcDir || process.argv.slice(2)?.[1] || '.'
|
|
12
|
+
const files = glob.sync(`${srcDir}/**/*.md`, { ignore: ['node_modules'] })
|
|
13
|
+
|
|
14
|
+
const data = files
|
|
15
|
+
.map((v) => {
|
|
16
|
+
let route = v
|
|
17
|
+
// 处理文件后缀名
|
|
18
|
+
.replace('.md', '')
|
|
19
|
+
|
|
20
|
+
// 去除 srcDir 处理目录名
|
|
21
|
+
if (route.startsWith('./')) {
|
|
22
|
+
route = route.replace(
|
|
23
|
+
new RegExp(`^\\.\\/${path.join(srcDir, '/')}`),
|
|
24
|
+
''
|
|
25
|
+
)
|
|
26
|
+
} else {
|
|
27
|
+
route = route.replace(new RegExp(`^${path.join(srcDir, '/')}`), '')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const fileContent = fs.readFileSync(v, 'utf-8')
|
|
31
|
+
|
|
32
|
+
// TODO: 支持JSON
|
|
33
|
+
const meta: Partial<Theme.PageMeta> = {
|
|
34
|
+
...matter(fileContent).data
|
|
35
|
+
}
|
|
36
|
+
if (!meta.title) {
|
|
37
|
+
meta.title = getDefaultTitle(fileContent)
|
|
38
|
+
}
|
|
39
|
+
if (!meta.date) {
|
|
40
|
+
// getGitTimestamp(v).then((v) => {
|
|
41
|
+
// meta.date = formatDate(v)
|
|
42
|
+
// })
|
|
43
|
+
meta.date = getFileBirthTime(v)
|
|
44
|
+
} else {
|
|
45
|
+
// TODO: 开放配置,设置时区
|
|
46
|
+
meta.date = formatDate(
|
|
47
|
+
new Date(`${new Date(meta.date).toUTCString()}+8`)
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 处理tags和categories,兼容历史文章
|
|
52
|
+
meta.tag = (meta.tag || []).concat([
|
|
53
|
+
...new Set([...(meta.categories || []), ...(meta.tags || [])])
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
// 获取摘要信息
|
|
57
|
+
const wordCount = 100
|
|
58
|
+
meta.description =
|
|
59
|
+
meta.description || getTextSummary(fileContent, wordCount)
|
|
60
|
+
|
|
61
|
+
// 获取封面图
|
|
62
|
+
meta.cover =
|
|
63
|
+
meta.cover ||
|
|
64
|
+
fileContent.match(/[!]\[.+?\]\((https:\/\/.+)\)/)?.[1] ||
|
|
65
|
+
''
|
|
66
|
+
return {
|
|
67
|
+
route: `/${route}`,
|
|
68
|
+
meta
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.filter((v) => v.meta.layout !== 'home')
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
blog: {
|
|
75
|
+
pagesData: data as Theme.PageData[],
|
|
76
|
+
...cfg
|
|
77
|
+
},
|
|
78
|
+
sidebar: [
|
|
79
|
+
{
|
|
80
|
+
text: '',
|
|
81
|
+
items: []
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getDefaultTitle(content: string) {
|
|
88
|
+
const title =
|
|
89
|
+
clearMatterContent(content)
|
|
90
|
+
.split('\n')
|
|
91
|
+
?.find((str) => {
|
|
92
|
+
return str.startsWith('# ')
|
|
93
|
+
})
|
|
94
|
+
?.slice(2)
|
|
95
|
+
.replace(/[\s]/g, '') || ''
|
|
96
|
+
return title
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function clearMatterContent(content: string) {
|
|
100
|
+
let first___: unknown
|
|
101
|
+
let second___: unknown
|
|
102
|
+
|
|
103
|
+
const lines = content.split('\n').reduce<string[]>((pre, line) => {
|
|
104
|
+
// 移除开头的空白行
|
|
105
|
+
if (!line.trim() && pre.length === 0) {
|
|
106
|
+
return pre
|
|
107
|
+
}
|
|
108
|
+
if (line.trim() === '---') {
|
|
109
|
+
if (first___ === undefined) {
|
|
110
|
+
first___ = pre.length
|
|
111
|
+
} else if (second___ === undefined) {
|
|
112
|
+
second___ = pre.length
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
pre.push(line)
|
|
116
|
+
return pre
|
|
117
|
+
}, [])
|
|
118
|
+
return (
|
|
119
|
+
lines
|
|
120
|
+
// 剔除---之间的内容
|
|
121
|
+
.slice((second___ as number) || 0)
|
|
122
|
+
.join('\n')
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function getFileBirthTime(url: string) {
|
|
127
|
+
// 参考 vitepress 中的 getGitTimestamp 实现
|
|
128
|
+
const infoStr = execSync(`git log -1 --pretty="%ci" ${url}`)
|
|
129
|
+
.toString('utf-8')
|
|
130
|
+
.trim()
|
|
131
|
+
let date = new Date()
|
|
132
|
+
if (infoStr) {
|
|
133
|
+
date = new Date(infoStr)
|
|
134
|
+
}
|
|
135
|
+
return formatDate(date)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getGitTimestamp(file: string) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const child = spawn('git', ['log', '-1', '--pretty="%ci"', file])
|
|
141
|
+
let output = ''
|
|
142
|
+
child.stdout.on('data', (d) => {
|
|
143
|
+
output += String(d)
|
|
144
|
+
})
|
|
145
|
+
child.on('close', () => {
|
|
146
|
+
resolve(+new Date(output))
|
|
147
|
+
})
|
|
148
|
+
child.on('error', reject)
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getTextSummary(text: string, count = 100) {
|
|
153
|
+
return (
|
|
154
|
+
clearMatterContent(text)
|
|
155
|
+
.match(/^# ([\s\S]+)/m)?.[1]
|
|
156
|
+
// 除去标题
|
|
157
|
+
?.replace(/#/g, '')
|
|
158
|
+
// 除去图片
|
|
159
|
+
?.replace(/!\[.*?\]\(.*?\)/g, '')
|
|
160
|
+
// 除去链接
|
|
161
|
+
?.replace(/\[(.*?)\]\(.*?\)/g, '$1')
|
|
162
|
+
// 除去加粗
|
|
163
|
+
?.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
164
|
+
?.split('\n')
|
|
165
|
+
?.filter((v) => !!v)
|
|
166
|
+
?.slice(1)
|
|
167
|
+
?.join('\n')
|
|
168
|
+
?.replace(/>(.*)/, '')
|
|
169
|
+
?.slice(0, count)
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function defineConfig(config: UserConfig<Theme.Config>) {
|
|
174
|
+
return config
|
|
175
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
html {
|
|
2
|
+
--bg-gradient: 255, 255, 255, 0.1;
|
|
3
|
+
--bg-gradient-home: 255, 255, 255;
|
|
4
|
+
--box-shadow: 0 1px 8px 0 rgba(0, 0, 0, 0.1);
|
|
5
|
+
|
|
6
|
+
// blog-item
|
|
7
|
+
--box-shadow-hover: 0 2px 16px 0 rgba(0, 0, 0, 0.2);
|
|
8
|
+
--nav-bgc: rgba(255, 255, 255, 0.9);
|
|
9
|
+
--badge-font-color: #4e5969;
|
|
10
|
+
--description-font-color: #86909c;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
html[class='dark'] {
|
|
14
|
+
--bg-gradient: 20, 20, 20;
|
|
15
|
+
--bg-gradient-home: 20, 20, 20;
|
|
16
|
+
--box-shadow: 0 1px 8px 0 rgba(0, 0, 0, 0.6);
|
|
17
|
+
--nav-bgc: rgba(0, 0, 0, 0.8);
|
|
18
|
+
--box-shadow-hover: 0 2px 16px 0 rgba(0, 0, 0, 0.7);
|
|
19
|
+
--badge-font-color: #bdc3cc;
|
|
20
|
+
--description-font-color: #9facba;
|
|
21
|
+
}
|
|
22
|
+
.VPHome {
|
|
23
|
+
&::before {
|
|
24
|
+
content: '';
|
|
25
|
+
inset: 0;
|
|
26
|
+
position: fixed;
|
|
27
|
+
top: 0;
|
|
28
|
+
z-index: -1;
|
|
29
|
+
background-image: url(./bg.png);
|
|
30
|
+
background-repeat: repeat;
|
|
31
|
+
min-height: 100%;
|
|
32
|
+
}
|
|
33
|
+
min-height: calc(100vh - var(--vp-nav-height));
|
|
34
|
+
background: radial-gradient(
|
|
35
|
+
ellipse,
|
|
36
|
+
rgba(var(--bg-gradient-home), 1) 0%,
|
|
37
|
+
rgba(var(--bg-gradient-home), 0) 700%
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media screen and (max-width: 959px) {
|
|
42
|
+
.VPNav {
|
|
43
|
+
background-color: var(--nav-bgc);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.el-pagination {
|
|
48
|
+
flex-wrap: wrap;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
}
|
|
51
|
+
.el-pagination > * {
|
|
52
|
+
margin-bottom: 10px !important;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@media screen and (min-width: 768px) and (max-width: 1200px) {
|
|
56
|
+
.VPNavBarMenuGroup .button span.text,
|
|
57
|
+
.VPNavBarMenuLink {
|
|
58
|
+
font-size: 10px;
|
|
59
|
+
}
|
|
60
|
+
.VPNavBar {
|
|
61
|
+
height: auto !important;
|
|
62
|
+
}
|
|
63
|
+
.VPNavBarMenu.menu {
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@media screen and (min-width: 960px) and (max-width: 1120px) {
|
|
69
|
+
.VPContent.has-sidebar {
|
|
70
|
+
margin-top: 60px !important;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// sidebar
|
|
75
|
+
@media (min-width: 1440px) {
|
|
76
|
+
aside.VPSidebar {
|
|
77
|
+
padding-left: max(
|
|
78
|
+
32px,
|
|
79
|
+
calc((100% - (var(--vp-layout-max-width) - 16px)) / 2)
|
|
80
|
+
) !important;
|
|
81
|
+
padding-right: 10px !important;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.VPDoc .content main {
|
|
86
|
+
img {
|
|
87
|
+
max-height: 300px;
|
|
88
|
+
margin: 0 auto;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.vp-doc a {
|
|
93
|
+
word-break: break-all;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function formatDate(d: any, fmt = 'yyyy-MM-dd hh:mm:ss') {
|
|
2
|
+
if (!(d instanceof Date)) {
|
|
3
|
+
d = new Date(d)
|
|
4
|
+
}
|
|
5
|
+
const o: any = {
|
|
6
|
+
'M+': d.getMonth() + 1, // 月份
|
|
7
|
+
'd+': d.getDate(), // 日
|
|
8
|
+
'h+': d.getHours(), // 小时
|
|
9
|
+
'm+': d.getMinutes(), // 分
|
|
10
|
+
's+': d.getSeconds(), // 秒
|
|
11
|
+
'q+': Math.floor((d.getMonth() + 3) / 3), // 季度
|
|
12
|
+
S: d.getMilliseconds() // 毫秒
|
|
13
|
+
}
|
|
14
|
+
if (/(y+)/.test(fmt)) {
|
|
15
|
+
fmt = fmt.replace(
|
|
16
|
+
RegExp.$1,
|
|
17
|
+
`${d.getFullYear()}`.substr(4 - RegExp.$1.length)
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
21
|
+
for (const k in o) {
|
|
22
|
+
if (new RegExp(`(${k})`).test(fmt))
|
|
23
|
+
fmt = fmt.replace(
|
|
24
|
+
RegExp.$1,
|
|
25
|
+
RegExp.$1.length === 1 ? o[k] : `00${o[k]}`.substr(`${o[k]}`.length)
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
return fmt
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isCurrentWeek(date: Date, target?: Date) {
|
|
32
|
+
const now = target || new Date()
|
|
33
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
|
34
|
+
const oneDay = 1000 * 60 * 60 * 24
|
|
35
|
+
const nowWeek = today.getDay()
|
|
36
|
+
// 本周一的时间
|
|
37
|
+
const startWeek = today.getTime() - (nowWeek === 0 ? 6 : nowWeek - 1) * oneDay
|
|
38
|
+
return +date >= startWeek && +date <= startWeek + 7 * oneDay
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function formatShowDate(date: Date | string) {
|
|
42
|
+
const source = +new Date(date)
|
|
43
|
+
const now = +new Date()
|
|
44
|
+
const diff = now - source
|
|
45
|
+
const oneSeconds = 1000
|
|
46
|
+
const oneMinute = oneSeconds * 60
|
|
47
|
+
const oneHour = oneMinute * 60
|
|
48
|
+
const oneDay = oneHour * 24
|
|
49
|
+
const oneWeek = oneDay * 7
|
|
50
|
+
if (diff < oneMinute) {
|
|
51
|
+
return `${Math.floor(diff / oneSeconds)}秒前`
|
|
52
|
+
}
|
|
53
|
+
if (diff < oneHour) {
|
|
54
|
+
return `${Math.floor(diff / oneMinute)}分钟前`
|
|
55
|
+
}
|
|
56
|
+
if (diff < oneDay) {
|
|
57
|
+
return `${Math.floor(diff / oneHour)}小时前`
|
|
58
|
+
}
|
|
59
|
+
if (diff < oneWeek) {
|
|
60
|
+
return `${Math.floor(diff / oneDay)}天前`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return formatDate(new Date(date), 'yyyy-MM-dd')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const pattern =
|
|
67
|
+
/[a-zA-Z0-9_\u0392-\u03c9\u00c0-\u00ff\u0600-\u06ff\u0400-\u04ff]+|[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/g
|
|
68
|
+
|
|
69
|
+
// copy from https://github.com/youngjuning/vscode-juejin-wordcount/blob/main/count-word.ts
|
|
70
|
+
export default function countWord(data: string) {
|
|
71
|
+
const m = data.match(pattern)
|
|
72
|
+
let count = 0
|
|
73
|
+
if (!m) {
|
|
74
|
+
return 0
|
|
75
|
+
}
|
|
76
|
+
for (let i = 0; i < m.length; i += 1) {
|
|
77
|
+
if (m[i].charCodeAt(0) >= 0x4e00) {
|
|
78
|
+
count += m[i].length
|
|
79
|
+
} else {
|
|
80
|
+
count += 1
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return count
|
|
84
|
+
}
|