@waline/client 1.6.0 → 2.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 (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,125 @@
1
+ <template>
2
+ <div class="wl-item" :id="comment.objectId">
3
+ <div class="wl-user" aria-hidden="true">
4
+ <img v-if="comment.avatar" :src="comment.avatar" />
5
+ <VerifiedIcon v-if="comment.type" />
6
+ </div>
7
+
8
+ <div class="wl-card">
9
+ <div class="wl-head">
10
+ <a
11
+ v-if="link"
12
+ class="wl-nick"
13
+ :href="link"
14
+ target="_blank"
15
+ rel="nofollow noreferrer"
16
+ >{{ comment.nick }}</a
17
+ >
18
+ <span v-else class="wl-nick">{{ comment.nick }}</span>
19
+
20
+ <span
21
+ v-if="comment.type === 'administrator'"
22
+ class="wl-badge"
23
+ v-text="locale.admin"
24
+ />
25
+ <span v-if="comment.sticky" class="wl-badge" v-text="locale.sticky" />
26
+
27
+ <span class="wl-time" v-text="timeAgo(comment.insertedAt, locale)" />
28
+
29
+ <button
30
+ class="wl-reply"
31
+ :class="{ active: isReplyingCurrent }"
32
+ :title="isReplyingCurrent ? locale.cancelReply : locale.reply"
33
+ @click="$emit('reply', isReplyingCurrent ? null : comment)"
34
+ >
35
+ <ReplyIcon />
36
+ </button>
37
+ </div>
38
+ <div class="wl-meta" aria-hidden="true">
39
+ <span v-if="comment.browser" v-text="comment.browser" />
40
+ <span v-if="comment.os" v-text="comment.os" />
41
+ </div>
42
+ <div class="wl-content" v-html="comment.comment" />
43
+
44
+ <div v-if="isReplyingCurrent" class="wl-reply-wrapper">
45
+ <CommentBox
46
+ :replyId="comment.objectId"
47
+ :replyUser="comment.nick"
48
+ :rootId="rootId"
49
+ @submit="$emit('submit', $event)"
50
+ @cancel-reply="$emit('reply', null)"
51
+ />
52
+ </div>
53
+ <div v-if="comment.children" class="wl-quote">
54
+ <CommentCard
55
+ v-for="child in comment.children"
56
+ :key="child.objectId"
57
+ :comment="child"
58
+ :reply="reply"
59
+ :rootId="rootId"
60
+ @reply="$emit('reply', $event)"
61
+ @submit="$emit('submit', $event)"
62
+ />
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </template>
67
+
68
+ <script lang="ts">
69
+ import { computed, defineComponent, inject } from 'vue';
70
+ import CommentBox from './CommentBox.vue';
71
+ import { ReplyIcon, VerifiedIcon } from './Icons';
72
+ import { isLinkHttp, timeAgo } from '../utils';
73
+
74
+ import type { ComputedRef, PropType } from 'vue';
75
+ import type { Config } from '../utils';
76
+ import type { WalineComment } from '../typings';
77
+
78
+ export default defineComponent({
79
+ props: {
80
+ comment: {
81
+ type: Object as PropType<WalineComment>,
82
+ required: true,
83
+ },
84
+ rootId: {
85
+ type: String,
86
+ required: true,
87
+ },
88
+ reply: {
89
+ type: Object as PropType<WalineComment | null>,
90
+ },
91
+ },
92
+
93
+ components: {
94
+ CommentBox,
95
+ ReplyIcon,
96
+ VerifiedIcon,
97
+ },
98
+
99
+ emits: ['submit', 'reply'],
100
+
101
+ setup(props) {
102
+ const config = inject<ComputedRef<Config>>('config') as ComputedRef<Config>;
103
+ const locale = computed(() => config.value.locale);
104
+
105
+ const link = computed(() => {
106
+ let { link } = props.comment;
107
+
108
+ return link ? (isLinkHttp(link) ? link : `https://${link}`) : '';
109
+ });
110
+
111
+ const isReplyingCurrent = computed(
112
+ () => props.comment.objectId === props.reply?.objectId
113
+ );
114
+
115
+ return {
116
+ config,
117
+ locale,
118
+
119
+ isReplyingCurrent,
120
+ link,
121
+ timeAgo,
122
+ };
123
+ },
124
+ });
125
+ </script>
@@ -0,0 +1,124 @@
1
+ import { h } from 'vue';
2
+ import type { FunctionalComponent } from 'vue';
3
+
4
+ export const CloseIcon: FunctionalComponent<{ size: number }> = ({ size }) =>
5
+ h(
6
+ 'svg',
7
+ {
8
+ class: 'vclose-icon',
9
+ viewBox: '0 0 1024 1024',
10
+ width: size,
11
+ height: size,
12
+ },
13
+ [
14
+ h('path', {
15
+ d: 'M697.173 85.333h-369.92c-144.64 0-241.92 101.547-241.92 252.587v348.587c0 150.613 97.28 252.16 241.92 252.16h369.92c144.64 0 241.494-101.547 241.494-252.16V337.92c0-151.04-96.854-252.587-241.494-252.587z',
16
+ fill: 'currentColor',
17
+ }),
18
+ h('path', {
19
+ d: 'm640.683 587.52-75.947-75.861 75.904-75.862a37.29 37.29 0 0 0 0-52.778 37.205 37.205 0 0 0-52.779 0l-75.946 75.818-75.862-75.946a37.419 37.419 0 0 0-52.821 0 37.419 37.419 0 0 0 0 52.821l75.947 75.947-75.776 75.733a37.29 37.29 0 1 0 52.778 52.821l75.776-75.776 75.947 75.947a37.376 37.376 0 0 0 52.779-52.821z',
20
+ fill: '#888',
21
+ }),
22
+ ]
23
+ );
24
+
25
+ export const EmojiIcon: FunctionalComponent = () =>
26
+ h(
27
+ 'svg',
28
+ { viewBox: '0 0 1024 1024', width: '24', height: '24' },
29
+ h('path', {
30
+ d: 'M563.2 463.3 677 540c1.7 1.2 3.7 1.8 5.8 1.8.7 0 1.4-.1 2-.2 2.7-.5 5.1-2.1 6.6-4.4l25.3-37.8c1.5-2.3 2.1-5.1 1.6-7.8s-2.1-5.1-4.4-6.6l-73.6-49.1 73.6-49.1c2.3-1.5 3.9-3.9 4.4-6.6.5-2.7 0-5.5-1.6-7.8l-25.3-37.8a10.1 10.1 0 0 0-6.6-4.4c-.7-.1-1.3-.2-2-.2-2.1 0-4.1.6-5.8 1.8l-113.8 76.6c-9.2 6.2-14.7 16.4-14.7 27.5.1 11 5.5 21.3 14.7 27.4zM387 348.8h-45.5c-5.7 0-10.4 4.7-10.4 10.4v153.3c0 5.7 4.7 10.4 10.4 10.4H387c5.7 0 10.4-4.7 10.4-10.4V359.2c0-5.7-4.7-10.4-10.4-10.4zm333.8 241.3-41-20a10.3 10.3 0 0 0-8.1-.5c-2.6.9-4.8 2.9-5.9 5.4-30.1 64.9-93.1 109.1-164.4 115.2-5.7.5-9.9 5.5-9.5 11.2l3.9 45.5c.5 5.3 5 9.5 10.3 9.5h.9c94.8-8 178.5-66.5 218.6-152.7 2.4-5 .3-11.2-4.8-13.6zm186-186.1c-11.9-42-30.5-81.4-55.2-117.1-24.1-34.9-53.5-65.6-87.5-91.2-33.9-25.6-71.5-45.5-111.6-59.2-41.2-14-84.1-21.1-127.8-21.1h-1.2c-75.4 0-148.8 21.4-212.5 61.7-63.7 40.3-114.3 97.6-146.5 165.8-32.2 68.1-44.3 143.6-35.1 218.4 9.3 74.8 39.4 145 87.3 203.3.1.2.3.3.4.5l36.2 38.4c1.1 1.2 2.5 2.1 3.9 2.6 73.3 66.7 168.2 103.5 267.5 103.5 73.3 0 145.2-20.3 207.7-58.7 37.3-22.9 70.3-51.5 98.1-85 27.1-32.7 48.7-69.5 64.2-109.1 15.5-39.7 24.4-81.3 26.6-123.8 2.4-43.6-2.5-87-14.5-129zm-60.5 181.1c-8.3 37-22.8 72-43 104-19.7 31.1-44.3 58.6-73.1 81.7-28.8 23.1-61 41-95.7 53.4-35.6 12.7-72.9 19.1-110.9 19.1-82.6 0-161.7-30.6-222.8-86.2l-34.1-35.8c-23.9-29.3-42.4-62.2-55.1-97.7-12.4-34.7-18.8-71-19.2-107.9-.4-36.9 5.4-73.3 17.1-108.2 12-35.8 30-69.2 53.4-99.1 31.7-40.4 71.1-72 117.2-94.1 44.5-21.3 94-32.6 143.4-32.6 49.3 0 97 10.8 141.8 32 34.3 16.3 65.3 38.1 92 64.8 26.1 26 47.5 56 63.6 89.2 16.2 33.2 26.6 68.5 31 105.1 4.6 37.5 2.7 75.3-5.6 112.3z',
31
+ fill: 'currentColor',
32
+ })
33
+ );
34
+
35
+ export const ImageIcon: FunctionalComponent = () =>
36
+ h('svg', { viewBox: '0 0 1024 1024', width: '24', height: '24' }, [
37
+ h('path', {
38
+ d: 'M784 112H240c-88 0-160 72-160 160v480c0 88 72 160 160 160h544c88 0 160-72 160-160V272c0-88-72-160-160-160zm96 640c0 52.8-43.2 96-96 96H240c-52.8 0-96-43.2-96-96V272c0-52.8 43.2-96 96-96h544c52.8 0 96 43.2 96 96v480z',
39
+ fill: 'currentColor',
40
+ }),
41
+ h('path', {
42
+ d: 'M352 480c52.8 0 96-43.2 96-96s-43.2-96-96-96-96 43.2-96 96 43.2 96 96 96zm0-128c17.6 0 32 14.4 32 32s-14.4 32-32 32-32-14.4-32-32 14.4-32 32-32zm462.4 379.2-3.2-3.2-177.6-177.6c-25.6-25.6-65.6-25.6-91.2 0l-80 80-36.8-36.8c-25.6-25.6-65.6-25.6-91.2 0L200 728c-4.8 6.4-8 14.4-8 24 0 17.6 14.4 32 32 32 9.6 0 16-3.2 22.4-9.6L380.8 640l134.4 134.4c6.4 6.4 14.4 9.6 24 9.6 17.6 0 32-14.4 32-32 0-9.6-4.8-17.6-9.6-24l-52.8-52.8 80-80L769.6 776c6.4 4.8 12.8 8 20.8 8 17.6 0 32-14.4 32-32 0-8-3.2-16-8-20.8z',
43
+ fill: 'currentColor',
44
+ }),
45
+ ]);
46
+
47
+ export const PreviewIcon: FunctionalComponent = () =>
48
+ h('svg', { viewBox: '0 0 1024 1024', width: '24', height: '24' }, [
49
+ h('path', {
50
+ d: 'M710.816 654.301c70.323-96.639 61.084-230.578-23.705-314.843-46.098-46.098-107.183-71.109-172.28-71.109-65.008 0-126.092 25.444-172.28 71.109-45.227 46.098-70.756 107.183-70.756 172.106 0 64.923 25.444 126.007 71.194 172.106 46.099 46.098 107.184 71.109 172.28 71.109 51.414 0 100.648-16.212 142.824-47.404l126.53 126.006c7.058 7.06 16.297 10.979 26.406 10.979 10.105 0 19.343-3.919 26.402-10.979 14.467-14.467 14.467-38.172 0-52.723L710.816 654.301zm-315.107-23.265c-65.88-65.88-65.88-172.54 0-238.42 32.069-32.07 74.245-49.149 119.471-49.149 45.227 0 87.407 17.603 119.472 49.149 65.88 65.879 65.88 172.539 0 238.42-63.612 63.178-175.242 63.178-238.943 0zm0 0',
51
+ fill: 'currentColor',
52
+ }),
53
+ h('path', {
54
+ d: 'M703.319 121.603H321.03c-109.8 0-199.469 89.146-199.469 199.38v382.034c0 109.796 89.236 199.38 199.469 199.38h207.397c20.653 0 37.384-16.645 37.384-37.299 0-20.649-16.731-37.296-37.384-37.296H321.03c-68.582 0-124.352-55.77-124.352-124.267V321.421c0-68.496 55.77-124.267 124.352-124.267h382.289c68.582 0 124.352 55.771 124.352 124.267V524.72c0 20.654 16.736 37.299 37.385 37.299 20.654 0 37.384-16.645 37.384-37.299V320.549c-.085-109.8-89.321-198.946-199.121-198.946zm0 0',
55
+ fill: 'currentColor',
56
+ }),
57
+ ]);
58
+
59
+ export const MarkdownIcon: FunctionalComponent = () =>
60
+ h(
61
+ 'svg',
62
+ { width: '16', height: '16', ariaHidden: 'true' },
63
+ h('path', {
64
+ d: 'M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z',
65
+ fill: 'currentColor',
66
+ })
67
+ );
68
+
69
+ export const ReplyIcon: FunctionalComponent = () =>
70
+ h(
71
+ 'svg',
72
+ { viewBox: '0 0 1024 1024', width: '18', height: '18' },
73
+ 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',
75
+ fill: 'currentColor',
76
+ })
77
+ );
78
+
79
+ export const VerifiedIcon: FunctionalComponent = () =>
80
+ h(
81
+ 'svg',
82
+ {
83
+ class: 'verified-icon',
84
+ viewBox: '0 0 1024 1024',
85
+ width: '14',
86
+ height: '14',
87
+ },
88
+ h('path', {
89
+ d: 'm894.4 461.56-54.4-63.2c-10.4-12-18.8-34.4-18.8-50.4v-68c0-42.4-34.8-77.2-77.2-77.2h-68c-15.6 0-38.4-8.4-50.4-18.8l-63.2-54.4c-27.6-23.6-72.8-23.6-100.8 0l-62.8 54.8c-12 10-34.8 18.4-50.4 18.4h-69.2c-42.4 0-77.2 34.8-77.2 77.2v68.4c0 15.6-8.4 38-18.4 50l-54 63.6c-23.2 27.6-23.2 72.4 0 100l54 63.6c10 12 18.4 34.4 18.4 50v68.4c0 42.4 34.8 77.2 77.2 77.2h69.2c15.6 0 38.4 8.4 50.4 18.8l63.2 54.4c27.6 23.6 72.8 23.6 100.8 0l63.2-54.4c12-10.4 34.4-18.8 50.4-18.8h68c42.4 0 77.2-34.8 77.2-77.2v-68c0-15.6 8.4-38.4 18.8-50.4l54.4-63.2c23.2-27.6 23.2-73.2-.4-100.8zm-216-25.2-193.2 193.2a30 30 0 0 1-42.4 0l-96.8-96.8a30.16 30.16 0 0 1 0-42.4c11.6-11.6 30.8-11.6 42.4 0l75.6 75.6 172-172c11.6-11.6 30.8-11.6 42.4 0 11.6 11.6 11.6 30.8 0 42.4z',
90
+ fill: '#27ae60',
91
+ })
92
+ );
93
+
94
+ export const LoadingIcon: FunctionalComponent<{ size: number }> = ({ size }) =>
95
+ h(
96
+ 'svg',
97
+ {
98
+ width: size,
99
+ height: size,
100
+ viewBox: '0 0 100 100',
101
+ preserveAspectRatio: 'xMidYMid',
102
+ },
103
+ h(
104
+ 'circle',
105
+ {
106
+ cx: 50,
107
+ cy: 50,
108
+ fill: 'none',
109
+ stroke: 'currentColor',
110
+ strokeWidth: '4',
111
+ r: '40',
112
+ // eslint-disable-next-line @typescript-eslint/naming-convention
113
+ 'stroke-dasharray': '85 30',
114
+ },
115
+ h('animateTransform', {
116
+ attributeName: 'transform',
117
+ type: 'rotate',
118
+ repeatCount: 'indefinite',
119
+ dur: '1s',
120
+ values: '0 50 50;360 50 50',
121
+ keyTimes: '0;1',
122
+ })
123
+ )
124
+ );
@@ -0,0 +1,359 @@
1
+ <template>
2
+ <div data-waline>
3
+ <CommentBox v-if="!reply" @submit="onSubmit" />
4
+ <div class="wl-count">
5
+ <span v-if="count" class="wl-num" v-text="count" />
6
+ {{ i18n.comment }}
7
+ </div>
8
+
9
+ <div v-if="status === 'error'" class="wl-action">
10
+ <button
11
+ type="button"
12
+ class="wl-btn"
13
+ @click="refresh"
14
+ v-text="i18n.refresh"
15
+ />
16
+ </div>
17
+
18
+ <div v-else-if="status === 'loading'" class="wl-loading">
19
+ <LoadingIcon :size="30" />
20
+ </div>
21
+
22
+ <template v-else>
23
+ <div v-if="!data.length" class="wl-empty" v-text="i18n.sofa" />
24
+
25
+ <div v-else class="wl-cards">
26
+ <CommentCard
27
+ v-for="comment in data"
28
+ :key="comment.objectId"
29
+ :root-id="comment.objectId"
30
+ :comment="comment"
31
+ :reply="reply"
32
+ @reply="onReply"
33
+ @submit="onSubmit"
34
+ />
35
+ </div>
36
+
37
+ <!-- Load more button -->
38
+ <div v-if="page < totalPages" class="wl-more">
39
+ <button
40
+ type="button"
41
+ class="wl-btn"
42
+ @click="loadMore"
43
+ v-text="i18n.more"
44
+ />
45
+ </div>
46
+ </template>
47
+
48
+ <!-- Copyright Information -->
49
+ <div v-if="config.copyright" class="wl-power">
50
+ Powered by
51
+ <a
52
+ href="https://github.com/walinejs/waline"
53
+ target="_blank"
54
+ rel="noreferrer"
55
+ >
56
+ Waline
57
+ </a>
58
+ v{{ version }}
59
+ </div>
60
+ </div>
61
+ </template>
62
+
63
+ <script lang="ts">
64
+ import {
65
+ computed,
66
+ defineComponent,
67
+ onBeforeUnmount,
68
+ onMounted,
69
+ provide,
70
+ ref,
71
+ watch,
72
+ watchEffect,
73
+ } from 'vue';
74
+ import CommentBox from './CommentBox.vue';
75
+ import CommentCard from './CommentCard.vue';
76
+ import { LoadingIcon } from './Icons';
77
+ import { useUserInfo } from '../composables';
78
+ import { locales } from '../config';
79
+ import { fetchCommentList, getConfig, getDarkStyle } from '../utils';
80
+
81
+ import type { PropType } from 'vue';
82
+ import type {
83
+ WalineComment,
84
+ WalineEmojiInfo,
85
+ WalineHighlighter,
86
+ WalineTexRenderer,
87
+ WalineImageUploader,
88
+ WalineLocale,
89
+ WalineProps,
90
+ } from '../typings';
91
+
92
+ declare const SHOULD_VALIDATE: boolean;
93
+ declare const VERSION: string;
94
+
95
+ export default defineComponent({
96
+ name: 'Waline-Root',
97
+
98
+ components: {
99
+ CommentBox,
100
+ CommentCard,
101
+ LoadingIcon,
102
+ },
103
+
104
+ props: {
105
+ serverURL: {
106
+ type: String,
107
+ required: true,
108
+ },
109
+
110
+ path: {
111
+ type: String,
112
+ required: true,
113
+ },
114
+
115
+ meta: {
116
+ type: Array,
117
+ // default: (): Meta[] => ['nick', 'mail', 'link'],
118
+ ...(SHOULD_VALIDATE
119
+ ? {
120
+ validator: (value: unknown): boolean =>
121
+ Array.isArray(value) &&
122
+ value.every((item) => ['nick', 'mail', 'link'].includes(item)),
123
+ }
124
+ : {}),
125
+ },
126
+
127
+ requiredMeta: {
128
+ type: Array,
129
+ // default: (): Meta[] => [],
130
+ ...(SHOULD_VALIDATE
131
+ ? {
132
+ validator: (value: unknown): boolean =>
133
+ Array.isArray(value) &&
134
+ value.every((item) => ['nick', 'mail', 'link'].includes(item)),
135
+ }
136
+ : {}),
137
+ },
138
+
139
+ visitor: {
140
+ type: Boolean,
141
+ // default: false,
142
+ },
143
+
144
+ dark: {
145
+ type: [String, Boolean],
146
+ // default: false,
147
+ },
148
+
149
+ lang: {
150
+ type: String,
151
+ // default: 'zh-CN',
152
+ ...(SHOULD_VALIDATE
153
+ ? {
154
+ validator: (value: unknown): boolean =>
155
+ Object.keys(locales).includes(value as string),
156
+ }
157
+ : {}),
158
+ },
159
+
160
+ locale: {
161
+ type: Object as PropType<Partial<WalineLocale>>,
162
+ },
163
+
164
+ pageSize: {
165
+ type: Number,
166
+ // default: 10,
167
+ },
168
+
169
+ wordLimit: {
170
+ type: [Number, Array] as PropType<number | [number, number]>,
171
+ // default: 0,
172
+ ...(SHOULD_VALIDATE
173
+ ? {
174
+ validator: (value: unknown): boolean =>
175
+ typeof value === 'number' ||
176
+ (Array.isArray(value) &&
177
+ value.length === 2 &&
178
+ value.every((item) => typeof item === 'number')),
179
+ }
180
+ : {}),
181
+ },
182
+
183
+ emoji: {
184
+ type: Array as PropType<(string | WalineEmojiInfo)[]>,
185
+ // default: (): string[] => [
186
+ // 'https://cdn.jsdelivr.net/gh/walinejs/emojis/weibo',
187
+ // ],
188
+ ...(SHOULD_VALIDATE
189
+ ? {
190
+ validator: (value: unknown): boolean =>
191
+ Array.isArray(value) &&
192
+ value.every(
193
+ (item) =>
194
+ typeof item === 'string' ||
195
+ (typeof item === 'object' &&
196
+ typeof item.name === 'string' &&
197
+ typeof item.folder === 'string' &&
198
+ typeof item.icon === 'string' &&
199
+ Array.isArray(item.items) &&
200
+ (item.items as unknown[]).every(
201
+ (icon) => typeof icon === 'string'
202
+ ))
203
+ ),
204
+ }
205
+ : {}),
206
+ },
207
+
208
+ login: {
209
+ type: String as PropType<'enable' | 'disable' | 'force'>,
210
+ // default: 'enable',
211
+ },
212
+
213
+ highlighter: {
214
+ type: Function as PropType<WalineHighlighter>,
215
+ // default: (text: string): string => text,
216
+ },
217
+
218
+ imageUploader: {
219
+ type: [Function, false] as PropType<WalineImageUploader>,
220
+ // default: (file: File): Promise<string> =>
221
+ // new Promise((resolve, reject) => {
222
+ // const reader = new FileReader();
223
+ // reader.readAsDataURL(file);
224
+ // reader.onload = (): void => resolve(reader.result?.toString() || '');
225
+ // reader.onerror = reject;
226
+ // }),
227
+ },
228
+
229
+ texRender: {
230
+ type: Function as PropType<WalineTexRenderer>,
231
+ // default: (blockMode: boolean): string =>
232
+ // blockMode === true
233
+ // ? '<p class="vtex">Tex is not available in preview</p>'
234
+ // : '<span class="vtex">Tex is not available in preview</span>',
235
+ },
236
+ },
237
+
238
+ setup(props) {
239
+ const config = computed(() => getConfig(props as WalineProps));
240
+
241
+ const { userInfo } = useUserInfo();
242
+
243
+ const status = ref<'loading' | 'success' | 'error'>('loading');
244
+
245
+ const count = ref(0);
246
+ const page = ref(1);
247
+ const totalPages = ref(0);
248
+
249
+ const data = ref<WalineComment[]>([]);
250
+ const reply = ref<WalineComment | null>(null);
251
+
252
+ const darkmodeStyle = computed(() => getDarkStyle(config.value.dark));
253
+
254
+ // eslint-disable-next-line vue/no-setup-props-destructure
255
+ let abort: () => void;
256
+ let stop: () => void;
257
+
258
+ const fetchComment = (pageNumber: number): void => {
259
+ const { serverURL, path, pageSize } = config.value;
260
+ const controller = new AbortController();
261
+
262
+ status.value = 'loading';
263
+
264
+ abort?.();
265
+
266
+ fetchCommentList({
267
+ serverURL,
268
+ path,
269
+ pageSize,
270
+ page: pageNumber,
271
+ signal: controller.signal,
272
+ token: userInfo.value?.token,
273
+ })
274
+ .then((resp) => {
275
+ status.value = 'success';
276
+ count.value = resp.count;
277
+ data.value.push(...resp.data);
278
+ page.value = pageNumber;
279
+ totalPages.value = resp.totalPages;
280
+ })
281
+ .catch((err) => {
282
+ if (err.name !== 'AbortError') {
283
+ console.error(err.message);
284
+ status.value = 'error';
285
+ }
286
+ });
287
+
288
+ abort = controller.abort.bind(controller);
289
+ };
290
+
291
+ const loadMore = (): void => fetchComment(page.value + 1);
292
+
293
+ const refresh = (): void => {
294
+ count.value = 0;
295
+ data.value = [];
296
+ fetchComment(1);
297
+ };
298
+
299
+ const onReply = (comment: WalineComment | null): void => {
300
+ reply.value = comment;
301
+ };
302
+
303
+ const onSubmit = (comment: WalineComment): void => {
304
+ if (comment.rid) {
305
+ const repliedComment = data.value.find(
306
+ ({ objectId }) => objectId === comment.rid
307
+ );
308
+
309
+ if (!repliedComment) return;
310
+
311
+ if (!Array.isArray(repliedComment.children))
312
+ repliedComment.children = [];
313
+
314
+ repliedComment.children.push(comment);
315
+ } else data.value.unshift(comment);
316
+ };
317
+
318
+ provide('config', config);
319
+
320
+ watch(() => props.path, refresh);
321
+
322
+ onMounted(() => {
323
+ refresh();
324
+
325
+ const style = document.createElement('style');
326
+
327
+ style.innerText = darkmodeStyle.value;
328
+
329
+ document.querySelector('[data-waline]')?.appendChild(style);
330
+
331
+ stop = watchEffect(() => {
332
+ style.innerText = darkmodeStyle.value;
333
+ });
334
+ });
335
+
336
+ onBeforeUnmount(() => stop());
337
+
338
+ return {
339
+ config,
340
+ darkmodeStyle,
341
+ i18n: computed(() => config.value.locale),
342
+
343
+ status,
344
+ count,
345
+ page,
346
+ totalPages,
347
+ data,
348
+ reply,
349
+
350
+ loadMore,
351
+ refresh,
352
+ onReply,
353
+ onSubmit,
354
+
355
+ version: VERSION,
356
+ };
357
+ },
358
+ });
359
+ </script>
@@ -0,0 +1,3 @@
1
+ export * from './inputs';
2
+ export * from './userInfo';
3
+ export * from './store';
@@ -0,0 +1,29 @@
1
+ import { reactive } from 'vue';
2
+ import { useStore } from './store';
3
+
4
+ import type { Store } from './store';
5
+
6
+ export interface Inputs {
7
+ nick: string;
8
+ mail: string;
9
+ link: string;
10
+ editor: string;
11
+ }
12
+
13
+ let store: Store;
14
+ let inputs: Inputs;
15
+
16
+ export const useInputs = (): { inputs: Inputs; store: Store } => {
17
+ if (!inputs) {
18
+ store = useStore('WALINE_USER_CACHE');
19
+
20
+ inputs = reactive({
21
+ nick: store.get<string>('nick') || '',
22
+ mail: store.get<string>('mail') || '',
23
+ link: store.get<string>('link') || '',
24
+ editor: '',
25
+ });
26
+ }
27
+
28
+ return { inputs, store };
29
+ };
@@ -0,0 +1,38 @@
1
+ export interface Store {
2
+ get: <T = unknown>(key: string) => T | null;
3
+ set: <T = unknown>(key: string, content: T) => void;
4
+ update: <T = unknown>(content: T) => void;
5
+ }
6
+
7
+ export const useStore = (cacheKey: string): Store => {
8
+ let storage: Record<string, unknown> = {};
9
+ const content = localStorage.getItem(cacheKey);
10
+
11
+ if (content) {
12
+ try {
13
+ storage = JSON.parse(content) as Record<string, unknown>;
14
+ } catch (err) {
15
+ // do nothing
16
+ }
17
+ }
18
+
19
+ return {
20
+ get: <T>(key: string): T | null => (storage[key] as T) || null,
21
+
22
+ set<T>(key: string, content: T): void {
23
+ try {
24
+ // make sure the content can be stringify and make a deep copy here
25
+ storage[key] = JSON.parse(JSON.stringify(content));
26
+ localStorage.setItem(cacheKey, JSON.stringify(storage));
27
+ } catch (err) {
28
+ // do nothing
29
+ }
30
+ },
31
+
32
+ update<T>(content: T): void {
33
+ // make sure the content can be stringify and make a deep copy here
34
+ storage = JSON.parse(JSON.stringify(content)) as Record<string, unknown>;
35
+ localStorage.setItem(cacheKey, JSON.stringify(storage));
36
+ },
37
+ };
38
+ };