@waline/client 2.3.1 → 2.4.1

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.esm.js +1 -1
  2. package/dist/component.esm.js.map +1 -1
  3. package/dist/component.js +1 -1
  4. package/dist/component.js.map +1 -1
  5. package/dist/legacy.d.ts +3 -8
  6. package/dist/legacy.js +1 -1
  7. package/dist/legacy.js.map +1 -1
  8. package/dist/pageview.cjs.d.ts +47 -0
  9. package/dist/pageview.cjs.js +1 -1
  10. package/dist/pageview.cjs.js.map +1 -1
  11. package/dist/pageview.esm.d.ts +47 -0
  12. package/dist/pageview.esm.js +1 -1
  13. package/dist/pageview.esm.js.map +1 -1
  14. package/dist/pageview.js +1 -1
  15. package/dist/pageview.js.map +1 -1
  16. package/dist/shim.d.ts +45 -10
  17. package/dist/shim.esm.d.ts +45 -10
  18. package/dist/shim.esm.js +1 -1
  19. package/dist/shim.esm.js.map +1 -1
  20. package/dist/shim.js +1 -1
  21. package/dist/shim.js.map +1 -1
  22. package/dist/waline.cjs.d.ts +45 -10
  23. package/dist/waline.cjs.js +1 -1
  24. package/dist/waline.cjs.js.map +1 -1
  25. package/dist/waline.css +1 -1
  26. package/dist/waline.css.map +1 -1
  27. package/dist/waline.d.ts +45 -10
  28. package/dist/waline.esm.d.ts +45 -10
  29. package/dist/waline.esm.js +1 -1
  30. package/dist/waline.esm.js.map +1 -1
  31. package/dist/waline.js +1 -1
  32. package/dist/waline.js.map +1 -1
  33. package/package.json +20 -5
  34. package/src/components/CommentBox.vue +1 -3
  35. package/src/components/CommentCard.vue +80 -14
  36. package/src/components/Icons.ts +28 -2
  37. package/src/components/Waline.vue +220 -98
  38. package/src/composables/index.ts +1 -0
  39. package/src/composables/like.ts +14 -0
  40. package/src/composables/userInfo.ts +9 -1
  41. package/src/config/i18n/en.ts +2 -0
  42. package/src/config/i18n/generate.ts +2 -0
  43. package/src/config/i18n/jp.ts +2 -0
  44. package/src/config/i18n/pt-BR.ts +2 -0
  45. package/src/config/i18n/ru.ts +2 -0
  46. package/src/config/i18n/vi-VN.ts +2 -0
  47. package/src/config/i18n/zh-CN.ts +2 -0
  48. package/src/config/i18n/zh-TW.ts +2 -0
  49. package/src/styles/card.scss +55 -27
  50. package/src/styles/nomalize.scss +0 -1
  51. package/src/styles/panel.scss +0 -1
  52. package/src/typings/comment.ts +56 -1
  53. package/src/typings/locale.ts +3 -8
  54. package/src/utils/config.ts +1 -1
  55. package/src/utils/fetch.ts +77 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waline/client",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
4
4
  "description": "client for waline comment system",
5
5
  "keywords": [
6
6
  "valine",
@@ -71,19 +71,34 @@
71
71
  ]
72
72
  },
73
73
  "dependencies": {
74
- "@vueuse/core": "^8.4.0",
74
+ "@vueuse/core": "^8.4.2",
75
75
  "autosize": "^5.0.1",
76
76
  "marked": "^4.0.15",
77
77
  "vue": "^3.2.33"
78
78
  },
