@waline/client 2.9.1 → 2.11.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.
Files changed (55) hide show
  1. package/dist/component.mjs +1 -1
  2. package/dist/component.mjs.map +1 -1
  3. package/dist/legacy.umd.d.ts +18 -0
  4. package/dist/legacy.umd.js +1 -1
  5. package/dist/legacy.umd.js.map +1 -1
  6. package/dist/pageview.cjs +1 -1
  7. package/dist/pageview.cjs.map +1 -1
  8. package/dist/pageview.js +1 -1
  9. package/dist/pageview.js.map +1 -1
  10. package/dist/pageview.mjs +1 -1
  11. package/dist/pageview.mjs.map +1 -1
  12. package/dist/shim.cjs +1 -1
  13. package/dist/shim.cjs.map +1 -1
  14. package/dist/shim.d.cts +22 -0
  15. package/dist/shim.d.mts +22 -0
  16. package/dist/shim.mjs +1 -1
  17. package/dist/shim.mjs.map +1 -1
  18. package/dist/waline.cjs +1 -1
  19. package/dist/waline.cjs.map +1 -1
  20. package/dist/waline.css +1 -1
  21. package/dist/waline.css.map +1 -1
  22. package/dist/waline.d.cts +22 -0
  23. package/dist/waline.d.mts +22 -0
  24. package/dist/waline.d.ts +22 -0
  25. package/dist/waline.js +1 -1
  26. package/dist/waline.js.map +1 -1
  27. package/dist/waline.mjs +1 -1
  28. package/dist/waline.mjs.map +1 -1
  29. package/package.json +20 -18
  30. package/src/components/ArticleReaction.vue +141 -0
  31. package/src/components/CommentBox.vue +13 -1
  32. package/src/components/RecaptchaV3/IReCaptchaOptions.ts +6 -0
  33. package/src/components/RecaptchaV3/README.md +3 -0
  34. package/src/components/RecaptchaV3/RecaptchaVuePlugin.ts +86 -0
  35. package/src/components/Waline.vue +11 -0
  36. package/src/composables/vote.ts +17 -0
  37. package/src/config/default.ts +9 -0
  38. package/src/config/i18n/en.ts +1 -0
  39. package/src/config/i18n/generate.ts +1 -0
  40. package/src/config/i18n/jp.ts +1 -0
  41. package/src/config/i18n/pt-BR.ts +1 -0
  42. package/src/config/i18n/ru.ts +1 -0
  43. package/src/config/i18n/vi-VN.ts +1 -0
  44. package/src/config/i18n/zh-CN.ts +1 -0
  45. package/src/config/i18n/zh-TW.ts +1 -0
  46. package/src/init.ts +11 -0
  47. package/src/styles/card.scss +3 -1
  48. package/src/styles/index.scss +1 -0
  49. package/src/styles/reaction.scss +66 -0
  50. package/src/typings/comment.ts +4 -0
  51. package/src/typings/locale.ts +10 -0
  52. package/src/typings/waline.ts +10 -0
  53. package/src/utils/config.ts +5 -1
  54. package/src/utils/fetch.ts +48 -14
  55. package/LICENSE +0 -339
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waline/client",
3
- "version": "2.9.1",
3
+ "version": "2.11.0",
4
4
  "description": "client for waline comment system",
5
5
  "keywords": [
6
6
  "valine",
@@ -71,6 +71,17 @@
71
71
  "dist",
72
72
  "src"
73
73
  ],
