@waline/vercel 1.32.3 → 1.32.4
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/Dockerfile.source-build-alpine +20 -0
- package/__tests__/xss.spec.js +3 -3
- package/development.js +30 -0
- package/package.json +22 -17
- package/src/config/adapter.js +3 -1
- package/src/config/extend.js +0 -1
- package/src/controller/comment.js +2 -2
- package/src/controller/oauth.js +9 -20
- package/src/controller/user.js +3 -6
- package/src/extend/think.js +25 -2
- package/src/locales/zh-TW.json +1 -1
- package/src/logic/base.js +45 -6
- package/src/service/markdown/mathjax.js +33 -42
- package/src/service/notify.js +0 -1
- package/src/service/storage/deta.js +4 -3
- package/src/service/storage/github.js +0 -1
- package/src/service/storage/postgresql.js +1 -0
- package/src/service/markdown/katex.js +0 -50
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
FROM node:lts-alpine AS base
|
|
2
|
+
ENV PNPM_HOME="/pnpm"
|
|
3
|
+
ENV PATH="$PNPM_HOME:$PATH"
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
RUN apk add --no-cache bash tzdata && \
|
|
7
|
+
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
|
8
|
+
echo "Asia/Shanghai" > /etc/timezone && \
|
|
9
|
+
corepack enable
|
|
10
|
+
|
|
11
|
+
COPY . .
|
|
12
|
+
|
|
13
|
+
FROM base AS prod-deps
|
|
14
|
+
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod
|
|
15
|
+
|
|
16
|
+
FROM base AS runtime
|
|
17
|
+
ENV NODE_ENV=production
|
|
18
|
+
COPY --from=prod-deps /app /app
|
|
19
|
+
EXPOSE 8360
|
|
20
|
+
CMD ["node", "vanilla.js"]
|
package/__tests__/xss.spec.js
CHANGED
|
@@ -60,14 +60,14 @@ Waline is a good framework. :money:
|
|
|
60
60
|
|
|
61
61
|
it('Should not autoplay or preload media', () => {
|
|
62
62
|
expect(parser('<audio autoplay preload="auto" src="x">')).toEqual(
|
|
63
|
-
'<audio
|
|
63
|
+
'<audio preload="none" src="x"></audio>',
|
|
64
64
|
);
|
|
65
65
|
expect(parser('<audio autoplay src="x"></audio>')).toEqual(
|
|
66
66
|
'<p><audio src="x" preload="none"></audio></p>\n',
|
|
67
67
|
);
|
|
68
68
|
|
|
69
69
|
expect(parser('<video autoplay preload="auto" src="x">')).toEqual(
|
|
70
|
-
'<video
|
|
70
|
+
'<video preload="none" src="x"></video>',
|
|
71
71
|
);
|
|
72
72
|
expect(parser('<video autoplay src="x"></video>')).toEqual(
|
|
73
73
|
'<p><video src="x" preload="none"></video></p>\n',
|
|
@@ -83,7 +83,7 @@ Waline is a good framework. :money:
|
|
|
83
83
|
'<p><a href="https://example.com" rel="opener prefetch">link</a></p>',
|
|
84
84
|
),
|
|
85
85
|
).toEqual(
|
|
86
|
-
'<p><a rel="nofollow noreferrer noopener"
|
|
86
|
+
'<p><a href="https://example.com" rel="nofollow noreferrer noopener" target="_blank">link</a></p>',
|
|
87
87
|
);
|
|
88
88
|
});
|
|
89
89
|
|
package/development.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
|
|
3
|
+
require('dotenv').config({
|
|
4
|
+
path: path.join(__dirname, '../../.env'),
|
|
5
|
+
quiet: true,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const watcher = require('think-watcher');
|
|
9
|
+
const Application = require('thinkjs');
|
|
10
|
+
|
|
11
|
+
const instance = new Application({
|
|
12
|
+
ROOT_PATH: __dirname,
|
|
13
|
+
APP_PATH: path.join(__dirname, 'src'),
|
|
14
|
+
proxy: false,
|
|
15
|
+
watcher: watcher,
|
|
16
|
+
env: 'development',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
instance.run();
|
|
20
|
+
|
|
21
|
+
let config = {};
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
config = require('./config.js');
|
|
25
|
+
} catch {
|
|
26
|
+
// do nothing
|
|
27
|
+
}
|
|
28
|
+
for (const k in config) {
|
|
29
|
+
think.config(k, config[k]);
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waline/vercel",
|
|
3
|
-
"version": "1.32.
|
|
3
|
+
"version": "1.32.4",
|
|
4
4
|
"description": "vercel server for waline comment system",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"waline",
|
|
@@ -14,32 +14,33 @@
|
|
|
14
14
|
},
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"author": "lizheming <i@imnerd.org>",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "node development.js 9090"
|
|
19
|
+
},
|
|
17
20
|
"dependencies": {
|
|
18
|
-
"@cloudbase/node-sdk": "^3.
|
|
21
|
+
"@cloudbase/node-sdk": "^3.10.1",
|
|
19
22
|
"@koa/cors": "^5.0.0",
|
|
20
|
-
"@mdit/plugin-katex": "0.
|
|
21
|
-
"@mdit/plugin-mathjax": "0.
|
|
22
|
-
"@mdit/plugin-sub": "0.
|
|
23
|
-
"@mdit/plugin-sup": "0.
|
|
23
|
+
"@mdit/plugin-katex": "0.23.2-cjs.0",
|
|
24
|
+
"@mdit/plugin-mathjax": "0.23.2-cjs.0",
|
|
25
|
+
"@mdit/plugin-sub": "0.22.2-cjs.0",
|
|
26
|
+
"@mdit/plugin-sup": "0.22.2-cjs.0",
|
|
24
27
|
"akismet": "^2.0.7",
|
|
25
28
|
"deta": "^2.0.0",
|
|
26
|
-
"dompurify": "^3.2.
|
|
29
|
+
"dompurify": "^3.2.7",
|
|
27
30
|
"dy-node-ip2region": "^1.0.1",
|
|
28
|
-
"fast-csv": "^5.0.
|
|
29
|
-
"form-data": "^4.0.
|
|
30
|
-
"jsdom": "^
|
|
31
|
+
"fast-csv": "^5.0.5",
|
|
32
|
+
"form-data": "^4.0.4",
|
|
33
|
+
"jsdom": "^27.0.0",
|
|
31
34
|
"jsonwebtoken": "^9.0.2",
|
|
32
|
-
"katex": "^0.16.11",
|
|
33
35
|
"koa-compose": "^4.1.0",
|
|
34
36
|
"leancloud-storage": "^4.15.2",
|
|
35
37
|
"markdown-it": "^14.1.0",
|
|
36
38
|
"markdown-it-emoji": "^3.0.0",
|
|
37
39
|
"mathjax-full": "^3.2.2",
|
|
38
|
-
"
|
|
39
|
-
"nodemailer": "^6.9.16",
|
|
40
|
+
"nodemailer": "^7.0.6",
|
|
40
41
|
"nunjucks": "^3.2.4",
|
|
41
42
|
"phpass": "^0.1.1",
|
|
42
|
-
"prismjs": "^1.
|
|
43
|
+
"prismjs": "^1.30.0",
|
|
43
44
|
"speakeasy": "^2.0.0",
|
|
44
45
|
"think-helper": "^1.1.4",
|
|
45
46
|
"think-logger3": "^1.4.0",
|
|
@@ -50,11 +51,15 @@
|
|
|
50
51
|
"think-model-sqlite": "^1.3.2",
|
|
51
52
|
"think-mongo": "^2.2.1",
|
|
52
53
|
"think-router-rest": "^1.0.5",
|
|
53
|
-
"thinkjs": "
|
|
54
|
-
"ua-parser-js": "^2.0.
|
|
54
|
+
"thinkjs": "4.0.0-alpha.0",
|
|
55
|
+
"ua-parser-js": "^2.0.5"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"dotenv": "17.2.2",
|
|
59
|
+
"think-watcher": "3.0.4"
|
|
55
60
|
},
|
|
56
61
|
"engines": {
|
|
57
|
-
"node": ">=
|
|
62
|
+
"node": ">=20"
|
|
58
63
|
},
|
|
59
64
|
"publishConfig": {
|
|
60
65
|
"provenance": true
|
package/src/config/adapter.js
CHANGED
|
@@ -3,11 +3,13 @@ const Mysql = require('think-model-mysql');
|
|
|
3
3
|
const Mysql2 = require('think-model-mysql2');
|
|
4
4
|
const Postgresql = require('think-model-postgresql');
|
|
5
5
|
|
|
6
|
-
let Sqlite
|
|
6
|
+
let Sqlite;
|
|
7
7
|
|
|
8
8
|
try {
|
|
9
9
|
Sqlite = require('think-model-sqlite');
|
|
10
10
|
} catch (err) {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
|
12
|
+
Sqlite = class {};
|
|
11
13
|
console.log(err);
|
|
12
14
|
}
|
|
13
15
|
|
package/src/config/extend.js
CHANGED
|
@@ -175,8 +175,8 @@ module.exports = class extends BaseRest {
|
|
|
175
175
|
think.logger.debug(`Comment initial status is ${data.status}`);
|
|
176
176
|
|
|
177
177
|
if (data.status === 'approved') {
|
|
178
|
-
const spam = await akismet(data, this.ctx.serverURL).catch((
|
|
179
|
-
console.log(
|
|
178
|
+
const spam = await akismet(data, this.ctx.serverURL).catch((err) =>
|
|
179
|
+
console.log(err),
|
|
180
180
|
); // ignore akismet error
|
|
181
181
|
|
|
182
182
|
if (spam === true) {
|
package/src/controller/oauth.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const jwt = require('jsonwebtoken');
|
|
2
|
-
const fetch = require('node-fetch');
|
|
3
2
|
|
|
4
3
|
module.exports = class extends think.Controller {
|
|
5
4
|
constructor(ctx) {
|
|
@@ -16,17 +15,12 @@ module.exports = class extends think.Controller {
|
|
|
16
15
|
|
|
17
16
|
if (!hasCode) {
|
|
18
17
|
const { serverURL } = this.ctx;
|
|
19
|
-
const redirectUrl = `${serverURL}/api/oauth
|
|
20
|
-
redirect,
|
|
21
|
-
type,
|
|
22
|
-
}).toString()}`;
|
|
18
|
+
const redirectUrl = think.buildUrl(`${serverURL}/api/oauth`, {redirect, type});
|
|
23
19
|
|
|
24
|
-
return this.redirect(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}).toString()}`,
|
|
29
|
-
);
|
|
20
|
+
return this.redirect(think.buildUrl(`${oauthUrl}/${type}`, {
|
|
21
|
+
redirect: redirectUrl,
|
|
22
|
+
state: this.ctx.state.token || ''
|
|
23
|
+
}));
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
/**
|
|
@@ -36,19 +30,16 @@ module.exports = class extends think.Controller {
|
|
|
36
30
|
|
|
37
31
|
if (type === 'facebook') {
|
|
38
32
|
const { serverURL } = this.ctx;
|
|
39
|
-
const redirectUrl = `${serverURL}/api/oauth
|
|
40
|
-
redirect,
|
|
41
|
-
type,
|
|
42
|
-
}).toString()}`;
|
|
33
|
+
const redirectUrl = think.buildUrl(`${serverURL}/api/oauth`, {redirect, type});
|
|
43
34
|
|
|
44
|
-
params.state =
|
|
35
|
+
params.state = think.buildUrl(undefined, {
|
|
45
36
|
redirect: redirectUrl,
|
|
46
37
|
state: this.ctx.state.token || '',
|
|
47
38
|
});
|
|
48
39
|
}
|
|
49
40
|
|
|
50
41
|
const user = await fetch(
|
|
51
|
-
`${oauthUrl}/${type}
|
|
42
|
+
think.buildUrl(`${oauthUrl}/${type}`, params),
|
|
52
43
|
{
|
|
53
44
|
method: 'GET',
|
|
54
45
|
headers: {
|
|
@@ -67,9 +58,7 @@ module.exports = class extends think.Controller {
|
|
|
67
58
|
const token = jwt.sign(userBySocial[0].email, this.config('jwtKey'));
|
|
68
59
|
|
|
69
60
|
if (redirect) {
|
|
70
|
-
return this.redirect(
|
|
71
|
-
redirect + (redirect.includes('?') ? '&' : '?') + 'token=' + token,
|
|
72
|
-
);
|
|
61
|
+
return this.redirect(think.buildUrl(redirect, { token }));
|
|
73
62
|
}
|
|
74
63
|
|
|
75
64
|
return this.success();
|
package/src/controller/user.js
CHANGED
|
@@ -91,10 +91,7 @@ module.exports = class extends BaseRest {
|
|
|
91
91
|
|
|
92
92
|
try {
|
|
93
93
|
const notify = this.service('notify', this);
|
|
94
|
-
const apiUrl =
|
|
95
|
-
this.ctx.serverURL +
|
|
96
|
-
'/verification?' +
|
|
97
|
-
new URLSearchParams({ token, email: data.email }).toString();
|
|
94
|
+
const apiUrl = think.buildUrl(this.ctx.serverURL + '/verification', { token, email: data.email });
|
|
98
95
|
|
|
99
96
|
await notify.transporter.sendMail({
|
|
100
97
|
from:
|
|
@@ -110,8 +107,8 @@ module.exports = class extends BaseRest {
|
|
|
110
107
|
{ url: apiUrl },
|
|
111
108
|
),
|
|
112
109
|
});
|
|
113
|
-
} catch (
|
|
114
|
-
console.log(
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.log(err);
|
|
115
112
|
|
|
116
113
|
return this.fail(
|
|
117
114
|
this.locale(
|
package/src/extend/think.js
CHANGED
|
@@ -83,8 +83,8 @@ module.exports = {
|
|
|
83
83
|
);
|
|
84
84
|
|
|
85
85
|
return address.slice(0, depth).join(' ');
|
|
86
|
-
} catch (
|
|
87
|
-
console.log(
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.log(err);
|
|
88
88
|
|
|
89
89
|
return '';
|
|
90
90
|
}
|
|
@@ -154,4 +154,27 @@ module.exports = {
|
|
|
154
154
|
)
|
|
155
155
|
.filter((v) => v);
|
|
156
156
|
},
|
|
157
|
+
buildUrl(path, query = {}) {
|
|
158
|
+
const notEmptyQuery = {};
|
|
159
|
+
|
|
160
|
+
for(const key in query) {
|
|
161
|
+
if (!query[key]) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
notEmptyQuery[key] = query[key];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const notEmptyQueryStr = new URLSearchParams(notEmptyQuery).toString();
|
|
168
|
+
|
|
169
|
+
let destUrl = path;
|
|
170
|
+
|
|
171
|
+
if (destUrl && notEmptyQueryStr) {
|
|
172
|
+
destUrl += destUrl.indexOf('?') !== -1 ? '&' : '?';
|
|
173
|
+
}
|
|
174
|
+
if (notEmptyQueryStr) {
|
|
175
|
+
destUrl += notEmptyQueryStr;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return destUrl;
|
|
179
|
+
}
|
|
157
180
|
};
|
package/src/locales/zh-TW.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
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
|
-
"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
|
|
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}} 上有新評論了",
|
|
18
18
|
"MAIL_TEMPLATE_ADMIN": "<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> <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></p><br/> </div>"
|
|
19
19
|
}
|
package/src/logic/base.js
CHANGED
|
@@ -2,7 +2,6 @@ const path = require('node:path');
|
|
|
2
2
|
const qs = require('node:querystring');
|
|
3
3
|
|
|
4
4
|
const jwt = require('jsonwebtoken');
|
|
5
|
-
const fetch = require('node-fetch');
|
|
6
5
|
const helper = require('think-helper');
|
|
7
6
|
|
|
8
7
|
module.exports = class extends think.Logic {
|
|
@@ -15,9 +14,21 @@ module.exports = class extends think.Logic {
|
|
|
15
14
|
|
|
16
15
|
async __before() {
|
|
17
16
|
const referrer = this.ctx.referrer(true);
|
|
17
|
+
let origin = this.ctx.origin;
|
|
18
|
+
|
|
19
|
+
if (origin) {
|
|
20
|
+
try {
|
|
21
|
+
const parsedOrigin = new URL(origin);
|
|
22
|
+
|
|
23
|
+
origin = parsedOrigin.hostname;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.error('Invalid origin format:', origin, e);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
let { secureDomains } = this.config();
|
|
19
30
|
|
|
20
|
-
if (secureDomains
|
|
31
|
+
if (secureDomains) {
|
|
21
32
|
secureDomains = think.isArray(secureDomains)
|
|
22
33
|
? secureDomains
|
|
23
34
|
: [secureDomains];
|
|
@@ -31,13 +42,41 @@ module.exports = class extends think.Logic {
|
|
|
31
42
|
'graph.qq.com',
|
|
32
43
|
);
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
// 转换可能的正则表达式字符串为正则表达式对象
|
|
46
|
+
secureDomains = secureDomains
|
|
47
|
+
.map((domain) => {
|
|
48
|
+
// 如果是正则表达式字符串,创建一个 RegExp 对象
|
|
49
|
+
if (
|
|
50
|
+
typeof domain === 'string' &&
|
|
51
|
+
domain.startsWith('/') &&
|
|
52
|
+
domain.endsWith('/')
|
|
53
|
+
) {
|
|
54
|
+
try {
|
|
55
|
+
return new RegExp(domain.slice(1, -1)); // 去掉斜杠并创建 RegExp 对象
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error(
|
|
58
|
+
'Invalid regex pattern in secureDomains:',
|
|
59
|
+
domain,
|
|
60
|
+
e,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return domain;
|
|
68
|
+
})
|
|
69
|
+
.filter(Boolean); // 过滤掉无效的正则表达式
|
|
70
|
+
|
|
71
|
+
// 有 referrer 检查 referrer,没有则检查 origin
|
|
72
|
+
const checking = referrer ? referrer : origin;
|
|
73
|
+
const isSafe = secureDomains.some((domain) =>
|
|
35
74
|
think.isFunction(domain.test)
|
|
36
|
-
? domain.test(
|
|
37
|
-
: domain ===
|
|
75
|
+
? domain.test(checking)
|
|
76
|
+
: domain === checking,
|
|
38
77
|
);
|
|
39
78
|
|
|
40
|
-
if (!
|
|
79
|
+
if (!isSafe) {
|
|
41
80
|
return this.ctx.throw(403);
|
|
42
81
|
}
|
|
43
82
|
}
|
|
@@ -8,56 +8,48 @@ const { SVG } = require('mathjax-full/js/output/svg.js');
|
|
|
8
8
|
const { inlineTeX, blockTeX } = require('./mathCommon');
|
|
9
9
|
const { escapeHtml } = require('./utils');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
constructor() {
|
|
14
|
-
const adaptor = liteAdaptor();
|
|
15
|
-
|
|
16
|
-
RegisterHTMLHandler(adaptor);
|
|
11
|
+
const mathjaxPlugin = (md) => {
|
|
12
|
+
const adaptor = liteAdaptor();
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
const tex = new TeX({ packages });
|
|
20
|
-
const svg = new SVG({ fontCache: 'none' });
|
|
14
|
+
RegisterHTMLHandler(adaptor);
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
const packages = AllPackages.sort();
|
|
17
|
+
const tex = new TeX({ packages });
|
|
18
|
+
const svg = new SVG({ fontCache: 'none' });
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
const node = this.texToNode.convert(tex, { display: false });
|
|
27
|
-
let svg = this.adaptor.innerHTML(node);
|
|
20
|
+
const texToNode = mathjax.document('', { InputJax: tex, OutputJax: svg });
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
const inline = (tex) => {
|
|
23
|
+
const node = texToNode.convert(tex, { display: false });
|
|
24
|
+
let svg = adaptor.innerHTML(node);
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)}'>${escapeHtml(tex)}</span>`;
|
|
35
|
-
}
|
|
26
|
+
if (svg.includes('data-mml-node="merror"')) {
|
|
27
|
+
const errorTitle = svg.match(/<title>(.*?)<\/title>/)[1];
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
svg = `<span class='mathjax-error' title='${escapeHtml(
|
|
30
|
+
errorTitle,
|
|
31
|
+
)}'>${escapeHtml(tex)}</span>`;
|
|
32
|
+
}
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let svg = this.adaptor.innerHTML(node);
|
|
34
|
+
return svg;
|
|
35
|
+
};
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
const block = (tex) => {
|
|
38
|
+
const node = texToNode.convert(tex, { display: true });
|
|
39
|
+
let svg = adaptor.innerHTML(node);
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)}'>${escapeHtml(tex)}</p>`;
|
|
50
|
-
} else {
|
|
51
|
-
svg = svg.replace(/(width=".*?")/, 'width="100%"');
|
|
52
|
-
}
|
|
41
|
+
if (svg.includes('data-mml-node="merror"')) {
|
|
42
|
+
const errorTitle = svg.match(/<title>(.*?)<\/title>/)[1];
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
44
|
+
svg = `<p class='mathjax-block mathjax-error' title='${escapeHtml(
|
|
45
|
+
errorTitle,
|
|
46
|
+
)}'>${escapeHtml(tex)}</p>`;
|
|
47
|
+
} else {
|
|
48
|
+
svg = svg.replace(/(width=".*?")/, 'width="100%"');
|
|
49
|
+
}
|
|
58
50
|
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
return svg;
|
|
52
|
+
};
|
|
61
53
|
|
|
62
54
|
md.inline.ruler.after('escape', 'inlineTeX', inlineTeX);
|
|
63
55
|
|
|
@@ -66,11 +58,10 @@ const mathjaxPlugin = (md) => {
|
|
|
66
58
|
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
|
67
59
|
});
|
|
68
60
|
|
|
69
|
-
md.renderer.rules.inlineTeX = (tokens, idx) =>
|
|
70
|
-
mathToSvg.inline(tokens[idx].content);
|
|
61
|
+
md.renderer.rules.inlineTeX = (tokens, idx) => inline(tokens[idx].content);
|
|
71
62
|
|
|
72
63
|
md.renderer.rules.blockTeX = (tokens, idx) =>
|
|
73
|
-
`${
|
|
64
|
+
`${block(tokens[idx].content)}\n`;
|
|
74
65
|
};
|
|
75
66
|
|
|
76
67
|
module.exports = {
|
package/src/service/notify.js
CHANGED
|
@@ -213,9 +213,10 @@ module.exports = class extends Base {
|
|
|
213
213
|
|
|
214
214
|
fieldMap.add('objectId');
|
|
215
215
|
data.forEach((item) => {
|
|
216
|
-
for (const
|
|
217
|
-
if (!fieldMap.has(
|
|
218
|
-
delete
|
|
216
|
+
for (const key in item) {
|
|
217
|
+
if (!fieldMap.has(key)) {
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
219
|
+
delete item[key];
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const katex = require('katex');
|
|
2
|
-
|
|
3
|
-
const { inlineTeX, blockTeX } = require('./mathCommon.js');
|
|
4
|
-
const { escapeHtml } = require('./utils.js');
|
|
5
|
-
|
|
6
|
-
// set KaTeX as the renderer for markdown-it-simplemath
|
|
7
|
-
const katexInline = (tex, options) => {
|
|
8
|
-
options.displayMode = false;
|
|
9
|
-
try {
|
|
10
|
-
return katex.renderToString(tex, options);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
if (options.throwOnError) console.warn(error);
|
|
13
|
-
|
|
14
|
-
return `<span class='katex-error' title='${escapeHtml(
|
|
15
|
-
error.toString(),
|
|
16
|
-
)}'>${escapeHtml(tex)}</span>`;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const katexBlock = (tex, options) => {
|
|
21
|
-
options.displayMode = true;
|
|
22
|
-
try {
|
|
23
|
-
return `<p class='katex-block'>${katex.renderToString(tex, options)}</p>`;
|
|
24
|
-
} catch (error) {
|
|
25
|
-
if (options.throwOnError) console.warn(error);
|
|
26
|
-
|
|
27
|
-
return `<p class='katex-block katex-error' title='${escapeHtml(
|
|
28
|
-
error.toString(),
|
|
29
|
-
)}'>${escapeHtml(tex)}</p>`;
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const katexPlugin = (md, options = { throwOnError: false }) => {
|
|
34
|
-
md.inline.ruler.after('escape', 'inlineTeX', inlineTeX);
|
|
35
|
-
|
|
36
|
-
// It’s a workaround here because types issue
|
|
37
|
-
md.block.ruler.after('blockquote', 'blockTeX', blockTeX, {
|
|
38
|
-
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
md.renderer.rules.inlineTeX = (tokens, idx) =>
|
|
42
|
-
katexInline(tokens[idx].content, options);
|
|
43
|
-
|
|
44
|
-
md.renderer.rules.blockTeX = (tokens, idx) =>
|
|
45
|
-
`${katexBlock(tokens[idx].content, options)}\n`;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
module.exports = {
|
|
49
|
-
katexPlugin,
|
|
50
|
-
};
|