@waline/client 2.15.8 → 3.0.0-alpha.10

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 (145) hide show
  1. package/dist/comment.d.ts +2 -1
  2. package/dist/comment.mjs +1 -1
  3. package/dist/comment.mjs.map +1 -1
  4. package/dist/component.js +9 -0
  5. package/dist/component.js.map +1 -0
  6. package/dist/pageview.d.ts +2 -1
  7. package/dist/pageview.js +1 -1
  8. package/dist/pageview.js.map +1 -1
  9. package/dist/{shim.d.cts → slim.d.ts} +7 -82
  10. package/dist/slim.js +9 -0
  11. package/dist/slim.js.map +1 -0
  12. package/dist/waline.css +1 -1
  13. package/dist/waline.css.map +1 -1
  14. package/dist/waline.d.ts +7 -82
  15. package/dist/waline.js +53 -1
  16. package/dist/waline.js.map +1 -1
  17. package/dist/waline.umd.js +54 -0
  18. package/dist/waline.umd.js.map +1 -0
  19. package/package.json +37 -74
  20. package/dist/api.cjs +0 -2
  21. package/dist/api.cjs.map +0 -1
  22. package/dist/api.d.cts +0 -445
  23. package/dist/api.d.mts +0 -445
  24. package/dist/api.d.ts +0 -445
  25. package/dist/api.mjs +0 -2
  26. package/dist/api.mjs.map +0 -1
  27. package/dist/comment.cjs +0 -2
  28. package/dist/comment.cjs.map +0 -1
  29. package/dist/comment.d.cts +0 -39
  30. package/dist/comment.d.mts +0 -39
  31. package/dist/comment.js +0 -2
  32. package/dist/comment.js.map +0 -1
  33. package/dist/component.mjs +0 -2
  34. package/dist/component.mjs.map +0 -1
  35. package/dist/legacy.umd.d.ts +0 -628
  36. package/dist/legacy.umd.js +0 -2
  37. package/dist/legacy.umd.js.map +0 -1
  38. package/dist/pageview.cjs +0 -2
  39. package/dist/pageview.cjs.map +0 -1
  40. package/dist/pageview.d.cts +0 -47
  41. package/dist/pageview.d.mts +0 -47
  42. package/dist/pageview.mjs +0 -2
  43. package/dist/pageview.mjs.map +0 -1
  44. package/dist/shim.cjs +0 -2
  45. package/dist/shim.cjs.map +0 -1
  46. package/dist/shim.d.mts +0 -718
  47. package/dist/shim.mjs +0 -2
  48. package/dist/shim.mjs.map +0 -1
  49. package/dist/waline.cjs +0 -2
  50. package/dist/waline.cjs.map +0 -1
  51. package/dist/waline.d.cts +0 -718
  52. package/dist/waline.d.mts +0 -718
  53. package/dist/waline.mjs +0 -2
  54. package/dist/waline.mjs.map +0 -1
  55. package/src/api/articleCounter.ts +0 -82
  56. package/src/api/comment.ts +0 -258
  57. package/src/api/commentCount.ts +0 -33
  58. package/src/api/index.ts +0 -7
  59. package/src/api/login.ts +0 -96
  60. package/src/api/pageview.ts +0 -54
  61. package/src/api/recentComment.ts +0 -42
  62. package/src/api/user.ts +0 -44
  63. package/src/api/utils.ts +0 -46
  64. package/src/comment.ts +0 -69
  65. package/src/compact/convert.ts +0 -80
  66. package/src/compact/dropped.ts +0 -35
  67. package/src/compact/index.ts +0 -4
  68. package/src/compact/logger.ts +0 -5
  69. package/src/compact/v1.ts +0 -103
  70. package/src/compact/valine.ts +0 -132
  71. package/src/components/ArticleReaction.vue +0 -159
  72. package/src/components/CommentBox.vue +0 -807
  73. package/src/components/CommentCard.vue +0 -271
  74. package/src/components/Icons.ts +0 -181
  75. package/src/components/ImageWall.vue +0 -169
  76. package/src/components/WalineComment.vue +0 -353
  77. package/src/composables/index.ts +0 -6
  78. package/src/composables/inputs.ts +0 -17
  79. package/src/composables/like.ts +0 -13
  80. package/src/composables/reaction.ts +0 -15
  81. package/src/composables/recaptchaV3.ts +0 -19
  82. package/src/composables/turnstile.ts +0 -50
  83. package/src/composables/userInfo.ts +0 -16
  84. package/src/config/default.ts +0 -93
  85. package/src/config/highlighter.ts +0 -74
  86. package/src/config/i18n/en.ts +0 -53
  87. package/src/config/i18n/generate.ts +0 -58
  88. package/src/config/i18n/index.ts +0 -30
  89. package/src/config/i18n/jp.ts +0 -53
  90. package/src/config/i18n/pt-BR.ts +0 -53
  91. package/src/config/i18n/ru.ts +0 -53
  92. package/src/config/i18n/vi-VN.ts +0 -53
  93. package/src/config/i18n/zh-CN.ts +0 -53
  94. package/src/config/i18n/zh-TW.ts +0 -53
  95. package/src/config/index.ts +0 -3
  96. package/src/entries/api.ts +0 -1
  97. package/src/entries/comment.ts +0 -2
  98. package/src/entries/components.ts +0 -2
  99. package/src/entries/full.ts +0 -7
  100. package/src/entries/init.ts +0 -4
  101. package/src/entries/legacy.ts +0 -31
  102. package/src/entries/pageview.ts +0 -2
  103. package/src/init.ts +0 -112
  104. package/src/pageview.ts +0 -116
  105. package/src/shims-vue.d.ts +0 -6
  106. package/src/styles/base.scss +0 -67
  107. package/src/styles/card.scss +0 -258
  108. package/src/styles/config.scss +0 -52
  109. package/src/styles/emoji.scss +0 -137
  110. package/src/styles/gif.scss +0 -73
  111. package/src/styles/helpers/_svg.scss +0 -51
  112. package/src/styles/highlight.scss +0 -138
  113. package/src/styles/index.scss +0 -12
  114. package/src/styles/layout.scss +0 -105
  115. package/src/styles/meta.scss +0 -82
  116. package/src/styles/normalize.scss +0 -117
  117. package/src/styles/panel.scss +0 -286
  118. package/src/styles/reaction.scss +0 -103
  119. package/src/styles/recent.scss +0 -3
  120. package/src/styles/user-list.scss +0 -159
  121. package/src/typings/base.ts +0 -123
  122. package/src/typings/comment.ts +0 -94
  123. package/src/typings/index.ts +0 -5
  124. package/src/typings/locale.ts +0 -73
  125. package/src/typings/options.ts +0 -44
  126. package/src/typings/waline.ts +0 -251
  127. package/src/utils/config.ts +0 -122
  128. package/src/utils/darkmode.ts +0 -11
  129. package/src/utils/date.ts +0 -71
  130. package/src/utils/email.ts +0 -8
  131. package/src/utils/emoji.ts +0 -73
  132. package/src/utils/error.ts +0 -3
  133. package/src/utils/getRoot.ts +0 -8
  134. package/src/utils/image.ts +0 -10
  135. package/src/utils/index.ts +0 -13
  136. package/src/utils/markdown.ts +0 -41
  137. package/src/utils/markedMathExtension.ts +0 -54
  138. package/src/utils/path.ts +0 -15
  139. package/src/utils/query.ts +0 -2
  140. package/src/utils/userAgent.ts +0 -31
  141. package/src/utils/wordCount.ts +0 -31
  142. package/src/version.ts +0 -3
  143. package/src/widgets/index.ts +0 -2
  144. package/src/widgets/recentComments.ts +0 -93
  145. package/src/widgets/userList.ts +0 -136