74
+ "scripts": {
75
+ "build": "pnpm rollup && pnpm style",
76
+ "clean": "rimraf ./dist",
77
+ "dev": "vite -c config/vite.config.js",
78
+ "lint": "eslint --ext .ts,.vue .",
79
+ "prepublishOnly": "pnpm clean && pnpm build",
80
+ "rollup": "rollup -c ./config/rollup.config.js",
81
+ "style": "pnpm style:main && pnpm style:meta",
82
+ "style:main": "sass ./src/styles/index.scss ./dist/waline.css --style=compressed",
83
+ "style:meta": "sass ./src/styles/meta.scss ./dist/waline-meta.css --style=compressed"
84
+ },
74
85
  "browserslist": {
75
86
  "production": [
76
87
  ">0.5%",
@@ -92,35 +103,26 @@
92
103
  "vue": "^3.2.39"
93
104
  },
94
105
  "devDependencies": {
95
- "@babel/core": "7.19.0",
96
- "@babel/preset-env": "7.19.0",
106
+ "@babel/core": "7.19.1",
107
+ "@babel/preset-env": "7.19.1",
97
108
  "@rollup/plugin-babel": "5.3.1",
98
109
  "@rollup/plugin-commonjs": "22.0.2",
99
- "@rollup/plugin-node-resolve": "14.0.1",
110
+ "@rollup/plugin-node-resolve": "14.1.0",
100
111
  "@rollup/plugin-replace": "4.0.0",
101
112
  "@types/autosize": "4.0.1",
102
113
  "@types/marked": "4.0.7",
103
- "@types/node": "18.7.16",
114
+ "@types/node": "18.7.21",
104
115
  "@vitejs/plugin-vue": "3.1.0",
116
+ "recaptcha-v3": "1.10.0",
105
117
  "rimraf": "3.0.2",
106
- "rollup": "2.79.0",
118
+ "rollup": "2.79.1",
107
119
  "rollup-plugin-dts": "4.2.2",
108
120
  "rollup-plugin-terser": "7.0.2",
109
121
  "rollup-plugin-ts": "3.0.2",
110
122
  "typescript": "4.8.3",
111
- "vite": "3.1.0"
123
+ "vite": "3.1.3"
112
124
  },
113
125
  "engines": {
114
126
  "node": ">=14"
115
- },
116
- "scripts": {
117
- "build": "pnpm rollup && pnpm style",
118
- "clean": "rimraf ./dist",
119
- "dev": "vite -c config/vite.config.js",
120
- "lint": "eslint --ext .ts,.vue .",
121
- "rollup": "rollup -c ./config/rollup.config.js",
122
- "style": "pnpm style:main && pnpm style:meta",
123
- "style:main": "sass ./src/styles/index.scss ./dist/waline.css --style=compressed",
124
- "style:meta": "sass ./src/styles/meta.scss ./dist/waline-meta.css --style=compressed"
125
127
  }
126
- }
128
+ }
@@ -0,0 +1,141 @@
1
+ <template>
2
+ <div v-if="reaction && reaction.length" class="wl-reaction">
3
+ <h4>{{ locale.reactionTitle }}</h4>
4
+ <ul>
5
+ <li
6
+ v-for="(item, index) in reaction"
7
+ :key="index"
8
+ :class="item.active ? 'active' : ''"
9
+ @click="onVote(index)"
10
+ >
11
+ <div class="wl-reaction__img">
12
+ <img :src="item.icon" :alt="item.desc" />
13
+ <div class="wl-reaction__votes">{{ item.vote }}</div>
14
+ </div>
15
+ <div class="wl-reaction__text">{{ item.desc }}</div>
16
+ </li>
17
+ </ul>
18
+ </div>
19
+ </template>
20
+
21
+ <script lang="ts">
22
+ import {
23
+ defineComponent,
24
+ inject,
25
+ ComputedRef,
26
+ computed,
27
+ onMounted,
28
+ onUnmounted,
29
+ ref,
30
+ } from 'vue';
31
+ import {
32
+ fetchArticleCounter,
33
+ updateArticleCounter,
34
+ WalineConfig,
35
+ } from '../utils';
36
+ import { useVoteStorage } from '../composables/vote';
37
+
38
+ interface ReactionItem {
39
+ icon: string;
40
+ vote: number;
41
+ desc: string;
42
+ active?: boolean;
43
+ }
44
+
45
+ export default defineComponent({
46
+ setup() {
47
+ const votes = ref<ReactionItem['vote'][]>([]);
48
+ const voteStorage = useVoteStorage();
49
+ const config = inject<ComputedRef<WalineConfig>>(
50
+ 'config'
51
+ ) as ComputedRef<WalineConfig>;
52
+ const locale = computed(() => config.value.locale);
53
+ const reaction = computed((): ReactionItem[] => {
54
+ const { path } = config.value;
55
+
56
+ if (!Array.isArray(config.value.reaction)) {
57
+ return [];
58
+ }
59
+
60
+ return config.value.reaction.map((icon, index) => ({
61
+ icon,
62
+ vote: votes.value[index] || 0,
63
+ desc: locale.value[`reaction${index}` as `reaction0`],
64
+ active: Boolean(
65
+ voteStorage.value.find(({ u, i }) => u === path && i === index)
66
+ ),
67
+ }));
68
+ });
69
+
70
+ const controller = new AbortController();
71
+
72
+ const fetchCounter = async (): Promise<void> => {
73
+ const { serverURL, lang, path, reaction } = config.value;
74
+
75
+ if (!Array.isArray(reaction)) {
76
+ return;
77
+ }
78
+
79
+ const resp = await fetchArticleCounter({
80
+ serverURL,
81
+ lang,
82
+ paths: [path],
83
+ type: reaction.map((_, k) => `reaction${k}`),
84
+ signal: controller.signal,
85
+ });
86
+
87
+ if (Array.isArray(resp) || typeof resp === 'number') {
88
+ return;
89
+ }
90
+
91
+ votes.value = reaction.map((_, k) => resp[`reaction${k}`]);
92
+ };
93
+
94
+ const onVote = async (index: number): Promise<void> => {
95
+ const { serverURL, lang, path } = config.value;
96
+ const hasVoted = voteStorage.value.find(({ u }) => u === path);
97
+ const hasVotedTheReaction = hasVoted && hasVoted.i === index;
98
+
99
+ if (hasVotedTheReaction) {
100
+ return;
101
+ }
102
+
103
+ await updateArticleCounter({
104
+ serverURL,
105
+ lang,
106
+ path,
107
+ type: `reaction${index}`,
108
+ });
109
+
110
+ votes.value[index] = (votes.value[index] || 0) + 1;
111
+ if (hasVoted) {
112
+ votes.value[hasVoted.i] = Math.max(votes.value[hasVoted.i] - 1, 0);
113
+ updateArticleCounter({
114
+ serverURL,
115
+ lang,
116
+ path,
117
+ type: `reaction${hasVoted.i}`,
118
+ action: 'desc',
119
+ });
120
+
121
+ hasVoted.i = index;
122
+ voteStorage.value = Array.from(voteStorage.value);
123
+ } else {
124
+ voteStorage.value = [...voteStorage.value, { u: path, i: index }];
125
+ }
126
+
127
+ if (voteStorage.value.length > 50)
128
+ voteStorage.value = voteStorage.value.slice(-50);
129
+ };
130
+
131
+ onMounted(() => fetchCounter());
132
+ onUnmounted(() => controller.abort());
133
+
134
+ return {
135
+ reaction,
136
+ locale,
137
+ onVote,
138
+ };
139
+ },
140
+ });
141
+ </script>
@@ -263,6 +263,7 @@
263
263
 
