2023-12-13 14:03:02 +01:00
|
|
|
<script lang="ts">
|
2023-08-17 14:05:01 +01:00
|
|
|
import { goto } from '$app/navigation';
|
|
|
|
|
import pluralize from 'pluralize';
|
2025-11-01 15:49:29 +01:00
|
|
|
import TrashIcon from '@lucide/svelte/icons/trash';
|
2024-02-13 22:17:15 +01:00
|
|
|
import { Dropdown, SkeletonPlaceholder, SkeletonText } from 'carbon-components-svelte';
|
2025-11-01 15:49:29 +01:00
|
|
|
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
2023-08-17 14:05:01 +01:00
|
|
|
import Vote from '$lib/components/Vote/index.svelte';
|
|
|
|
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
|
|
|
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
|
|
|
|
import Avatar from '$lib/components/Avatar/index.svelte';
|
2025-11-01 15:49:29 +01:00
|
|
|
import { IconButton } from '$lib/components/IconButton';
|
|
|
|
|
import CircleCheckIcon from '$lib/components/Icons/CircleCheckIcon.svelte';
|
2024-02-13 22:17:15 +01:00
|
|
|
import { currentOrg, isOrgAdmin } from '$lib/utils/store/org';
|
2023-08-17 14:05:01 +01:00
|
|
|
import { profile } from '$lib/utils/store/user';
|
|
|
|
|
import { supabase } from '$lib/utils/functions/supabase';
|
2025-11-01 15:49:29 +01:00
|
|
|
import { askCommunityValidation, commentInCommunityValidation } from '$lib/utils/functions/validator';
|
2023-09-27 05:35:06 +01:00
|
|
|
import { snackbar } from '$lib/components/Snackbar/store';
|
2023-08-17 14:05:01 +01:00
|
|
|
import TextField from '$lib/components/Form/TextField.svelte';
|
2023-12-13 14:03:02 +01:00
|
|
|
import DeleteModal from '$lib/components/Org/Community/DeleteModal.svelte';
|
2023-09-05 13:21:44 +00:00
|
|
|
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
2023-10-02 04:15:01 +01:00
|
|
|
import { calDateDiff } from '$lib/utils/functions/date';
|
2025-11-01 15:49:29 +01:00
|
|
|
import { untrack } from 'svelte';
|
2024-07-21 03:02:33 +01:00
|
|
|
import { fetchCourses } from '$lib/utils/services/courses';
|
2024-05-22 05:49:10 +01:00
|
|
|
import { t } from '$lib/utils/functions/translations';
|
|
|
|
|
import { courses } from '$lib/components/Courses/store';
|
|
|
|
|
import type { Course } from '$lib/utils/types';
|
2023-08-17 14:05:01 +01:00
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
let { data } = $props();
|
2023-08-17 14:05:01 +01:00
|
|
|
const { slug } = data;
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
interface Comment {
|
|
|
|
|
id: string;
|
|
|
|
|
authorId: string;
|
|
|
|
|
name: string;
|
|
|
|
|
avatar: string;
|
|
|
|
|
votes: number;
|
|
|
|
|
comment: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
}
|
|
|
|
|
interface Question {
|
|
|
|
|
id: string;
|
|
|
|
|
title: string;
|
|
|
|
|
votes: number;
|
|
|
|
|
author: {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
avatar: string;
|
|
|
|
|
};
|
|
|
|
|
body: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
comments: Comment[];
|
|
|
|
|
totalComments: number;
|
2024-02-13 22:17:15 +01:00
|
|
|
courseId: string;
|
2023-12-13 14:03:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
let question: Question | undefined = $state();
|
|
|
|
|
let comment = $state('');
|
2023-12-13 14:03:02 +01:00
|
|
|
let errors: {
|
|
|
|
|
title?: string;
|
2024-02-13 22:17:15 +01:00
|
|
|
courseId?: string;
|
2025-11-01 15:49:29 +01:00
|
|
|
} = $state({});
|
2023-08-17 14:05:01 +01:00
|
|
|
let isValidAnswer = false; // V2 allow admin mark an answer as accepted
|
2023-12-13 14:03:02 +01:00
|
|
|
let voted: {
|
|
|
|
|
question: boolean;
|
|
|
|
|
comment: {
|
|
|
|
|
[key: string]: boolean;
|
|
|
|
|
};
|
2025-11-01 15:49:29 +01:00
|
|
|
} = $state({ question: false, comment: {} });
|
|
|
|
|
let isEditMode = $state(false);
|
|
|
|
|
let deleteComment = $state({
|
2023-08-17 14:05:01 +01:00
|
|
|
shouldDelete: false,
|
2023-12-13 14:28:10 +03:00
|
|
|
commentId: '',
|
|
|
|
|
isDeleting: false
|
2025-11-01 15:49:29 +01:00
|
|
|
});
|
|
|
|
|
let deleteQuestion = $state({
|
2023-12-13 14:03:02 +01:00
|
|
|
shouldDelete: false,
|
2023-12-13 14:28:10 +03:00
|
|
|
questionId: '',
|
|
|
|
|
isDeleting: false
|
2025-11-01 15:49:29 +01:00
|
|
|
});
|
|
|
|
|
let editContent = $state({
|
2023-08-17 14:05:01 +01:00
|
|
|
title: '',
|
2024-02-13 22:17:15 +01:00
|
|
|
body: '',
|
|
|
|
|
courseId: ''
|
2025-11-01 15:49:29 +01:00
|
|
|
});
|
2023-08-17 14:05:01 +01:00
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
let editorInstance = $state(false);
|
|
|
|
|
let fetchedCourses: Course[] = $state([]);
|
2023-09-05 13:21:44 +00:00
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
function mapResToQuestion(data): Question {
|
2023-08-17 14:05:01 +01:00
|
|
|
return {
|
|
|
|
|
id: data.id,
|
|
|
|
|
title: data.title,
|
|
|
|
|
votes: data.votes,
|
|
|
|
|
author: {
|
2023-12-13 14:03:02 +01:00
|
|
|
id: data?.author?.id || '',
|
|
|
|
|
name: data?.author?.fullname || '',
|
|
|
|
|
avatar: data?.author?.avatar_url || ''
|
2023-08-17 14:05:01 +01:00
|
|
|
},
|
|
|
|
|
body: data.body,
|
2023-10-02 04:15:01 +01:00
|
|
|
createdAt: calDateDiff(data.created_at),
|
2023-08-17 14:05:01 +01:00
|
|
|
comments: data.comments.map((c) => ({
|
|
|
|
|
id: c.id,
|
2023-12-13 14:03:02 +01:00
|
|
|
authorId: c.author?.id || '',
|
|
|
|
|
name: c.author?.fullname || '',
|
|
|
|
|
avatar: c.author?.avatar_url || '',
|
2023-08-17 14:05:01 +01:00
|
|
|
votes: c.votes,
|
|
|
|
|
comment: c.body,
|
2023-10-02 04:15:01 +01:00
|
|
|
createdAt: calDateDiff(c.created_at)
|
2023-08-17 14:05:01 +01:00
|
|
|
})),
|
2024-02-13 22:17:15 +01:00
|
|
|
totalComments: 0,
|
|
|
|
|
courseId: data.course_id
|
2023-08-17 14:05:01 +01:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-13 22:17:15 +01:00
|
|
|
async function getCourses(userId: string | null, orgId: string) {
|
2024-05-22 05:49:10 +01:00
|
|
|
if ($courses.length) {
|
|
|
|
|
fetchedCourses = [...$courses];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-13 22:17:15 +01:00
|
|
|
const coursesResults = await fetchCourses(userId, orgId);
|
2024-05-22 05:49:10 +01:00
|
|
|
fetchedCourses = coursesResults?.allCourses || [];
|
2024-02-13 22:17:15 +01:00
|
|
|
}
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
async function fetchCommunityQuestion(slug: string) {
|
2023-08-17 14:05:01 +01:00
|
|
|
if (!slug) return;
|
|
|
|
|
|
|
|
|
|
const { data, error } = await supabase
|
|
|
|
|
.from('community_question')
|
|
|
|
|
.select(
|
|
|
|
|
`
|
|
|
|
|
id,
|
|
|
|
|
title,
|
|
|
|
|
body,
|
|
|
|
|
votes,
|
|
|
|
|
created_at,
|
2024-02-13 22:17:15 +01:00
|
|
|
course_id,
|
2023-12-13 14:03:02 +01:00
|
|
|
slug,
|
2023-08-17 14:05:01 +01:00
|
|
|
comments:community_answer(
|
|
|
|
|
id,
|
|
|
|
|
body,
|
|
|
|
|
votes,
|
|
|
|
|
created_at,
|
2023-12-13 14:03:02 +01:00
|
|
|
author:profile(id, fullname, avatar_url)
|
2023-08-17 14:05:01 +01:00
|
|
|
),
|
2024-02-13 22:17:15 +01:00
|
|
|
author:profile(id, fullname, avatar_url),
|
|
|
|
|
course!inner (
|
|
|
|
|
title
|
|
|
|
|
)
|
2023-08-17 14:05:01 +01:00
|
|
|
`
|
|
|
|
|
)
|
|
|
|
|
.eq('slug', slug)
|
|
|
|
|
.single();
|
|
|
|
|
|
|
|
|
|
if (error) {
|
2023-12-13 14:03:02 +01:00
|
|
|
console.error('[LMS] Error loading community', error);
|
|
|
|
|
return goto(`/lms`);
|
2023-08-17 14:05:01 +01:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
untrack(() => {
|
|
|
|
|
question = mapResToQuestion(data);
|
|
|
|
|
question.totalComments = question.comments.length;
|
|
|
|
|
});
|
2023-08-17 14:05:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function submitComment() {
|
2025-11-01 15:49:29 +01:00
|
|
|
if (!question) return;
|
|
|
|
|
|
2023-08-17 14:05:01 +01:00
|
|
|
errors = commentInCommunityValidation({ comment });
|
|
|
|
|
console.log('submitComment errors', errors);
|
|
|
|
|
|
|
|
|
|
if (Object.keys(errors).length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { data, error } = await supabase
|
|
|
|
|
.from('community_answer')
|
|
|
|
|
.insert({
|
|
|
|
|
id: undefined,
|
|
|
|
|
body: comment,
|
|
|
|
|
question_id: question.id,
|
2023-12-13 14:03:02 +01:00
|
|
|
author_profile_id: $profile.id,
|
2023-08-17 14:05:01 +01:00
|
|
|
votes: 0
|
|
|
|
|
})
|
|
|
|
|
.select();
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
console.error('Error: commenting', error);
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.error('snackbar.community.error.try_again');
|
2023-08-17 14:05:01 +01:00
|
|
|
} else {
|
|
|
|
|
console.log('Success: commenting', data);
|
|
|
|
|
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.success('snackbar.community.success.comment_submitted');
|
2023-08-17 14:05:01 +01:00
|
|
|
|
|
|
|
|
// Add to comment
|
|
|
|
|
const _c = data?.[0];
|
|
|
|
|
question.comments = [
|
|
|
|
|
...question.comments,
|
|
|
|
|
{
|
|
|
|
|
id: _c.id,
|
2023-12-13 14:03:02 +01:00
|
|
|
authorId: $profile.id || '',
|
2023-08-17 14:05:01 +01:00
|
|
|
name: $profile?.fullname || '',
|
|
|
|
|
avatar: $profile?.avatar_url || '',
|
|
|
|
|
votes: 0,
|
|
|
|
|
comment: _c.body,
|
2023-10-02 04:15:01 +01:00
|
|
|
createdAt: calDateDiff(_c.created_at)
|
2023-08-17 14:05:01 +01:00
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Reset input
|
|
|
|
|
comment = '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
async function upvoteQuestion(type: string, commentId?: string) {
|
2025-11-01 15:49:29 +01:00
|
|
|
if (!question) return;
|
|
|
|
|
|
2023-08-17 14:05:01 +01:00
|
|
|
const isQuestion = type === 'question';
|
|
|
|
|
|
|
|
|
|
if (isQuestion && voted.question) return;
|
2023-12-13 14:03:02 +01:00
|
|
|
if (!isQuestion && commentId && voted.comment[commentId]) return;
|
2023-08-17 14:05:01 +01:00
|
|
|
|
|
|
|
|
const table = isQuestion ? 'community_question' : 'community_answer';
|
|
|
|
|
const matchId = isQuestion ? question.id : commentId;
|
|
|
|
|
let votes = 0;
|
|
|
|
|
|
|
|
|
|
if (isQuestion) {
|
|
|
|
|
question.votes = question.votes + 1;
|
|
|
|
|
votes = question.votes;
|
|
|
|
|
} else {
|
|
|
|
|
question.comments = question.comments.map((c) => {
|
|
|
|
|
if (c.id === commentId) {
|
|
|
|
|
c.votes = c.votes + 1;
|
|
|
|
|
votes = c.votes;
|
|
|
|
|
}
|
|
|
|
|
return c;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
const { error } = await supabase.from(table).update({ votes }).match({ id: matchId });
|
|
|
|
|
if (error) {
|
|
|
|
|
console.error('Error: upvoteQuestion', error);
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.error('snackbar.community.error.try_again');
|
2023-08-17 14:05:01 +01:00
|
|
|
} else {
|
|
|
|
|
if (isQuestion) {
|
|
|
|
|
voted.question = true;
|
2023-12-13 14:03:02 +01:00
|
|
|
} else if (commentId) {
|
2023-08-17 14:05:01 +01:00
|
|
|
voted.comment[commentId] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleQuestionEdit() {
|
2025-11-01 15:49:29 +01:00
|
|
|
if (!question) return;
|
|
|
|
|
|
2023-08-17 14:05:01 +01:00
|
|
|
if (isEditMode) {
|
2024-02-13 22:17:15 +01:00
|
|
|
errors = askCommunityValidation(editContent);
|
2023-08-17 14:05:01 +01:00
|
|
|
console.log('handleQuestionEdit errors', errors);
|
|
|
|
|
|
|
|
|
|
if (Object.keys(errors).length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isEditMode = !isEditMode;
|
2023-09-05 13:21:44 +00:00
|
|
|
editorInstance = !editorInstance;
|
2023-08-17 14:05:01 +01:00
|
|
|
|
|
|
|
|
if (!isEditMode) {
|
2024-02-13 22:17:15 +01:00
|
|
|
errors = askCommunityValidation(editContent);
|
2023-08-17 14:05:01 +01:00
|
|
|
console.log('handleQuestionEdit errors', errors);
|
|
|
|
|
|
|
|
|
|
if (Object.keys(errors).length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const { error } = await supabase
|
|
|
|
|
.from('community_question')
|
2024-02-13 22:17:15 +01:00
|
|
|
.update({
|
2024-04-19 05:45:48 +01:00
|
|
|
title: editContent.title,
|
|
|
|
|
body: editContent.body,
|
2024-02-13 22:17:15 +01:00
|
|
|
course_id: editContent.courseId
|
|
|
|
|
})
|
2023-08-17 14:05:01 +01:00
|
|
|
.match({ id: question.id });
|
|
|
|
|
if (error) {
|
|
|
|
|
console.error('Error: handleQuestionEdit', error);
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.error('snackbar.community.error.try_again');
|
2023-08-17 14:05:01 +01:00
|
|
|
} else {
|
2024-02-13 22:17:15 +01:00
|
|
|
question.title = editContent.title;
|
|
|
|
|
question.body = editContent.body;
|
|
|
|
|
question.courseId = editContent.courseId;
|
2023-08-17 14:05:01 +01:00
|
|
|
|
|
|
|
|
editContent.title = '';
|
|
|
|
|
editContent.body = '';
|
2024-02-13 22:17:15 +01:00
|
|
|
editContent.courseId = '';
|
2023-08-17 14:05:01 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
editContent.title = question.title;
|
|
|
|
|
editContent.body = question.body;
|
2024-02-13 22:17:15 +01:00
|
|
|
editContent.courseId = question.courseId;
|
2023-08-17 14:05:01 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
async function handleDelete(isQuestion: boolean) {
|
2025-11-01 15:49:29 +01:00
|
|
|
if (!question) return;
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
if (!isQuestion) {
|
2023-12-13 14:28:10 +03:00
|
|
|
deleteComment.isDeleting = true;
|
|
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
const { error } = await supabase.from('community_answer').delete().match({ id: deleteComment.commentId });
|
2023-12-13 14:03:02 +01:00
|
|
|
|
2023-12-13 14:28:10 +03:00
|
|
|
deleteComment.isDeleting = false;
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
if (error) {
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.error('snackbar.community.error.deleting_comments');
|
2023-12-13 14:03:02 +01:00
|
|
|
console.log('Error deleting comments', error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.success('snackbar.community.success.success_delete');
|
2023-12-13 14:03:02 +01:00
|
|
|
|
|
|
|
|
question.comments = question.comments.filter((c) => c.id !== deleteComment.commentId);
|
|
|
|
|
deleteComment.shouldDelete = false;
|
|
|
|
|
deleteComment.commentId = '';
|
|
|
|
|
|
|
|
|
|
// Handle only delete comment
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-12-13 14:28:10 +03:00
|
|
|
deleteQuestion.isDeleting = true;
|
2023-12-13 14:03:02 +01:00
|
|
|
|
|
|
|
|
const { error: commentDeleteError } = await supabase
|
2023-08-17 14:05:01 +01:00
|
|
|
.from('community_answer')
|
|
|
|
|
.delete()
|
2023-12-13 14:03:02 +01:00
|
|
|
.match({ question_id: deleteQuestion.questionId });
|
2023-08-17 14:05:01 +01:00
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
if (commentDeleteError) {
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.error('snackbar.community.error.deleting_comments');
|
2023-12-13 14:03:02 +01:00
|
|
|
console.log('Error deleting comments', commentDeleteError);
|
2023-12-13 14:28:10 +03:00
|
|
|
|
|
|
|
|
deleteQuestion.isDeleting = false;
|
2023-12-13 14:03:02 +01:00
|
|
|
return;
|
2023-08-17 14:05:01 +01:00
|
|
|
}
|
2023-12-13 14:03:02 +01:00
|
|
|
|
|
|
|
|
const { error: questionDeleteError } = await supabase
|
|
|
|
|
.from('community_question')
|
|
|
|
|
.delete()
|
|
|
|
|
.match({ id: deleteQuestion.questionId });
|
|
|
|
|
|
|
|
|
|
if (questionDeleteError) {
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.error('snackbar.community.error.deleting_question');
|
2023-12-13 14:03:02 +01:00
|
|
|
console.log('Error deleting question', questionDeleteError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-19 05:45:48 +01:00
|
|
|
snackbar.success('snackbar.community.success.success_delete');
|
2023-12-13 14:03:02 +01:00
|
|
|
goto(`/lms/community`);
|
2023-12-13 14:28:10 +03:00
|
|
|
deleteQuestion.isDeleting = false;
|
2023-08-17 14:05:01 +01:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
$effect(() => {
|
|
|
|
|
fetchCommunityQuestion(slug);
|
|
|
|
|
});
|
|
|
|
|
$effect(() => {
|
2024-02-13 22:17:15 +01:00
|
|
|
if ($profile.id && $currentOrg.id) {
|
|
|
|
|
getCourses($profile.id, $currentOrg.id);
|
|
|
|
|
}
|
2025-11-01 15:49:29 +01:00
|
|
|
});
|
2023-08-17 14:05:01 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head>
|
|
|
|
|
<title>{question?.title || 'Question'}</title>
|
|
|
|
|
</svelte:head>
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
<DeleteModal
|
|
|
|
|
bind:open={deleteQuestion.shouldDelete}
|
2025-11-01 15:49:29 +01:00
|
|
|
isDeleting={deleteQuestion.isDeleting}
|
2023-12-13 14:03:02 +01:00
|
|
|
onCancel={() => {
|
|
|
|
|
deleteQuestion.shouldDelete = false;
|
|
|
|
|
deleteQuestion.questionId = '';
|
|
|
|
|
}}
|
|
|
|
|
onDelete={() => handleDelete(true)}
|
|
|
|
|
isQuestion={true}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<DeleteModal
|
2023-08-17 14:05:01 +01:00
|
|
|
bind:open={deleteComment.shouldDelete}
|
2023-12-13 14:28:10 +03:00
|
|
|
isDeleting={deleteComment.isDeleting}
|
2023-08-17 14:05:01 +01:00
|
|
|
onCancel={() => {
|
|
|
|
|
deleteComment.shouldDelete = false;
|
2023-12-13 14:03:02 +01:00
|
|
|
deleteComment.commentId = '';
|
2023-08-17 14:05:01 +01:00
|
|
|
}}
|
2023-12-13 14:03:02 +01:00
|
|
|
onDelete={() => handleDelete(false)}
|
2023-08-17 14:05:01 +01:00
|
|
|
/>
|
2025-11-01 15:49:29 +01:00
|
|
|
<section class="mx-auto max-w-3xl md:mx-10 lg:mb-20">
|
2023-08-17 14:05:01 +01:00
|
|
|
{#if !question}
|
2025-11-01 15:49:29 +01:00
|
|
|
<div class="mb-3 px-5 py-10">
|
2023-08-17 14:05:01 +01:00
|
|
|
<SkeletonText style="width: 25%;" />
|
|
|
|
|
<SkeletonText style="width: 100%; margin-bottom: 2rem" />
|
|
|
|
|
<SkeletonPlaceholder style="width: 100%; height: 20rem;" />
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
2025-11-01 15:49:29 +01:00
|
|
|
<div class="px-5 py-10">
|
|
|
|
|
<a class="text-md flex items-center text-gray-500 dark:text-white" href="/lms/community">
|
|
|
|
|
<ArrowLeftIcon size={16} />
|
2024-04-24 23:01:20 +01:00
|
|
|
{$t('community.ask.go_back')}
|
2023-08-17 14:05:01 +01:00
|
|
|
</a>
|
2025-11-01 15:49:29 +01:00
|
|
|
<div class="my-5 flex items-center justify-between">
|
2023-08-17 14:05:01 +01:00
|
|
|
{#if isEditMode}
|
2025-11-01 15:49:29 +01:00
|
|
|
<TextField bind:value={editContent.title} className="w-full mr-2" errorMessage={errors.title} />
|
2024-02-13 22:17:15 +01:00
|
|
|
<Dropdown
|
2025-11-01 15:49:29 +01:00
|
|
|
class="h-full w-[25%]"
|
2024-02-13 22:17:15 +01:00
|
|
|
size="xl"
|
|
|
|
|
label="Select Course"
|
|
|
|
|
items={fetchedCourses.map((course) => ({ id: course.id, text: course.title }))}
|
|
|
|
|
bind:selectedId={editContent.courseId}
|
|
|
|
|
/>
|
2023-08-17 14:05:01 +01:00
|
|
|
{:else}
|
|
|
|
|
<div class="flex items-center">
|
2025-11-01 15:49:29 +01:00
|
|
|
<Vote value={question.votes} upVote={() => upvoteQuestion('question')} disabled={voted.question} />
|
2023-08-17 14:05:01 +01:00
|
|
|
<h2 class="text-3xl">{question.title}</h2>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if question.author.id === $profile.id}
|
|
|
|
|
<PrimaryButton
|
|
|
|
|
label={isEditMode ? 'Save' : 'Edit'}
|
|
|
|
|
variant={VARIANTS.OUTLINED}
|
|
|
|
|
onClick={handleQuestionEdit}
|
|
|
|
|
className="h-fit"
|
|
|
|
|
/>
|
|
|
|
|
{#if isEditMode}
|
|
|
|
|
<PrimaryButton
|
|
|
|
|
label="Cancel"
|
|
|
|
|
variant={VARIANTS.TEXT}
|
|
|
|
|
onClick={() => (isEditMode = !isEditMode)}
|
|
|
|
|
className="py-3 px-6 rounded-sm h-fit"
|
|
|
|
|
disablePadding={true}
|
|
|
|
|
/>
|
|
|
|
|
{/if}
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
2025-11-01 15:49:29 +01:00
|
|
|
<div class="border-1 border-gray my-1 rounded-lg border px-1">
|
|
|
|
|
<header class="flex items-center justify-between p-2 leading-none">
|
|
|
|
|
<div class="flex items-center text-black no-underline hover:underline">
|
|
|
|
|
<Avatar src={question.author.avatar} name={question.author.name} width="w-7" height="h-7" />
|
|
|
|
|
<p class="ml-2 text-sm dark:text-white">{question.author.name}</p>
|
|
|
|
|
<p class="ml-2 text-sm text-gray-500 dark:text-white">
|
2023-08-17 14:05:01 +01:00
|
|
|
{question.createdAt}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2023-12-13 14:03:02 +01:00
|
|
|
{#if question.author.id === $profile.id || $isOrgAdmin}
|
|
|
|
|
<IconButton
|
|
|
|
|
value="delete-question"
|
|
|
|
|
onClick={() => {
|
2025-11-01 15:49:29 +01:00
|
|
|
if (!question) return;
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
deleteQuestion.shouldDelete = true;
|
|
|
|
|
deleteQuestion.questionId = question.id;
|
|
|
|
|
}}
|
|
|
|
|
>
|
2025-11-01 15:49:29 +01:00
|
|
|
<TrashIcon size={16} />
|
2023-12-13 14:03:02 +01:00
|
|
|
</IconButton>
|
|
|
|
|
{/if}
|
2023-08-17 14:05:01 +01:00
|
|
|
</header>
|
2023-09-05 13:21:44 +00:00
|
|
|
{#if isEditMode && editorInstance}
|
2023-08-17 14:05:01 +01:00
|
|
|
<div class="my-2">
|
|
|
|
|
<TextEditor
|
2023-09-05 13:21:44 +00:00
|
|
|
bind:value={editContent.body}
|
|
|
|
|
placeholder="Give an answer"
|
|
|
|
|
onChange={(html) => (editContent.body = html)}
|
2023-08-17 14:05:01 +01:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<section class="prose prose-sm sm:prose p-2">
|
2023-10-08 15:34:37 +01:00
|
|
|
{@html question.body}
|
2023-08-17 14:05:01 +01:00
|
|
|
</section>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="my-8 font-bold">
|
2024-04-24 23:01:20 +01:00
|
|
|
{pluralize($t('community.answers'), question.totalComments, true)}
|
2023-08-17 14:05:01 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#each question.comments as comment}
|
2025-11-01 15:49:29 +01:00
|
|
|
<div class="my-5 flex items-start px-1">
|
2023-08-17 14:05:01 +01:00
|
|
|
<Vote
|
|
|
|
|
value={comment.votes}
|
|
|
|
|
upVote={() => upvoteQuestion('comment', comment.id)}
|
|
|
|
|
disabled={voted.comment[comment.id]}
|
|
|
|
|
/>
|
2025-11-01 15:49:29 +01:00
|
|
|
<div class="border-1 border-gray w-full rounded-lg border">
|
|
|
|
|
<header class="flex items-center justify-between p-2 leading-none">
|
2023-08-17 14:05:01 +01:00
|
|
|
<div class="flex items-center text-black">
|
|
|
|
|
<Avatar src={comment.avatar} name={comment.name} width="w-7" height="h-7" />
|
2025-11-01 15:49:29 +01:00
|
|
|
<p class="ml-2 text-sm dark:text-white">{comment.name}</p>
|
|
|
|
|
<p class="ml-2 text-sm text-gray-500 dark:text-white">
|
2023-08-17 14:05:01 +01:00
|
|
|
{comment.createdAt}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if isValidAnswer}
|
2025-11-01 15:49:29 +01:00
|
|
|
<CircleCheckIcon size={16} />
|
2023-08-17 14:05:01 +01:00
|
|
|
{/if}
|
|
|
|
|
|
2023-12-13 14:03:02 +01:00
|
|
|
{#if comment.authorId === $profile.id || $isOrgAdmin}
|
2023-08-17 14:05:01 +01:00
|
|
|
<IconButton
|
|
|
|
|
value="delete-comment"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
deleteComment.shouldDelete = true;
|
|
|
|
|
deleteComment.commentId = comment.id;
|
|
|
|
|
}}
|
|
|
|
|
>
|
2025-11-01 15:49:29 +01:00
|
|
|
<TrashIcon size={16} />
|
2023-08-17 14:05:01 +01:00
|
|
|
</IconButton>
|
|
|
|
|
{/if}
|
|
|
|
|
</header>
|
|
|
|
|
<article class="prose prose-sm sm:prose p-2">
|
2023-10-08 15:34:37 +01:00
|
|
|
{@html comment.comment}
|
2023-08-17 14:05:01 +01:00
|
|
|
</article>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
|
|
|
|
|
<hr />
|
|
|
|
|
|
|
|
|
|
<div>
|
2023-09-05 13:21:44 +00:00
|
|
|
{#if !editorInstance}
|
2025-11-01 15:49:29 +01:00
|
|
|
<TextEditor bind:value={comment} placeholder="Give an answer" onChange={(html) => (comment = html)} />
|
2023-09-05 13:21:44 +00:00
|
|
|
{/if}
|
2023-08-17 14:05:01 +01:00
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
<div class="mr-2 flex justify-end">
|
2023-08-17 14:05:01 +01:00
|
|
|
<PrimaryButton label="Comment" onClick={submitComment} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</section>
|