@waline/client 1.5.3 → 2.0.0-alpha.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/dist/component.js +2 -0
- package/dist/component.js.map +1 -0
- package/dist/pageview.cjs.js +2 -0
- package/dist/pageview.cjs.js.map +1 -0
- package/dist/pageview.d.ts +33 -0
- package/dist/pageview.esm.js +2 -0
- package/dist/pageview.esm.js.map +1 -0
- package/dist/pageview.js +2 -0
- package/dist/pageview.js.map +1 -0
- package/dist/{Waline.min.d.ts → shim.d.ts} +192 -261
- package/dist/{Waline.noStyle.d.ts → shim.esm.d.ts} +192 -261
- package/dist/shim.esm.js +2 -0
- package/dist/shim.esm.js.map +1 -0
- package/dist/shim.js +2 -0
- package/dist/shim.js.map +1 -0
- package/dist/waline.cjs.d.ts +388 -0
- package/dist/waline.cjs.js +2 -0
- package/dist/waline.cjs.js.map +1 -0
- package/dist/waline.css +1 -0
- package/dist/waline.css.map +1 -0
- package/dist/waline.d.ts +388 -0
- package/dist/waline.esm.d.ts +388 -0
- package/dist/waline.esm.js +2 -0
- package/dist/waline.esm.js.map +1 -0
- package/dist/waline.js +2 -0
- package/dist/waline.js.map +1 -0
- package/package.json +33 -18
- package/src/comment.ts +39 -0
- package/src/components/CommentBox.vue +667 -0
- package/src/components/CommentCard.vue +125 -0
- package/src/components/Icons.ts +124 -0
- package/src/components/Waline.vue +359 -0
- package/src/composables/index.ts +3 -0
- package/src/composables/inputs.ts +29 -0
- package/src/composables/store.ts +38 -0
- package/src/composables/userInfo.ts +27 -0
- package/src/config/default.ts +21 -0
- package/src/config/i18n/en.ts +34 -0
- package/src/config/i18n/generate.ts +39 -0
- package/src/config/i18n/index.ts +30 -0
- package/src/config/i18n/jp.ts +34 -0
- package/src/config/i18n/pt-BR.ts +34 -0
- package/src/config/i18n/ru.ts +34 -0
- package/src/config/i18n/vi-VN.ts +34 -0
- package/src/config/i18n/zh-CN.ts +34 -0
- package/src/config/i18n/zh-TW.ts +34 -0
- package/src/config/index.ts +2 -0
- package/src/entrys/components.ts +2 -0
- package/src/entrys/full.ts +7 -0
- package/src/entrys/init.ts +4 -0
- package/src/entrys/pageview.ts +2 -0
- package/src/init.ts +92 -0
- package/src/pageview.ts +100 -0
- package/src/shims-hanabi.d.ts +9 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/styles/base.scss +67 -0
- package/src/styles/card.scss +223 -0
- package/src/styles/config.scss +52 -0
- package/src/styles/emoji.scss +118 -0
- package/src/styles/highlight.scss +135 -0
- package/src/styles/index.scss +12 -0
- package/src/styles/layout.scss +78 -0
- package/src/styles/nomalize.scss +112 -0
- package/src/styles/panel.scss +293 -0
- package/src/styles/recent.scss +3 -0
- package/src/typings/base.ts +54 -0
- package/src/typings/comment.ts +25 -0
- package/src/typings/index.ts +5 -0
- package/src/typings/locale.ts +32 -0
- package/src/typings/options.ts +41 -0
- package/src/typings/waline.ts +208 -0
- package/src/utils/config.ts +99 -0
- package/src/utils/darkmode.ts +11 -0
- package/src/utils/data.ts +10 -0
- package/src/utils/emoji.ts +75 -0
- package/src/utils/error.ts +3 -0
- package/src/utils/fetch.ts +177 -0
- package/src/utils/getRoot.ts +8 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/markdown.ts +41 -0
- package/src/utils/markedMathExtension.ts +52 -0
- package/src/utils/path.ts +15 -0
- package/src/utils/query.ts +2 -0
- package/src/utils/timeAgo.ts +75 -0
- package/src/utils/userInfo.ts +26 -0
- package/src/utils/wordCount.ts +20 -0
- package/src/version.ts +3 -0
- package/src/widgets/index.ts +1 -0
- package/src/widgets/recentComments.ts +52 -0
- package/dist/Waline.min.js +0 -2
- package/dist/Waline.min.js.map +0 -1
- package/dist/Waline.noStyle.js +0 -2
- package/dist/Waline.noStyle.js.map +0 -1
- package/dist/index.html +0 -11
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultLang,
|
|
3
|
+
defaultUploadImage,
|
|
4
|
+
defaultTexRenderer,
|
|
5
|
+
getMeta,
|
|
6
|
+
locales,
|
|
7
|
+
} from '../config';
|
|
8
|
+
|
|
9
|
+
import { decodePath, isLinkHttp, removeEndingSplash } from './path';
|
|
10
|
+
import { getEmojis } from './emoji';
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
WalineEmojiInfo,
|
|
14
|
+
WalineEmojiMaps,
|
|
15
|
+
WalineLocale,
|
|
16
|
+
WalineProps,
|
|
17
|
+
} from '../typings';
|
|
18
|
+
import hanabi from 'hanabi';
|
|
19
|
+
|
|
20
|
+
export interface EmojiConfig {
|
|
21
|
+
tabs: Pick<WalineEmojiInfo, 'name' | 'icon' | 'items'>[];
|
|
22
|
+
map: WalineEmojiMaps;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Config
|
|
26
|
+
extends Required<
|
|
27
|
+
Pick<
|
|
28
|
+
WalineProps,
|
|
29
|
+
| 'path'
|
|
30
|
+
| 'lang'
|
|
31
|
+
| 'meta'
|
|
32
|
+
| 'pageSize'
|
|
33
|
+
| 'requiredMeta'
|
|
34
|
+
| 'imageUploader'
|
|
35
|
+
| 'highlighter'
|
|
36
|
+
| 'texRenderer'
|
|
37
|
+
| 'copyright'
|
|
38
|
+
| 'login'
|
|
39
|
+
>
|
|
40
|
+
>,
|
|
41
|
+
Pick<WalineProps, 'dark' | 'serverURL'> {
|
|
42
|
+
locale: WalineLocale;
|
|
43
|
+
wordLimit: [number, number] | false;
|
|
44
|
+
emoji: Promise<EmojiConfig>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const getServerURL = (serverURL: string): string => {
|
|
48
|
+
const result = removeEndingSplash(serverURL);
|
|
49
|
+
|
|
50
|
+
return isLinkHttp(result) ? result : `https://${result}`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const fallback = <T = unknown>(
|
|
54
|
+
value: T | false | undefined,
|
|
55
|
+
fallback: T
|
|
56
|
+
): T | false =>
|
|
57
|
+
typeof value === 'function' ? value : value === false ? false : fallback;
|
|
58
|
+
|
|
59
|
+
export const getConfig = ({
|
|
60
|
+
serverURL,
|
|
61
|
+
|
|
62
|
+
path = location.pathname,
|
|
63
|
+
lang = defaultLang,
|
|
64
|
+
locale,
|
|
65
|
+
emoji = ['https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/weibo'],
|
|
66
|
+
meta = ['nick', 'mail', 'link'],
|
|
67
|
+
requiredMeta = [],
|
|
68
|
+
pageSize = 10,
|
|
69
|
+
wordLimit,
|
|
70
|
+
imageUploader,
|
|
71
|
+
highlighter,
|
|
72
|
+
texRenderer,
|
|
73
|
+
copyright = true,
|
|
74
|
+
login = 'enable',
|
|
75
|
+
...more
|
|
76
|
+
}: WalineProps): Config => ({
|
|
77
|
+
serverURL: getServerURL(serverURL),
|
|
78
|
+
path: decodePath(path),
|
|
79
|
+
lang,
|
|
80
|
+
locale: {
|
|
81
|
+
...(locales[lang] || locales[defaultLang]),
|
|
82
|
+
...(typeof locale === 'object' ? locale : {}),
|
|
83
|
+
},
|
|
84
|
+
emoji: getEmojis(emoji),
|
|
85
|
+
wordLimit: Array.isArray(wordLimit)
|
|
86
|
+
? wordLimit
|
|
87
|
+
: wordLimit
|
|
88
|
+
? [0, wordLimit]
|
|
89
|
+
: false,
|
|
90
|
+
meta: getMeta(meta),
|
|
91
|
+
requiredMeta: getMeta(requiredMeta),
|
|
92
|
+
pageSize,
|
|
93
|
+
login,
|
|
94
|
+
imageUploader: fallback(imageUploader, defaultUploadImage),
|
|
95
|
+
highlighter: fallback(highlighter, hanabi),
|
|
96
|
+
texRenderer: fallback(texRenderer, defaultTexRenderer),
|
|
97
|
+
copyright,
|
|
98
|
+
...more,
|
|
99
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const style = `{--waline-white:#000;--waline-light-grey:#666;--waline-dark-grey:#999;--waline-color:#888;--waline-bgcolor:#1e1e1e;--waline-bgcolor-light:#272727;--waline-bgcolor-hover: #444;--waline-border-color:#333;--waline-disable-bgcolor:#444;--waline-disable-color:#272727;--waline-bq-color:#272727;--waline-info-bgcolor:#272727;--waline-info-color:#666}`;
|
|
2
|
+
|
|
3
|
+
export const getDarkStyle = (selector?: string | boolean): string => {
|
|
4
|
+
if (typeof selector === 'string') {
|
|
5
|
+
return selector === 'auto'
|
|
6
|
+
? `@media(prefers-color-scheme:dark){body${style}}`
|
|
7
|
+
: `${selector}${style}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return selector === true ? `:root${style}` : '';
|
|
11
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const isImage = (item: DataTransferItem): boolean =>
|
|
2
|
+
item.type.includes('image');
|
|
3
|
+
|
|
4
|
+
export const getImagefromDataTransfer = (
|
|
5
|
+
items: DataTransferItemList
|
|
6
|
+
): File | null => {
|
|
7
|
+
const image = Array.from(items).find(isImage);
|
|
8
|
+
|
|
9
|
+
return image ? (image.getAsFile() as File) : null;
|
|
10
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Store, useStore } from '../composables/store';
|
|
2
|
+
import { removeEndingSplash } from './path';
|
|
3
|
+
|
|
4
|
+
import type { EmojiConfig } from './config';
|
|
5
|
+
import type { WalineEmojiInfo } from '../typings';
|
|
6
|
+
|
|
7
|
+
let store: Store;
|
|
8
|
+
|
|
9
|
+
const hasVersion = (url: string): boolean =>
|
|
10
|
+
Boolean(/@[0-9]+\.[0-9]+\.[0-9]+/.test(url));
|
|
11
|
+
|
|
12
|
+
const fetchEmoji = (link: string): Promise<WalineEmojiInfo> => {
|
|
13
|
+
if (!store) store = useStore('WALINE_EMOJI');
|
|
14
|
+
|
|
15
|
+
const result = hasVersion(link);
|
|
16
|
+
|
|
17
|
+
if (result) {
|
|
18
|
+
const info = store.get<WalineEmojiInfo>(link);
|
|
19
|
+
if (info) return Promise.resolve(info);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return fetch(`${link}/info.json`)
|
|
23
|
+
.then((resp) => resp.json() as Promise<Omit<WalineEmojiInfo, 'folder'>>)
|
|
24
|
+
.then((emojiInfo) => {
|
|
25
|
+
const info = {
|
|
26
|
+
folder: link,
|
|
27
|
+
...emojiInfo,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (result) store.set(link, info);
|
|
31
|
+
|
|
32
|
+
return info;
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getLink = (
|
|
37
|
+
name: string,
|
|
38
|
+
folder: string,
|
|
39
|
+
prefix = '',
|
|
40
|
+
type = ''
|
|
41
|
+
): string => `${folder}/${prefix}${name}${type ? `.${type}` : ''}`;
|
|
42
|
+
|
|
43
|
+
export const getEmojis = (
|
|
44
|
+
emojis: (string | WalineEmojiInfo)[]
|
|
45
|
+
): Promise<EmojiConfig> =>
|
|
46
|
+
Promise.all(
|
|
47
|
+
emojis.map((emoji) =>
|
|
48
|
+
typeof emoji === 'string'
|
|
49
|
+
? fetchEmoji(removeEndingSplash(emoji))
|
|
50
|
+
: Promise.resolve(emoji)
|
|
51
|
+
)
|
|
52
|
+
).then((emojiInfos) => {
|
|
53
|
+
const emojiConfig: EmojiConfig = {
|
|
54
|
+
tabs: [],
|
|
55
|
+
map: {},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
emojiInfos.forEach((emojiInfo) => {
|
|
59
|
+
const { name, folder, icon, prefix, type, items } = emojiInfo;
|
|
60
|
+
|
|
61
|
+
emojiConfig.tabs.push({
|
|
62
|
+
name,
|
|
63
|
+
icon: getLink(icon, folder, prefix, type),
|
|
64
|
+
items: items.map((item) => {
|
|
65
|
+
const key = `${prefix || ''}${item}`;
|
|
66
|
+
|
|
67
|
+
emojiConfig.map[key] = getLink(item, folder, prefix, type);
|
|
68
|
+
|
|
69
|
+
return key;
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return emojiConfig;
|
|
75
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { WalineComment, WalineCommentData } from '../typings';
|
|
2
|
+
|
|
3
|
+
export interface FetchErrorData {
|
|
4
|
+
errno: number;
|
|
5
|
+
errmsg: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const errorCheck = <T = unknown>(data: T | FetchErrorData, name = ''): T => {
|
|
9
|
+
if (typeof data === 'object' && (data as FetchErrorData).errno)
|
|
10
|
+
throw new TypeError(
|
|
11
|
+
`Fetch ${name} failed with ${(data as FetchErrorData).errno}: ${
|
|
12
|
+
(data as FetchErrorData).errmsg
|
|
13
|
+
}`
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
return data as T;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface FetchCountOptions {
|
|
20
|
+
serverURL: string;
|
|
21
|
+
paths: string[];
|
|
22
|
+
signal: AbortSignal;
|
|
23
|
+
token?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const fetchCommentCount = ({
|
|
27
|
+
serverURL,
|
|
28
|
+
paths,
|
|
29
|
+
signal,
|
|
30
|
+
token,
|
|
31
|
+
}: FetchCountOptions): Promise<number[]> => {
|
|
32
|
+
const headers: Record<string, string> = {};
|
|
33
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
fetch(
|
|
37
|
+
`${serverURL}/comment?type=count&url=${encodeURIComponent(
|
|
38
|
+
paths.join(',')
|
|
39
|
+
)}`,
|
|
40
|
+
{ signal, headers }
|
|
41
|
+
)
|
|
42
|
+
.then((resp) => resp.json() as Promise<number | number[]>)
|
|
43
|
+
.then((data) => errorCheck(data, 'comment count'))
|
|
44
|
+
// TODO: Improve this API
|
|
45
|
+
.then((counts) => (Array.isArray(counts) ? counts : [counts]))
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
export interface FetchRecentOptions {
|
|
49
|
+
serverURL: string;
|
|
50
|
+
count: number;
|
|
51
|
+
signal: AbortSignal;
|
|
52
|
+
token?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const fetchRecentComment = ({
|
|
56
|
+
serverURL,
|
|
57
|
+
count,
|
|
58
|
+
signal,
|
|
59
|
+
token,
|
|
60
|
+
}: FetchRecentOptions): Promise<WalineComment[]> => {
|
|
61
|
+
const headers: Record<string, string> = {};
|
|
62
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
63
|
+
|
|
64
|
+
return fetch(`${serverURL}/comment?type=recent&count=${count}`, {
|
|
65
|
+
signal,
|
|
66
|
+
headers,
|
|
67
|
+
})
|
|
68
|
+
.then((resp) => resp.json() as Promise<WalineComment[]>)
|
|
69
|
+
.then((data) => errorCheck(data, 'recent comment'));
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export interface FetchListOptions {
|
|
73
|
+
serverURL: string;
|
|
74
|
+
path: string;
|
|
75
|
+
page: number;
|
|
76
|
+
pageSize: number;
|
|
77
|
+
signal: AbortSignal;
|
|
78
|
+
token?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface FetchListResult {
|
|
82
|
+
count: number;
|
|
83
|
+
data: WalineComment[];
|
|
84
|
+
totalPages: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const fetchCommentList = ({
|
|
88
|
+
serverURL,
|
|
89
|
+
path,
|
|
90
|
+
page,
|
|
91
|
+
pageSize,
|
|
92
|
+
signal,
|
|
93
|
+
token,
|
|
94
|
+
}: FetchListOptions): Promise<FetchListResult> => {
|
|
95
|
+
const headers: Record<string, string> = {};
|
|
96
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
97
|
+
|
|
98
|
+
return fetch(
|
|
99
|
+
`${serverURL}/comment?path=${encodeURIComponent(
|
|
100
|
+
path
|
|
101
|
+
)}&pageSize=${pageSize}&page=${page}`,
|
|
102
|
+
{ signal, headers }
|
|
103
|
+
)
|
|
104
|
+
.then((resp) => resp.json() as Promise<FetchListResult>)
|
|
105
|
+
.then((data) => errorCheck(data, 'comment list'));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export interface PostCommentOptions {
|
|
109
|
+
serverURL: string;
|
|
110
|
+
lang: string;
|
|
111
|
+
token?: string;
|
|
112
|
+
comment: WalineCommentData;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface PostCommentResponse {
|
|
116
|
+
data?: WalineComment;
|
|
117
|
+
errmsg?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const postComment = ({
|
|
121
|
+
serverURL,
|
|
122
|
+
lang,
|
|
123
|
+
token,
|
|
124
|
+
comment,
|
|
125
|
+
}: PostCommentOptions): Promise<PostCommentResponse> => {
|
|
126
|
+
const headers: Record<string, string> = {
|
|
127
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
128
|
+
'Content-Type': 'application/json',
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
132
|
+
|
|
133
|
+
return fetch(`${serverURL}/comment?lang=${lang}`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers,
|
|
136
|
+
body: JSON.stringify(comment),
|
|
137
|
+
}).then((resp) => resp.json() as Promise<PostCommentResponse>);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export interface FetchPageviewsOptions {
|
|
141
|
+
serverURL: string;
|
|
142
|
+
paths: string[];
|
|
143
|
+
signal: AbortSignal;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const fetchPageviews = ({
|
|
147
|
+
serverURL,
|
|
148
|
+
paths,
|
|
149
|
+
signal,
|
|
150
|
+
}: FetchPageviewsOptions): Promise<number[]> =>
|
|
151
|
+
fetch(`${serverURL}/article?path=${encodeURIComponent(paths.join(','))}`, {
|
|
152
|
+
signal,
|
|
153
|
+
})
|
|
154
|
+
.then((resp) => resp.json() as Promise<number[] | number>)
|
|
155
|
+
.then((data) => errorCheck(data, 'visit count'))
|
|
156
|
+
// TODO: Improve this API
|
|
157
|
+
.then((counts) => (Array.isArray(counts) ? counts : [counts]));
|
|
158
|
+
|
|
159
|
+
export interface UpdatePageviewsOptions {
|
|
160
|
+
serverURL: string;
|
|
161
|
+
path: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const updatePageviews = ({
|
|
165
|
+
serverURL,
|
|
166
|
+
path,
|
|
167
|
+
}: UpdatePageviewsOptions): Promise<number> =>
|
|
168
|
+
fetch(`${serverURL}/article`, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: {
|
|
171
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
172
|
+
'Content-Type': 'application/json',
|
|
173
|
+
},
|
|
174
|
+
body: JSON.stringify({ path }),
|
|
175
|
+
})
|
|
176
|
+
.then((resp) => resp.json() as Promise<number>)
|
|
177
|
+
.then((data) => errorCheck(data, 'visit count'));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './config';
|
|
2
|
+
export * from './darkmode';
|
|
3
|
+
export * from './data';
|
|
4
|
+
export * from './emoji';
|
|
5
|
+
export * from './error';
|
|
6
|
+
export * from './fetch';
|
|
7
|
+
export * from './getRoot';
|
|
8
|
+
export * from './markdown';
|
|
9
|
+
export * from './path';
|
|
10
|
+
export * from './query';
|
|
11
|
+
export * from './timeAgo';
|
|
12
|
+
export * from './userInfo';
|
|
13
|
+
export * from './wordCount';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { marked } from 'marked';
|
|
2
|
+
import { markedTexExtensions } from './markedMathExtension';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
WalineEmojiMaps,
|
|
6
|
+
WalineHighlighter,
|
|
7
|
+
WalineTexRenderer,
|
|
8
|
+
} from '../typings';
|
|
9
|
+
|
|
10
|
+
export const parseEmoji = (text = '', emojiMap: WalineEmojiMaps = {}): string =>
|
|
11
|
+
text.replace(/:(.+?):/g, (placeholder, key: string) =>
|
|
12
|
+
emojiMap[key]
|
|
13
|
+
? `<img class="vemoji" src="${emojiMap[key]}" alt="${key}">`
|
|
14
|
+
: placeholder
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export interface ParseMarkdownOptions {
|
|
18
|
+
emojiMap: WalineEmojiMaps;
|
|
19
|
+
highlighter: WalineHighlighter | false;
|
|
20
|
+
texRenderer: WalineTexRenderer | false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const parseMarkdown = (
|
|
24
|
+
content: string,
|
|
25
|
+
{ emojiMap, highlighter, texRenderer }: ParseMarkdownOptions
|
|
26
|
+
): string => {
|
|
27
|
+
marked.setOptions({
|
|
28
|
+
highlight: highlighter || undefined,
|
|
29
|
+
breaks: true,
|
|
30
|
+
smartLists: true,
|
|
31
|
+
smartypants: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (texRenderer) {
|
|
35
|
+
const extensions = markedTexExtensions(texRenderer);
|
|
36
|
+
|
|
37
|
+
marked.use({ extensions });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return marked.parse(parseEmoji(content, emojiMap));
|
|
41
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { marked } from 'marked';
|
|
2
|
+
import type { WalineTexRenderer } from '../typings';
|
|
3
|
+
|
|
4
|
+
const inlineMathStart = /\$.*?\$/;
|
|
5
|
+
const inlineMathReg = /^\$(.*?)\$/;
|
|
6
|
+
const blockMathReg = /^(?:\s{0,3})\$\$((?:[^\n]|\n[^\n])+?)\n{0,1}\$\$/;
|
|
7
|
+
|
|
8
|
+
export const markedTexExtensions = (
|
|
9
|
+
texRenderer: WalineTexRenderer
|
|
10
|
+
): marked.TokenizerExtension[] => {
|
|
11
|
+
const blockMathExtension: marked.TokenizerExtension = {
|
|
12
|
+
name: 'blockMath',
|
|
13
|
+
level: 'block',
|
|
14
|
+
tokenizer(src: string) {
|
|
15
|
+
const cap = blockMathReg.exec(src);
|
|
16
|
+
|
|
17
|
+
if (cap !== null) {
|
|
18
|
+
return {
|
|
19
|
+
type: 'html',
|
|
20
|
+
raw: cap[0],
|
|
21
|
+
text: texRenderer(true, cap[1]),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return undefined;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const inlineMathExtension: marked.TokenizerExtension = {
|
|
30
|
+
name: 'inlineMath',
|
|
31
|
+
level: 'inline',
|
|
32
|
+
start(src: string) {
|
|
33
|
+
const idx = src.search(inlineMathStart);
|
|
34
|
+
return idx !== -1 ? idx : src.length;
|
|
35
|
+
},
|
|
36
|
+
tokenizer(src: string) {
|
|
37
|
+
const cap = inlineMathReg.exec(src);
|
|
38
|
+
|
|
39
|
+
if (cap !== null) {
|
|
40
|
+
return {
|
|
41
|
+
type: 'html',
|
|
42
|
+
raw: cap[0],
|
|
43
|
+
text: texRenderer(false, cap[1]),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return undefined;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return [blockMathExtension, inlineMathExtension];
|
|
52
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const decodePath = (path: string): string => {
|
|
2
|
+
try {
|
|
3
|
+
path = decodeURI(path);
|
|
4
|
+
} catch (err) {
|
|
5
|
+
// ignore error
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return path;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const removeEndingSplash = (content = ''): string =>
|
|
12
|
+
content.replace(/\/$/u, '');
|
|
13
|
+
|
|
14
|
+
export const isLinkHttp = (link: string): boolean =>
|
|
15
|
+
/^(https?:)?\/\//.test(link);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { WalineLocale } from '../typings';
|
|
2
|
+
|
|
3
|
+
const padWithZeros = (vNumber: number, width: number): string => {
|
|
4
|
+
let numAsString = vNumber.toString();
|
|
5
|
+
|
|
6
|
+
while (numAsString.length < width) {
|
|
7
|
+
numAsString = '0' + numAsString;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return numAsString;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const dateFormat = (date: Date): string => {
|
|
14
|
+
const vDay = padWithZeros(date.getDate(), 2);
|
|
15
|
+
const vMonth = padWithZeros(date.getMonth() + 1, 2);
|
|
16
|
+
const vYear = padWithZeros(date.getFullYear(), 2);
|
|
17
|
+
|
|
18
|
+
return `${vYear}-${vMonth}-${vDay}`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const timeAgo = (date: Date | string, locale: WalineLocale): string => {
|
|
22
|
+
if (date)
|
|
23
|
+
try {
|
|
24
|
+
if (typeof date === 'string') {
|
|
25
|
+
// compat with mysql output date
|
|
26
|
+
date = new Date(
|
|
27
|
+
date.indexOf(' ') !== -1 ? date.replace(/-/g, '/') : date
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const oldTime = date.getTime();
|
|
31
|
+
const currTime = new Date().getTime();
|
|
32
|
+
const diffValue = currTime - oldTime;
|
|
33
|
+
|
|
34
|
+
const days = Math.floor(diffValue / (24 * 3600 * 1000));
|
|
35
|
+
|
|
36
|
+
// 计算相差小时数
|
|
37
|
+
if (days === 0) {
|
|
38
|
+
// 计算天数后剩余的毫秒数
|
|
39
|
+
const leave1 = diffValue % (24 * 3600 * 1000);
|
|
40
|
+
const hours = Math.floor(leave1 / (3600 * 1000));
|
|
41
|
+
|
|
42
|
+
//计算相差分钟数
|
|
43
|
+
if (hours === 0) {
|
|
44
|
+
// 计算小时数后剩余的毫秒数
|
|
45
|
+
const leave2 = leave1 % (3600 * 1000);
|
|
46
|
+
const minutes = Math.floor(leave2 / (60 * 1000));
|
|
47
|
+
|
|
48
|
+
// 计算相差秒数
|
|
49
|
+
if (minutes === 0) {
|
|
50
|
+
// 计算分钟数后剩余的毫秒数
|
|
51
|
+
const leave3 = leave2 % (60 * 1000);
|
|
52
|
+
const seconds = Math.round(leave3 / 1000);
|
|
53
|
+
|
|
54
|
+
return `${seconds} ${locale.seconds}`;
|
|
55
|
+
}
|
|
56
|
+
return `${minutes} ${locale.minutes}`;
|
|
57
|
+
}
|
|
58
|
+
return `${hours} ${locale.hours}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (days < 0) {
|
|
62
|
+
return locale.now;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (days < 8) {
|
|
66
|
+
return `${days} ${locale.days}`;
|
|
67
|
+
} else {
|
|
68
|
+
return dateFormat(date);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.log(error);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return '';
|
|
75
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface UserInfo {
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
3
|
+
display_name: string;
|
|
4
|
+
email: string;
|
|
5
|
+
url: string;
|
|
6
|
+
token: string;
|
|
7
|
+
avatar: string;
|
|
8
|
+
mailMd5: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const USER_KEY = 'WALINE_USER';
|
|
12
|
+
|
|
13
|
+
export const getUserInfo = (): UserInfo | null => {
|
|
14
|
+
try {
|
|
15
|
+
const localStorageData = localStorage.getItem(USER_KEY);
|
|
16
|
+
const sessionStorageData = sessionStorage.getItem(USER_KEY);
|
|
17
|
+
|
|
18
|
+
return localStorageData
|
|
19
|
+
? (JSON.parse(localStorageData) as UserInfo)
|
|
20
|
+
: sessionStorageData
|
|
21
|
+
? (JSON.parse(sessionStorageData) as UserInfo)
|
|
22
|
+
: null;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wordCount module should be lightweight as it's packed into client.
|
|
3
|
+
*
|
|
4
|
+
* So We just make a simple implement here
|
|
5
|
+
*
|
|
6
|
+
* Forked from https://github.com/vuepress-theme-hope/vuepress-theme-hope/blob/main/packages/reading-time2/src/node/reading-time.ts
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const getWords = (content: string): string[] =>
|
|
10
|
+
content.match(/[\w\d\s\u00C0-\u024F]+/giu) || [];
|
|
11
|
+
|
|
12
|
+
export const getChinese = (content: string): string[] =>
|
|
13
|
+
content.match(/[\u4E00-\u9FA5]/gu) || [];
|
|
14
|
+
|
|
15
|
+
export const getWordNumber = (content: string): number =>
|
|
16
|
+
getWords(content).reduce(
|
|
17
|
+
(accumulator, word) =>
|
|
18
|
+
accumulator + (word.trim() === '' ? 0 : word.trim().split(/\s+/u).length),
|
|
19
|
+
0
|
|
20
|
+
) + getChinese(content).length;
|
package/src/version.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './recentComments';
|