Skip to content

Commit 7a56e38

Browse files
authored
Merge pull request #2405 from themeum/qna-reply-from-list
Feat: Qna reply right from list view
2 parents 0f1b1cf + 6026d45 commit 7a56e38

11 files changed

Lines changed: 572 additions & 295 deletions

File tree

assets/src/js/frontend/dashboard/pages/discussions.ts

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface ReplyCommentPayload {
88
comment_post_ID: number;
99
comment_parent: number;
1010
comment: string;
11+
reply_context?: 'list' | 'single';
1112
}
1213

1314
interface DeleteCommentPayload {
@@ -25,6 +26,7 @@ interface ReplyQnAPayload {
2526
course_id: number;
2627
question_id: number;
2728
answer: string;
29+
reply_context?: 'list' | 'single';
2830
}
2931
interface UpdateQnAPayload {
3032
question_id: number;
@@ -36,6 +38,8 @@ interface DeleteQnAPayload {
3638
context?: 'question' | 'reply';
3739
}
3840

41+
type DiscussionCardType = 'qna' | 'comment';
42+
3943
const FORM_ID_PREFIXES = {
4044
COMMENT_EDIT: 'lesson-comment-edit-',
4145
COMMENT_REPLY: 'lesson-comment-reply-form-',
@@ -87,6 +91,8 @@ const discussionsPage = () => {
8791
isArchived: false,
8892
editingId: null as number | null,
8993
editingFormId: null as string | null,
94+
replyingId: null as number | null,
95+
replyingCommentId: null as number | null,
9096
loadingReplies: false,
9197
repliesOrder: 'DESC',
9298
$nextTick: undefined as ((callback: () => void) => void) | undefined,
@@ -118,12 +124,19 @@ const discussionsPage = () => {
118124
this.replyCommentMutation = this.query.useMutation(this.replyComment, {
119125
onSuccess: (_, payload) => {
120126
toast.success(__('Reply saved successfully', 'tutor'));
121-
this.reloadReplies();
122127

123128
const formId = `${FORM_ID_PREFIXES.COMMENT_REPLY}${payload.comment_parent}`;
124129
if (form.hasForm(formId)) {
125130
form.reset(formId);
126131
}
132+
133+
if (payload.reply_context === 'single') {
134+
this.reloadReplies();
135+
} else {
136+
this.setReplyingComment(null);
137+
this.updateCommentReplyCount(payload.comment_parent);
138+
this.highlightCard(payload.comment_parent, 'comment');
139+
}
127140
},
128141
onError: (error: Error) => {
129142
toast.error(convertToErrorMessage(error));
@@ -197,11 +210,19 @@ const discussionsPage = () => {
197210
this.replyQnAMutation = this.query.useMutation(this.replyQnA, {
198211
onSuccess: (_, payload) => {
199212
toast.success(__('Reply saved successfully', 'tutor'));
200-
this.reloadReplies();
213+
201214
const formId = `${FORM_ID_PREFIXES.QNA_REPLY}${payload.question_id}`;
202215
if (form.hasForm(formId)) {
203216
form.reset(formId);
204217
}
218+
219+
if (payload.reply_context === 'single') {
220+
this.reloadReplies();
221+
} else {
222+
this.setReplying(null);
223+
this.updateReplyCount(payload.question_id);
224+
this.highlightCard(payload.question_id);
225+
}
205226
},
206227
onError: (error: Error) => {
207228
toast.error(convertToErrorMessage(error));
@@ -308,11 +329,17 @@ const discussionsPage = () => {
308329
}
309330
},
310331

311-
handleReplyComment(data: { comment: string }, commentId: number, courseId: number) {
332+
handleReplyComment(
333+
data: { comment: string },
334+
commentId: number,
335+
courseId: number,
336+
context: ReplyCommentPayload['reply_context'] = 'single',
337+
) {
312338
return this.replyCommentMutation?.mutate({
313339
comment: data.comment,
314340
comment_parent: commentId,
315341
comment_post_ID: courseId,
342+
reply_context: context,
316343
});
317344
},
318345

@@ -345,6 +372,86 @@ const discussionsPage = () => {
345372
});
346373
}
347374
},
375+
376+
setReplying(id: number | null) {
377+
this.replyingId = id;
378+
379+
if (id) {
380+
const formId = `${FORM_ID_PREFIXES.QNA_REPLY}${id}`;
381+
this.$nextTick?.(() => {
382+
if (form.hasForm(formId)) {
383+
form.setFocus(formId, 'answer');
384+
}
385+
});
386+
}
387+
},
388+
389+
toggleReply(id: number) {
390+
if (this.replyingId === id) {
391+
this.setReplying(null);
392+
} else {
393+
this.setReplying(id);
394+
}
395+
},
396+
397+
updateReplyCount(questionId: number) {
398+
// Find the reply count element and increment it
399+
const card = document.querySelector(`[data-question-id="${questionId}"]`);
400+
if (card) {
401+
const countElement = card.querySelector('.tutor-discussion-card-reply-count');
402+
if (countElement) {
403+
const currentCount = parseInt(countElement.textContent || '0', 10);
404+
countElement.textContent = String(currentCount + 1);
405+
}
406+
}
407+
},
408+
409+
setReplyingComment(id: number | null) {
410+
this.replyingCommentId = id;
411+
412+
if (id) {
413+
const formId = `${FORM_ID_PREFIXES.COMMENT_REPLY}${id}`;
414+
this.$nextTick?.(() => {
415+
if (form.hasForm(formId)) {
416+
form.setFocus(formId, 'comment');
417+
}
418+
});
419+
}
420+
},
421+
422+
toggleCommentReply(id: number) {
423+
if (this.replyingCommentId === id) {
424+
this.setReplyingComment(null);
425+
} else {
426+
this.setReplyingComment(id);
427+
}
428+
},
429+
430+
updateCommentReplyCount(commentId: number) {
431+
// Find the reply count element and increment it
432+
const card = document.querySelector(`[data-comment-id="${commentId}"]`);
433+
if (card) {
434+
const countElement = card.querySelector('.tutor-discussion-card-reply-count');
435+
if (countElement) {
436+
const currentCount = parseInt(countElement.textContent || '0', 10);
437+
countElement.textContent = String(currentCount + 1);
438+
}
439+
}
440+
},
441+
442+
highlightCard(id: number, type: DiscussionCardType = 'qna') {
443+
const attr = type === 'comment' ? 'data-comment-id' : 'data-question-id';
444+
const el = document.querySelector(`[${attr}="${id}"]`);
445+
const card = (el?.closest('.tutor-discussion-card') ?? el) as HTMLElement | null;
446+
if (!card) return;
447+
448+
if (window.innerWidth > 576) {
449+
card.style.boxShadow = '0 0 0 2px var(--tutor-text-brand-secondary)';
450+
setTimeout(() => {
451+
card.style.boxShadow = '';
452+
}, 300);
453+
}
454+
},
348455
};
349456
};
350457

assets/src/js/frontend/learning-area/pages/qna.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface ReplyQnaPayload {
1818
course_id: number;
1919
question_id: number;
2020
answer: string;
21+
reply_context?: 'list' | 'single';
2122
}
2223

2324
interface DeleteQnaPayload {
@@ -63,6 +64,7 @@ const qnaPage = () => {
6364
deleteQnAMutation: null as MutationState<unknown, unknown> | null,
6465
editingId: null as number | null,
6566
editingFormId: null as string | null,
67+
replyingId: null as number | null,
6668
loadingReplies: false,
6769
repliesOrder: 'DESC',
6870
$nextTick: undefined as ((callback: () => void) => void) | undefined,
@@ -106,11 +108,19 @@ const qnaPage = () => {
106108
this.replyQnAMutation = this.query.useMutation(this.replyQnA, {
107109
onSuccess: (_, payload) => {
108110
toast.success(__('Reply saved successfully', 'tutor'));
109-
this.reloadReplies();
111+
110112
const formId = `${FORM_ID_PREFIXES.QNA_REPLY}${payload.question_id}`;
111113
if (form.hasForm(formId)) {
112114
form.reset(formId);
113115
}
116+
117+
if (payload.reply_context === 'single') {
118+
this.reloadReplies();
119+
} else {
120+
this.setReplying(null);
121+
this.updateReplyCount(payload.question_id);
122+
this.highlightCard(payload.question_id);
123+
}
114124
},
115125
onError: (error: Error) => {
116126
toast.error(convertToErrorMessage(error));
@@ -204,6 +214,51 @@ const qnaPage = () => {
204214
}
205215
},
206216

217+
setReplying(id: number | null) {
218+
this.replyingId = id;
219+
220+
if (id) {
221+
const formId = `${FORM_ID_PREFIXES.QNA_REPLY}${id}`;
222+
this.$nextTick?.(() => {
223+
if (form.hasForm(formId)) {
224+
form.setFocus(formId, 'answer');
225+
}
226+
});
227+
}
228+
},
229+
230+
toggleReply(id: number) {
231+
if (this.replyingId === id) {
232+
this.setReplying(null);
233+
} else {
234+
this.setReplying(id);
235+
}
236+
},
237+
238+
updateReplyCount(questionId: number) {
239+
const card = document.querySelector(`[data-question-id="${questionId}"]`);
240+
if (card) {
241+
const countElement = card.querySelector('.tutor-discussion-card-reply-count');
242+
if (countElement) {
243+
const currentCount = parseInt(countElement.textContent || '0', 10);
244+
countElement.textContent = String(currentCount + 1);
245+
}
246+
}
247+
},
248+
249+
highlightCard(questionId: number) {
250+
const el = document.querySelector(`[data-question-id="${questionId}"]`);
251+
const card = (el?.closest('.tutor-discussion-card') ?? el) as HTMLElement | null;
252+
if (!card) return;
253+
254+
if (window.innerWidth > 576) {
255+
card.style.boxShadow = '0 0 0 2px var(--tutor-text-brand-secondary)';
256+
setTimeout(() => {
257+
card.style.boxShadow = '';
258+
}, 300);
259+
}
260+
},
261+
207262
handleKeydown(event: KeyboardEvent) {
208263
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
209264
(event.target as HTMLFormElement).closest('form')?.requestSubmit();

assets/src/scss/frontend/components/_discussion-card.scss

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
@use '@Core/scss/mixins' as *;
55

66
.tutor-discussion-card {
7-
@include tutor-flex(row, flex-start);
7+
@include tutor-flex(column, flex-start);
88
gap: $tutor-spacing-4;
99
background-color: $tutor-surface-l1;
1010
border: 1px solid $tutor-border-idle;
@@ -115,6 +115,12 @@
115115
@include tutor-button-reset();
116116
@include tutor-typography(tiny, medium, subdued);
117117
margin-right: auto;
118+
min-height: unset;
119+
background: unset !important;
120+
&:hover,
121+
&:focus {
122+
color: $tutor-text-brand !important;
123+
}
118124

119125
@include tutor-breakpoint-up(sm) {
120126
display: none;
@@ -177,4 +183,4 @@
177183
}
178184
}
179185
}
180-
}
186+
}

assets/src/scss/frontend/learning-area/layout/_layout.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
}
4141

4242
@include tutor-breakpoint-down(sm) {
43-
padding-bottom: 62px;
43+
padding-bottom : 62px;
4444
}
4545
}
46-
}
46+
}

templates/dashboard/account/billing.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
<?php Nav::make()->items( $page_nav_items )->render(); ?>
5454
</div>
5555
<?php } ?>
56-
<div class="tutor-sm-border tutor-sm-rounded-2xl tutor-sm-mt-4">
56+
<div class="tutor-sm-rounded-2xl tutor-sm-mt-4">
5757
<?php
5858
if ( file_exists( $tab_template ) ) {
5959
require_once $tab_template;

0 commit comments

Comments
 (0)