feat(vocab): add lesson and completed lesson vocab pool endpoints
All checks were successful
Deploy to production / deploy (push) Successful in 3m5s
All checks were successful
Deploy to production / deploy (push) Successful in 3m5s
- Implemented new endpoints in VocabController for retrieving vocab pools based on lessons and completed lessons. - Updated vocabRouter to include routes for accessing lesson vocab pools and completed lesson vocab pools. - Enhanced VocabService with methods to extract vocab from exercises and lesson didactics, improving vocabulary retrieval for users. - Modified VocabPracticeDialog and VocabCourseView components to support new vocab pool functionalities, enhancing user experience in vocabulary practice.
This commit is contained in:
@@ -22,11 +22,15 @@ class VocabController {
|
|||||||
this.getChapter = this._wrapWithUser((userId, req) => this.service.getChapter(userId, req.params.chapterId));
|
this.getChapter = this._wrapWithUser((userId, req) => this.service.getChapter(userId, req.params.chapterId));
|
||||||
this.listChapterVocabs = this._wrapWithUser((userId, req) => this.service.listChapterVocabs(userId, req.params.chapterId));
|
this.listChapterVocabs = this._wrapWithUser((userId, req) => this.service.listChapterVocabs(userId, req.params.chapterId));
|
||||||
this.addVocabToChapter = this._wrapWithUser((userId, req) => this.service.addVocabToChapter(userId, req.params.chapterId, req.body), { successStatus: 201 });
|
this.addVocabToChapter = this._wrapWithUser((userId, req) => this.service.addVocabToChapter(userId, req.params.chapterId, req.body), { successStatus: 201 });
|
||||||
|
this.getLessonVocabPool = this._wrapWithUser((userId, req) => this.service.getLessonVocabPool(userId, req.params.lessonId));
|
||||||
|
|
||||||
// Courses
|
// Courses
|
||||||
this.createCourse = this._wrapWithUser((userId, req) => this.service.createCourse(userId, req.body), { successStatus: 201 });
|
this.createCourse = this._wrapWithUser((userId, req) => this.service.createCourse(userId, req.body), { successStatus: 201 });
|
||||||
this.getCourses = this._wrapWithUser((userId, req) => this.service.getCourses(userId, req.query));
|
this.getCourses = this._wrapWithUser((userId, req) => this.service.getCourses(userId, req.query));
|
||||||
this.getCourse = this._wrapWithUser((userId, req) => this.service.getCourse(userId, req.params.courseId));
|
this.getCourse = this._wrapWithUser((userId, req) => this.service.getCourse(userId, req.params.courseId));
|
||||||
|
this.getCompletedLessonVocabPool = this._wrapWithUser((userId, req) =>
|
||||||
|
this.service.getCompletedLessonVocabPool(userId, req.params.courseId, req.query.untilLessonId)
|
||||||
|
);
|
||||||
this.getVocabDistractorPool = this._wrapWithUser((userId, req) =>
|
this.getVocabDistractorPool = this._wrapWithUser((userId, req) =>
|
||||||
this.service.getVocabDistractorPool(userId, req.params.courseId, req.query.beforeLessonId)
|
this.service.getVocabDistractorPool(userId, req.params.courseId, req.query.beforeLessonId)
|
||||||
);
|
);
|
||||||
@@ -80,4 +84,3 @@ class VocabController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default VocabController;
|
export default VocabController;
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ router.get('/languages/:languageId/search', vocabController.searchVocabs);
|
|||||||
router.get('/chapters/:chapterId', vocabController.getChapter);
|
router.get('/chapters/:chapterId', vocabController.getChapter);
|
||||||
router.get('/chapters/:chapterId/vocabs', vocabController.listChapterVocabs);
|
router.get('/chapters/:chapterId/vocabs', vocabController.listChapterVocabs);
|
||||||
router.post('/chapters/:chapterId/vocabs', vocabController.addVocabToChapter);
|
router.post('/chapters/:chapterId/vocabs', vocabController.addVocabToChapter);
|
||||||
|
router.get('/lessons/:lessonId/vocab-pool', vocabController.getLessonVocabPool);
|
||||||
|
|
||||||
// Courses
|
// Courses
|
||||||
router.post('/courses', vocabController.createCourse);
|
router.post('/courses', vocabController.createCourse);
|
||||||
router.get('/courses', vocabController.getCourses);
|
router.get('/courses', vocabController.getCourses);
|
||||||
router.get('/courses/my', vocabController.getMyCourses);
|
router.get('/courses/my', vocabController.getMyCourses);
|
||||||
router.post('/courses/find-by-code', vocabController.getCourseByShareCode);
|
router.post('/courses/find-by-code', vocabController.getCourseByShareCode);
|
||||||
|
router.get('/courses/:courseId/completed-lesson-vocabs', vocabController.getCompletedLessonVocabPool);
|
||||||
router.get('/courses/:courseId/distractor-pool', vocabController.getVocabDistractorPool);
|
router.get('/courses/:courseId/distractor-pool', vocabController.getVocabDistractorPool);
|
||||||
router.get('/courses/:courseId', vocabController.getCourse);
|
router.get('/courses/:courseId', vocabController.getCourse);
|
||||||
router.put('/courses/:courseId', vocabController.updateCourse);
|
router.put('/courses/:courseId', vocabController.updateCourse);
|
||||||
@@ -59,4 +61,3 @@ router.put('/grammar-exercises/:exerciseId', vocabController.updateGrammarExerci
|
|||||||
router.delete('/grammar-exercises/:exerciseId', vocabController.deleteGrammarExercise);
|
router.delete('/grammar-exercises/:exerciseId', vocabController.deleteGrammarExercise);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,110 @@ export default class VocabService {
|
|||||||
.replace(/\s+/g, ' ');
|
.replace(/\s+/g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_parseExercisePayload(value) {
|
||||||
|
if (!value) return {};
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
_extractTrainerVocabsFromExercises(exercises = []) {
|
||||||
|
const vocabMap = new Map();
|
||||||
|
|
||||||
|
exercises.forEach((exercise) => {
|
||||||
|
try {
|
||||||
|
const qData = this._parseExercisePayload(exercise.questionData);
|
||||||
|
const aData = this._parseExercisePayload(exercise.answerData);
|
||||||
|
const exerciseType = exercise.exerciseType?.name || qData.type || '';
|
||||||
|
|
||||||
|
if (exerciseType === 'multiple_choice') {
|
||||||
|
const options = Array.isArray(qData.options) ? qData.options : [];
|
||||||
|
const correctAnswer = Array.isArray(aData.correctAnswer)
|
||||||
|
? options[aData.correctAnswer[0]]
|
||||||
|
: options[aData.correctAnswer ?? aData.correct ?? 0];
|
||||||
|
const question = String(qData.question || qData.text || '');
|
||||||
|
|
||||||
|
let match = question.match(/Wie sagt man ['"]([^'"]+)['"]/i);
|
||||||
|
if (match && match[1] && correctAnswer && match[1].trim() !== String(correctAnswer).trim()) {
|
||||||
|
vocabMap.set(`${match[1]}-${correctAnswer}`, {
|
||||||
|
learning: match[1],
|
||||||
|
reference: String(correctAnswer)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match = question.match(/Was bedeutet ['"]([^'"]+)['"]/i);
|
||||||
|
if (match && match[1] && correctAnswer && match[1].trim() !== String(correctAnswer).trim()) {
|
||||||
|
vocabMap.set(`${correctAnswer}-${match[1]}`, {
|
||||||
|
learning: String(correctAnswer),
|
||||||
|
reference: match[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exerciseType === 'gap_fill') {
|
||||||
|
const answers = Array.isArray(aData.answers)
|
||||||
|
? aData.answers
|
||||||
|
: (aData.correct ? (Array.isArray(aData.correct) ? aData.correct : [aData.correct]) : []);
|
||||||
|
const text = String(qData.text || '');
|
||||||
|
const nativeWords = Array.from(text.matchAll(/\(([^)]+)\)/g), (m) => String(m[1] || '').trim());
|
||||||
|
|
||||||
|
if (!answers.length || !nativeWords.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
answers.forEach((answer, index) => {
|
||||||
|
const nativeWord = nativeWords[index];
|
||||||
|
const normalizedAnswer = String(answer || '').trim();
|
||||||
|
if (!nativeWord || !normalizedAnswer || nativeWord === normalizedAnswer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vocabMap.set(`${nativeWord}-${normalizedAnswer}`, {
|
||||||
|
learning: nativeWord,
|
||||||
|
reference: normalizedAnswer
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Fehler beim Extrahieren von Trainer-Vokabeln:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(vocabMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
_extractTrainerVocabsFromLessonDidactics(lesson) {
|
||||||
|
const vocabMap = new Map();
|
||||||
|
const speakingPrompts = Array.isArray(lesson?.speakingPrompts) ? lesson.speakingPrompts : [];
|
||||||
|
const practicalTasks = Array.isArray(lesson?.practicalTasks) ? lesson.practicalTasks : [];
|
||||||
|
const corePatterns = Array.isArray(lesson?.corePatterns) ? lesson.corePatterns : [];
|
||||||
|
|
||||||
|
speakingPrompts.forEach((prompt, index) => {
|
||||||
|
const learning = String(prompt?.prompt || prompt?.title || '').trim();
|
||||||
|
const reference = String(prompt?.cue || corePatterns[index] || corePatterns[0] || '').trim();
|
||||||
|
if (!learning || !reference || learning === reference) return;
|
||||||
|
vocabMap.set(`${learning}-${reference}`, { learning, reference });
|
||||||
|
});
|
||||||
|
|
||||||
|
practicalTasks.forEach((task, index) => {
|
||||||
|
const learning = String(task?.text || task?.title || '').trim();
|
||||||
|
const reference = String(corePatterns[index] || corePatterns[0] || '').trim();
|
||||||
|
if (!learning || !reference || learning === reference) return;
|
||||||
|
vocabMap.set(`${learning}-${reference}`, { learning, reference });
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(vocabMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
_normalizeStringList(value) {
|
_normalizeStringList(value) {
|
||||||
if (!value) return [];
|
if (!value) return [];
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
@@ -810,6 +914,175 @@ export default class VocabService {
|
|||||||
return { languageId: access.id, isOwner: access.isOwner, vocabs: rows };
|
return { languageId: access.id, isOwner: access.isOwner, vocabs: rows };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getLessonVocabPool(hashedUserId, lessonId) {
|
||||||
|
const user = await this._getUserByHashedId(hashedUserId);
|
||||||
|
const lesson = await VocabCourseLesson.findByPk(lessonId, {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VocabCourse,
|
||||||
|
as: 'course'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: VocabGrammarExercise,
|
||||||
|
as: 'grammarExercises',
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VocabGrammarExerciseType,
|
||||||
|
as: 'exerciseType'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!lesson) {
|
||||||
|
const err = new Error('Lesson not found');
|
||||||
|
err.status = 404;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lesson.course.ownerUserId !== user.id && !lesson.course.isPublic) {
|
||||||
|
const err = new Error('Access denied');
|
||||||
|
err.status = 403;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = await VocabCourseProgress.findOne({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
lessonId: lesson.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lesson.course.ownerUserId !== user.id && !progress?.completed) {
|
||||||
|
const err = new Error('Lesson must be completed first');
|
||||||
|
err.status = 403;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractedFromExercises = this._extractTrainerVocabsFromExercises(
|
||||||
|
(lesson.grammarExercises || []).map((exercise) => exercise.get({ plain: true }))
|
||||||
|
);
|
||||||
|
const vocabs = extractedFromExercises.length > 0
|
||||||
|
? extractedFromExercises
|
||||||
|
: this._extractTrainerVocabsFromLessonDidactics(lesson.get({ plain: true }));
|
||||||
|
|
||||||
|
return {
|
||||||
|
lesson: {
|
||||||
|
id: lesson.id,
|
||||||
|
title: lesson.title,
|
||||||
|
courseId: lesson.courseId,
|
||||||
|
courseTitle: lesson.course.title
|
||||||
|
},
|
||||||
|
vocabs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCompletedLessonVocabPool(hashedUserId, courseId, untilLessonId = null) {
|
||||||
|
const user = await this._getUserByHashedId(hashedUserId);
|
||||||
|
const course = await VocabCourse.findByPk(courseId);
|
||||||
|
|
||||||
|
if (!course) {
|
||||||
|
const err = new Error('Course not found');
|
||||||
|
err.status = 404;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (course.ownerUserId !== user.id && !course.isPublic) {
|
||||||
|
const err = new Error('Access denied');
|
||||||
|
err.status = 403;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxLessonNumber = null;
|
||||||
|
if (untilLessonId) {
|
||||||
|
const untilLesson = await VocabCourseLesson.findOne({
|
||||||
|
where: {
|
||||||
|
id: untilLessonId,
|
||||||
|
courseId: course.id
|
||||||
|
},
|
||||||
|
attributes: ['lessonNumber']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!untilLesson) {
|
||||||
|
const err = new Error('Lesson not found');
|
||||||
|
err.status = 404;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxLessonNumber = untilLesson.lessonNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
const completedProgress = await VocabCourseProgress.findAll({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
courseId: course.id,
|
||||||
|
completed: true
|
||||||
|
},
|
||||||
|
attributes: ['lessonId']
|
||||||
|
});
|
||||||
|
|
||||||
|
const completedLessonIds = completedProgress.map((entry) => entry.lessonId);
|
||||||
|
if (completedLessonIds.length === 0) {
|
||||||
|
return { courseId: course.id, vocabs: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const lessonWhere = {
|
||||||
|
id: {
|
||||||
|
[Op.in]: completedLessonIds
|
||||||
|
},
|
||||||
|
courseId: course.id
|
||||||
|
};
|
||||||
|
|
||||||
|
if (maxLessonNumber != null) {
|
||||||
|
lessonWhere.lessonNumber = {
|
||||||
|
[Op.lte]: maxLessonNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const lessons = await VocabCourseLesson.findAll({
|
||||||
|
where: lessonWhere,
|
||||||
|
attributes: ['id', 'speakingPrompts', 'practicalTasks', 'corePatterns'],
|
||||||
|
order: [['lessonNumber', 'ASC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
const lessonIds = lessons.map((lesson) => lesson.id);
|
||||||
|
if (!lessonIds.length) {
|
||||||
|
return { courseId: course.id, vocabs: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const exercises = await VocabGrammarExercise.findAll({
|
||||||
|
where: {
|
||||||
|
lessonId: {
|
||||||
|
[Op.in]: lessonIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VocabGrammarExerciseType,
|
||||||
|
as: 'exerciseType'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
order: [['lessonId', 'ASC'], ['exerciseNumber', 'ASC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
const extractedFromExercises = this._extractTrainerVocabsFromExercises(exercises.map((exercise) => exercise.get({ plain: true })));
|
||||||
|
const fallbackVocabs = lessons.flatMap((lesson) =>
|
||||||
|
this._extractTrainerVocabsFromLessonDidactics(lesson.get({ plain: true }))
|
||||||
|
);
|
||||||
|
const mergedVocabs = new Map();
|
||||||
|
[...extractedFromExercises, ...fallbackVocabs].forEach((entry) => {
|
||||||
|
if (!entry?.learning || !entry?.reference) return;
|
||||||
|
mergedVocabs.set(`${entry.learning}-${entry.reference}`, entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
courseId: course.id,
|
||||||
|
vocabs: Array.from(mergedVocabs.values())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async searchVocabs(hashedUserId, languageId, { q = '', learning = '', motherTongue = '' } = {}) {
|
async searchVocabs(hashedUserId, languageId, { q = '', learning = '', motherTongue = '' } = {}) {
|
||||||
const user = await this._getUserByHashedId(hashedUserId);
|
const user = await this._getUserByHashedId(hashedUserId);
|
||||||
const access = await this._getLanguageAccess(user.id, languageId);
|
const access = await this._getLanguageAccess(user.id, languageId);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default {
|
|||||||
components: { DialogWidget },
|
components: { DialogWidget },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
openParams: null, // { languageId, chapterId }
|
openParams: null, // { languageId, chapterId, lessonId, courseId }
|
||||||
onClose: null,
|
onClose: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
allVocabs: false,
|
allVocabs: false,
|
||||||
@@ -168,12 +168,12 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open({ languageId, chapterId, onClose = null }) {
|
open({ languageId, chapterId, lessonId, courseId, onClose = null }) {
|
||||||
if (this.autoAdvanceTimer) {
|
if (this.autoAdvanceTimer) {
|
||||||
clearTimeout(this.autoAdvanceTimer);
|
clearTimeout(this.autoAdvanceTimer);
|
||||||
this.autoAdvanceTimer = null;
|
this.autoAdvanceTimer = null;
|
||||||
}
|
}
|
||||||
this.openParams = { languageId, chapterId };
|
this.openParams = { languageId, chapterId, lessonId, courseId };
|
||||||
this.onClose = typeof onClose === 'function' ? onClose : null;
|
this.onClose = typeof onClose === 'function' ? onClose : null;
|
||||||
this.allVocabs = false;
|
this.allVocabs = false;
|
||||||
this.simpleMode = false;
|
this.simpleMode = false;
|
||||||
@@ -231,7 +231,7 @@ export default {
|
|||||||
},
|
},
|
||||||
resetQuestion() {
|
resetQuestion() {
|
||||||
this.current = null;
|
this.current = null;
|
||||||
this.direction = Math.random() < 0.5 ? 'L2R' : 'R2L';
|
this.direction = this.openParams?.lessonId ? 'L2R' : (Math.random() < 0.5 ? 'L2R' : 'R2L');
|
||||||
this.acceptableAnswers = [];
|
this.acceptableAnswers = [];
|
||||||
this.choiceOptions = [];
|
this.choiceOptions = [];
|
||||||
this.typedAnswer = '';
|
this.typedAnswer = '';
|
||||||
@@ -272,7 +272,19 @@ export default {
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
let res;
|
let res;
|
||||||
if (this.allVocabs) {
|
if (this.openParams.lessonId) {
|
||||||
|
if (this.allVocabs && this.openParams.courseId) {
|
||||||
|
res = await apiClient.get(`/api/vocab/courses/${this.openParams.courseId}/completed-lesson-vocabs`, {
|
||||||
|
params: {
|
||||||
|
untilLessonId: this.openParams.lessonId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.pool = res.data?.vocabs || [];
|
||||||
|
} else {
|
||||||
|
res = await apiClient.get(`/api/vocab/lessons/${this.openParams.lessonId}/vocab-pool`);
|
||||||
|
this.pool = res.data?.vocabs || [];
|
||||||
|
}
|
||||||
|
} else if (this.allVocabs) {
|
||||||
res = await apiClient.get(`/api/vocab/languages/${this.openParams.languageId}/vocabs`);
|
res = await apiClient.get(`/api/vocab/languages/${this.openParams.languageId}/vocabs`);
|
||||||
this.pool = res.data?.vocabs || [];
|
this.pool = res.data?.vocabs || [];
|
||||||
} else {
|
} else {
|
||||||
@@ -530,5 +542,3 @@ export default {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,13 @@
|
|||||||
>
|
>
|
||||||
{{ getLessonProgress(lesson.id)?.completed ? $t('socialnetwork.vocab.courses.review') : $t('socialnetwork.vocab.courses.start') }}
|
{{ getLessonProgress(lesson.id)?.completed ? $t('socialnetwork.vocab.courses.review') : $t('socialnetwork.vocab.courses.start') }}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="getLessonProgress(lesson.id)?.completed"
|
||||||
|
@click="openLessonPractice(lesson)"
|
||||||
|
class="btn-edit"
|
||||||
|
>
|
||||||
|
Im Trainer üben
|
||||||
|
</button>
|
||||||
<button v-if="isOwner" @click="editLesson(lesson.id)" class="btn-edit">{{ $t('socialnetwork.vocab.courses.edit') }}</button>
|
<button v-if="isOwner" @click="editLesson(lesson.id)" class="btn-edit">{{ $t('socialnetwork.vocab.courses.edit') }}</button>
|
||||||
<button v-if="isOwner" @click="deleteLesson(lesson.id)" class="btn-delete">{{ $t('general.delete') }}</button>
|
<button v-if="isOwner" @click="deleteLesson(lesson.id)" class="btn-delete">{{ $t('general.delete') }}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,6 +106,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<VocabPracticeDialog ref="practiceDialog" />
|
||||||
|
|
||||||
<!-- Add Lesson Dialog -->
|
<!-- Add Lesson Dialog -->
|
||||||
<div v-if="showAddLessonDialog" class="dialog-overlay" @click="showAddLessonDialog = false">
|
<div v-if="showAddLessonDialog" class="dialog-overlay" @click="showAddLessonDialog = false">
|
||||||
<div class="dialog" @click.stop>
|
<div class="dialog" @click.stop>
|
||||||
@@ -138,9 +147,11 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import apiClient from '@/utils/axios.js';
|
import apiClient from '@/utils/axios.js';
|
||||||
import { confirmAction, showApiError, showInfo, showSuccess } from '@/utils/feedback.js';
|
import { confirmAction, showApiError, showInfo, showSuccess } from '@/utils/feedback.js';
|
||||||
|
import VocabPracticeDialog from '@/dialogues/socialnetwork/VocabPracticeDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VocabCourseView',
|
name: 'VocabCourseView',
|
||||||
|
components: { VocabPracticeDialog },
|
||||||
props: {
|
props: {
|
||||||
courseId: {
|
courseId: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -325,6 +336,12 @@ export default {
|
|||||||
openLesson(lessonId) {
|
openLesson(lessonId) {
|
||||||
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${lessonId}`);
|
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${lessonId}`);
|
||||||
},
|
},
|
||||||
|
openLessonPractice(lesson) {
|
||||||
|
this.$refs.practiceDialog?.open?.({
|
||||||
|
courseId: this.courseId,
|
||||||
|
lessonId: lesson.id
|
||||||
|
});
|
||||||
|
},
|
||||||
openLessonAssistant(lessonId) {
|
openLessonAssistant(lessonId) {
|
||||||
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${lessonId}?assistant=1`);
|
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${lessonId}?assistant=1`);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user