79
79
  "devDependencies": {
80
+ "@babel/core": "^7.17.10",
81
+ "@babel/preset-env": "^7.17.10",
82
+ "@rollup/plugin-babel": "^5.3.1",
83
+ "@rollup/plugin-commonjs": "^22.0.0",
84
+ "@rollup/plugin-node-resolve": "^13.3.0",
85
+ "@rollup/plugin-replace": "^4.0.0",
80
86
  "@types/autosize": "^4.0.1",
81
87
  "@types/marked": "^4.0.3",
82
- "@vitejs/plugin-vue": "^2.3.1",
83
- "vite": "^2.9.7"
88
+ "@types/node": "^17.0.31",
89
+ "@vitejs/plugin-vue": "^2.3.2",
90
+ "@vue/compiler-sfc": "^3.2.33",
91
+ "rimraf": "^3.0.2",
92
+ "rollup": "^2.72.1",
93
+ "rollup-plugin-dts": "^4.2.1",
94
+ "rollup-plugin-terser": "^7.0.2",
95
+ "rollup-plugin-ts": "^2.0.7",
96
+ "rollup-plugin-vue": "^6.0.0",
97
+ "typescript": "^4.6.4",
98
+ "vite": "^2.9.8"
84
99
  },
85
100
  "engines": {
86
- "node": ">=12.20.0"
101
+ "node": ">=14"
87
102
  },
