mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
Merge pull request #114 from learnhouse/swve/eng-107-quiz-block
Quiz Blocks
This commit is contained in:
commit
ab69824746
9 changed files with 340 additions and 142 deletions
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
import Link from "next/link";
|
||||
import { getUriWithOrg } from "@services/config/config";
|
||||
import Canva from "@components/Pages/Activities/DynamicCanva/DynamicCanva";
|
||||
import VideoActivity from "@components/Pages/Activities/Video/Video";
|
||||
import Canva from "@components/Objects/Activities/DynamicCanva/DynamicCanva";
|
||||
import VideoActivity from "@components/Objects/Activities/Video/Video";
|
||||
import { Check } from "lucide-react";
|
||||
import { markActivityAsComplete } from "@services/courses/activity";
|
||||
import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/DocumentPdf";
|
||||
import DocumentPdfActivity from "@components/Objects/Activities/DocumentPdf/DocumentPdf";
|
||||
import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators";
|
||||
import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import VideoBlock from "@components/Objects/Editor/Extensions/Video/VideoBlock";
|
|||
import MathEquationBlock from "@components/Objects/Editor/Extensions/MathEquation/MathEquationBlock";
|
||||
import PDFBlock from "@components/Objects/Editor/Extensions/PDF/PDFBlock";
|
||||
import { OrderedList } from "@tiptap/extension-ordered-list";
|
||||
import QuizBlock from "@components/Objects/Editor/Extensions/Quiz/QuizBlock";
|
||||
|
||||
interface Editor {
|
||||
content: string;
|
||||
|
|
@ -46,6 +47,10 @@ function Canva(props: Editor) {
|
|||
editable: true,
|
||||
activity: props.activity,
|
||||
}),
|
||||
QuizBlock.configure({
|
||||
editable: isEditable,
|
||||
activity: props.activity,
|
||||
}),
|
||||
Youtube.configure({
|
||||
controls: true,
|
||||
modestBranding: true,
|
||||
|
|
@ -6,7 +6,6 @@ import QuizBlockComponent from "./QuizBlockComponent";
|
|||
export default Node.create({
|
||||
name: "blockQuiz",
|
||||
group: "block",
|
||||
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
|
|
@ -14,6 +13,9 @@ export default Node.create({
|
|||
quizId: {
|
||||
value: null,
|
||||
},
|
||||
questions: {
|
||||
default: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,164 +1,323 @@
|
|||
import { NodeViewWrapper } from "@tiptap/react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { twJoin, twMerge } from 'tailwind-merge'
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { submitQuizBlock } from "@services/blocks/Quiz/quiz";
|
||||
import { BadgeHelp, Check, Info, Minus, MoreVertical, Plus, RefreshCcw, X } from "lucide-react";
|
||||
import ReactConfetti from "react-confetti";
|
||||
|
||||
function ImageBlockComponent(props: any) {
|
||||
const [questions, setQuestions] = React.useState([]) as any;
|
||||
const [answers, setAnswers] = React.useState([]) as any;
|
||||
interface Answer {
|
||||
answer_id: string;
|
||||
answer: string;
|
||||
correct: boolean;
|
||||
}
|
||||
interface Question {
|
||||
question_id: string;
|
||||
question: string;
|
||||
type: "multiple_choice" | 'custom_answer'
|
||||
answers: Answer[];
|
||||
}
|
||||
|
||||
function QuizBlockComponent(props: any) {
|
||||
const [questions, setQuestions] = React.useState(props.node.attrs.questions) as [Question[], any];
|
||||
const [userAnswers, setUserAnswers] = React.useState([]) as [any[], any];
|
||||
const [submitted, setSubmitted] = React.useState(false) as [boolean, any];
|
||||
const [submissionMessage, setSubmissionMessage] = React.useState("") as [string, any];
|
||||
const isEditable = props.extension.options.editable;
|
||||
|
||||
const handleAnswerClick = (question_id: string, answer_id: string) => {
|
||||
// if the quiz is submitted, do nothing
|
||||
if (submitted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userAnswer = {
|
||||
question_id: question_id,
|
||||
answer_id: answer_id
|
||||
}
|
||||
const newAnswers = [...userAnswers, userAnswer];
|
||||
|
||||
// only accept one answer per question
|
||||
const filteredAnswers = newAnswers.filter((answer: any) => answer.question_id !== question_id);
|
||||
|
||||
setUserAnswers([...filteredAnswers, userAnswer]);
|
||||
|
||||
function addSampleQuestion() {
|
||||
setQuestions([
|
||||
...questions,
|
||||
{
|
||||
question_id: "question_" + uuidv4(),
|
||||
question_value: "",
|
||||
options: [
|
||||
{
|
||||
option_id: "option_" + uuidv4(),
|
||||
option_data: "",
|
||||
option_type: "text",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const deleteQuestion = (index: number) => {
|
||||
let modifiedQuestions = [...questions];
|
||||
modifiedQuestions.splice(index, 1);
|
||||
setQuestions(modifiedQuestions);
|
||||
const refreshUserSubmission = () => {
|
||||
setUserAnswers([]);
|
||||
setSubmitted(false);
|
||||
}
|
||||
|
||||
const handleUserSubmission = () => {
|
||||
|
||||
// remove the answers from the answers array
|
||||
let modifiedAnswers = [...answers];
|
||||
modifiedAnswers = modifiedAnswers.filter((answer: any) => answer.question_id !== questions[index].question_id);
|
||||
setAnswers(modifiedAnswers);
|
||||
};
|
||||
if (userAnswers.length === 0) {
|
||||
setSubmissionMessage("Please answer at least one question!");
|
||||
return;
|
||||
}
|
||||
|
||||
const onQuestionChange = (e: any, index: number) => {
|
||||
let modifiedQuestions = [...questions];
|
||||
modifiedQuestions[index].question_value = e.target.value;
|
||||
setQuestions(modifiedQuestions);
|
||||
};
|
||||
setSubmitted(true);
|
||||
|
||||
const addOption = (question_id: string) => {
|
||||
// find the question index from the question_id and add the option to that question index
|
||||
let modifiedQuestions = [...questions];
|
||||
let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id);
|
||||
modifiedQuestions[questionIndex].options.push({
|
||||
option_id: "option_" + uuidv4(),
|
||||
option_data: "",
|
||||
option_type: "text",
|
||||
// check if all submitted answers are correct
|
||||
const correctAnswers = questions.map((question: Question) => {
|
||||
const correctAnswer: any = question.answers.find((answer: Answer) => answer.correct);
|
||||
const userAnswer = userAnswers.find((userAnswer: any) => userAnswer.question_id === question.question_id);
|
||||
if (correctAnswer.answer_id === userAnswer.answer_id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
setQuestions(modifiedQuestions);
|
||||
};
|
||||
|
||||
const deleteOption = (question_id: string, option_id: string) => {
|
||||
// find the option index from the option_id and delete the option from that option index
|
||||
let modifiedQuestions = [...questions];
|
||||
let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id);
|
||||
let optionIndex = modifiedQuestions[questionIndex].options.findIndex((option: any) => option.option_id === option_id);
|
||||
modifiedQuestions[questionIndex].options.splice(optionIndex, 1);
|
||||
setQuestions(modifiedQuestions);
|
||||
// check if all answers are correct
|
||||
const allCorrect = correctAnswers.every((answer: boolean) => answer === true);
|
||||
|
||||
// remove the answer from the answers array
|
||||
let answerIndex = answers.findIndex((answer: any) => answer.option_id === option_id);
|
||||
if (answerIndex !== -1) {
|
||||
let modifiedAnswers = [...answers];
|
||||
modifiedAnswers.splice(answerIndex, 1);
|
||||
setAnswers(modifiedAnswers);
|
||||
if (allCorrect) {
|
||||
setSubmissionMessage("All answers are correct!");
|
||||
console.log("All answers are correct!");
|
||||
}
|
||||
else {
|
||||
setSubmissionMessage("Some answers are incorrect!");
|
||||
console.log("Some answers are incorrect!");
|
||||
}
|
||||
};
|
||||
|
||||
const markOptionAsCorrect = (question_id: string, option_id: string) => {
|
||||
// find the option index from the option_id and mark the option as correct
|
||||
let answer = {
|
||||
question_id: question_id,
|
||||
option_id: option_id,
|
||||
};
|
||||
setAnswers([...answers, answer]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
const getAnswerID = (answerIndex: number, questionId : string) => {
|
||||
const alphabet = Array.from({ length: 26 }, (_, i) => String.fromCharCode('A'.charCodeAt(0) + i));
|
||||
let alphabetID = alphabet[answerIndex];
|
||||
|
||||
// Get question index
|
||||
const questionIndex = questions.findIndex((question: Question) => question.question_id === questionId);
|
||||
let questionID = questionIndex + 1;
|
||||
|
||||
return `${alphabetID}`;
|
||||
}
|
||||
|
||||
const saveQuestions = (questions: any) => {
|
||||
props.updateAttributes({
|
||||
questions: questions,
|
||||
});
|
||||
setQuestions(questions);
|
||||
|
||||
};
|
||||
|
||||
const saveQuiz = async () => {
|
||||
// save the questions and answers to the backend
|
||||
|
||||
|
||||
|
||||
try {
|
||||
let res = await submitQuizBlock(props.extension.options.activity.activity_id, {questions : questions , answers : answers})
|
||||
|
||||
props.updateAttributes({
|
||||
quizId: {
|
||||
value : res.block_id
|
||||
const addSampleQuestion = () => {
|
||||
const newQuestion = {
|
||||
question_id: uuidv4(),
|
||||
question: "",
|
||||
type: "multiple_choice",
|
||||
answers: [
|
||||
{
|
||||
answer_id: uuidv4(),
|
||||
answer: "",
|
||||
correct: false
|
||||
},
|
||||
});
|
||||
|
||||
]
|
||||
}
|
||||
catch (error) {
|
||||
setQuestions([...questions, newQuestion]);
|
||||
}
|
||||
|
||||
const addAnswer = (question_id: string) => {
|
||||
const newAnswer = {
|
||||
answer_id: uuidv4(),
|
||||
answer: "",
|
||||
correct: false
|
||||
}
|
||||
|
||||
// check if there is already more thqn 5 answers
|
||||
const question: any = questions.find((question: Question) => question.question_id === question_id);
|
||||
if (question.answers.length >= 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
const onOptionChange = (e: any, questionIndex: number, optionIndex: number) => {
|
||||
let modifiedQuestions = [...questions];
|
||||
modifiedQuestions[questionIndex].options[optionIndex].option_data = e.target.value;
|
||||
setQuestions(modifiedQuestions);
|
||||
};
|
||||
const newQuestions = questions.map((question: Question) => {
|
||||
if (question.question_id === question_id) {
|
||||
question.answers.push(newAnswer);
|
||||
}
|
||||
return question;
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
// fetch the questions and options from the backend
|
||||
saveQuestions(newQuestions);
|
||||
}
|
||||
|
||||
const changeAnswerValue = (question_id: string, answer_id: string, value: string) => {
|
||||
const newQuestions = questions.map((question: Question) => {
|
||||
if (question.question_id === question_id) {
|
||||
question.answers.map((answer: Answer) => {
|
||||
if (answer.answer_id === answer_id) {
|
||||
answer.answer = value;
|
||||
}
|
||||
return answer;
|
||||
});
|
||||
}
|
||||
return question;
|
||||
});
|
||||
saveQuestions(newQuestions);
|
||||
}
|
||||
|
||||
const changeQuestionValue = (question_id: string, value: string) => {
|
||||
const newQuestions = questions.map((question: Question) => {
|
||||
if (question.question_id === question_id) {
|
||||
question.question = value;
|
||||
}
|
||||
return question;
|
||||
});
|
||||
saveQuestions(newQuestions);
|
||||
}
|
||||
|
||||
const deleteQuestion = (question_id: string) => {
|
||||
const newQuestions = questions.filter((question: Question) => question.question_id !== question_id);
|
||||
saveQuestions(newQuestions);
|
||||
}
|
||||
|
||||
const deleteAnswer = (question_id: string, answer_id: string) => {
|
||||
const newQuestions = questions.map((question: Question) => {
|
||||
if (question.question_id === question_id) {
|
||||
question.answers = question.answers.filter((answer: Answer) => answer.answer_id !== answer_id);
|
||||
}
|
||||
return question;
|
||||
});
|
||||
saveQuestions(newQuestions);
|
||||
}
|
||||
|
||||
const markAnswerCorrect = (question_id: string, answer_id: string) => {
|
||||
const newQuestions = questions.map((question: Question) => {
|
||||
if (question.question_id === question_id) {
|
||||
question.answers.map((answer: Answer) => {
|
||||
if (answer.answer_id === answer_id) {
|
||||
answer.correct = true;
|
||||
} else {
|
||||
answer.correct = false;
|
||||
}
|
||||
|
||||
return answer;
|
||||
});
|
||||
}
|
||||
return question;
|
||||
});
|
||||
saveQuestions(newQuestions);
|
||||
}
|
||||
|
||||
}, [questions, answers]);
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="block-quiz">
|
||||
<QuizBlockWrapper>
|
||||
Questions <button onClick={addSampleQuestion}>Add Question</button> <button onClick={() => saveQuiz()}>Save</button>
|
||||
<hr />
|
||||
{questions.map((question: any, qIndex: number) => (
|
||||
<>
|
||||
<div key={qIndex} style={{ marginTop: "10px" }}>
|
||||
Question : <input type="text" value={question.question} onChange={(e) => onQuestionChange(e, qIndex)} />
|
||||
<button onClick={() => deleteQuestion(qIndex)}>Delete</button>
|
||||
</div>
|
||||
Answers : <br />
|
||||
{question.options.map((option: any, oIndex: number) => (
|
||||
<>
|
||||
<div key={oIndex}>
|
||||
<input type="text" value={option.option_data} onChange={(e) => onOptionChange(e, qIndex, oIndex)} />
|
||||
|
||||
<button onClick={() => deleteOption(question.question_id, option.option_id)}>Delete</button>
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={(e) =>
|
||||
// check if checkbox is checked or not
|
||||
// if checked then add the answer to the answers array
|
||||
// if unchecked then remove the answer from the answers array
|
||||
e.target.checked ? markOptionAsCorrect(question.question_id, option.option_id) : null
|
||||
}
|
||||
/>
|
||||
<div
|
||||
//style={{ background: "radial-gradient(152.15% 150.08% at 56.45% -6.67%, rgba(180, 255, 250, 0.10) 5.53%, rgba(202, 201, 255, 0.10) 66.76%)" }}
|
||||
className="rounded-xl px-5 py-2 bg-slate-100 transition-all ease-linear"
|
||||
>
|
||||
<div className="flex space-x-2 pt-1 items-center text-sm overflow-hidden">
|
||||
{(submitted && submissionMessage == "All answers are correct!") &&
|
||||
<ReactConfetti
|
||||
numberOfPieces={submitted ? 1400 : 0}
|
||||
recycle={false}
|
||||
className="w-full h-screen"
|
||||
/>
|
||||
}
|
||||
<div className="flex space-x-2 items-center text-sm">
|
||||
<BadgeHelp className='text-slate-400' size={15} />
|
||||
<p className="uppercase tracking-widest text-xs font-bold py-1 text-slate-400">Quiz</p>
|
||||
</div>
|
||||
<div className="grow flex items-center justify-center">
|
||||
|
||||
</div>
|
||||
{isEditable ?
|
||||
<div>
|
||||
<button onClick={addSampleQuestion} className="bg-slate-200 hover:bg-slate-300 text-slate-800 font-bold py-1 px-2 rounded-lg text-xs">Add Question</button>
|
||||
</div>
|
||||
:
|
||||
<div className="flex space-x-1 items-center">
|
||||
<div onClick={() => refreshUserSubmission()} className="cursor-pointer px-2">
|
||||
<RefreshCcw className='text-slate-400 cursor-pointer' size={15} />
|
||||
</div>
|
||||
<button onClick={() => handleUserSubmission()} className="bg-slate-200 hover:bg-slate-300 text-slate-800 font-bold py-1 px-2 rounded-lg text-xs">Submit</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
{questions.map((question: Question) => (
|
||||
<div key={question.question_id} className="pt-1 space-y-2">
|
||||
<div className="question">
|
||||
<div className="flex space-x-2 items-center">
|
||||
<div className="flex-grow">
|
||||
{isEditable ?
|
||||
<input value={question.question} placeholder="Your Question" onChange={(e) => changeQuestionValue(question.question_id, e.target.value)} className="text-slate-800 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-md font-bold w-full"></input>
|
||||
:
|
||||
<p className="text-slate-800 bg-[#00008b00] rounded-md text-md font-bold w-full">{question.question}</p>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
<button onClick={() => addOption(question.question_id)}>Add Option</button>
|
||||
</>
|
||||
{isEditable &&
|
||||
<div
|
||||
onClick={() => deleteQuestion(question.question_id)}
|
||||
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer">
|
||||
<Minus
|
||||
className="mx-auto text-slate-400" size={12} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="answers flex py-2 space-x-3">
|
||||
{question.answers.map((answer: Answer) => (
|
||||
<div
|
||||
key={answer.answer_id}
|
||||
className={twMerge(
|
||||
'outline outline-3 pr-2 shadow w-full flex items-center space-x-2 h-[30px] bg-opacity-50 hover:bg-opacity-100 hover:shadow-md rounded-s rounded-lg bg-white text-sm hover:scale-105 active:scale-110 duration-150 cursor-pointer ease-linear',
|
||||
answer.correct && isEditable ? 'outline-lime-300' : 'outline-white',
|
||||
userAnswers.find((userAnswer: any) => (userAnswer.question_id === question.question_id && userAnswer.answer_id === answer.answer_id) && !isEditable) ? 'outline-slate-300' : '',
|
||||
(submitted && answer.correct) ? 'outline-lime-300 text-lime' : '',
|
||||
(submitted && !answer.correct) && userAnswers.find((userAnswer: any) => userAnswer.question_id === question.question_id && userAnswer.answer_id === answer.answer_id) ? 'outline-red-400' : '',
|
||||
)
|
||||
}
|
||||
onClick={() => handleAnswerClick(question.question_id, answer.answer_id)}
|
||||
>
|
||||
<div className={twMerge(
|
||||
"bg-white font-bold text-base flex items-center h-full w-[40px] rounded-l-md text-slate-800",
|
||||
answer.correct && isEditable ? 'bg-lime-300 text-lime-800 outline-none' : 'bg-white',
|
||||
(submitted && answer.correct) ? 'bg-lime-300 text-lime-800 outline-none' : '',
|
||||
(submitted && !answer.correct) && userAnswers.find((userAnswer: any) => userAnswer.question_id === question.question_id && userAnswer.answer_id === answer.answer_id) ? 'bg-red-400 text-red-800 outline-none' : '',
|
||||
)}>
|
||||
<p className="mx-auto font-bold text-sm ">{getAnswerID(question.answers.indexOf(answer),question.question_id)}</p>
|
||||
</div>
|
||||
{isEditable ?
|
||||
<input value={answer.answer} onChange={(e) => changeAnswerValue(question.question_id, answer.answer_id, e.target.value)} placeholder="Answer" className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] border-2 border-gray-200 rounded-md border-dotted text-sm font-bold"></input>
|
||||
:
|
||||
<p className="w-full mx-2 px-3 pr-6 text-neutral-600 bg-[#00008b00] rounded-md ext-sm font-bold">{answer.answer}</p>
|
||||
}
|
||||
{isEditable &&
|
||||
<div className="flex space-x-1 items-center">
|
||||
<div
|
||||
onClick={() => markAnswerCorrect(question.question_id, answer.answer_id)}
|
||||
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-lime-300 hover:bg-lime-400 transition-all ease-linear text-sm cursor-pointer ">
|
||||
<Check
|
||||
className="mx-auto text-lime-800" size={12} />
|
||||
</div>
|
||||
<div
|
||||
onClick={() => deleteAnswer(question.question_id, answer.answer_id)}
|
||||
className="w-[20px] flex-none flex items-center h-[20px] rounded-lg bg-slate-200 hover:bg-slate-300 text-sm transition-all ease-linear cursor-pointer">
|
||||
<Minus
|
||||
className="mx-auto text-slate-400" size={12} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
))}
|
||||
{isEditable &&
|
||||
<div onClick={() => addAnswer(question.question_id)} className="outline outline-3 w-[30px] flex-none flex items-center h-[30px] outline-white hover:bg-opacity-100 hover:shadow-md rounded-lg bg-white text-sm hover:scale-105 active:scale-110 duration-150 cursor-pointer ease-linear">
|
||||
<Plus className="mx-auto text-slate-800" size={15} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</QuizBlockWrapper>
|
||||
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const QuizBlockWrapper = styled.div`
|
||||
background-color: #0000001d;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
`;
|
||||
export default ImageBlockComponent;
|
||||
|
||||
export default QuizBlockComponent;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import styled from "styled-components";
|
||||
import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon, DividerVerticalIcon, ListBulletIcon } from "@radix-ui/react-icons";
|
||||
import { AlertCircle, AlertTriangle, FileText, GraduationCap, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react";
|
||||
import { AlertCircle, AlertTriangle, FileText, GraduationCap, HelpCircle, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react";
|
||||
import ToolTip from "@components/StyledElements/Tooltip/Tooltip";
|
||||
|
||||
export const ToolbarButtons = ({ editor, props }: any) => {
|
||||
|
|
@ -59,7 +59,7 @@ export const ToolbarButtons = ({ editor, props }: any) => {
|
|||
<option value="6">Heading 6</option>
|
||||
</ToolSelect>
|
||||
{/* TODO: fix this : toggling only works one-way */}
|
||||
<DividerVerticalIcon style={{marginTop:"auto", marginBottom:"auto", color : "grey"}}/>
|
||||
<DividerVerticalIcon style={{ marginTop: "auto", marginBottom: "auto", color: "grey" }} />
|
||||
<ToolTip content={"Info Callout"}>
|
||||
<ToolBtn onClick={() => editor.chain().focus().toggleNode("calloutInfo").run()}>
|
||||
<AlertCircle size={15} />
|
||||
|
|
@ -136,7 +136,7 @@ export const ToolbarButtons = ({ editor, props }: any) => {
|
|||
<FileText size={15} />
|
||||
</ToolBtn>
|
||||
</ToolTip>
|
||||
{/* <ToolTip content={"Interactive Quiz"}>
|
||||
<ToolTip content={"Interactive Quiz"}>
|
||||
<ToolBtn
|
||||
onClick={() =>
|
||||
editor
|
||||
|
|
@ -148,9 +148,9 @@ export const ToolbarButtons = ({ editor, props }: any) => {
|
|||
.run()
|
||||
}
|
||||
>
|
||||
<GraduationCap size={15} />
|
||||
<HelpCircle size={15} />
|
||||
</ToolBtn>
|
||||
</ToolTip> */}
|
||||
</ToolTip>
|
||||
</ToolButtonsWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
30
front/package-lock.json
generated
30
front/package-lock.json
generated
|
|
@ -30,6 +30,7 @@
|
|||
"re-resizable": "^6.9.9",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-katex": "^3.0.1",
|
||||
|
|
@ -37,6 +38,7 @@
|
|||
"react-youtube": "^10.1.0",
|
||||
"styled-components": "^6.0.0-beta.9",
|
||||
"swr": "^2.0.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"uuid": "^9.0.0",
|
||||
"y-indexeddb": "^9.0.9",
|
||||
"y-webrtc": "^10.2.3",
|
||||
|
|
@ -7842,6 +7844,20 @@
|
|||
"react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-confetti": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.1.0.tgz",
|
||||
"integrity": "sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==",
|
||||
"dependencies": {
|
||||
"tween-functions": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0 || ^17.0.1 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
|
|
@ -8750,6 +8766,15 @@
|
|||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
|
||||
|
|
@ -8922,6 +8947,11 @@
|
|||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/tween-functions": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
|
||||
"integrity": "sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
"re-resizable": "^6.9.9",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-confetti": "^6.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-katex": "^3.0.1",
|
||||
|
|
@ -38,6 +39,7 @@
|
|||
"react-youtube": "^10.1.0",
|
||||
"styled-components": "^6.0.0-beta.9",
|
||||
"swr": "^2.0.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"uuid": "^9.0.0",
|
||||
"y-indexeddb": "^9.0.9",
|
||||
"y-webrtc": "^10.2.3",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue