@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.
- package/dist/component.js +2 -0
- package/dist/component.js.map +1 -0
- package/dist/pageview.cjs.js +2 -0
- package/dist/pageview.cjs.js.map +1 -0
- package/dist/pageview.d.ts +33 -0
- package/dist/pageview.esm.js +2 -0
- package/dist/pageview.esm.js.map +1 -0
- package/dist/pageview.js +2 -0
- package/dist/pageview.js.map +1 -0
- package/dist/{Waline.min.d.ts → shim.d.ts} +192 -261
- package/dist/{Waline.noStyle.d.ts → shim.esm.d.ts} +192 -261
- package/dist/shim.esm.js +2 -0
- package/dist/shim.esm.js.map +1 -0
- package/dist/shim.js +2 -0
- package/dist/shim.js.map +1 -0
- package/dist/waline.cjs.d.ts +388 -0
- package/dist/waline.cjs.js +2 -0
- package/dist/waline.cjs.js.map +1 -0
- package/dist/waline.css +1 -0
- package/dist/waline.css.map +1 -0
- package/dist/waline.d.ts +388 -0
- package/dist/waline.esm.d.ts +388 -0
- package/dist/waline.esm.js +2 -0
- package/dist/waline.esm.js.map +1 -0
- package/dist/waline.js +2 -0
- package/dist/waline.js.map +1 -0
- package/package.json +33 -18
- package/src/comment.ts +39 -0
- package/src/components/CommentBox.vue +667 -0
- package/src/components/CommentCard.vue +125 -0
- package/src/components/Icons.ts +124 -0
- package/src/components/Waline.vue +359 -0
- package/src/composables/index.ts +3 -0
- package/src/composables/inputs.ts +29 -0
- package/src/composables/store.ts +38 -0
- package/src/composables/userInfo.ts +27 -0
- package/src/config/default.ts +21 -0
- package/src/config/i18n/en.ts +34 -0
- package/src/config/i18n/generate.ts +39 -0
- package/src/config/i18n/index.ts +30 -0
- package/src/config/i18n/jp.ts +34 -0
- package/src/config/i18n/pt-BR.ts +34 -0
- package/src/config/i18n/ru.ts +34 -0
- package/src/config/i18n/vi-VN.ts +34 -0
- package/src/config/i18n/zh-CN.ts +34 -0
- package/src/config/i18n/zh-TW.ts +34 -0
- package/src/config/index.ts +2 -0
- package/src/entrys/components.ts +2 -0
- package/src/entrys/full.ts +7 -0
- package/src/entrys/init.ts +4 -0
- package/src/entrys/pageview.ts +2 -0
- package/src/init.ts +92 -0
- package/src/pageview.ts +100 -0
- package/src/shims-hanabi.d.ts +9 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/styles/base.scss +67 -0
- package/src/styles/card.scss +223 -0
- package/src/styles/config.scss +52 -0
- package/src/styles/emoji.scss +118 -0
- package/src/styles/highlight.scss +135 -0
- package/src/styles/index.scss +12 -0
- package/src/styles/layout.scss +78 -0
- package/src/styles/nomalize.scss +112 -0
- package/src/styles/panel.scss +293 -0
- package/src/styles/recent.scss +3 -0
- package/src/typings/base.ts +54 -0
- package/src/typings/comment.ts +25 -0
- package/src/typings/index.ts +5 -0
- package/src/typings/locale.ts +32 -0
- package/src/typings/options.ts +41 -0
- package/src/typings/waline.ts +208 -0
- package/src/utils/config.ts +99 -0
- package/src/utils/darkmode.ts +11 -0
- package/src/utils/data.ts +10 -0
- package/src/utils/emoji.ts +75 -0
- package/src/utils/error.ts +3 -0
- package/src/utils/fetch.ts +177 -0
- package/src/utils/getRoot.ts +8 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/markdown.ts +41 -0
- package/src/utils/markedMathExtension.ts +52 -0
- package/src/utils/path.ts +15 -0
- package/src/utils/query.ts +2 -0
- package/src/utils/timeAgo.ts +75 -0
- package/src/utils/userInfo.ts +26 -0
- package/src/utils/wordCount.ts +20 -0
- package/src/version.ts +3 -0
- package/src/widgets/index.ts +1 -0
- package/src/widgets/recentComments.ts +52 -0
- package/dist/Waline.min.js +0 -2
- package/dist/Waline.min.js.map +0 -1
- package/dist/Waline.noStyle.js +0 -2
- package/dist/Waline.noStyle.js.map +0 -1
- 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,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
|
+
};
|