Skip to content

Commit f30787e

Browse files
HeZeBangGregTaooCNDY1390
authored
feat: oauth and reply (#49)
* feat: make search case-insensitive * fix: add autocomplete attributes to enable password autofill support (#42) * feat: revision date and footer (#44) * feat: revision date and hash * feat: workflow to create release * feat: different release * chore: v1.5.0 * fix: release quote * feat: finish oauth to geekpie uni-auth (#46) * feat: casdoor callback * fix: casdoor callback * feat: ui update and binding indicator * fix: move binding to user page * feat: reply and chore update (#47) * feat: reply support * fix: annonymuous bug * feat: reply orders * feat: scroll to comment * temp: disabled share reply * chore: ui fix * chore: v1.6.0-beta * chore: v1.6.0 --------- Co-authored-by: GregTao <gregtaoo@outlook.com> Co-authored-by: CNDY <60209538+CNDY1390@users.noreply.github.com>
1 parent 81e3c90 commit f30787e

File tree

19 files changed

+912
-17
lines changed

19 files changed

+912
-17
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "coursebench-frontend",
3-
"version": "1.5.1",
3+
"version": "1.6.0",
44
"private": true,
55
"scripts": {
66
"serve": "vue-cli-service serve",

src/components/courses/CourseCommentCard.vue

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<v-lazy>
2+
<v-lazy v-model="isActive">
33
<v-card class="mb-3" flat outlined ref="commentCard">
44
<CommentCardBar :comment="comment">
55
<template v-slot:headerAvatar="{ localComment }">
@@ -33,7 +33,7 @@
3333
{{
3434
localComment.user
3535
? gradeItems[localComment.user.grade]
36-
: '由匿名用户发送,请仔细分辨其真实性'
36+
: '匿名发送,请仔细分辨其真实性'
3737
}}
3838
</div>
3939
</div>
@@ -157,6 +157,7 @@
157157
</div>
158158
</template>
159159
</CommentCardContent>
160+
<ReplySection :comment-id="comment.id" class="px-2 px-sm-4 pb-3" />
160161
</v-card>
161162
</v-lazy>
162163
</template>
@@ -176,16 +177,22 @@ import {
176177
shareLogoLight,
177178
shareLogoTitle,
178179
} from '@/composables/global/useShare';
180+
import ReplySection from '@/components/courses/ReplySection';
179181
180182
export default {
181183
props: {
182184
comment: Object,
185+
disableLazy: {
186+
type: Boolean,
187+
default: false,
188+
},
183189
},
184190
components: {
185191
CommentCardContent,
186192
CommentCardBar,
187193
AvatarContainer,
188194
CommentFold,
195+
ReplySection,
189196
},
190197
setup() {
191198
const { doLike, doDislike, doUndo, formStatus, statics } =
@@ -208,6 +215,21 @@ export default {
208215
showSnackbar,
209216
};
210217
},
218+
data() {
219+
return {
220+
localIsActive: false,
221+
};
222+
},
223+
computed: {
224+
isActive: {
225+
get() {
226+
return this.disableLazy || this.localIsActive;
227+
},
228+
set(val) {
229+
this.localIsActive = val;
230+
},
231+
},
232+
},
211233
mounted() {
212234
this.formStatus.likeStatus = this.comment.like_status;
213235
},
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<template>
2+
<v-dialog :value="value" max-width="760" @input="$emit('input', $event)">
3+
<v-card>
4+
<v-card-title class="d-flex justify-space-between">
5+
<span>查看对话</span>
6+
<v-btn icon @click="$emit('input', false)">
7+
<v-icon>{{ icons.mdiClose }}</v-icon>
8+
</v-btn>
9+
</v-card-title>
10+
<v-divider></v-divider>
11+
<v-card-text style="max-height: 70vh; overflow-y: auto">
12+
<div v-if="loading" class="py-4 d-flex justify-center">
13+
<v-progress-circular indeterminate color="primary" />
14+
</div>
15+
<div v-else-if="!chain">
16+
<div class="text-caption grey--text">暂无对话内容</div>
17+
</div>
18+
<div v-else>
19+
<div class="text-subtitle-2 mb-2">上文</div>
20+
<v-card
21+
v-for="ancestor in chain.ancestors"
22+
:key="ancestor.id"
23+
outlined
24+
class="pa-2 mb-2"
25+
>
26+
<div class="d-flex align-start">
27+
<AvatarContainer
28+
:name="displayName(ancestor.user)"
29+
:src="ancestor.user ? ancestor.user.avatar : ''"
30+
size="28"
31+
small
32+
tile
33+
slice
34+
/>
35+
<div class="pl-2 flex-grow-1">
36+
<div class="text-caption font-weight-bold">
37+
{{ displayName(ancestor.user) }}
38+
<span
39+
v-if="ancestor.reply_to && ancestor.reply_to.user"
40+
class="grey--text text--darken-1"
41+
>
42+
回复 {{ displayName(ancestor.reply_to.user) }}
43+
</span>
44+
<span
45+
v-else-if="ancestor.reply_to"
46+
class="grey--text text--darken-1"
47+
>
48+
回复 匿名用户
49+
</span>
50+
</div>
51+
<div class="text-body-2 break-word">{{ ancestor.content }}</div>
52+
<div class="text-caption grey--text text--darken-1">
53+
{{ unixToReadable(ancestor.post_time) }}
54+
</div>
55+
</div>
56+
</div>
57+
</v-card>
58+
59+
<div class="text-subtitle-2 mb-2 mt-4">当前回复</div>
60+
<v-card outlined class="pa-2 mb-3 primary--text">
61+
<div class="d-flex align-start">
62+
<AvatarContainer
63+
:name="displayName(chain.current.user)"
64+
:src="chain.current.user ? chain.current.user.avatar : ''"
65+
size="28"
66+
small
67+
tile
68+
slice
69+
/>
70+
<div class="pl-2 flex-grow-1">
71+
<div class="text-caption font-weight-bold">
72+
{{ displayName(chain.current.user) }}
73+
<span
74+
v-if="chain.current.reply_to && chain.current.reply_to.user"
75+
class="grey--text text--darken-1"
76+
>
77+
回复 {{ displayName(chain.current.reply_to.user) }}
78+
</span>
79+
<span
80+
v-else-if="chain.current.reply_to"
81+
class="grey--text text--darken-1"
82+
>
83+
回复 匿名用户
84+
</span>
85+
</div>
86+
<div class="text-body-2 break-word">{{ chain.current.content }}</div>
87+
<div class="text-caption grey--text text--darken-1">
88+
{{ unixToReadable(chain.current.post_time) }}
89+
</div>
90+
</div>
91+
</div>
92+
</v-card>
93+
94+
<div class="text-subtitle-2 mb-2">下文</div>
95+
<ReplyChainTree :nodes="chain.descendants || []" :depth="0" />
96+
</div>
97+
</v-card-text>
98+
</v-card>
99+
</v-dialog>
100+
</template>
101+
102+
<script>
103+
import { mdiClose } from '@mdi/js';
104+
import AvatarContainer from '@/components/users/profile/AvatarContainer';
105+
import ReplyChainTree from '@/components/courses/ReplyChainTree';
106+
import useUserName from '@/composables/global/useUserName';
107+
import { unixToReadable } from '@/composables/global/useTimeUtils';
108+
109+
export default {
110+
components: {
111+
AvatarContainer,
112+
ReplyChainTree,
113+
},
114+
props: {
115+
value: {
116+
type: Boolean,
117+
required: true,
118+
},
119+
chain: {
120+
type: Object,
121+
default: null,
122+
},
123+
loading: {
124+
type: Boolean,
125+
default: false,
126+
},
127+
},
128+
data() {
129+
return {
130+
icons: {
131+
mdiClose,
132+
},
133+
};
134+
},
135+
methods: {
136+
unixToReadable,
137+
displayName(user) {
138+
return user ? useUserName(user) : '匿名用户';
139+
},
140+
},
141+
};
142+
</script>
143+
144+
<style scoped>
145+
.break-word {
146+
word-break: break-word;
147+
}
148+
</style>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<template>
2+
<div>
3+
<div
4+
v-for="node in nodes"
5+
:key="node.reply.id"
6+
class="mb-2"
7+
>
8+
<v-card outlined class="pa-2">
9+
<div class="d-flex align-start">
10+
<AvatarContainer
11+
:name="displayName(node.reply.user)"
12+
:src="node.reply.user ? node.reply.user.avatar : ''"
13+
size="28"
14+
small
15+
tile
16+
slice
17+
/>
18+
<div class="pl-2 flex-grow-1">
19+
<div class="text-caption font-weight-bold">
20+
{{ displayName(node.reply.user) }}
21+
<span v-if="node.reply.reply_to && node.reply.reply_to.user" class="grey--text text--darken-1">
22+
回复 {{ displayName(node.reply.reply_to.user) }}
23+
</span>
24+
<span v-else-if="node.reply.reply_to" class="grey--text text--darken-1">
25+
回复 匿名用户
26+
</span>
27+
</div>
28+
<div class="text-body-2 break-word">{{ node.reply.content }}</div>
29+
<div class="text-caption grey--text text--darken-1">
30+
{{ unixToReadable(node.reply.post_time) }}
31+
</div>
32+
</div>
33+
</div>
34+
</v-card>
35+
<ReplyChainTree
36+
v-if="node.children && node.children.length > 0"
37+
:nodes="node.children"
38+
:depth="depth + 1"
39+
/>
40+
</div>
41+
</div>
42+
</template>
43+
44+
<script>
45+
import AvatarContainer from '@/components/users/profile/AvatarContainer';
46+
import useUserName from '@/composables/global/useUserName';
47+
import { unixToReadable } from '@/composables/global/useTimeUtils';
48+
49+
export default {
50+
name: 'ReplyChainTree',
51+
components: {
52+
AvatarContainer,
53+
},
54+
props: {
55+
nodes: {
56+
type: Array,
57+
required: true,
58+
},
59+
depth: {
60+
type: Number,
61+
default: 0,
62+
},
63+
},
64+
methods: {
65+
unixToReadable,
66+
displayName(user) {
67+
return user ? useUserName(user) : '匿名用户';
68+
},
69+
},
70+
};
71+
</script>
72+
73+
<style scoped>
74+
.break-word {
75+
word-break: break-word;
76+
}
77+
</style>

0 commit comments

Comments
 (0)