@@ -1,807 +0,0 @@
1
- <template>
2
- <div class="wl-comment">
3
- <div
4
- v-if="config.login !== 'disable' && isLogin && !edit?.objectId"
5
- class="wl-login-info"
6
- >
7
- <div class="wl-avatar">
8
- <button
9
- type="submit"
10
- class="wl-logout-btn"
11
- :title="locale.logout"
12
- @click="onLogout"
13
- >
14
- <CloseIcon :size="14" />
15
- </button>
16
-
17
- <a
18
- href="#"
19
- class="wl-login-nick"
20
- aria-label="Profile"
21
- :title="locale.profile"
22
- @click="onProfile"
23
- >
24
- <img :src="userInfo.avatar" alt="avatar" />
25
- </a>
26
- </div>
27
-
28
- <a
29
- href="#"
30
- class="wl-login-nick"
31
- aria-label="Profile"
32
- :title="locale.profile"
33
- @click="onProfile"
34
- v-text="userInfo.display_name"
35
- />
36
- </div>
37
-
38
- <div class="wl-panel">
39
- <div
40
- v-if="config.login !== 'force' && config.meta.length && !isLogin"
41
- class="wl-header"
42
- :class="`item${config.meta.length}`"
43
- >
44
- <div v-for="kind in config.meta" :key="kind" class="wl-header-item">
45
- <label
46
- :for="`wl-${kind}`"
47
- v-text="
48
- locale[kind] +
49
- (config.requiredMeta.includes(kind) || !config.requiredMeta.length
50
- ? ''
51
- : `(${locale.optional})`)
52
- "
53
- />
54
-
55
- <input
56
- :id="`wl-${kind}`"
57
- :ref="
58
- (element) => {
59
- if (element) inputRefs[kind] = element as HTMLInputElement;
60
- }
61
- "
62
- v-model="userMeta[kind]"
63
- class="wl-input"
64
- :class="`wl-${kind}`"
65
- :name="kind"
66
- :type="kind === 'mail' ? 'email' : 'text'"
67
- />
68
- </div>
69
- </div>
70
-
71
- <textarea
72
- id="wl-edit"
73
- ref="editorRef"
74
- v-model="editor"
75
- class="wl-editor"
76
- :placeholder="replyUser ? `@${replyUser}` : locale.placeholder"
77
- @keydown="onKeyDown"
78
- @drop="onDrop"
79
- @paste="onPaste"
80
- />
81
-
82
- <div v-show="showPreview" class="wl-preview">
83
- <hr />
84
-
85
- <h4>{{ locale.preview }}:</h4>
86
- <!-- eslint-disable-next-line vue/no-v-html -->
87
- <div class="wl-content" v-html="previewText" />
88
- </div>
89
-
90
- <div class="wl-footer">
91
- <div class="wl-actions">
92
- <a
93
- href="https://guides.github.com/features/mastering-markdown/"
94
- title="Markdown Guide"
95
- aria-label="Markdown is supported"
96
- class="wl-action"
97
- target="_blank"
98
- rel="noopener noreferrer"
99
- >
100
- <MarkdownIcon />
101
- </a>
102
-
103
- <button
104
- v-show="emoji.tabs.length"
105
- ref="emojiButtonRef"
106
- type="button"
107
- class="wl-action"
108
- :class="{ active: showEmoji }"
109
- :title="locale.emoji"
110
- @click="showEmoji = !showEmoji"
111
- >
112
- <EmojiIcon />
113
- </button>
114
-
115
- <button
116
- v-if="config.search"
117
- ref="gifButtonRef"
118
- type="button"
119
- class="wl-action"
120
- :class="{ active: showGif }"
121
- :title="locale.gif"
122
- @click="showGif = !showGif"
123
- >
124
- <GifIcon />
125
- </button>
126
-
127
- <input
128
- id="wl-image-upload"
129
- ref="imageUploadRef"
130
- class="upload"
131
- type="file"
132
- accept=".png,.jpg,.jpeg,.webp,.bmp,.gif"
133
- @change="onChange"
134
- />
135
-
136
- <label
137
- v-if="canUploadImage"
138
- for="wl-image-upload"
139
- class="wl-action"
140
- :title="locale.uploadImage"
141
- >
142
- <ImageIcon />
143
- </label>
144
-
145
- <button
146
- type="button"
147
- class="wl-action"
148
- :class="{ active: showPreview }"
149
- :title="locale.preview"
150
- @click="showPreview = !showPreview"
151
- >
152
- <PreviewIcon />
153
- </button>
154
- </div>
155
-
156
- <div class="wl-info">
157
- <div class="wl-captcha-container"></div>
158
-
159
- <div class="wl-text-number">
160
- {{ wordNumber }}
161
-
162
- <span v-if="config.wordLimit">
163
- &nbsp;/&nbsp;
164
- <span
165
- :class="{ illegal: !isWordNumberLegal }"
166
- v-text="wordLimit"
167
- />
168
- </span>
169
-
170
- &nbsp;{{ locale.word }}
171
- </div>
172
-
173
- <button
174
- v-if="config.login !== 'disable' && !isLogin"
175
- type="button"
176
- class="wl-btn"
177
- @click="onLogin"
178
- v-text="locale.login"
179
- />
180
-
181
- <button
182
- v-if="config.login !== 'force' || isLogin"
183
- type="submit"
184
- class="primary wl-btn"
185
- title="Cmd|Ctrl + Enter"
186
- :disabled="isSubmitting"
187
- @click="submitComment"
188
- >
189
- <LoadingIcon v-if="isSubmitting" :size="16" />
190
-
191
- <template v-else>
192
- {{ locale.submit }}
193
- </template>
194
- </button>
195
- </div>
196
-
197
- <div
198
- ref="gifPopupRef"
199
- class="wl-gif-popup"
200
- :class="{ display: showGif }"
201
- >
202
- <input
203
- ref="gifSearchInputRef"
204
- type="text"
205
- :placeholder="locale.gifSearchPlaceholder"
206
- @input="onGifSearch"
207
- />
208
-
209
- <ImageWall
210
- v-if="searchResults.list.length"
211
- :items="searchResults.list"
212
- :column-width="200"
213
- :gap="6"
214
- @insert="insert($event)"
215
- @scroll="onImageWallScroll"
216
- >
217
- </ImageWall>
218
-
219
- <div v-if="searchResults.loading" class="wl-loading">
220
- <LoadingIcon :size="30" />
221
- </div>
222
- </div>
223
-
224
- <div
225
- ref="emojiPopupRef"
226
- class="wl-emoji-popup"
227
- :class="{ display: showEmoji }"
228
- >
229
- <template
230
- v-for="(emojiItem, index) in emoji.tabs"
231
- :key="emojiItem.name"
232
- >
233
- <div v-if="index === emojiTabIndex" class="wl-tab-wrapper">
234
- <button
235
- v-for="key in emojiItem.items"
236
- :key="key"
237
- type="button"
238
- :title="key"
239
- @click="insert(`:${key}:`)"
240
- >
241
- <img
242
- v-if="showEmoji"
243
- class="wl-emoji"
244
- :src="emoji.map[key]"
245
- :alt="key"
246
- loading="lazy"
247
- referrerPolicy="no-referrer"
248
- />
249
- </button>
250
- </div>
251
- </template>
252
-
253
- <div v-if="emoji.tabs.length > 1" class="wl-tabs">
254
- <button
255
- v-for="(emojiItem, index) in emoji.tabs"
256
- :key="emojiItem.name"
257
- type="button"
258
- class="wl-tab"
259
- :class="{ active: emojiTabIndex === index }"
260
- @click="emojiTabIndex = index"
261
- >
262
- <img
263
- class="wl-emoji"
264
- :src="emojiItem.icon"
265
- :alt="emojiItem.name"
266
- :title="emojiItem.name"
267
- loading="lazy"
268
- referrerPolicy="no-referrer"
269
- />
270
- </button>
271
- </div>
272
- </div>
273
- </div>
274
- </div>
275
-
276
- <button
277
- v-if="replyId || edit?.objectId"
278
- type="button"
279
- class="wl-close"
280
- :title="locale.cancelReply"
281
- @click="$emit(replyId ? 'cancelReply' : 'cancelEdit')"
282
- >
283
- <CloseIcon :size="24" />
284
- </button>
285
- </div>
286
- </template>
287
-
288
- <script setup lang="ts">
289
- import { useDebounceFn } from '@vueuse/core';
290
- import autosize from 'autosize';
291
- import {
292
- type ComputedRef,
293
- type DeepReadonly,
294
- computed,
295
- inject,
296
- onMounted,
297
- onUnmounted,
298
- reactive,
299
- ref,
300
- watch,
301
- } from 'vue';
302
-
303
- import {
304
- CloseIcon,
305
- EmojiIcon,
306
- GifIcon,
307
- ImageIcon,
308
- LoadingIcon,
309
- MarkdownIcon,
310
- PreviewIcon,
311
- } from './Icons.js';
312
- import ImageWall from './ImageWall.vue';
313
- import { addComment, login, updateComment, UserInfo } from '../api/index.js';
314
- import {
315
- useEditor,
316
- useReCaptcha,
317
- useTurnstile,
318
- useUserInfo,
319
- useUserMeta,
320
- } from '../composables/index.js';
321
- import {
322
- type WalineComment,
323
- type WalineCommentData,
324
- type WalineImageUploader,
325
- type WalineSearchOptions,
326
- type WalineSearchResult,
327
- } from '../typings/index.js';
328
- import {
329
- type WalineConfig,
330
- type WalineEmojiConfig,
331
- getEmojis,
332
- getImageFromDataTransfer,
333
- isValidEmail,
334
- getWordNumber,
335
- parseEmoji,
336
- parseMarkdown,
337
- userAgent,
338
- } from '../utils/index.js';
339
-
340
- const props = withDefaults(
341
- defineProps<{
342
- /**
343
- * Current comment to be edited
344
- */
345
- edit?: WalineComment | null;
346
- /**
347
- * Root comment id
348
- */
349
- rootId?: string;
350
- /**
351
- * Comment id to be replied
352
- */
353
- replyId?: string;
354
- /**
355
- * User name to be replied
356
- */
357
- replyUser?: string;
358
- }>(),
359
- {
360
- edit: null,
361
- rootId: '',
362
- replyId: '',
363
- replyUser: '',
364
- },
365
- );
366
-
367
- const emit = defineEmits<{
368
- (event: 'log'): void;
369
- (event: 'cancelEdit'): void;
370
- (event: 'cancelReply'): void;
371
- (event: 'submit', comment: WalineComment): void;
372
- }>();
373
-
374
- defineExpose();
375
-
376
- const config = inject<ComputedRef<WalineConfig>>('config')!;
377
-
378
- const editor = useEditor();
379
- const userMeta = useUserMeta();
380
- const userInfo = useUserInfo();
381
-
382
- const inputRefs = ref<Record<string, HTMLInputElement>>({});
383
- const editorRef = ref<HTMLTextAreaElement | null>(null);
384
- const imageUploadRef = ref<HTMLInputElement | null>(null);
385
- const emojiButtonRef = ref<HTMLDivElement | null>(null);
386
- const emojiPopupRef = ref<HTMLDivElement | null>(null);
387
- const gifButtonRef = ref<HTMLDivElement | null>(null);
388
- const gifPopupRef = ref<HTMLDivElement | null>(null);
389
- const gifSearchInputRef = ref<HTMLInputElement | null>(null);
390
-
391
- const emoji = ref<DeepReadonly<WalineEmojiConfig>>({ tabs: [], map: {} });
392
- const emojiTabIndex = ref(0);
393
- const showEmoji = ref(false);
394
- const showGif = ref(false);
395
- const showPreview = ref(false);
396
- const previewText = ref('');
397
- const wordNumber = ref(0);
398
-
399
- const searchResults = reactive({
400
- loading: true,
401
- list: [] as WalineSearchResult,
402
- });
403
-
404
- const wordLimit = ref(0);
405
- const isWordNumberLegal = ref(false);
406
-
407
- const content = ref('');
408
-
409
- const isSubmitting = ref(false);
410
-
411
- const isImageListEnd = ref(false);
412
-
413
- const locale = computed(() => config.value.locale);
414
-
415
- const isLogin = computed(() => Boolean(userInfo.value?.token));
416
-
417
- const canUploadImage = computed(() => config.value.imageUploader !== false);
418
-
419
- const insert = (content: string): void => {
420
- const textArea = editorRef.value!;
421
- const startPosition = textArea.selectionStart;
422
- const endPosition = textArea.selectionEnd || 0;
423
- const scrollTop = textArea.scrollTop;
424
-
425
- editor.value =
426
- textArea.value.substring(0, startPosition) +
427
- content +
428
- textArea.value.substring(endPosition, textArea.value.length);
429
- textArea.focus();
430
- textArea.selectionStart = startPosition + content.length;
431
- textArea.selectionEnd = startPosition + content.length;
432
- textArea.scrollTop = scrollTop;
433
- };
434
-
435
- const onKeyDown = (event: KeyboardEvent): void => {
436
- const key = event.key;
437
-
438
- // Shortcut key
439
- if ((event.ctrlKey || event.metaKey) && key === 'Enter') void submitComment();
440
- };
441
-
442
- const uploadImage = (file: File): Promise<void> => {
443
- const uploadText = `![${config.value.locale.uploading} ${file.name}]()`;
444
-
445
- insert(uploadText);
446
- isSubmitting.value = true;
447
-
448
- return Promise.resolve()
449
- .then(() => (config.value.imageUploader as WalineImageUploader)(file))
450
- .then((url) => {
451
- editor.value = editor.value.replace(
452
- uploadText,
453
- `\r\n![${file.name}](${url})`,
454
- );
455
- })
456
- .catch((err: Error) => {
457
- alert(err.message);
458
- editor.value = editor.value.replace(uploadText, '');
459
- })
460
- .then(() => {
461
- isSubmitting.value = false;
462
- });
463
- };
464
-
465
- const onDrop = (event: DragEvent): void => {
466
- if (event.dataTransfer?.items) {
467
- const file = getImageFromDataTransfer(event.dataTransfer.items);
468
-
469
- if (file && canUploadImage.value) {
470
- void uploadImage(file);
471
- event.preventDefault();
472
- }
473
- }
474
- };
475
-
476
- const onPaste = (event: ClipboardEvent): void => {
477
- if (event.clipboardData) {
478
- const file = getImageFromDataTransfer(event.clipboardData.items);
479
-
480
- if (file && canUploadImage.value) void uploadImage(file);
481
- }
482
- };
483
-
484
- const onChange = (): void => {
485
- const inputElement = imageUploadRef.value!;
486
-
487
- if (inputElement.files && canUploadImage.value)
488
- void uploadImage(inputElement.files[0]).then(() => {
489
- // clear input so a same image can be uploaded later
490
- inputElement.value = '';
491
- });
492
- };
493
-
494
- const submitComment = async (): Promise<void> => {
495
- const {
496
- serverURL,
497
- lang,
498
- login,
499
- wordLimit,
500
- requiredMeta,
501
- recaptchaV3Key,
502
- turnstileKey,
503
- } = config.value;
504
-
505
- const ua = await userAgent();
506
- const comment: WalineCommentData = {
507
- comment: content.value,
508
- nick: userMeta.value.nick,
509
- mail: userMeta.value.mail,
510
- link: userMeta.value.link,
511
- url: config.value.path,
512
- ua,
513
- };
514
-
515
- if (userInfo.value?.token) {
516
- // login user
517
-
518
- comment.nick = userInfo.value.display_name;
519
- comment.mail = userInfo.value.email;
520
- comment.link = userInfo.value.url;
521
- } else {
522
- if (login === 'force') return;
523
-
524
- // check nick
525
- if (requiredMeta.indexOf('nick') > -1 && !comment.nick) {
526
- inputRefs.value.nick?.focus();
527
-
528
- return alert(locale.value.nickError);
529
- }
530
-
531
- // check mail
532
- if (
533
- (requiredMeta.indexOf('mail') > -1 && !comment.mail) ||
534
- (comment.mail && !isValidEmail(comment.mail))
535
- ) {
536
- inputRefs.value.mail?.focus();
537
-
538
- return alert(locale.value.mailError);
539
- }
540
-
541
- if (!comment.nick) comment.nick = locale.value.anonymous;
542
- }
543
-
544
- // check comment
545
- if (!comment.comment) {
546
- editorRef.value?.focus();
547
-
548
- return;
549
- }
550
-
551
- if (!isWordNumberLegal.value)
552
- return alert(
553
- locale.value.wordHint
554
- .replace('$0', (wordLimit as [number, number])[0].toString())
555
- .replace('$1', (wordLimit as [number, number])[1].toString())
556
- .replace('$2', wordNumber.value.toString()),
557
- );
558
-
559
- comment.comment = parseEmoji(comment.comment, emoji.value.map);
560
-
561
- if (props.replyId && props.rootId) {
562
- comment.pid = props.replyId;
563
- comment.rid = props.rootId;
564
- comment.at = props.replyUser;
565
- }
566
-
567
- isSubmitting.value = true;
568
-
569
- try {
570
- if (recaptchaV3Key)
571
- comment.recaptchaV3 =
572
- await useReCaptcha(recaptchaV3Key).execute('social');
573
-
574
- if (turnstileKey)
575
- comment.turnstile = await useTurnstile(turnstileKey).execute('social');
576
-
577
- const options = {
578
- serverURL,
579
- lang,
580
- token: userInfo.value?.token,
581
- comment,
582
- };
583
-
584
- const resp = await (props.edit
585
- ? updateComment({
586
- objectId: props.edit.objectId,
587
- ...options,
588
- })
589
- : addComment(options));
590
-
591
- isSubmitting.value = false;
592
-
593
- if (resp.errmsg) return alert(resp.errmsg);
594
-
595
- emit('submit', resp.data!);
596
-
597
- editor.value = '';
598
-
599
- previewText.value = '';
600
-
601
- if (props.replyId) emit('cancelReply');
602
- if (props.edit?.objectId) emit('cancelEdit');
603
- } catch (err: unknown) {
604
- isSubmitting.value = false;
605
-
606
- alert((err as TypeError).message);
607
- }
608
- };
609
-
610
- const onLogin = (event: Event): void => {
611
- event.preventDefault();
612
- const { lang, serverURL } = config.value;
613
-
614
- void login({
615
- serverURL,
616
- lang,
617
- }).then((data) => {
618
- userInfo.value = data;
619
- (data.remember ? localStorage : sessionStorage).setItem(
620
- 'WALINE_USER',
621
- JSON.stringify(data),
622
- );
623
- emit('log');
624
- });
625
- };
626
-
627
- const onLogout = (): void => {
628
- userInfo.value = {};
629
- localStorage.setItem('WALINE_USER', 'null');
630
- sessionStorage.setItem('WALINE_USER', 'null');
631
- emit('log');
632
- };
633
-
634
- const onProfile = (event: Event): void => {
635
- event.preventDefault();
636
-
637
- const { lang, serverURL } = config.value;
638
-
639
- const width = 800;
640
- const height = 800;
641
- const left = (window.innerWidth - width) / 2;
642
- const top = (window.innerHeight - height) / 2;
643
- const query = new URLSearchParams({
644
- lng: lang,
645
- token: userInfo.value.token,
646
- });
647
- const handler = window.open(
648
- `${serverURL}/ui/profile?${query.toString()}`,
649
- '_blank',
650
- `width=${width},height=${height},left=${left},top=${top},scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no`,
651
- );
652
-
653
- handler?.postMessage({ type: 'TOKEN', data: userInfo.value.token }, '*');
654
- };
655
-
656
- const popupHandler = (event: MouseEvent): void => {
657
- if (
658
- !emojiButtonRef.value?.contains(event.target as Node) &&
659
- !emojiPopupRef.value?.contains(event.target as Node)
660
- )
661
- showEmoji.value = false;
662
-
663
- if (
664
- !gifButtonRef.value?.contains(event.target as Node) &&
665
- !gifPopupRef.value?.contains(event.target as Node)
666
- )
667
- showGif.value = false;
668
- };
669
-
670
- const onImageWallScroll = async (event: Event): Promise<void> => {
671
- const { scrollTop, clientHeight, scrollHeight } =
672
- event.target as HTMLDivElement;
673
- const percent = (clientHeight + scrollTop) / scrollHeight;
674
- const searchOptions = config.value.search as WalineSearchOptions;
675
- const keyword = gifSearchInputRef.value?.value || '';
676
-
677
- if (percent < 0.9 || searchResults.loading || isImageListEnd.value) return;
678
-
679
- searchResults.loading = true;
680
-
681
- const searchResult =
682
- searchOptions.more && searchResults.list.length
683
- ? await searchOptions.more(keyword, searchResults.list.length)
684
- : await searchOptions.search(keyword);
685
-
686
- if (searchResult.length)
687
- searchResults.list = [
688
- ...searchResults.list,
689
- ...(searchOptions.more && searchResults.list.length
690
- ? await searchOptions.more(keyword, searchResults.list.length)
691
- : await searchOptions.search(keyword)),
692
- ];
693
- else isImageListEnd.value = true;
694
-
695
- searchResults.loading = false;
696
-
697
- setTimeout(() => {
698
- (event.target as HTMLDivElement).scrollTop = scrollTop;
699
- }, 50);
700
- };
701
-
702
- const onGifSearch = useDebounceFn((event: Event) => {
703
- searchResults.list = [];
704
- isImageListEnd.value = false;
705
- void onImageWallScroll(event);
706
- }, 300);
707
-
708
- // update wordNumber
709
- watch(
710
- [config, wordNumber],
711
- ([config, wordNumber]) => {
712
- const { wordLimit: limit } = config;
713
-
714
- if (limit) {
715
- if (wordNumber < limit[0] && limit[0] !== 0) {
716
- wordLimit.value = limit[0];
717
- isWordNumberLegal.value = false;
718
- } else if (wordNumber > limit[1]) {
719
- wordLimit.value = limit[1];
720
- isWordNumberLegal.value = false;
721
- } else {
722
- wordLimit.value = limit[1];
723
- isWordNumberLegal.value = true;
724
- }
725
- } else {
726
- wordLimit.value = 0;
727
- isWordNumberLegal.value = true;
728
- }
729
- },
730
- { immediate: true },
731
- );
732
-
733
- const onMessageReceive = ({
734
- data,
735
- }: {
736
- data: { type: 'profile'; data: UserInfo };
737
- }): void => {
738
- if (!data || data.type !== 'profile') return;
739
-
740
- userInfo.value = { ...userInfo.value, ...data.data };
741
-
742
- [localStorage, sessionStorage]
743
- .filter((store) => store.getItem('WALINE_USER'))
744
- .forEach((store) => store.setItem('WALINE_USER', JSON.stringify(userInfo)));
745
- };
746
-
747
- onMounted(() => {
748
- document.body.addEventListener('click', popupHandler);
749
- window.addEventListener('message', onMessageReceive);
750
- if (props.edit?.objectId) {
751
- editor.value = props.edit.orig;
752
- }
753
-
754
- // watch gif
755
- watch(showGif, async (showGif) => {
756
- if (!showGif) return;
757
-
758
- const searchOptions = config.value.search as WalineSearchOptions;
759
-
760
- // clear input
761
- if (gifSearchInputRef.value) gifSearchInputRef.value.value = '';
762
-
763
- searchResults.loading = true;
764
-
765
- searchResults.list = searchOptions.default
766
- ? await searchOptions.default()
767
- : await searchOptions.search('');
768
-
769
- searchResults.loading = false;
770
- });
771
-
772
- // watch editor
773
- watch(
774
- () => editor.value,
775
- (value) => {
776
- const { highlighter, texRenderer } = config.value;
777
-
778
- content.value = value;
779
- previewText.value = parseMarkdown(value, {
780
- emojiMap: emoji.value.map,
781
- highlighter,
782
- texRenderer,
783
- });
784
- wordNumber.value = getWordNumber(value);
785
-
786
- if (value) autosize(editorRef.value!);
787
- else autosize.destroy(editorRef.value!);
788
- },
789
- { immediate: true },
790
- );
791
-
792
- // watch emoji value change
793
- watch(
794
- () => config.value.emoji,
795
- (emojiConfig) =>
796
- getEmojis(emojiConfig).then((config) => {
797
- emoji.value = config;
798
- }),
799
- { immediate: true },
800
- );
801
- });
802
-
803
- onUnmounted(() => {
804
- document.body.removeEventListener('click', popupHandler);
805
- window.removeEventListener('message', onMessageReceive);
806
- });
807
- </script>