@waline/vercel 1.25.0 → 1.25.2
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/__tests__/katex.spec.js +4 -7
- package/__tests__/mathjax.spec.js +2 -2
- package/package.json +6 -6
- package/src/controller/comment.js +8 -12
- package/src/controller/user/password.js +2 -2
- package/src/controller/user.js +3 -3
- package/src/controller/verification.js +1 -1
- package/src/extend/controller.js +1 -1
- package/src/locales/en.json +2 -2
- package/src/locales/zh-CN.json +2 -2
- package/src/locales/zh-TW.json +2 -2
- package/src/logic/base.js +7 -1
- package/src/logic/comment.js +12 -4
- package/src/logic/db.js +8 -0
- package/src/logic/token.js +5 -0
- package/src/logic/user.js +33 -8
- package/src/service/markdown/index.js +1 -1
- package/src/service/markdown/mathCommon.js +5 -5
- package/src/service/markdown/mathjax.js +3 -3
- package/src/service/notify.js +9 -9
- package/src/service/storage/github.js +2 -4
package/__tests__/katex.spec.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
1
2
|
import { describe, expect, it } from 'vitest';
|
|
2
3
|
import MarkdownIt from 'markdown-it';
|
|
3
4
|
import { katexPlugin } from '../src/service/markdown/katex';
|
|
@@ -12,7 +13,7 @@ const markdownItWithError = MarkdownIt({ linkify: true }).use(katexPlugin, {
|
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
describe('inline katex', () => {
|
|
15
|
-
it('
|
|
16
|
+
it('Should render', () => {
|
|
16
17
|
expect(markdownIt.render(`$a=1$`)).toEqual(
|
|
17
18
|
`<p><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">a=1</annotation></semantics></math></span></p>\n`
|
|
18
19
|
);
|
|
@@ -33,7 +34,7 @@ describe('inline katex', () => {
|
|
|
33
34
|
);
|
|
34
35
|
});
|
|
35
36
|
|
|
36
|
-
it('Should render when the first one is after a
|
|
37
|
+
it('Should render when the first one is after a character', () => {
|
|
37
38
|
expect(markdownIt.render(`The next$a = 1$ won't work`)).toEqual(
|
|
38
39
|
`<p>The next<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">a = 1</annotation></semantics></math></span> won't work</p>\n`
|
|
39
40
|
);
|
|
@@ -46,7 +47,6 @@ describe('inline katex', () => {
|
|
|
46
47
|
});
|
|
47
48
|
|
|
48
49
|
it('Should render error msg when content is wrong', () => {
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
50
50
|
const originalWarn = global.console.warn;
|
|
51
51
|
|
|
52
52
|
global.console.warn = vi.fn();
|
|
@@ -55,14 +55,13 @@ describe('inline katex', () => {
|
|
|
55
55
|
"<p><span class='katex-error' title='ParseError: KaTeX parse error: Undefined control sequence: \\fra at position 1: \\̲f̲r̲a̲{a}{b}'>\\fra{a}{b}</span></p>\n"
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
59
58
|
expect(global.console.warn).toHaveBeenCalledTimes(1);
|
|
60
59
|
global.console.warn = originalWarn;
|
|
61
60
|
});
|
|
62
61
|
});
|
|
63
62
|
|
|
64
63
|
describe('block katex', () => {
|
|
65
|
-
it('
|
|
64
|
+
it('Should render', () => {
|
|
66
65
|
expect(markdownIt.render(`$$a=1$$`)).toEqual(
|
|
67
66
|
`<p class='katex-block'><span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>a</mi><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">a=1\n</annotation></semantics></math></span></p>\n`
|
|
68
67
|
);
|
|
@@ -118,7 +117,6 @@ $$
|
|
|
118
117
|
});
|
|
119
118
|
|
|
120
119
|
it('Should render error msg when content is wrong', () => {
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
122
120
|
const originalWarn = global.console.warn;
|
|
123
121
|
|
|
124
122
|
global.console.warn = vi.fn();
|
|
@@ -136,7 +134,6 @@ $$
|
|
|
136
134
|
/<p class='katex-block katex-error' title='[\s\S]*?'>[\s\S]*?<\/p>/
|
|
137
135
|
);
|
|
138
136
|
|
|
139
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
140
137
|
expect(global.console.warn).toHaveBeenCalledTimes(2);
|
|
141
138
|
global.console.warn = originalWarn;
|
|
142
139
|
});
|
|
@@ -5,7 +5,7 @@ import { mathjaxPlugin } from '../src/service/markdown/mathjax';
|
|
|
5
5
|
const markdownIt = MarkdownIt({ linkify: true }).use(mathjaxPlugin);
|
|
6
6
|
|
|
7
7
|
describe('inline mathjax', () => {
|
|
8
|
-
it('
|
|
8
|
+
it('Should render', () => {
|
|
9
9
|
expect(markdownIt.render(`$a=1$`)).toEqual(
|
|
10
10
|
`<p><svg style="vertical-align: -0.186ex" xmlns="http://www.w3.org/2000/svg" width="5.345ex" height="1.692ex" role="img" focusable="false" viewBox="0 -666 2362.6 748"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mo" transform="translate(806.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mn" transform="translate(1862.6,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g></svg></p>\n`
|
|
11
11
|
);
|
|
@@ -40,7 +40,7 @@ describe('inline mathjax', () => {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
describe('block mathjax', () => {
|
|
43
|
-
it('
|
|
43
|
+
it('Should render', () => {
|
|
44
44
|
expect(markdownIt.render(`$$a=1$$`)).toEqual(
|
|
45
45
|
`<svg style="vertical-align: -0.186ex" xmlns="http://www.w3.org/2000/svg" width="100%" height="1.692ex" role="img" focusable="false" viewBox="0 -666 2362.6 748"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mo" transform="translate(806.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mn" transform="translate(1862.6,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g></svg>\n`
|
|
46
46
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waline/vercel",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.2",
|
|
4
4
|
"description": "vercel server for waline comment system",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"waline",
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"@koa/cors": "4.0.0",
|
|
20
20
|
"akismet": "2.0.7",
|
|
21
21
|
"deta": "1.1.0",
|
|
22
|
-
"dompurify": "2.4.
|
|
22
|
+
"dompurify": "2.4.1",
|
|
23
23
|
"dy-node-ip2region": "1.0.1",
|
|
24
24
|
"fast-csv": "4.3.6",
|
|
25
25
|
"form-data": "4.0.0",
|
|
26
|
-
"jsdom": "20.0.
|
|
27
|
-
"jsonwebtoken": "
|
|
28
|
-
"katex": "0.16.
|
|
29
|
-
"leancloud-storage": "4.
|
|
26
|
+
"jsdom": "20.0.3",
|
|
27
|
+
"jsonwebtoken": "9.0.0",
|
|
28
|
+
"katex": "0.16.4",
|
|
29
|
+
"leancloud-storage": "4.14.0",
|
|
30
30
|
"markdown-it": "13.0.1",
|
|
31
31
|
"markdown-it-emoji": "2.0.2",
|
|
32
32
|
"markdown-it-sub": "1.0.0",
|
|
@@ -296,7 +296,7 @@ module.exports = class extends BaseRest {
|
|
|
296
296
|
if (order === 'desc') {
|
|
297
297
|
selectOptions.desc = field;
|
|
298
298
|
} else if (order === 'asc') {
|
|
299
|
-
// do nothing because of ascending order is default
|
|
299
|
+
// do nothing because of ascending order is default behavior
|
|
300
300
|
}
|
|
301
301
|
}
|
|
302
302
|
|
|
@@ -311,9 +311,9 @@ module.exports = class extends BaseRest {
|
|
|
311
311
|
*
|
|
312
312
|
* Why we have limit and the limit is 1000?
|
|
313
313
|
* Many serverless storages have fetch data limit, for example LeanCloud is 100, and CloudBase is 1000
|
|
314
|
-
* If we have much
|
|
314
|
+
* If we have much comments, We should use more request to fetch all comments
|
|
315
315
|
* If we have 3000 comments, We have to use 30 http request to fetch comments, things go athwart.
|
|
316
|
-
* And Serverless Service like vercel have
|
|
316
|
+
* And Serverless Service like vercel have execute time limit
|
|
317
317
|
* if we have more http requests in a serverless function, it may timeout easily.
|
|
318
318
|
* so we use limit to avoid it.
|
|
319
319
|
*/
|
|
@@ -521,7 +521,7 @@ module.exports = class extends BaseRest {
|
|
|
521
521
|
|
|
522
522
|
think.logger.debug('Comment duplicate check OK!');
|
|
523
523
|
|
|
524
|
-
/** IP
|
|
524
|
+
/** IP frequency */
|
|
525
525
|
const { IPQPS = 60 } = process.env;
|
|
526
526
|
|
|
527
527
|
const recent = await this.modelInstance.select({
|
|
@@ -530,21 +530,17 @@ module.exports = class extends BaseRest {
|
|
|
530
530
|
});
|
|
531
531
|
|
|
532
532
|
if (!think.isEmpty(recent)) {
|
|
533
|
-
think.logger.debug(`The author has posted in ${IPQPS}
|
|
533
|
+
think.logger.debug(`The author has posted in ${IPQPS} seconds.`);
|
|
534
534
|
|
|
535
535
|
return this.fail(this.locale('Comment too fast!'));
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
think.logger.debug(`Comment post
|
|
538
|
+
think.logger.debug(`Comment post frequency check OK!`);
|
|
539
539
|
|
|
540
540
|
/** Akismet */
|
|
541
|
-
const { COMMENT_AUDIT
|
|
542
|
-
const AUTHOR = AUTHOR_EMAIL;
|
|
543
|
-
const isAuthorComment = AUTHOR
|
|
544
|
-
? data.mail.toLowerCase() === AUTHOR.toLowerCase()
|
|
545
|
-
: false;
|
|
541
|
+
const { COMMENT_AUDIT } = process.env;
|
|
546
542
|
|
|
547
|
-
data.status = COMMENT_AUDIT
|
|
543
|
+
data.status = COMMENT_AUDIT ? 'waiting' : 'approved';
|
|
548
544
|
|
|
549
545
|
think.logger.debug(`Comment initial status is ${data.status}`);
|
|
550
546
|
|
|
@@ -11,9 +11,9 @@ module.exports = class extends BaseRest {
|
|
|
11
11
|
SMTP_USER,
|
|
12
12
|
SITE_NAME,
|
|
13
13
|
} = process.env;
|
|
14
|
-
const
|
|
14
|
+
const hasMailService = SMTP_HOST || SMTP_SERVICE;
|
|
15
15
|
|
|
16
|
-
if (!
|
|
16
|
+
if (!hasMailService) {
|
|
17
17
|
return this.fail();
|
|
18
18
|
}
|
|
19
19
|
|
package/src/controller/user.js
CHANGED
|
@@ -71,12 +71,12 @@ module.exports = class extends BaseRest {
|
|
|
71
71
|
SMTP_USER,
|
|
72
72
|
SITE_NAME,
|
|
73
73
|
} = process.env;
|
|
74
|
-
const
|
|
74
|
+
const hasMailService = SMTP_HOST || SMTP_SERVICE;
|
|
75
75
|
|
|
76
76
|
const token = Array.from({ length: 4 }, () =>
|
|
77
77
|
Math.round(Math.random() * 9)
|
|
78
78
|
).join('');
|
|
79
|
-
const normalType =
|
|
79
|
+
const normalType = hasMailService
|
|
80
80
|
? `verify:${token}:${Date.now() + 1 * 60 * 60 * 1000}`
|
|
81
81
|
: 'guest';
|
|
82
82
|
|
|
@@ -119,7 +119,7 @@ module.exports = class extends BaseRest {
|
|
|
119
119
|
|
|
120
120
|
return this.fail(
|
|
121
121
|
this.locale(
|
|
122
|
-
'
|
|
122
|
+
'Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.',
|
|
123
123
|
{ isAdmin: think.isEmpty(count) }
|
|
124
124
|
)
|
|
125
125
|
);
|
|
@@ -21,7 +21,7 @@ module.exports = class extends BaseRest {
|
|
|
21
21
|
const match = user.type.match(/^verify:(\d{4}):(\d+)$/i);
|
|
22
22
|
|
|
23
23
|
if (!match) {
|
|
24
|
-
return this.fail(this.locale('
|
|
24
|
+
return this.fail(this.locale('USER_REGISTERED'));
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (token === match[1] && Date.now() < parseInt(match[2])) {
|
package/src/extend/controller.js
CHANGED
|
@@ -14,7 +14,7 @@ module.exports = {
|
|
|
14
14
|
},
|
|
15
15
|
locale(message, variables) {
|
|
16
16
|
const { lang } = this.get();
|
|
17
|
-
const locale = locales[(lang || 'zh-cn').toLowerCase()];
|
|
17
|
+
const locale = locales[(lang || 'zh-cn').toLowerCase()] || locales['zh-cn'];
|
|
18
18
|
|
|
19
19
|
if (locale && locale[message]) {
|
|
20
20
|
message = locale[message];
|
package/src/locales/en.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"import data format not support!": "import data format not support!",
|
|
3
3
|
"USER_EXIST": "USER_EXIST",
|
|
4
4
|
"USER_NOT_EXIST": "USER_NOT_EXIST",
|
|
5
|
-
"
|
|
5
|
+
"USER_REGISTERED": "USER_REGISTERED",
|
|
6
6
|
"TOKEN_EXPIRED": "TOKEN_EXPIRED",
|
|
7
7
|
"TWO_FACTOR_AUTH_ERROR_DETAIL": "TWO_FACTOR_AUTH_ERROR_DETAIL",
|
|
8
8
|
"[{{name | safe}}] Registration Confirm Mail": "[{{name | safe}}] Registration Confirm Mail",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!",
|
|
12
12
|
"Duplicate Content": "Duplicate Content",
|
|
13
13
|
"Comment too fast": "Comment too fast",
|
|
14
|
-
"
|
|
14
|
+
"Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.",
|
|
15
15
|
"MAIL_SUBJECT": "Your comment on {{site.name | safe}} received a reply",
|
|
16
16
|
"MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> Your comment on <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> received a reply </h2>{{parent.nick}}, you wrote: <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong> replied:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p><a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>View full reply</a> or visit <a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a>.</p><br/> </div></div>",
|
|
17
17
|
"MAIL_SUBJECT_ADMIN": "New comment on {{site.name | safe}}",
|
package/src/locales/zh-CN.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"import data format not support!": "文件格式不支持",
|
|
3
3
|
"USER_EXIST": "用户已存在",
|
|
4
4
|
"USER_NOT_EXIST": "用户不存在",
|
|
5
|
-
"
|
|
5
|
+
"USER_REGISTERED": "用户已注册",
|
|
6
6
|
"TOKEN_EXPIRED": "密钥已过期",
|
|
7
7
|
"TWO_FACTOR_AUTH_ERROR_DETAIL": "二步验证失败",
|
|
8
8
|
"[{{name | safe}}] Registration Confirm Mail": "【{{name | safe}}】注册确认邮件",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "请尽快点击链接 <a href=\"{{url}}\">{{url}}</a> 登录并修改你的密码!",
|
|
12
12
|
"Duplicate Content": "发送的内容之前已经发过",
|
|
13
13
|
"Comment too fast": "评论太快啦,请慢点!",
|
|
14
|
-
"
|
|
14
|
+
"Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "注册确认邮件发送失败,请{%- if isAdmin -%}检查一下网站的邮件相关配置{% else %}确认你的邮箱输入无误并联系管理员{%- endif -%}。",
|
|
15
15
|
"MAIL_SUBJECT": "{{parent.nick | safe}},『{{site.name | safe}}』上的评论收到了回复",
|
|
16
16
|
"MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> 您在<a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a>上的评论有了新的回复 </h2>{{parent.nick}}同学,您曾发表评论: <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong>回复说:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>您可以点击<a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>查看回复的完整內容</a>,欢迎再次光临<a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a>。</p><br/> </div></div>",
|
|
17
17
|
"MAIL_SUBJECT_ADMIN": "{{site.name | safe}} 上有新评论了",
|
package/src/locales/zh-TW.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"import data format not support!": "文件格式不支持",
|
|
3
3
|
"USER_EXIST": "用戶已存在",
|
|
4
4
|
"USER_NOT_EXIST": "用戶不存在",
|
|
5
|
-
"
|
|
5
|
+
"USER_REGISTERED": "用戶已註冊",
|
|
6
6
|
"TOKEN_EXPIRED": "密鑰已過期",
|
|
7
7
|
"TWO_FACTOR_AUTH_ERROR_DETAIL": "二步驗證失敗",
|
|
8
8
|
"[{{name | safe}}] Registration Confirm Mail": "『{{name | safe}}』註冊確認郵件",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "請盡快點擊鏈接 <a href=\"{{url}}\">{{url}}</a> 登錄並修改你的密碼!",
|
|
12
12
|
"Duplicate Content": "發送的內容之前已經發過",
|
|
13
13
|
"Comment too fast": "評論太快啦,請慢點!",
|
|
14
|
-
"
|
|
14
|
+
"Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "註冊確認郵件發送失敗,{%- if isAdmin -%}檢查一下網站的郵件相關配置{% else %}確認你的郵箱輸入無誤後聯繫管理員{%- endif -%}。",
|
|
15
15
|
"MAIL_SUBJECT": "{{parent.nick | safe}},『{{site.name | safe}}』上的評論收到回復",
|
|
16
16
|
"MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> 您在<a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a>上的品論有新的回復 </h2>{{parent.nick}}同學,您層發表評論: <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong>回復說:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>您可以點擊<a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>查看回復的完整內容</a>,歡迎再次光臨<a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a>。</p><br/> </div></div>",
|
|
17
17
|
"MAIL_SUBJECT_ADMIN": "{{site.name | safe}} 上有新評論了",
|
package/src/logic/base.js
CHANGED
|
@@ -50,7 +50,13 @@ module.exports = class extends think.Logic {
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
const token = state || authorization.replace(/^Bearer /, '');
|
|
53
|
-
|
|
53
|
+
let userMail = '';
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
userMail = jwt.verify(token, think.config('jwtKey'));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
think.logger.debug(e);
|
|
59
|
+
}
|
|
54
60
|
|
|
55
61
|
if (think.isEmpty(userMail) || !think.isString(userMail)) {
|
|
56
62
|
return;
|
package/src/logic/comment.js
CHANGED
|
@@ -20,8 +20,9 @@ module.exports = class extends Base {
|
|
|
20
20
|
*
|
|
21
21
|
* @apiParam {String} path comment url path
|
|
22
22
|
* @apiParam {String} page page
|
|
23
|
-
* @apiParam {String}
|
|
23
|
+
* @apiParam {String} pageSize page size
|
|
24
24
|
* @apiParam {String} sortBy comment sort type, one of 'insertedAt_desc', 'insertedAt_asc', 'like_desc'
|
|
25
|
+
* @apiParam {String} lang language
|
|
25
26
|
*
|
|
26
27
|
* @apiSuccess (200) {Number} page return current comments list page
|
|
27
28
|
* @apiSuccess (200) {Number} pageSize to return error message if error
|
|
@@ -52,7 +53,8 @@ module.exports = class extends Base {
|
|
|
52
53
|
* @apiVersion 0.0.1
|
|
53
54
|
*
|
|
54
55
|
* @apiParam {String} page page
|
|
55
|
-
* @apiParam {String}
|
|
56
|
+
* @apiParam {String} pageSize page size
|
|
57
|
+
* @apiParam {String} lang language
|
|
56
58
|
*
|
|
57
59
|
* @apiSuccess (200) {Number} errno 0
|
|
58
60
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
@@ -80,6 +82,7 @@ module.exports = class extends Base {
|
|
|
80
82
|
* @apiVersion 0.0.1
|
|
81
83
|
*
|
|
82
84
|
* @apiParam {String} url a array string join by comma just like `a` or `a,b`, return site comment count if url empty
|
|
85
|
+
* @apiParam {String} lang language
|
|
83
86
|
*
|
|
84
87
|
* @apiSuccessExample {Number} Single Path Response:
|
|
85
88
|
* 300
|
|
@@ -92,6 +95,7 @@ module.exports = class extends Base {
|
|
|
92
95
|
* @apiVersion 0.0.1
|
|
93
96
|
*
|
|
94
97
|
* @apiParam {String} count return comments number, default value is 10
|
|
98
|
+
* @apiParam {String} lang language
|
|
95
99
|
*
|
|
96
100
|
* @apiSuccess (200) {Object[]} response
|
|
97
101
|
* @apiSuccess (200) {String} response.nick comment user nick name
|
|
@@ -181,11 +185,12 @@ module.exports = class extends Base {
|
|
|
181
185
|
* @apiParam {String} mail post comment user mail address
|
|
182
186
|
* @apiParam {String} link post comment user link
|
|
183
187
|
* @apiParam {String} comment post comment text
|
|
184
|
-
* @apiParam {String} url the
|
|
188
|
+
* @apiParam {String} url the article url path of comment
|
|
185
189
|
* @apiParam {String} ua browser user agent
|
|
186
190
|
* @apiParam {String} pid parent comment id
|
|
187
191
|
* @apiParam {String} rid root comment id
|
|
188
192
|
* @apiParam {String} at parent comment user nick name
|
|
193
|
+
* @apiParam {String} lang language
|
|
189
194
|
*
|
|
190
195
|
* @apiSuccess (200) {Number} errno 0
|
|
191
196
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
@@ -224,8 +229,9 @@ module.exports = class extends Base {
|
|
|
224
229
|
* @apiParam {String} [mail] post comment user mail address
|
|
225
230
|
* @apiParam {String} [link] post comment user link
|
|
226
231
|
* @apiParam {String} [comment] post comment text
|
|
227
|
-
* @apiParam {String} [url] the
|
|
232
|
+
* @apiParam {String} [url] the article url path of comment
|
|
228
233
|
* @apiParam {Boolean} [like] like comment
|
|
234
|
+
* @apiParam {String} lang language
|
|
229
235
|
*
|
|
230
236
|
* @apiSuccess (200) {Number} errno 0
|
|
231
237
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
@@ -277,6 +283,8 @@ module.exports = class extends Base {
|
|
|
277
283
|
* @apiGroup Comment
|
|
278
284
|
* @apiVersion 0.0.1
|
|
279
285
|
*
|
|
286
|
+
* @apiParam {String} lang language
|
|
287
|
+
*
|
|
280
288
|
* @apiSuccess (200) {Number} errno 0
|
|
281
289
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
282
290
|
*/
|
package/src/logic/db.js
CHANGED
|
@@ -19,6 +19,8 @@ module.exports = class extends Base {
|
|
|
19
19
|
* @api {GET} /db export site data
|
|
20
20
|
* @apiGroup Site
|
|
21
21
|
* @apiVersion 0.0.1
|
|
22
|
+
*
|
|
23
|
+
* @apiParam {String} lang language
|
|
22
24
|
*/
|
|
23
25
|
async getAction() {}
|
|
24
26
|
|
|
@@ -26,6 +28,8 @@ module.exports = class extends Base {
|
|
|
26
28
|
* @api {POST} /db import site data
|
|
27
29
|
* @apiGroup Site
|
|
28
30
|
* @apiVersion 0.0.1
|
|
31
|
+
*
|
|
32
|
+
* @apiParam {String} lang language
|
|
29
33
|
*/
|
|
30
34
|
async postAction() {
|
|
31
35
|
this.rules = {
|
|
@@ -41,6 +45,8 @@ module.exports = class extends Base {
|
|
|
41
45
|
* @api {PUT} /db update site table data
|
|
42
46
|
* @apiGroup Site
|
|
43
47
|
* @apiVersion 0.0.1
|
|
48
|
+
*
|
|
49
|
+
* @apiParam {String} lang language
|
|
44
50
|
*/
|
|
45
51
|
async putAction() {
|
|
46
52
|
this.rules = {
|
|
@@ -60,6 +66,8 @@ module.exports = class extends Base {
|
|
|
60
66
|
* @api {DELETE} /db clean site data
|
|
61
67
|
* @apiGroup Site
|
|
62
68
|
* @apiVersion 0.0.1
|
|
69
|
+
*
|
|
70
|
+
* @apiParam {String} lang language
|
|
63
71
|
*/
|
|
64
72
|
async deleteAction() {
|
|
65
73
|
this.rules = {
|
package/src/logic/token.js
CHANGED
|
@@ -6,6 +6,8 @@ module.exports = class extends Base {
|
|
|
6
6
|
* @apiGroup User
|
|
7
7
|
* @apiVersion 0.0.1
|
|
8
8
|
*
|
|
9
|
+
* @apiParam {String} lang language
|
|
10
|
+
*
|
|
9
11
|
* @apiSuccess (200) {Number} errno 0
|
|
10
12
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
11
13
|
* @apiSuccess (200) {Object} data user info
|
|
@@ -28,6 +30,7 @@ module.exports = class extends Base {
|
|
|
28
30
|
*
|
|
29
31
|
* @apiParam {String} email login user email
|
|
30
32
|
* @apiParam {String} password login user password
|
|
33
|
+
* @apiParam {String} lang language
|
|
31
34
|
*
|
|
32
35
|
* @apiSuccess (200) {Number} errno 0
|
|
33
36
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
@@ -41,6 +44,8 @@ module.exports = class extends Base {
|
|
|
41
44
|
* @apiGroup User
|
|
42
45
|
* @apiVersion 0.0.1
|
|
43
46
|
*
|
|
47
|
+
* @apiParam {String} lang language
|
|
48
|
+
*
|
|
44
49
|
* @apiSuccess (200) {Number} errno 0
|
|
45
50
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
46
51
|
*/
|
package/src/logic/user.js
CHANGED
|
@@ -2,14 +2,43 @@ const Base = require('./base');
|
|
|
2
2
|
|
|
3
3
|
module.exports = class extends Base {
|
|
4
4
|
/**
|
|
5
|
-
* @api {GET} /user user list
|
|
5
|
+
* @api {GET} /user user top list without admin
|
|
6
6
|
* @apiGroup User
|
|
7
7
|
* @apiVersion 0.0.1
|
|
8
8
|
*
|
|
9
9
|
* @apiParam {String} pageSize page size
|
|
10
|
+
* @apiParam {String} lang language
|
|
10
11
|
*
|
|
11
12
|
* @apiSuccess (200) {Number} errno 0
|
|
12
13
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
14
|
+
* @apiSuccess (200) {Object[]} data user list
|
|
15
|
+
* @apiSuccess (200) {String} data.nick comment user nick name
|
|
16
|
+
* @apiSuccess (200) {String} data.link comment user link
|
|
17
|
+
* @apiSuccess (200) {String} data.avatar comment user avatar
|
|
18
|
+
* @apiSuccess (200) {String} data.level comment user level
|
|
19
|
+
* @apiSuccess (200) {String} data.count user comment count
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* @api {GET} /user?token user list with admin login
|
|
23
|
+
* @apiGroup User
|
|
24
|
+
* @apiVersion 0.0.1
|
|
25
|
+
*
|
|
26
|
+
* @apiParam {String} page page
|
|
27
|
+
* @apiParam {String} pageSize page size
|
|
28
|
+
* @apiParam {String} lang language
|
|
29
|
+
*
|
|
30
|
+
* @apiSuccess (200) {Number} errno 0
|
|
31
|
+
* @apiSuccess (200) {String} errmsg return error message if error
|
|
32
|
+
* @apiSuccess (200) {Object} data user list
|
|
33
|
+
* @apiSuccess (200) {Number} data.page user list current page
|
|
34
|
+
* @apiSuccess (200) {Number} data.pageSize user list page size
|
|
35
|
+
* @apiSuccess (200) {Number} data.totalPages user list total pages
|
|
36
|
+
* @apiSuccess (200) {Object[]} data.data user list data
|
|
37
|
+
* @apiSuccess (200) {String} data.data.nick comment user nick name
|
|
38
|
+
* @apiSuccess (200) {String} data.data.link comment user link
|
|
39
|
+
* @apiSuccess (200) {String} data.data.avatar comment user avatar
|
|
40
|
+
* @apiSuccess (200) {String} data.data.level comment user level
|
|
41
|
+
* @apiSuccess (200) {String} data.data.label comment user label
|
|
13
42
|
*/
|
|
14
43
|
getAction() {
|
|
15
44
|
const { userInfo } = this.ctx.state;
|
|
@@ -49,15 +78,10 @@ module.exports = class extends Base {
|
|
|
49
78
|
* @apiParam {String} email user email
|
|
50
79
|
* @apiParam {String} password user password
|
|
51
80
|
* @apiParam {String} url user link
|
|
81
|
+
* @apiParam {String} lang language
|
|
52
82
|
*
|
|
53
83
|
* @apiSuccess (200) {Number} errno 0
|
|
54
84
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
55
|
-
* @apiSuccess (200) {Object[]} data user list
|
|
56
|
-
* @apiSuccess (200) {String} data.nick comment user nick name
|
|
57
|
-
* @apiSuccess (200) {String} data.link comment user link
|
|
58
|
-
* @apiSuccess (200) {String} data.avatar comment user avatar
|
|
59
|
-
* @apiSuccess (200) {String} data.level comment user level
|
|
60
|
-
* @apiSuccess (200) {String} data.label comment user label
|
|
61
85
|
*/
|
|
62
86
|
postAction() {
|
|
63
87
|
return this.useCaptchaCheck();
|
|
@@ -72,6 +96,7 @@ module.exports = class extends Base {
|
|
|
72
96
|
* @apiParam {String} [url] user new link
|
|
73
97
|
* @apiParam {String} [password] user new password
|
|
74
98
|
* @apiParam {String} [github] user github account name
|
|
99
|
+
* @apiParam {String} lang language
|
|
75
100
|
*
|
|
76
101
|
* @apiSuccess (200) {Number} errno 0
|
|
77
102
|
* @apiSuccess (200) {String} errmsg return error message if error
|
|
@@ -84,7 +109,7 @@ module.exports = class extends Base {
|
|
|
84
109
|
return this.fail();
|
|
85
110
|
}
|
|
86
111
|
|
|
87
|
-
// you should be a administrator to update
|
|
112
|
+
// you should be a administrator to update others info
|
|
88
113
|
if (this.id && userInfo.type !== 'administrator') {
|
|
89
114
|
return this.fail();
|
|
90
115
|
}
|
|
@@ -14,7 +14,7 @@ const getMarkdownParser = () => {
|
|
|
14
14
|
// markdown-it instance
|
|
15
15
|
const markdownIt = MarkdownIt({
|
|
16
16
|
breaks: true,
|
|
17
|
-
linkify: true, //
|
|
17
|
+
linkify: true, // Auto convert URL-like text to links
|
|
18
18
|
typographer: true, // Enable some language-neutral replacement + quotes beautification
|
|
19
19
|
|
|
20
20
|
// default highlight
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Test if potential opening or closing
|
|
2
|
+
* Test if potential opening or closing delimiter
|
|
3
3
|
* Assumes that there is a "$" at state.src[pos]
|
|
4
4
|
*/
|
|
5
5
|
const isValidDelim = (state, pos) => {
|
|
@@ -10,7 +10,7 @@ const isValidDelim = (state, pos) => {
|
|
|
10
10
|
canOpen: nextChar !== ' ' && nextChar !== '\t',
|
|
11
11
|
/*
|
|
12
12
|
* Check non-whitespace conditions for opening and closing, and
|
|
13
|
-
* check that closing
|
|
13
|
+
* check that closing delimiter isn’t followed by a number
|
|
14
14
|
*/
|
|
15
15
|
canClose: !(
|
|
16
16
|
prevChar === ' ' ||
|
|
@@ -36,10 +36,10 @@ const inlineTex = (state, silent) => {
|
|
|
36
36
|
return true;
|
|
37
37
|
}
|
|
38
38
|
/*
|
|
39
|
-
* First check for and bypass all properly escaped
|
|
39
|
+
* First check for and bypass all properly escaped delimiters
|
|
40
40
|
* This loop will assume that the first leading backtick can not
|
|
41
41
|
* be the first character in state.src, which is known since
|
|
42
|
-
* we have found an opening
|
|
42
|
+
* we have found an opening delimiter already.
|
|
43
43
|
*/
|
|
44
44
|
const start = state.pos + 1;
|
|
45
45
|
|
|
@@ -56,7 +56,7 @@ const inlineTex = (state, silent) => {
|
|
|
56
56
|
match += 1;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// No closing
|
|
59
|
+
// No closing delimiter found. Consume $ and continue.
|
|
60
60
|
if (match === -1) {
|
|
61
61
|
if (!silent) state.pending += '$';
|
|
62
62
|
state.pos = start;
|
|
@@ -8,7 +8,7 @@ const { AllPackages } = require('mathjax-full/js/input/tex/AllPackages.js');
|
|
|
8
8
|
const { escapeHtml } = require('./utils');
|
|
9
9
|
const { inlineTex, blockTex } = require('./mathCommon');
|
|
10
10
|
|
|
11
|
-
// set MathJax as the renderer
|
|
11
|
+
// set MathJax as the renderer
|
|
12
12
|
class MathToSvg {
|
|
13
13
|
constructor() {
|
|
14
14
|
const adaptor = liteAdaptor();
|
|
@@ -29,7 +29,7 @@ class MathToSvg {
|
|
|
29
29
|
if (svg.includes('data-mml-node="merror"')) {
|
|
30
30
|
const errorTitle = svg.match(/<title>(.*?)<\/title>/)[1];
|
|
31
31
|
|
|
32
|
-
svg = `<span class='
|
|
32
|
+
svg = `<span class='mathjax-error' title='${escapeHtml(
|
|
33
33
|
errorTitle
|
|
34
34
|
)}'>${escapeHtml(tex)}</span>`;
|
|
35
35
|
}
|
|
@@ -44,7 +44,7 @@ class MathToSvg {
|
|
|
44
44
|
if (svg.includes('data-mml-node="merror"')) {
|
|
45
45
|
const errorTitle = svg.match(/<title>(.*?)<\/title>/)[1];
|
|
46
46
|
|
|
47
|
-
svg = `<p class='
|
|
47
|
+
svg = `<p class='mathjax-block mathjax-error' title='${escapeHtml(
|
|
48
48
|
errorTitle
|
|
49
49
|
)}'>${escapeHtml(tex)}</p>`;
|
|
50
50
|
} else {
|
package/src/service/notify.js
CHANGED
|
@@ -333,13 +333,13 @@ module.exports = class extends think.Service {
|
|
|
333
333
|
|
|
334
334
|
const form = new FormData();
|
|
335
335
|
|
|
336
|
-
form.append('topic', topic);
|
|
337
|
-
form.append('template', template);
|
|
338
|
-
form.append('channel', channel);
|
|
339
|
-
form.append('webhook', webhook);
|
|
340
|
-
form.append('callbackUrl', callbackUrl);
|
|
341
|
-
form.append('title', title);
|
|
342
|
-
form.append('content', content);
|
|
336
|
+
topic && form.append('topic', topic);
|
|
337
|
+
template && form.append('template', template);
|
|
338
|
+
channel && form.append('channel', channel);
|
|
339
|
+
webhook && form.append('webhook', webhook);
|
|
340
|
+
callbackUrl && form.append('callbackUrl', callbackUrl);
|
|
341
|
+
title && form.append('title', title);
|
|
342
|
+
content && form.append('content', content);
|
|
343
343
|
|
|
344
344
|
return fetch(`http://www.pushplus.plus/send/${PUSH_PLUS_KEY}`, {
|
|
345
345
|
method: 'POST',
|
|
@@ -499,8 +499,7 @@ module.exports = class extends think.Service {
|
|
|
499
499
|
if (
|
|
500
500
|
[wechat, qq, telegram, qywxAmWechat, pushplus, discord, lark].every(
|
|
501
501
|
think.isEmpty
|
|
502
|
-
)
|
|
503
|
-
!isReplyAuthor
|
|
502
|
+
)
|
|
504
503
|
) {
|
|
505
504
|
mailList.push({ to: AUTHOR, title, content });
|
|
506
505
|
}
|
|
@@ -515,6 +514,7 @@ module.exports = class extends think.Service {
|
|
|
515
514
|
parent &&
|
|
516
515
|
!fakeMail.test(parent.mail) &&
|
|
517
516
|
!isCommentSelf &&
|
|
517
|
+
!isReplyAuthor &&
|
|
518
518
|
comment.status !== 'waiting'
|
|
519
519
|
) {
|
|
520
520
|
mailList.push({
|
|
@@ -308,7 +308,6 @@ module.exports = class extends Base {
|
|
|
308
308
|
return data.map(({ id, ...cmt }) => ({ ...cmt, objectId: id }));
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
// eslint-disable-next-line no-unused-vars
|
|
312
311
|
async count(where = {}, { group } = {}) {
|
|
313
312
|
const instance = await this.collection(this.tableName);
|
|
314
313
|
const data = this.where(instance, where);
|
|
@@ -335,9 +334,8 @@ module.exports = class extends Base {
|
|
|
335
334
|
}
|
|
336
335
|
|
|
337
336
|
async add(
|
|
338
|
-
data
|
|
339
|
-
//
|
|
340
|
-
{ access: { read = true, write = true } = { read: true, write: true } } = {}
|
|
337
|
+
data
|
|
338
|
+
// { access: { read = true, write = true } = { read: true, write: true } } = {}
|
|
341
339
|
) {
|
|
342
340
|
const instance = await this.collection(this.tableName);
|
|
343
341
|
const id = Math.random().toString(36).substr(2, 15);
|