@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.
- package/dist/component.mjs +1 -1
- package/dist/component.mjs.map +1 -1
- package/dist/legacy.umd.d.ts +18 -0
- package/dist/legacy.umd.js +1 -1
- package/dist/legacy.umd.js.map +1 -1
- package/dist/pageview.cjs +1 -1
- package/dist/pageview.cjs.map +1 -1
- package/dist/pageview.js +1 -1
- package/dist/pageview.js.map +1 -1
- package/dist/pageview.mjs +1 -1
- package/dist/pageview.mjs.map +1 -1
- package/dist/shim.cjs +1 -1
- package/dist/shim.cjs.map +1 -1
- package/dist/shim.d.cts +22 -0
- package/dist/shim.d.mts +22 -0
- package/dist/shim.mjs +1 -1
- package/dist/shim.mjs.map +1 -1
- package/dist/waline.cjs +1 -1
- package/dist/waline.cjs.map +1 -1
- package/dist/waline.css +1 -1
- package/dist/waline.css.map +1 -1
- package/dist/waline.d.cts +22 -0
- package/dist/waline.d.mts +22 -0
- package/dist/waline.d.ts +22 -0
- package/dist/waline.js +1 -1
- package/dist/waline.js.map +1 -1
- package/dist/waline.mjs +1 -1
- package/dist/waline.mjs.map +1 -1
- package/package.json +20 -18
- package/src/components/ArticleReaction.vue +141 -0
- package/src/components/CommentBox.vue +13 -1
- package/src/components/RecaptchaV3/IReCaptchaOptions.ts +6 -0
- package/src/components/RecaptchaV3/README.md +3 -0
- package/src/components/RecaptchaV3/RecaptchaVuePlugin.ts +86 -0
- package/src/components/Waline.vue +11 -0
- package/src/composables/vote.ts +17 -0
- package/src/config/default.ts +9 -0
- package/src/config/i18n/en.ts +1 -0
- package/src/config/i18n/generate.ts +1 -0
- package/src/config/i18n/jp.ts +1 -0
- package/src/config/i18n/pt-BR.ts +1 -0
- package/src/config/i18n/ru.ts +1 -0
- package/src/config/i18n/vi-VN.ts +1 -0
- package/src/config/i18n/zh-CN.ts +1 -0
- package/src/config/i18n/zh-TW.ts +1 -0
- package/src/init.ts +11 -0
- package/src/styles/card.scss +3 -1
- package/src/styles/index.scss +1 -0
- package/src/styles/reaction.scss +66 -0
- package/src/typings/comment.ts +4 -0
- package/src/typings/locale.ts +10 -0
- package/src/typings/waline.ts +10 -0
- package/src/utils/config.ts +5 -1
- package/src/utils/fetch.ts +48 -14
- package/LICENSE +0 -339
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waline/client",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
96
|
-
"@babel/preset-env": "7.19.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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,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, []));
|
package/src/config/default.ts
CHANGED
|
@@ -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
|
+
];
|
package/src/config/i18n/en.ts
CHANGED
package/src/config/i18n/jp.ts
CHANGED
package/src/config/i18n/pt-BR.ts
CHANGED
package/src/config/i18n/ru.ts
CHANGED
package/src/config/i18n/vi-VN.ts
CHANGED
package/src/config/i18n/zh-CN.ts
CHANGED
package/src/config/i18n/zh-TW.ts
CHANGED
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);
|
package/src/styles/card.scss
CHANGED
|
@@ -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;
|
package/src/styles/index.scss
CHANGED
|
@@ -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
|
+
}
|
package/src/typings/comment.ts
CHANGED
package/src/typings/locale.ts
CHANGED
|
@@ -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
|
}
|
package/src/typings/waline.ts
CHANGED
package/src/utils/config.ts
CHANGED
|
@@ -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
|
|
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
|
});
|