@waline/client 2.15.8 → 3.0.0-alpha.2

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 (139) hide show
  1. package/dist/comment.cjs +1 -1
  2. package/dist/comment.cjs.map +1 -1
  3. package/dist/comment.d.cts +2 -1
  4. package/dist/comment.d.mts +2 -1
  5. package/dist/comment.d.ts +2 -1
  6. package/dist/comment.js +1 -1
  7. package/dist/comment.js.map +1 -1
  8. package/dist/comment.mjs +1 -1
  9. package/dist/comment.mjs.map +1 -1
  10. package/dist/component.mjs +8 -1
  11. package/dist/component.mjs.map +1 -1
  12. package/dist/pageview.cjs +1 -1
  13. package/dist/pageview.cjs.map +1 -1
  14. package/dist/pageview.d.cts +2 -1
  15. package/dist/pageview.d.mts +2 -1
  16. package/dist/pageview.d.ts +2 -1
  17. package/dist/pageview.js +1 -1
  18. package/dist/pageview.js.map +1 -1
  19. package/dist/pageview.mjs +1 -1
  20. package/dist/pageview.mjs.map +1 -1
  21. package/dist/shim.cjs +8 -1
  22. package/dist/shim.cjs.map +1 -1
  23. package/dist/shim.d.cts +7 -82
  24. package/dist/shim.d.mts +7 -82
  25. package/dist/shim.mjs +8 -1
  26. package/dist/shim.mjs.map +1 -1
  27. package/dist/waline.cjs +53 -1
  28. package/dist/waline.cjs.map +1 -1
  29. package/dist/waline.css +1 -1
  30. package/dist/waline.css.map +1 -1
  31. package/dist/waline.d.cts +7 -82
  32. package/dist/waline.d.mts +7 -82
  33. package/dist/waline.d.ts +7 -82
  34. package/dist/waline.js +53 -1
  35. package/dist/waline.js.map +1 -1
  36. package/dist/waline.mjs +53 -1
  37. package/dist/waline.mjs.map +1 -1
  38. package/package.json +20 -53
  39. package/dist/api.cjs +0 -2
  40. package/dist/api.cjs.map +0 -1
  41. package/dist/api.d.cts +0 -445
  42. package/dist/api.d.mts +0 -445
  43. package/dist/api.d.ts +0 -445
  44. package/dist/api.mjs +0 -2
  45. package/dist/api.mjs.map +0 -1
  46. package/dist/legacy.umd.d.ts +0 -628
  47. package/dist/legacy.umd.js +0 -2
  48. package/dist/legacy.umd.js.map +0 -1
  49. package/src/api/articleCounter.ts +0 -82
  50. package/src/api/comment.ts +0 -258
  51. package/src/api/commentCount.ts +0 -33
  52. package/src/api/index.ts +0 -7
  53. package/src/api/login.ts +0 -96
  54. package/src/api/pageview.ts +0 -54
  55. package/src/api/recentComment.ts +0 -42
  56. package/src/api/user.ts +0 -44
  57. package/src/api/utils.ts +0 -46
  58. package/src/comment.ts +0 -69
  59. package/src/compact/convert.ts +0 -80
  60. package/src/compact/dropped.ts +0 -35
  61. package/src/compact/index.ts +0 -4
  62. package/src/compact/logger.ts +0 -5
  63. package/src/compact/v1.ts +0 -103
  64. package/src/compact/valine.ts +0 -132
  65. package/src/components/ArticleReaction.vue +0 -159
  66. package/src/components/CommentBox.vue +0 -807
  67. package/src/components/CommentCard.vue +0 -271
  68. package/src/components/Icons.ts +0 -181
  69. package/src/components/ImageWall.vue +0 -169
  70. package/src/components/WalineComment.vue +0 -353
  71. package/src/composables/index.ts +0 -6
  72. package/src/composables/inputs.ts +0 -17
  73. package/src/composables/like.ts +0 -13
  74. package/src/composables/reaction.ts +0 -15
  75. package/src/composables/recaptchaV3.ts +0 -19
  76. package/src/composables/turnstile.ts +0 -50
  77. package/src/composables/userInfo.ts +0 -16
  78. package/src/config/default.ts +0 -93
  79. package/src/config/highlighter.ts +0 -74
  80. package/src/config/i18n/en.ts +0 -53
  81. package/src/config/i18n/generate.ts +0 -58
  82. package/src/config/i18n/index.ts +0 -30
  83. package/src/config/i18n/jp.ts +0 -53
  84. package/src/config/i18n/pt-BR.ts +0 -53
  85. package/src/config/i18n/ru.ts +0 -53
  86. package/src/config/i18n/vi-VN.ts +0 -53
  87. package/src/config/i18n/zh-CN.ts +0 -53
  88. package/src/config/i18n/zh-TW.ts +0 -53
  89. package/src/config/index.ts +0 -3
  90. package/src/entries/api.ts +0 -1
  91. package/src/entries/comment.ts +0 -2
  92. package/src/entries/components.ts +0 -2
  93. package/src/entries/full.ts +0 -7
  94. package/src/entries/init.ts +0 -4
  95. package/src/entries/legacy.ts +0 -31
  96. package/src/entries/pageview.ts +0 -2
  97. package/src/init.ts +0 -112
  98. package/src/pageview.ts +0 -116
  99. package/src/shims-vue.d.ts +0 -6
  100. package/src/styles/base.scss +0 -67
  101. package/src/styles/card.scss +0 -258
  102. package/src/styles/config.scss +0 -52
  103. package/src/styles/emoji.scss +0 -137
  104. package/src/styles/gif.scss +0 -73
  105. package/src/styles/helpers/_svg.scss +0 -51
  106. package/src/styles/highlight.scss +0 -138
  107. package/src/styles/index.scss +0 -12
  108. package/src/styles/layout.scss +0 -105
  109. package/src/styles/meta.scss +0 -82
  110. package/src/styles/normalize.scss +0 -117
  111. package/src/styles/panel.scss +0 -286
  112. package/src/styles/reaction.scss +0 -103
  113. package/src/styles/recent.scss +0 -3
  114. package/src/styles/user-list.scss +0 -159
  115. package/src/typings/base.ts +0 -123
  116. package/src/typings/comment.ts +0 -94
  117. package/src/typings/index.ts +0 -5
  118. package/src/typings/locale.ts +0 -73
  119. package/src/typings/options.ts +0 -44
  120. package/src/typings/waline.ts +0 -251
  121. package/src/utils/config.ts +0 -122
  122. package/src/utils/darkmode.ts +0 -11
  123. package/src/utils/date.ts +0 -71
  124. package/src/utils/email.ts +0 -8
  125. package/src/utils/emoji.ts +0 -73
  126. package/src/utils/error.ts +0 -3
  127. package/src/utils/getRoot.ts +0 -8
  128. package/src/utils/image.ts +0 -10
  129. package/src/utils/index.ts +0 -13
  130. package/src/utils/markdown.ts +0 -41
  131. package/src/utils/markedMathExtension.ts +0 -54
  132. package/src/utils/path.ts +0 -15
  133. package/src/utils/query.ts +0 -2
  134. package/src/utils/userAgent.ts +0 -31
  135. package/src/utils/wordCount.ts +0 -31
  136. package/src/version.ts +0 -3
  137. package/src/widgets/index.ts +0 -2
  138. package/src/widgets/recentComments.ts +0 -93
  139. 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>