@waline/client 1.6.0 → 2.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/component.js +2 -0
  2. package/dist/component.js.map +1 -0
  3. package/dist/pageview.cjs.js +2 -0
  4. package/dist/pageview.cjs.js.map +1 -0
  5. package/dist/pageview.d.ts +33 -0
  6. package/dist/pageview.esm.js +2 -0
  7. package/dist/pageview.esm.js.map +1 -0
  8. package/dist/pageview.js +2 -0
  9. package/dist/pageview.js.map +1 -0
  10. package/dist/{Waline.min.d.ts → shim.d.ts} +192 -261
  11. package/dist/{Waline.noStyle.d.ts → shim.esm.d.ts} +192 -261
  12. package/dist/shim.esm.js +2 -0
  13. package/dist/shim.esm.js.map +1 -0
  14. package/dist/shim.js +2 -0
  15. package/dist/shim.js.map +1 -0
  16. package/dist/waline.cjs.d.ts +388 -0
  17. package/dist/waline.cjs.js +2 -0
  18. package/dist/waline.cjs.js.map +1 -0
  19. package/dist/waline.css +1 -0
  20. package/dist/waline.css.map +1 -0
  21. package/dist/waline.d.ts +388 -0
  22. package/dist/waline.esm.d.ts +388 -0
  23. package/dist/waline.esm.js +2 -0
  24. package/dist/waline.esm.js.map +1 -0
  25. package/dist/waline.js +2 -0
  26. package/dist/waline.js.map +1 -0
  27. package/package.json +33 -18
  28. package/src/comment.ts +39 -0
  29. package/src/components/CommentBox.vue +667 -0
  30. package/src/components/CommentCard.vue +125 -0
  31. package/src/components/Icons.ts +124 -0
  32. package/src/components/Waline.vue +359 -0
  33. package/src/composables/index.ts +3 -0
  34. package/src/composables/inputs.ts +29 -0
  35. package/src/composables/store.ts +38 -0
  36. package/src/composables/userInfo.ts +27 -0
  37. package/src/config/default.ts +21 -0
  38. package/src/config/i18n/en.ts +34 -0
  39. package/src/config/i18n/generate.ts +39 -0
  40. package/src/config/i18n/index.ts +30 -0
  41. package/src/config/i18n/jp.ts +34 -0
  42. package/src/config/i18n/pt-BR.ts +34 -0
  43. package/src/config/i18n/ru.ts +34 -0
  44. package/src/config/i18n/vi-VN.ts +34 -0
  45. package/src/config/i18n/zh-CN.ts +34 -0
  46. package/src/config/i18n/zh-TW.ts +34 -0
  47. package/src/config/index.ts +2 -0
  48. package/src/entrys/components.ts +2 -0
  49. package/src/entrys/full.ts +7 -0
  50. package/src/entrys/init.ts +4 -0
  51. package/src/entrys/pageview.ts +2 -0
  52. package/src/init.ts +92 -0
  53. package/src/pageview.ts +100 -0
  54. package/src/shims-hanabi.d.ts +9 -0
  55. package/src/shims-vue.d.ts +5 -0
  56. package/src/styles/base.scss +67 -0
  57. package/src/styles/card.scss +223 -0
  58. package/src/styles/config.scss +52 -0
  59. package/src/styles/emoji.scss +118 -0
  60. package/src/styles/highlight.scss +135 -0
  61. package/src/styles/index.scss +12 -0
  62. package/src/styles/layout.scss +78 -0
  63. package/src/styles/nomalize.scss +112 -0
  64. package/src/styles/panel.scss +293 -0
  65. package/src/styles/recent.scss +3 -0
  66. package/src/typings/base.ts +54 -0
  67. package/src/typings/comment.ts +25 -0
  68. package/src/typings/index.ts +5 -0
  69. package/src/typings/locale.ts +32 -0
  70. package/src/typings/options.ts +41 -0
  71. package/src/typings/waline.ts +208 -0
  72. package/src/utils/config.ts +99 -0
  73. package/src/utils/darkmode.ts +11 -0
  74. package/src/utils/data.ts +10 -0
  75. package/src/utils/emoji.ts +75 -0
  76. package/src/utils/error.ts +3 -0
  77. package/src/utils/fetch.ts +177 -0
  78. package/src/utils/getRoot.ts +8 -0
  79. package/src/utils/index.ts +13 -0
  80. package/src/utils/markdown.ts +41 -0
  81. package/src/utils/markedMathExtension.ts +52 -0
  82. package/src/utils/path.ts +15 -0
  83. package/src/utils/query.ts +2 -0
  84. package/src/utils/timeAgo.ts +75 -0
  85. package/src/utils/userInfo.ts +26 -0
  86. package/src/utils/wordCount.ts +20 -0
  87. package/src/version.ts +3 -0
  88. package/src/widgets/index.ts +1 -0
  89. package/src/widgets/recentComments.ts +52 -0
  90. package/dist/Waline.min.js +0 -2
  91. package/dist/Waline.min.js.map +0 -1
  92. package/dist/Waline.noStyle.js +0 -2
  93. package/dist/Waline.noStyle.js.map +0 -1
  94. package/dist/index.html +0 -11