264
264
  <script lang="ts">
265
265
  import { useDebounceFn } from '@vueuse/core';
266
+ import { useReCaptcha } from './RecaptchaV3/RecaptchaVuePlugin';
266
267
  import autosize from 'autosize';
267
268
  import {
268
269
  computed,
@@ -349,6 +350,7 @@ export default defineComponent({
349
350
  const editor = useEditor();
350
351
  const userMeta = useUserMeta();
351
352
  const userInfo = useUserInfo();
353
+ const recaptchaHandler = useReCaptcha();
352
354
 
353
355
  const inputRefs = ref<Record<string, HTMLInputElement>>({});
354
356
  const editorRef = ref<HTMLTextAreaElement | null>(null);
@@ -456,9 +458,18 @@ export default defineComponent({
456
458
  });
457
459
  };
458
460
 
459
- const submitComment = (): void => {
461
+ const submitComment = async (): Promise<void> => {
460
462
  const { serverURL, lang, login, wordLimit, requiredMeta } = config.value;
461
463
 
464
+ let token = '';
465
+
466
+ if (recaptchaHandler) {
467
+ const { executeRecaptcha, recaptchaLoaded } = recaptchaHandler;
468
+
469
+ await recaptchaLoaded();
470
+ token = await executeRecaptcha('social');
471
+ }
472
+
462
473
  const comment: WalineCommentData = {
463
474
  comment: content.value,
464
475
  nick: userMeta.value.nick,
@@ -466,6 +477,7 @@ export default defineComponent({
466
477
  link: userMeta.value.link,
467
478
  ua: navigator.userAgent,
468
479
  url: config.value.path,
480
+ recaptchaV3: token,
469
481
  };
470
482
 
471
483
  if (userInfo.value?.token) {
@@ -0,0 +1,6 @@
1
+ import { IReCaptchaLoaderOptions } from 'recaptcha-v3/dist/ReCaptchaLoader';
2
+
3
+ export interface IReCaptchaOptions {
4
+ siteKey: string;
5
+ loaderOptions: IReCaptchaLoaderOptions;
6
+ }
@@ -0,0 +1,3 @@
1
+ # Vue reCAPTCHA-v3
2
+
3
+ This is a fork from https://github.com/AurityLab/vue-recaptcha-v3 because of commonjs bundle conflict with vite.
@@ -0,0 +1,86 @@
1
+ import { load as loadReCaptcha, ReCaptchaInstance } from 'recaptcha-v3';
2
+ import { App, Ref, ref, inject, InjectionKey } from 'vue';
3
+ import { IReCaptchaOptions } from './IReCaptchaOptions'
4
+
5
+ const VueReCaptchaInjectKey: InjectionKey<IReCaptchaComposition> = Symbol(
6
+ 'VueReCaptchaInjectKey'
7
+ );
8
+
9
+ interface IGlobalConfig {
10
+ loadedWaiters: Array<{
11
+ resolve: (resolve: boolean) => void;
12
+ reject: (reject: Error) => void;
13
+ }>;
14
+ error: Error | null;
15
+ }
16
+ const globalConfig: IGlobalConfig = {
17
+ loadedWaiters: [],
18
+ error: null,
19
+ };
20
+
21
+ export const VueReCaptcha = {
22
+ install(app: App, options: IReCaptchaOptions): void {
23
+ const isLoaded = ref(false);
24
+ const instance: Ref<ReCaptchaInstance | undefined> = ref(undefined);
25
+
26
+ app.config.globalProperties.$recaptchaLoaded = recaptchaLoaded(isLoaded);
27
+
28
+ initializeReCaptcha(options)
29
+ .then((wrapper) => {
30
+ isLoaded.value = true;
31
+ instance.value = wrapper;
32
+
33
+ app.config.globalProperties.$recaptcha = recaptcha(instance);
34
+ app.config.globalProperties.$recaptchaInstance = instance;
35
+
36
+ globalConfig.loadedWaiters.forEach((v) => v.resolve(true));
37
+ })
38
+ .catch((error: Error) => {
39
+ globalConfig.error = error;
40
+ globalConfig.loadedWaiters.forEach((v) => v.reject(error));
41
+ });
42
+
43
+ app.provide(VueReCaptchaInjectKey, {
44
+ instance,
45
+ isLoaded,
46
+ executeRecaptcha: recaptcha(instance),
47
+ recaptchaLoaded: recaptchaLoaded(isLoaded),
48
+ });
49
+ },
50
+ };
51
+
52
+ export function useReCaptcha(): IReCaptchaComposition | undefined {
53
+ return inject(VueReCaptchaInjectKey);
54
+ }
55
+
56
+ async function initializeReCaptcha(
57
+ options: IReCaptchaOptions
58
+ ): Promise<ReCaptchaInstance> {
59
+ return await loadReCaptcha(options.siteKey, options.loaderOptions)
60
+ }
61
+
62
+ function recaptchaLoaded(isLoaded: Ref<boolean>) {
63
+ return (): Promise<boolean> =>
64
+ new Promise<boolean>((resolve, reject) => {
65
+ if (globalConfig.error !== null) {
66
+ return reject(globalConfig.error);
67
+ }
68
+ if (isLoaded.value) {
69
+ return resolve(true);
70
+ }
71
+ globalConfig.loadedWaiters.push({ resolve, reject });
72
+ });
73
+ }
74
+
75
+ function recaptcha(instance: Ref<ReCaptchaInstance | undefined>) {
76
+ return async (action: string): Promise<string | undefined> => {
77
+ return await instance.value?.execute(action);
78
+ };
79
+ }
80
+
81
+ export interface IReCaptchaComposition {
82
+ isLoaded: Ref<boolean>;
83
+ instance: Ref<ReCaptchaInstance | undefined>;
84
+ executeRecaptcha: (action: string) => Promise<string>;
85
+ recaptchaLoaded: () => Promise<boolean>;
86
+ }
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <div data-waline>
3
+ <Reaction />
3
4
  <CommentBox v-if="!reply" @submit="onSubmit" />
4
5
  <div class="wl-meta-head">
5
6
  <div class="wl-count">
@@ -81,6 +82,7 @@
81
82
  <script lang="ts">
82
83
  import { useStyleTag } from '@vueuse/core';
83
84
  import { computed, defineComponent, onMounted, provide, ref, watch } from 'vue';
85
+ import Reaction from './ArticleReaction.vue';
84
86
  import CommentBox from './CommentBox.vue';
85
87
  import CommentCard from './CommentCard.vue';
86
88
  import { LoadingIcon } from './Icons';
@@ -237,12 +239,21 @@ const propsWithValidate = {
237
239
  },
238
240
 
239
241
  copyright: { type: Boolean, default: true },
242
+
243
+ recaptchav3key: {
244
+ type: String,
245
+ },
246
+
247
+ reaction: {
248
+ type: [Array, Boolean] as PropType<string[] | false>,
249
+ },
240
250
  };
241
251
 
242
252
  export default defineComponent({
243
253
  name: 'WalineRoot',
244
254
 
245
255
  components: {
256
+ Reaction,
246
257
  CommentBox,
247
258
  CommentCard,
248
259
  LoadingIcon,
@@ -0,0 +1,17 @@
1
+ import { useStorage } from '@vueuse/core';
2
+
3
+ import type { Ref } from 'vue';
4
+
5
+ const VOTE_KEY = 'WALINE_VOTE';
6
+
7
+ export interface VoteLogItem {
8
+ u: string;
9
+ i: number;
10
+ }
11
+
12
+ export type VoteRef = Ref<VoteLogItem[]>;
13
+
14
+ let voteStorage: VoteRef | null = null;
15
+
16
+ export const useVoteStorage = (): VoteRef =>
17
+ voteStorage || (voteStorage = useStorage<VoteLogItem[]>(VOTE_KEY, []));
@@ -115,3 +115,12 @@ export const getDefaultSearchOptions = (): WalineSearchOptions => {
115
115
  }),
116
116
  };
117
117
  };
118
+
119
+ export const defaultReaction = [
120
+ '//unpkg.com/@waline/emojis/tieba/tieba_agree.png',
121
+ '//unpkg.com/@waline/emojis/tieba/tieba_look_down.png',
122
+ '//unpkg.com/@waline/emojis/tieba/tieba_sunglasses.png',
123
+ '//unpkg.com/@waline/emojis/tieba/tieba_pick_nose.png',
124
+ '//unpkg.com/@waline/emojis/tieba/tieba_awkward.png',
125
+ '//unpkg.com/@waline/emojis/tieba/tieba_sleep.png',
126
+ ];
@@ -49,4 +49,5 @@ export default generateLocale([
49
49
  'Oldest',
50
50
  'Latest',
51
51
  'Hottest',
52
+ 'What do you think?',
52
53
  ]);
@@ -49,6 +49,7 @@ const localeKeys = [
49
49
  'oldest',
50
50
  'latest',
51
51
  'hottest',
52
+ 'reactionTitle',
52
53
  ];
53
54
 
54
55
  export const generateLocale = (locale: string[]): WalineLocale =>
@@ -49,4 +49,5 @@ export default generateLocale([
49
49
  '逆順',
50
50
  '正順',
51
51
  '人気順',
52
+ 'どう思いますか?',
52
53
  ]);
@@ -49,4 +49,5 @@ export default generateLocale([
49
49
  'Mais velho',
50
50
  'Mais recentes',
51
51
  'Mais quente',
52
+ 'O que você acha?',
52
53
  ]);
@@ -49,4 +49,5 @@ export default generateLocale([
49
49
  'самый старый',
50
50
  'последний',
51
51
  'самый горячий',
52
+ 'Что вы думаете?',
52
53
  ]);
@@ -49,4 +49,5 @@ export default generateLocale([
49
49
  'lâu đời nhất',
50
50
  'muộn nhất',
51
51
  'nóng nhất',
52
+ 'What do you think?',
52
53
  ]);
@@ -49,4 +49,5 @@ export default generateLocale([
49
49
  '按倒序',
50
50
  '按正序',
51
51
  '按热度',
52
+ '你认为这篇文章怎么样?',
52
53
  ]);
@@ -49,4 +49,5 @@ export default generateLocale([
49
49
  '按倒序',
50
50
  '按正序',
51
51
  '按熱度',
52
+ '你認為這篇文章怎麼樣?',
52
53
  ]);
package/src/init.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createApp, h, reactive, watchEffect } from 'vue';
2
+ import { VueReCaptcha } from './components/RecaptchaV3/RecaptchaVuePlugin';
2
3
 
3
4
  import Waline from './components/Waline.vue';
4
5
  import { commentCount } from './comment';
@@ -80,6 +81,16 @@ export const init = ({
80
81
  ? createApp(() => h(Waline, { path: state.path, ...props }))
81
82
  : null;
82
83
 
84
+ if (app && initProps.recaptchaV3Key) {
85
+ app.use(VueReCaptcha, {
86
+ siteKey: initProps.recaptchaV3Key,
87
+ loaderOptions: {
88
+ useRecaptchaNet: true,
89
+ autoHideBadge: true,
90
+ },
91
+ });
92
+ }
93
+
83
94
  if (app) app.mount(root!);
84
95
 
85
96
  const stopComment = watchEffect(updateCommentCount);
@@ -57,6 +57,7 @@
57
57
 
58
58
  .wl-head {
59
59
  line-height: 1.5;
60
+ overflow: hidden; // bfc to fix https://github.com/walinejs/waline/issues/1415
60
61
  }
61
62
 
62
63
  .wl-nick {
@@ -126,6 +127,7 @@
126
127
 
127
128
  .wl-comment-actions {
128
129
  float: right;
130
+ line-height: 1;
129
131
  }
130
132
 
131
133
  .wl-delete,
@@ -135,7 +137,7 @@
135
137
  display: inline-flex;
136
138
  align-items: center;
137
139
 
138
- padding: 4px;
140
+ // padding: 4px;
139
141
  border: none;
140
142
 
141
143
  background: transparent;
@@ -13,3 +13,4 @@
13
13
  @use 'highlight';
14
14
 
15
15
  @use 'recent';
16
+ @use 'reaction';
@@ -0,0 +1,66 @@
1
+ .wl-reaction {
2
+ text-align: center;
3
+ margin-bottom: 1.75em;
4
+
5
+ ul {
6
+ margin: 0;
7
+ list-style-type: none;
8
+ display: flex;
9
+ flex-direction: row;
10
+ justify-content: center;
11
+ gap: 16px;
12
+ }
13
+
14
+ li {
15
+ cursor: pointer;
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: center;
19
+
20
+ &:hover img,
21
+ &.active img {
22
+ transform: scale(1.15);
23
+ }
24
+ }
25
+
26
+ li.active .wl-reaction {
27
+ &__votes {
28
+ color: var(--waline-bgcolor);
29
+ background: var(--waline-theme-color);
30
+ }
31
+ &__text {
32
+ color: var(--waline-theme-color);
33
+ }
34
+ }
35
+
36
+ img {
37
+ width: 100%;
38
+ height: 100%;
39
+ transition: all 250ms ease-in-out;
40
+ }
41
+
42
+ &__img {
43
+ position: relative;
44
+ width: 42px;
45
+ height: 42px;
46
+ }
47
+
48
+ &__votes {
49
+ position: absolute;
50
+ top: -4px;
51
+ right: -5px;
52
+ font-size: 0.75em;
53
+ color: var(--waline-theme-color);
54
+ background: var(--waline-bgcolor);
55
+ border: 1px solid var(--waline-theme-color);
56
+ padding: 2px;
57
+ border-radius: 1em;
58
+ line-height: 1;
59
+ min-width: 1em;
60
+ font-weight: 700;
61
+ }
62
+
63
+ &__text {
64
+ font-size: 0.875em;
65
+ }
66
+ }
@@ -49,6 +49,10 @@ export interface WalineCommentData {
49
49
  * Comment link
50
50
  */
51
51
  url: string;
52
+ /**
53
+ * Recaptcha Token
54
+ */
55
+ recaptchaV3?: string;
52
56
  }
53
57
 
54
58
  export type WalineCommentStatus = 'approved' | 'waiting' | 'spam';
@@ -46,4 +46,14 @@ export interface WalineLocale extends WalineDateLocale, WalineLevelLocale {
46
46
  oldest: string;
47
47
  latest: string;
48
48
  hottest: string;
49
+ reactionTitle: string;
50
+ reaction0: string;
51
+ reaction1: string;
52
+ reaction2: string;
53
+ reaction3: string;
54
+ reaction4: string;
55
+ reaction5: string;
56
+ reaction6: string;
57
+ reaction7: string;
58
+ reaction8: string;
49
59
  }
@@ -213,4 +213,14 @@ export interface WalineProps {
213
213
  * @default true
214
214
  */
215
215
  copyright?: boolean;
216
+
217
+ /**
218
+ * recaptcha v3 client key
219
+ */
220
+ recaptchaV3Key?: string;
221
+
222
+ /**
223
+ * reaction
224
+ */
225
+ reaction?: string[] | boolean;
216
226
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  defaultLang,
3
3
  defaultLocales,
4
+ defaultReaction,
4
5
  defaultUploadImage,
5
6
  defaultHighlighter,
6
7
  defaultTexRenderer,
@@ -22,7 +23,8 @@ export interface WalineEmojiConfig {
22
23
  map: WalineEmojiMaps;
23
24
  }
24
25
 
25
- export interface WalineConfig extends Required<Omit<WalineProps, 'wordLimit'>> {
26
+ export interface WalineConfig
27
+ extends Required<Omit<WalineProps, 'wordLimit' | 'recaptchaV3Key'>> {
26
28
  locale: WalineLocale;
27
29
  wordLimit: [number, number] | false;
28
30
  // emoji: Promise<EmojiConfig>;
@@ -63,6 +65,7 @@ export const getConfig = ({
63
65
  copyright = true,
64
66
  login = 'enable',
65
67
  search = getDefaultSearchOptions(),
68
+ reaction,
66
69
  ...more
67
70
  }: WalineProps): WalineConfig => ({
68
71
  serverURL: getServerURL(serverURL),
@@ -84,5 +87,6 @@ export const getConfig = ({
84
87
  login,
85
88
  copyright,
86
89
  search,
90
+ reaction: reaction === true ? defaultReaction : reaction || false,
87
91
  ...more,
88
92
  });