88
103
  "scripts": {
89
104
  "build": "pnpm rollup && pnpm style",
@@ -405,9 +405,7 @@ export default defineComponent({
405
405
  if (
406
406
  (requiredMeta.indexOf('mail') > -1 && !comment.mail) ||
407
407
  (comment.mail &&
408
- !/^\w(?:[\w._-]*\w)?@(?:\w(?:[\w-]*\w)?\.){0,2}\w+$/.exec(
409
- comment.mail
410
- ))
408
+ !/^\w(?:[\w._-]*\w)?@(?:\w(?:[\w-]*\w)?\.)*\w+$/.exec(comment.mail))
411
409
  ) {
412
410
  inputRefs.value.mail?.focus();
413
411
  return alert(locale.value.mailError);
@@ -25,21 +25,39 @@
25
25
  <span v-if="comment.label" class="wl-badge" v-text="comment.label" />
26
26
  <span v-if="comment.sticky" class="wl-badge" v-text="locale.sticky" />
27
27
  <span
28
- v-if="comment.level >= 0"
28
+ v-if="comment.level && comment.level >= 0"
29
29
  :class="`wl-badge level${comment.level}`"
30
30
  v-text="locale[`level${comment.level}`] || `Level ${comment.level}`"
31
31
  />
32
-
33
32
  <span class="wl-time" v-text="time" />
34
33
 
35
- <button
36
- class="wl-reply"
37
- :class="{ active: isReplyingCurrent }"
38
- :title="isReplyingCurrent ? locale.cancelReply : locale.reply"
39
- @click="$emit('reply', isReplyingCurrent ? null : comment)"
40
- >
41
- <ReplyIcon />
42
- </button>
34
+ <div class="wl-comment-actions">
35
+ <button
36
+ class="wl-delete"
37
+ v-if="isAdmin || isOwner"
38
+ @click="$emit('delete', comment)"
39
+ >
40
+ <DeleteIcon />
41
+ </button>
42
+
43
+ <button
44
+ class="wl-like"
45
+ @click="$emit('like', comment)"
46
+ :title="like ? locale.cancelLike : locale.like"
47
+ >
48
+ <LikeIcon :active="like" />
49
+ <span v-if="'like' in comment" v-text="comment.like" />
50
+ </button>
51
+
52
+ <button
53
+ class="wl-reply"
54
+ :class="{ active: isReplyingCurrent }"
55
+ :title="isReplyingCurrent ? locale.cancelReply : locale.reply"
56
+ @click="$emit('reply', isReplyingCurrent ? null : comment)"
57
+ >
58
+ <ReplyIcon />
59
+ </button>
60
+ </div>
43
61
  </div>
44
62
  <div class="wl-meta" aria-hidden="true">
45
63
  <span v-if="comment.addr" v-text="comment.addr" />
@@ -48,6 +66,27 @@
48
66
  </div>
49
67
  <div class="wl-content" v-html="comment.comment" />
50
68
 
69
+ <div v-if="isAdmin" class="wl-admin-actions">
70
+ <span class="wl-comment-status">
71
+ <button
72
+ v-for="status in commentStatus"
73
+ :key="status"
74
+ :class="`wl-btn wl-${status}`"
75
+ :disabled="comment.status === status"
76
+ @click="$emit('status', { status, comment })"
77
+ v-text="status"
78
+ />
79
+ </span>
80
+
81
+ <button
82
+ class="wl-btn wl-sticky"
83
+ v-if="isAdmin && !comment.rid"
84
+ @click="$emit('sticky', comment)"
85
+ >
86
+ {{ comment.sticky ? 'unsticky' : 'sticky' }}
87
+ </button>
88
+ </div>
89
+
51
90
  <div v-if="isReplyingCurrent" class="wl-reply-wrapper">
52
91
  <CommentBox
53
92
  :replyId="comment.objectId"
@@ -66,6 +105,10 @@
66
105
  :rootId="rootId"
67
106
  @reply="$emit('reply', $event)"
68
107
  @submit="$emit('submit', $event)"
108
+ @like="$emit('like', $event)"
109
+ @delete="$emit('delete', $event)"
110
+ @status="$emit('status', $event)"
111
+ @sticky="$emit('sticky', $event)"
69
112
  />
70
113
  </div>
71
114
  </div>
@@ -75,13 +118,15 @@
75
118
  <script lang="ts">
76
119
  import { computed, defineComponent, inject } from 'vue';
77
120
  import CommentBox from './CommentBox.vue';
78
- import { ReplyIcon, VerifiedIcon } from './Icons';
121
+ import { DeleteIcon, LikeIcon, ReplyIcon, VerifiedIcon } from './Icons';
79
122
  import { isLinkHttp } from '../utils';
80
- import { useTimeAgo } from '../composables';
123
+ import { useTimeAgo, useLikeStorage, useUserInfo } from '../composables';
81
124
 
82
125
  import type { ComputedRef, PropType } from 'vue';
83
126
  import type { WalineConfig } from '../utils';
84
- import type { WalineComment } from '../typings';
127
+ import type { WalineComment, WalineCommentStatus } from '../typings';
128
+
129
+ const commentStatus: WalineCommentStatus[] = ['approved', 'waiting', 'spam'];
85
130
 
86
131
  export default defineComponent({
87
132
  props: {
@@ -100,16 +145,21 @@ export default defineComponent({
100
145
 
101
146
  components: {
102
147
  CommentBox,
148
+ DeleteIcon,
149
+ LikeIcon,
103
150
  ReplyIcon,
104
151
  VerifiedIcon,
105
152
  },
106
153
 
107
- emits: ['submit', 'reply'],
154
+ emits: ['submit', 'reply', 'like', 'delete', 'status', 'sticky'],
108
155
 
109
156
  setup(props) {
110
157
  const config = inject<ComputedRef<WalineConfig>>(
111
158
  'config'
112
159
  ) as ComputedRef<WalineConfig>;
160
+ const likes = useLikeStorage();
161
+ const userInfo = useUserInfo();
162
+
113
163
  const locale = computed(() => config.value.locale);
114
164
 
115
165
  const link = computed(() => {
@@ -118,8 +168,18 @@ export default defineComponent({
118
168
  return link ? (isLinkHttp(link) ? link : `https://${link}`) : '';
119
169
  });
120
170
 
171
+ const like = computed(() => likes.value.includes(props.comment.objectId));
172
+
121
173
  const time = useTimeAgo(props.comment.insertedAt, locale.value);
122
174
 
175
+ const isAdmin = computed(() => userInfo.value.type === 'administrator');
176
+
177
+ const isOwner = computed(
178
+ () =>
179
+ props.comment.user_id &&
180
+ userInfo.value.objectId === props.comment.user_id
181
+ );
182
+
123
183
  const isReplyingCurrent = computed(
124
184
  () => props.comment.objectId === props.reply?.objectId
125
185
  );
@@ -130,7 +190,13 @@ export default defineComponent({
130
190
 
131
191
  isReplyingCurrent,
132
192
  link,
193
+ like,
133
194
  time,
195
+
196
+ isAdmin,
197
+ isOwner,
198
+
199
+ commentStatus,
134
200
  };
135
201
  },
136
202
  });
@@ -22,6 +22,16 @@ export const CloseIcon: FunctionalComponent<{ size: number }> = ({ size }) =>
22
22
  ]
23
23
  );
24
24
 
25
+ export const DeleteIcon: FunctionalComponent = () =>
26
+ h(
27
+ 'svg',
28
+ { viewBox: '0 0 1024 1024', width: '24', height: '24' },
29
+ h('path', {
30
+ d: 'm341.013 394.667 27.755 393.45h271.83l27.733-393.45h64.106l-28.01 397.952a64 64 0 0 1-63.83 59.498H368.768a64 64 0 0 1-63.83-59.52l-28.053-397.93h64.128zm139.307 19.818v298.667h-64V414.485h64zm117.013 0v298.667h-64V414.485h64zM181.333 288h640v64h-640v-64zm453.483-106.667v64h-256v-64h256z',
31
+ fill: 'red',
32
+ })
33
+ );
34
+
25
35
  export const EmojiIcon: FunctionalComponent = () =>
26
36
  h(
27
37
  'svg',
@@ -44,6 +54,22 @@ export const ImageIcon: FunctionalComponent = () =>
44
54
  }),
45
55
  ]);
46
56
 
57
+ export const LikeIcon: FunctionalComponent<{ active: boolean }> = ({
58
+ active = false,
59
+ }: {
60
+ active?: boolean;
61
+ }) =>
62
+ h('svg', { viewBox: '0 0 1024 1024', width: '24', height: '24' }, [
63
+ h('path', {
64
+ d: `M850.654 323.804c-11.042-25.625-26.862-48.532-46.885-68.225-20.022-19.61-43.258-34.936-69.213-45.73-26.78-11.124-55.124-16.727-84.375-16.727-40.622 0-80.256 11.123-114.698 32.135A214.79 214.79 0 0 0 512 241.819a214.79 214.79 0 0 0-23.483-16.562c-34.442-21.012-74.076-32.135-114.698-32.135-29.25 0-57.595 5.603-84.375 16.727-25.872 10.711-49.19 26.12-69.213 45.73-20.105 19.693-35.843 42.6-46.885 68.225-11.453 26.615-17.303 54.877-17.303 83.963 0 27.439 5.603 56.03 16.727 85.117 9.31 24.307 22.659 49.52 39.715 74.981 27.027 40.293 64.188 82.316 110.33 124.915 76.465 70.615 152.189 119.394 155.402 121.371l19.528 12.525c8.652 5.52 19.776 5.52 28.427 0l19.529-12.525c3.213-2.06 78.854-50.756 155.401-121.371 46.143-42.6 83.304-84.622 110.33-124.915 17.057-25.46 30.487-50.674 39.716-74.981 11.124-29.087 16.727-57.678 16.727-85.117.082-29.086-5.768-57.348-17.221-83.963z${
65
+ active
66
+ ? ''
67
+ : 'M512 761.5S218.665 573.55 218.665 407.767c0-83.963 69.461-152.023 155.154-152.023 60.233 0 112.473 33.618 138.181 82.727 25.708-49.109 77.948-82.727 138.18-82.727 85.694 0 155.155 68.06 155.155 152.023C805.335 573.551 512 761.5 512 761.5z'
68
+ }`,
69
+ fill: active ? 'red' : 'currentColor',
70
+ }),
71
+ ]);
72
+
47
73
  export const PreviewIcon: FunctionalComponent = () =>
48
74
  h('svg', { viewBox: '0 0 1024 1024', width: '24', height: '24' }, [
49
75
  h('path', {
@@ -69,9 +95,9 @@ export const MarkdownIcon: FunctionalComponent = () =>
69
95
  export const ReplyIcon: FunctionalComponent = () =>
70
96
  h(
71
97
  'svg',
72
- { viewBox: '0 0 1024 1024', width: '18', height: '18' },
98
+ { viewBox: '0 0 1024 1024', width: '24', height: '24' },
73
99
  h('path', {
74
- d: 'M1019.2 720C1001.6 625.6 968 566.4 904 497.6c-89.6-89.6-214.4-150.4-347.2-176v-120c0-25.6-8-51.2-25.6-64-33.6-30.4-81.6-30.4-112-4.8L33.6 441.6C12.8 459.2 0 484.8 0 510.4c0 25.6 12.8 51.2 30.4 68.8l385.6 312c17.6 12.8 33.6 17.6 51.2 17.6 12.8 0 25.6-4.8 38.4-8C536 888 552 857.6 552 824v-99.2c124.8 20.8 248 86.4 339.2 140.8 25.6 17.6 59.2 17.6 89.6 0 25.6-17.6 43.2-46.4 43.2-76.8 0-33.6 0-56-4.8-68.8zm-500.8-89.6-46.4-4.8v193.6L86.4 510.4 472 201.6V400l38.4 4.8c128 12.8 248 68.8 334.4 153.6 51.2 56 76.8 102.4 94.4 179.2 0 4.8 4.8 20.8 4.8 51.2C835.2 720 672 640 518.4 630.4z',
100
+ d: 'M810.667 213.333a64 64 0 0 1 64 64V704a64 64 0 0 1-64 64H478.336l-146.645 96.107a21.333 21.333 0 0 1-33.024-17.856V768h-85.334a64 64 0 0 1-64-64V277.333a64 64 0 0 1 64-64h597.334zm0 64H213.333V704h149.334v63.296L459.243 704h351.424V277.333zm-271.36 213.334v64h-176.64v-64h176.64zm122.026-128v64H362.667v-64h298.666z',
75
101
  fill: 'currentColor',
76
102
  })
77
103
  );
@@ -15,6 +15,10 @@
15
15
  :reply="reply"
16
16
  @reply="onReply"
17
17
  @submit="onSubmit"
18
+ @status="onStatusChange"
19
+ @delete="onDelete"
20
+ @sticky="onSticky"
21
+ @like="onLike"
18
22
  />
19
23
  </div>
20
24
 
@@ -66,134 +70,152 @@ import { computed, defineComponent, onMounted, provide, ref, watch } from 'vue';
66
70
  import CommentBox from './CommentBox.vue';
67
71
  import CommentCard from './CommentCard.vue';
68
72
  import { LoadingIcon } from './Icons';
69
- import { useUserInfo } from '../composables';
73
+ import { useUserInfo, useLikeStorage } from '../composables';
70
74
  import { defaultLocales } from '../config';
71
- import { fetchCommentList, getConfig, getDarkStyle } from '../utils';
75
+ import {
76
+ deleteComment,
77
+ fetchCommentList,
78
+ likeComment,
79
+ getConfig,
80
+ getDarkStyle,
81
+ updateComment,
82
+ } from '../utils';
72
83
 
73
84
  import type { PropType } from 'vue';
74
85
  import type {
75
86
  WalineComment,
87
+ WalineCommentStatus,
76
88
  WalineEmojiInfo,
77
89
  WalineHighlighter,
78
90
  WalineTexRenderer,
79
91
  WalineImageUploader,
80
92
  WalineLocale,
81
93
  WalineProps,
94
+ WalineMeta,
82
95
  } from '../typings';
83
96
 
84
97
  declare const SHOULD_VALIDATE: boolean;
85
98
  declare const VERSION: string;
86
99
 
87
- export default defineComponent({
88
- name: 'WalineRoot',
100
+ const props = [
101
+ 'serverURL',
102
+ 'path',
103
+ 'meta',
104
+ 'requiredMeta',
105
+ 'dark',
106
+ 'lang',
107
+ 'locale',
108
+ 'pageSize',
109
+ 'wordLimit',
110
+ 'emoji',
111
+ 'login',
112
+ 'highlighter',
113
+ 'texRenderer',
114
+ 'imageUploader',
115
+ 'copyright',
116
+ ];
117
+
118
+ const propsWithValidate = {
119
+ serverURL: {
120
+ type: String,
121
+ required: true,
122
+ },
89
123
 
90
- components: {
91
- CommentBox,
92
- CommentCard,
93
- LoadingIcon,
124
+ path: {
125
+ type: String,
126
+ required: true,
94
127
  },
95
128
 
96
- props: {
97
- serverURL: {
98
- type: String,
99
- required: true,
100
- },
101
-
102
- path: {
103
- type: String,
104
- required: true,
105
- },
106
-
107
- meta: {
108
- type: Array,
109
- ...(SHOULD_VALIDATE
110
- ? {
111
- validator: (value: unknown): boolean =>
112
- Array.isArray(value) &&
113
- value.every((item) => ['nick', 'mail', 'link'].includes(item)),
114
- }
115
- : {}),
116
- },
117
-
118
- requiredMeta: {
119
- type: Array,
120
- ...(SHOULD_VALIDATE
121
- ? {
122
- validator: (value: unknown): boolean =>
123
- Array.isArray(value) &&
124
- value.every((item) => ['nick', 'mail', 'link'].includes(item)),
125
- }
126
- : {}),
127
- },
129
+ meta: {
130
+ type: Array as PropType<WalineMeta[]>,
131
+ default: (): WalineMeta[] => ['nick', 'mail', 'link'],
132
+ validator: (value: unknown): boolean =>
133
+ Array.isArray(value) &&
134
+ value.every((item) => ['nick', 'mail', 'link'].includes(item)),
135
+ },
128
136
 
129
- dark: [String, Boolean],
137
+ requiredMeta: {
138
+ type: Array,
139
+ default: (): WalineMeta[] => [],
140
+ validator: (value: unknown): boolean =>
141
+ Array.isArray(value) &&
142
+ value.every((item) => ['nick', 'mail', 'link'].includes(item)),
143
+ },
130
144
 
131
- lang: {
132
- type: String,
133
- ...(SHOULD_VALIDATE
134
- ? {
135
- validator: (value: unknown): boolean =>
136
- Object.keys(defaultLocales).includes(value as string),
137
- }
138
- : {}),
139
- },
140
-
141
- locale: Object as PropType<Partial<WalineLocale>>,
142
-
143
- pageSize: Number,
144
-
145
- wordLimit: {
146
- type: [Number, Array] as PropType<number | [number, number]>,
147
- // default: 0,
148
- ...(SHOULD_VALIDATE
149
- ? {
150
- validator: (value: unknown): boolean =>
151
- typeof value === 'number' ||
152
- (Array.isArray(value) &&
153
- value.length === 2 &&
154
- value.every((item) => typeof item === 'number')),
155
- }
156
- : {}),
157
- },
158
-
159
- emoji: {
160
- type: [Array, Boolean] as PropType<(string | WalineEmojiInfo)[] | false>,
161
- ...(SHOULD_VALIDATE
162
- ? {
163
- validator: (value: unknown): boolean =>
164
- value === false ||
165
- (Array.isArray(value) &&
166
- value.every(
167
- (item) =>
168
- typeof item === 'string' ||
169
- (typeof item === 'object' &&
170
- typeof item.name === 'string' &&
171
- typeof item.folder === 'string' &&
172
- typeof item.icon === 'string' &&
173
- Array.isArray(item.items) &&
174
- (item.items as unknown[]).every(
175
- (icon) => typeof icon === 'string'
176
- ))
177
- )),
178
- }
179
- : {}),
180
- },
145
+ dark: [String, Boolean],
146
+
147
+ lang: {
148
+ type: String,
149
+ default: 'zh-CN',
150
+ validator: (value: unknown): boolean =>
151
+ Object.keys(defaultLocales).includes(value as string),
152
+ },
153
+
154
+ locale: Object as PropType<Partial<WalineLocale>>,
181
155
 
182
- login: String as PropType<'enable' | 'disable' | 'force'>,
156
+ pageSize: { type: Number, default: 10 },
183
157
 
184
- highlighter: Function as PropType<WalineHighlighter>,
158
+ wordLimit: {
159
+ type: [Number, Array] as PropType<number | [number, number]>,
160
+ validator: (value: unknown): boolean =>
161
+ typeof value === 'number' ||
162
+ (Array.isArray(value) &&
163
+ value.length === 2 &&
164
+ value.every((item) => typeof item === 'number')),
165
+ },
166
+
167
+ emoji: {
168
+ type: [Array, Boolean] as PropType<(string | WalineEmojiInfo)[] | false>,
169
+ validator: (value: unknown): boolean =>
170
+ value === false ||
171
+ (Array.isArray(value) &&
172
+ value.every(
173
+ (item) =>
174
+ typeof item === 'string' ||
175
+ (typeof item === 'object' &&
176
+ typeof item.name === 'string' &&
177
+ typeof item.folder === 'string' &&
178
+ typeof item.icon === 'string' &&
179
+ Array.isArray(item.items) &&
180
+ (item.items as unknown[]).every(
181
+ (icon) => typeof icon === 'string'
182
+ ))
183
+ )),
184
+ },
185
185
 
186
- imageUploader: [Function, Boolean] as PropType<WalineImageUploader | false>,
186
+ login: String as PropType<'enable' | 'disable' | 'force'>,
187
187
 
188
- texRenderer: [Function, Boolean] as PropType<WalineTexRenderer | false>,
188
+ highlighter: Function as PropType<WalineHighlighter>,
189
+
190
+ imageUploader: {
191
+ type: [Function, Boolean] as PropType<WalineImageUploader | false>,
192
+ default: undefined,
193
+ },
189
194
 
190
- copyright: Boolean,
195
+ texRenderer: {
196
+ type: [Function, Boolean] as PropType<WalineTexRenderer | false>,
197
+ default: undefined,
191
198
  },
192
199
 
200
+ copyright: { type: Boolean, default: true },
201
+ };
202
+
203
+ export default defineComponent({
204
+ name: 'WalineRoot',
205
+
206
+ components: {
207
+ CommentBox,
208
+ CommentCard,
209
+ LoadingIcon,
210
+ },
211
+
212
+ props: SHOULD_VALIDATE ? propsWithValidate : props,
213
+
193
214
  setup(props) {
194
- const config = computed(() => getConfig(props as WalineProps));
215
+ const config = computed(() => getConfig(props as unknown as WalineProps));
195
216
 
196
217
  const userInfo = useUserInfo();
218
+ const likeStorage = useLikeStorage();
197
219
 
198
220
  const status = ref<'loading' | 'success' | 'error'>('loading');
199
221
 
@@ -272,9 +294,105 @@ export default defineComponent({
272
294
  } else data.value.unshift(comment);
273
295
  };
274
296
 
297
+ const onStatusChange = async ({
298
+ comment,
299
+ status,
300
+ }: {
301
+ comment: WalineComment;
302
+ status: WalineCommentStatus;
303
+ }): Promise<void> => {
304
+ if (comment.status === status) return;
305
+
306
+ const { serverURL, lang } = config.value;
307
+
308
+ await updateComment({
309
+ serverURL,
310
+ lang,
311
+ token: userInfo.value?.token,
312
+ objectId: comment.objectId,
313
+ status,
314
+ });
315
+
316
+ comment.status = status;
317
+ };
318
+
319
+ const onSticky = async (comment: WalineComment): Promise<void> => {
320
+ if (comment.rid) return;
321
+
322
+ const { serverURL, lang } = config.value;
323
+
324
+ await updateComment({
325
+ serverURL,
326
+ lang,
327
+ token: userInfo.value?.token,
328
+ objectId: comment.objectId,
329
+ sticky: comment.sticky ? 0 : 1,
330
+ });
331
+
332
+ comment.sticky = !comment.sticky;
333
+ };
334
+
335
+ const onDelete = async ({ objectId }: WalineComment): Promise<void> => {
336
+ if (!confirm('Are you sure you want to delete this comment?')) return;
337
+
338
+ const { serverURL, lang } = config.value;
339
+
340
+ await deleteComment({
341
+ serverURL,
342
+ lang,
343
+ token: userInfo.value?.token,
344
+ objectId: objectId,
345
+ });
346
+
347
+ // delete comment from data
348
+ data.value.some((item, index) => {
349
+ if (item.objectId === objectId) {
350
+ data.value = data.value.filter((_item, i) => i !== index);
351
+
352
+ return true;
353
+ }
354
+
355
+ return item.children.some((child, childIndex) => {
356
+ if (child.objectId === objectId) {
357
+ data.value[index].children = item.children.filter(
358
+ (_item, i) => i !== childIndex
359
+ );
360
+
361
+ return true;
362
+ }
363
+
364
+ return false;
365
+ });
366
+ });
367
+ };
368
+
369
+ const onLike = async (comment: WalineComment): Promise<void> => {
370
+ const { serverURL, lang } = config.value;
371
+ const { objectId } = comment;
372
+ const hasLiked = likeStorage.value.includes(objectId);
373
+
374
+ await likeComment({
375
+ serverURL,
376
+ lang,
377
+ objectId,
378
+ like: !hasLiked,
379
+ });
380
+
381
+ if (hasLiked)
382
+ likeStorage.value = likeStorage.value.filter((id) => id !== objectId);
383
+ else {
384
+ likeStorage.value = [...likeStorage.value, objectId];
385
+
386
+ if (likeStorage.value.length > 50)
387
+ likeStorage.value = likeStorage.value.slice(-50);
388
+ }
389
+
390
+ comment.like = (comment.like || 0) + (hasLiked ? -1 : 1);
391
+ };
392
+
275
393
  provide('config', config);
276
394
 
277
- watch(() => props.path, refresh);
395
+ watch(() => (props as unknown as WalineProps).path, refresh);
278
396
 
279
397
  onMounted(() => refresh());
280
398
 
@@ -294,6 +412,10 @@ export default defineComponent({
294
412
  refresh,
295
413
  onReply,
296
414
  onSubmit,
415
+ onStatusChange,
416
+ onDelete,
417
+ onSticky,
418
+ onLike,
297
419
 
298
420
  version: VERSION,
299
421
  };
@@ -1,3 +1,4 @@
1
1
  export * from './inputs';
2
2
  export * from './timeAgo';
3
3
  export * from './userInfo';
4
+ export * from './like';
@@ -0,0 +1,14 @@
1
+ import { useStorage } from '@vueuse/core';
2
+
3
+ import type { Ref } from 'vue';
4
+
5
+ const LIKE_KEY = 'WALIKE_LIKE';
6
+
7
+ export type LikeID = number | string;
8
+
9
+ export type LikeRef = Ref<LikeID[]>;
10
+
11
+ let likeStorage: LikeRef | null = null;
12
+
13
+ export const useLikeStorage = (): LikeRef =>
14
+ likeStorage || (likeStorage = useStorage<LikeID[]>(LIKE_KEY, []));