@@ -0,0 +1,667 @@
1
+ <template>
2
+ <div class="wl-comment">
3
+ <div v-if="config.login !== 'disable' && isLogin" class="wl-login-info">
4
+ <div class="wl-avatar">
5
+ <button class="wl-logout-btn" :title="locale.logout" @click="onLogout">
6
+ <CloseIcon :size="14" />
7
+ </button>
8
+
9
+ <img :src="userInfo.avatar" alt="avatar" />
10
+ </div>
11
+ <a
12
+ href="#"
13
+ class="wl-login-nick"
14
+ aria-label="Profile"
15
+ @click="onProfile"
16
+ v-text="userInfo.display_name"
17
+ />
18
+ </div>
19
+
20
+ <div class="wl-panel">
21
+ <div
22
+ v-if="config.login !== 'force' && config.meta.length && !isLogin"
23
+ :class="['wl-header', `item${config.meta.length}`]"
24
+ >
25
+ <div v-for="kind in config.meta" class="wl-header-item" :key="kind">
26
+ <label
27
+ :for="kind"
28
+ v-text="
29
+ locale[kind] +
30
+ (config.requiredMeta.includes(kind) || !config.requiredMeta.length
31
+ ? ''
32
+ : `(${locale.optional})`)
33
+ "
34
+ />
35
+ <input
36
+ :ref="
37
+ (element) => {
38
+ if (element) inputRefs[kind] = element;
39
+ }
40
+ "
41
+ :id="`wl-${kind}`"
42
+ :class="['wl-input', `wl-${kind}`]"
43
+ :name="kind"
44
+ :type="kind === 'mail' ? 'email' : 'text'"
45
+ v-model="inputs[kind]"
46
+ />
47
+ </div>
48
+ </div>
49
+
50
+ <textarea
51
+ class="wl-editor"
52
+ ref="editorRef"
53
+ id="wl-edit"
54
+ :placeholder="replyUser ? `@${replyUser}` : locale.placeholder"
55
+ v-model="inputs.editor"
56
+ @keydown="onKeyDown"
57
+ @drop="onDrop"
58
+ @paste="onPaste"
59
+ />
60
+
61
+ <div
62
+ class="wl-preview"
63
+ :style="{ display: showPreview ? 'block' : 'none' }"
64
+ >
65
+ <h4>{{ locale.preview }}:</h4>
66
+ <div class="wl-content" v-html="previewText" />
67
+ </div>
68
+
69
+ <div class="wl-footer">
70
+ <div class="wl-actions">
71
+ <a
72
+ href="https://guides.github.com/features/mastering-markdown/"
73
+ title="Markdown Guide"
74
+ aria-label="Markdown is supported"
75
+ class="wl-action"
76
+ target="_blank"
77
+ rel="noreferrer"
78
+ >
79
+ <MarkdownIcon />
80
+ </a>
81
+
82
+ <button
83
+ ref="emojiButtonRef"
84
+ class="wl-action"
85
+ :class="{ actived: showEmoji }"
86
+ :title="locale.emoji"
87
+ @click="showEmoji = !showEmoji"
88
+ >
89
+ <EmojiIcon />
90
+ </button>
91
+
92
+ <input
93
+ ref="imageUploadRef"
94
+ class="upload"
95
+ id="wl-image-upload"
96
+ type="file"
97
+ accept=".png,.jpg,.jpeg,.webp,.bmp,.gif"
98
+ @change="onChange"
99
+ />
100
+
101
+ <label
102
+ v-if="canUploadImage"
103
+ for="wl-image-upload"
104
+ class="wl-action"
105
+ :title="locale.uploadImage"
106
+ >
107
+ <ImageIcon />
108
+ </label>
109
+
110
+ <button
111
+ class="wl-action"
112
+ :class="{ actived: showPreview }"
113
+ :title="locale.preview"
114
+ @click="showPreview = !showPreview"
115
+ >
116
+ <PreviewIcon />
117
+ </button>
118
+ </div>
119
+
120
+ <div class="wl-info">
121
+ <div class="wl-text-number">
122
+ {{ wordNumber }}
123
+
124
+ <span v-if="config.wordLimit">
125
+ &nbsp;/&nbsp;
126
+ <span
127
+ :class="{ illegal: !isWordNumberLegal }"
128
+ v-text="wordLimit"
129
+ />
130
+ </span>
131
+
132
+ &nbsp;{{ locale.word }}
133
+ </div>
134
+
135
+ <button
136
+ v-if="config.login !== 'disable' && !isLogin"
137
+ class="wl-btn"
138
+ @click="onLogin"
139
+ v-text="locale.login"
140
+ />
141
+
142
+ <button
143
+ v-if="config.login !== 'force' || isLogin"
144
+ class="wl-btn primary"
145
+ title="Cmd|Ctrl + Enter"
146
+ :disabled="isSubmitting"
147
+ @click="submitComment"
148
+ >
149
+ <LoadingIcon v-if="isSubmitting" :size="16" />
150
+ <template v-else>
151
+ {{ locale.submit }}
152
+ </template>
153
+ </button>
154
+ </div>
155
+
156
+ <div
157
+ ref="emojiPopupRef"
158
+ class="wl-emoji-popup"
159
+ :class="{ display: showEmoji }"
160
+ >
161
+ <template v-for="(config, index) in emoji.tabs" :key="config.name">
162
+ <div v-if="index === emojiTabIndex" class="wl-tab-wrapper">
163
+ <button
164
+ v-for="key in config.items"
165
+ :key="key"
166
+ :title="key"
167
+ @click="insert(`:${key}:`)"
168
+ >
169
+ <img
170
+ v-if="showEmoji"
171
+ class="wl-emoji"
172
+ :src="emoji.map[key]"
173
+ :alt="key"
174
+ loading="lazy"
175
+ referrerPolicy="no-referrer"
176
+ />
177
+ </button>
178
+ </div>
179
+ </template>
180
+ <div v-if="emoji.tabs.length > 1" class="wl-tabs">
181
+ <button
182
+ v-for="(config, index) in emoji.tabs"
183
+ :key="config.name"
184
+ class="wl-tab"
185
+ :class="{ active: emojiTabIndex === index }"
186
+ @click="emojiTabIndex = index"
187
+ >
188
+ <img
189
+ class="wl-emoji"
190
+ :src="config.icon"
191
+ :alt="config.name"
192
+ :title="config.name"
193
+ loading="lazy"
194
+ referrerPolicy="no-referrer"
195
+ />
196
+ </button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </div>
201
+
202
+ <button
203
+ v-if="replyId"
204
+ class="wl-close"
205
+ :title="locale.cancelReply"
206
+ @click="$emit('cancel-reply')"
207
+ >
208
+ <CloseIcon :size="24" />
209
+ </button>
210
+ </div>
211
+ </template>
212
+
213
+ <script lang="ts">
214
+ import autosize from 'autosize';
215
+ import {
216
+ computed,
217
+ defineComponent,
218
+ inject,
219
+ onMounted,
220
+ onUnmounted,
221
+ ref,
222
+ watch,
223
+ } from 'vue';
224
+
225
+ import {
226
+ CloseIcon,
227
+ EmojiIcon,
228
+ ImageIcon,
229
+ MarkdownIcon,
230
+ PreviewIcon,
231
+ LoadingIcon,
232
+ } from './Icons';
233
+ import { useInputs, useUserInfo } from '../composables';
234
+ import {
235
+ getImagefromDataTransfer,
236
+ parseMarkdown,
237
+ getWordNumber,
238
+ parseEmoji,
239
+ postComment,
240
+ } from '../utils';
241
+
242
+ import type { ComputedRef, DeepReadonly } from 'vue';
243
+ import type { WalineCommentData, WalineImageUploader } from '../typings';
244
+ import type { Config, EmojiConfig } from '../utils';
245
+
246
+ export default defineComponent({
247
+ name: 'CommentBox',
248
+
249
+ components: {
250
+ CloseIcon,
251
+ EmojiIcon,
252
+ ImageIcon,
253
+ MarkdownIcon,
254
+ PreviewIcon,
255
+ LoadingIcon,
256
+ },
257
+
258
+ props: {
259
+ rootId: {
260
+ type: String,
261
+ default: '',
262
+ },
263
+ replyId: {
264
+ type: String,
265
+ default: '',
266
+ },
267
+ replyUser: {
268
+ type: String,
269
+ default: '',
270
+ },
271
+ },
272
+
273
+ emits: ['submit', 'cancel-reply'],
274
+
275
+ setup(props, { emit }) {
276
+ const config = inject<ComputedRef<Config>>('config') as ComputedRef<Config>;
277
+
278
+ const { inputs, store } = useInputs();
279
+ const { userInfo, setUserInfo } = useUserInfo();
280
+
281
+ const inputRefs = ref<Record<string, HTMLInputElement>>({});
282
+ const editorRef = ref<HTMLTextAreaElement | null>(null);
283
+ const imageUploadRef = ref<HTMLInputElement | null>(null);
284
+ const emojiButtonRef = ref<HTMLDivElement | null>(null);
285
+ const emojiPopupRef = ref<HTMLDivElement | null>(null);
286
+
287
+ const emoji = ref<DeepReadonly<EmojiConfig>>({ tabs: [], map: {} });
288
+ const emojiTabIndex = ref(0);
289
+ const showEmoji = ref(false);
290
+ const showPreview = ref(false);
291
+ const previewText = ref('');
292
+ const wordNumber = ref(0);
293
+
294
+ const wordLimit = ref(0);
295
+ const isWordNumberLegal = ref(false);
296
+
297
+ const content = ref('');
298
+
299
+ const isSubmitting = ref(false);
300
+
301
+ const locale = computed(() => config.value.locale);
302
+
303
+ const isLogin = computed(() => Boolean(userInfo.value?.token));
304
+
305
+ const canUploadImage = computed(() => config.value.imageUploader !== false);
306
+
307
+ const insert = (content: string): void => {
308
+ const textArea = editorRef.value as HTMLTextAreaElement;
309
+ const startPosition = textArea.selectionStart;
310
+ const endPosition = textArea.selectionEnd || 0;
311
+ const scrollTop = textArea.scrollTop;
312
+
313
+ inputs.editor =
314
+ textArea.value.substring(0, startPosition) +
315
+ content +
316
+ textArea.value.substring(endPosition, textArea.value.length);
317
+ textArea.focus();
318
+ textArea.selectionStart = startPosition + content.length;
319
+ textArea.selectionEnd = startPosition + content.length;
320
+ textArea.scrollTop = scrollTop;
321
+ };
322
+
323
+ const onKeyDown = (event: KeyboardEvent): void => {
324
+ const key = event.key;
325
+
326
+ // Shortcut key
327
+ if ((event.ctrlKey || event.metaKey) && key === 'Enter') submitComment();
328
+ };
329
+
330
+ const uploadImage = (file: File): Promise<void> => {
331
+ const uploadText = `![${config.value.locale.uploading} ${file.name}]()`;
332
+
333
+ insert(uploadText);
334
+
335
+ return Promise.resolve()
336
+ .then(() => (config.value.imageUploader as WalineImageUploader)(file))
337
+ .then((url) => {
338
+ inputs.editor = inputs.editor.replace(
339
+ uploadText,
340
+ `\r\n![${file.name}](${url})`
341
+ );
342
+ });
343
+ };
344
+
345
+ const onDrop = (event: DragEvent): void => {
346
+ if (event.dataTransfer?.items) {
347
+ const file = getImagefromDataTransfer(event.dataTransfer.items);
348
+
349
+ if (file && canUploadImage.value) {
350
+ uploadImage(file);
351
+ event.preventDefault();
352
+ }
353
+ }
354
+ };
355
+
356
+ const onPaste = (event: ClipboardEvent): void => {
357
+ if (event.clipboardData) {
358
+ const file = getImagefromDataTransfer(event.clipboardData.items);
359
+
360
+ if (file && canUploadImage.value) uploadImage(file);
361
+ }
362
+ };
363
+
364
+ const onChange = (): void => {
365
+ const inputElement = imageUploadRef.value as HTMLInputElement;
366
+
367
+ if (inputElement.files && canUploadImage.value)
368
+ uploadImage(inputElement.files[0]).then(() => {
369
+ // clear input so a same image can be uploaded later
370
+ inputElement.value = '';
371
+ });
372
+ };
373
+
374
+ const submitComment = (): void => {
375
+ const { serverURL, lang, login, wordLimit, requiredMeta } = config.value;
376
+
377
+ const comment: WalineCommentData = {
378
+ comment: content.value,
379
+ nick: inputs.nick,
380
+ mail: inputs.mail,
381
+ link: inputs.link,
382
+ ua: navigator.userAgent,
383
+ url: config.value.path,
384
+ };
385
+
386
+ if (userInfo.value?.token) {
387
+ // login user
388
+
389
+ comment.nick = userInfo.value.display_name;
390
+ comment.mail = userInfo.value.email;
391
+ comment.link = userInfo.value.url;
392
+ } else {
393
+ if (login === 'force') return;
394
+
395
+ // check nick
396
+ if (
397
+ (requiredMeta.indexOf('nick') > -1 || comment.nick) &&
398
+ !comment.nick
399
+ ) {
400
+ inputRefs.value.nick?.focus();
401
+ return alert(locale.value.nickError);
402
+ }
403
+
404
+ // check mail
405
+ if (requiredMeta.indexOf('mail') > -1 && !comment.mail) {
406
+ inputRefs.value.mail?.focus();
407
+ return alert(locale.value.mailError);
408
+ }
409
+
410
+ // check comment
411
+ if (!comment.comment) {
412
+ editorRef.value?.focus();
413
+ return;
414
+ }
415
+
416
+ if (!comment.nick) comment.nick = locale.value.anonymous;
417
+ }
418
+
419
+ if (!isWordNumberLegal.value)
420
+ return alert(
421
+ locale.value.wordHint
422
+ .replace('$0', (wordLimit as [number, number])[0].toString())
423
+ .replace('$1', (wordLimit as [number, number])[1].toString())
424
+ .replace('$2', wordNumber.value.toString())
425
+ );
426
+
427
+ comment.comment = parseEmoji(comment.comment, emoji.value.map);
428
+
429
+ if (props.replyId && props.rootId) {
430
+ comment.pid = props.replyId;
431
+ comment.rid = props.rootId;
432
+ comment.at = props.replyUser;
433
+ }
434
+
435
+ isSubmitting.value = true;
436
+
437
+ postComment({
438
+ serverURL,
439
+ lang,
440
+ token: userInfo.value?.token,
441
+ comment,
442
+ })
443
+ .then((resp) => {
444
+ isSubmitting.value = false;
445
+
446
+ store.update({
447
+ nick: comment.nick,
448
+ link: comment.link,
449
+ mail: comment.mail,
450
+ });
451
+
452
+ if (resp.errmsg) return alert(resp.errmsg);
453
+
454
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
455
+ emit('submit', resp.data!);
456
+
457
+ inputs.editor = '';
458
+
459
+ previewText.value = '';
460
+
461
+ if (props.replyId) emit('cancel-reply');
462
+ })
463
+ .catch((err: TypeError) => {
464
+ isSubmitting.value = false;
465
+
466
+ alert(err.message);
467
+ });
468
+ };
469
+
470
+ const onLogin = (event: Event): void => {
471
+ event.preventDefault();
472
+ const { lang, serverURL } = config.value;
473
+
474
+ const width = 450;
475
+ const height = 450;
476
+ const left = (window.innerWidth - width) / 2;
477
+ const top = (window.innerHeight - height) / 2;
478
+
479
+ const handler = window.open(
480
+ `${serverURL}/ui/login?lng=${encodeURIComponent(lang)}`,
481
+ '_blank',
482
+ `width=${width},height=${height},left=${left},top=${top},scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no`
483
+ );
484
+
485
+ handler?.postMessage({ type: 'TOKEN', data: null }, '*');
486
+
487
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
488
+ const receiver = ({ data }: any): void => {
489
+ if (!data || data.type !== 'userInfo') return;
490
+
491
+ if (data.data.token) {
492
+ handler?.close();
493
+ setUserInfo(data.data);
494
+ (data.data.remember ? localStorage : sessionStorage).setItem(
495
+ 'WALINE_USER',
496
+ JSON.stringify(data.data)
497
+ );
498
+
499
+ window.removeEventListener('message', receiver);
500
+ }
501
+ };
502
+
503
+ window.addEventListener('message', receiver);
504
+ };
505
+
506
+ const onLogout = (): void => {
507
+ setUserInfo({});
508
+ localStorage.setItem('WALINE_USER', 'null');
509
+ sessionStorage.setItem('WALINE_USER', 'null');
510
+ };
511
+
512
+ const onProfile = (event: Event): void => {
513
+ event.preventDefault();
514
+
515
+ const { lang, serverURL } = config.value;
516
+
517
+ const width = 800;
518
+ const height = 800;
519
+ const left = (window.innerWidth - width) / 2;
520
+ const top = (window.innerHeight - height) / 2;
521
+ const handler = window.open(
522
+ `${serverURL}/ui/profile?lng=${encodeURIComponent(lang)}`,
523
+ '_blank',
524
+ `width=${width},height=${height},left=${left},top=${top},scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no`
525
+ );
526
+
527
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
528
+ handler?.postMessage({ type: 'TOKEN', data: userInfo.value!.token }, '*');
529
+
530
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
531
+ const receiver = ({ data }: any): void => {
532
+ if (!data || data.type !== 'profile') return;
533
+
534
+ setUserInfo(Object.assign({}, userInfo.value, data));
535
+ [localStorage, sessionStorage]
536
+ .filter((store) => store.getItem('WALINE_USER'))
537
+ .forEach((store) =>
538
+ store.setItem('WALINE_USER', JSON.stringify(userInfo))
539
+ );
540
+ window.removeEventListener('message', receiver);
541
+ };
542
+
543
+ window.addEventListener('message', receiver);
544
+ };
545
+
546
+ const popupHandler = (event: MouseEvent): void => {
547
+ if (
548
+ !(emojiButtonRef.value as HTMLElement).contains(event.target as Node) &&
549
+ !(emojiPopupRef.value as HTMLElement).contains(event.target as Node)
550
+ )
551
+ showEmoji.value = false;
552
+ };
553
+
554
+ // watch editor
555
+ watch(
556
+ () => inputs.editor,
557
+ (value) => {
558
+ const { highlighter, texRenderer } = config.value;
559
+
560
+ content.value = value;
561
+ previewText.value = parseMarkdown(value, {
562
+ emojiMap: emoji.value.map,
563
+ highlighter,
564
+ texRenderer,
565
+ });
566
+ wordNumber.value = getWordNumber(value);
567
+
568
+ if (editorRef.value)
569
+ if (value) autosize(editorRef.value);
570
+ else autosize.destroy(editorRef.value);
571
+ },
572
+ { immediate: true }
573
+ );
574
+
575
+ // watch emoji value change
576
+ watch(
577
+ () => config.value.emoji,
578
+ (emojiConfig) =>
579
+ emojiConfig.then((config) => {
580
+ emoji.value = config;
581
+ }),
582
+ { immediate: true }
583
+ );
584
+
585
+ // update wordNumber
586
+ watch(
587
+ [config, wordNumber],
588
+ ([config, wordNumber]) => {
589
+ const { wordLimit: limit } = config;
590
+
591
+ if (limit) {
592
+ if (wordNumber < limit[0] && limit[0] !== 0) {
593
+ wordLimit.value = limit[0];
594
+ isWordNumberLegal.value = false;
595
+ } else if (wordNumber > limit[1]) {
596
+ wordLimit.value = limit[1];
597
+ isWordNumberLegal.value = false;
598
+ } else {
599
+ wordLimit.value = limit[1];
600
+ isWordNumberLegal.value = true;
601
+ }
602
+ } else {
603
+ wordLimit.value = 0;
604
+ isWordNumberLegal.value = true;
605
+ }
606
+ },
607
+ { immediate: true }
608
+ );
609
+
610
+ onMounted(() => {
611
+ document.body.addEventListener('click', popupHandler);
612
+ });
613
+
614
+ onUnmounted(() => {
615
+ document.body.removeEventListener('click', popupHandler);
616
+ });
617
+
618
+ return {
619
+ // config
620
+ config,
621
+ locale,
622
+
623
+ // events
624
+ insert,
625
+ onChange,
626
+ onDrop,
627
+ onKeyDown,
628
+ onPaste,
629
+ onLogin,
630
+ onLogout,
631
+ onProfile,
632
+ submitComment,
633
+
634
+ isLogin,
635
+ userInfo,
636
+ isSubmitting,
637
+
638
+ // word
639
+ wordNumber,
640
+ wordLimit,
641
+ isWordNumberLegal,
642
+
643
+ // inputs
644
+ inputs,
645
+
646
+ // emoji
647
+ emoji,
648
+ emojiTabIndex,
649
+ showEmoji,
650
+
651
+ // image
652
+ canUploadImage,
653
+
654
+ // preview
655
+ previewText,
656
+ showPreview,
657
+
658
+ // ref
659
+ inputRefs,
660
+ editorRef,
661
+ emojiButtonRef,
662
+ emojiPopupRef,
663
+ imageUploadRef,
664
+ };
665
+ },
666
+ });
667
+ </script>