feat: batch creation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="h-screen text-base">
|
||||
<div class="grid grid-cols-[75%,25%] h-full">
|
||||
<div>
|
||||
<div class="border-r">
|
||||
<header
|
||||
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
|
||||
>
|
||||
@@ -10,53 +10,56 @@
|
||||
{{ __('Save') }}
|
||||
</Button>
|
||||
</header>
|
||||
<div class="w-5/6 mx-auto py-5">
|
||||
<div class="flex items-center justify-between mb-5">
|
||||
<div class="text-lg font-semibold">
|
||||
{{ __('Lesson Details') }}
|
||||
<div class="py-5">
|
||||
<div class="w-5/6 mx-auto">
|
||||
<FormControl v-model="lesson.title" label="Title" class="mb-4" />
|
||||
<FormControl
|
||||
v-model="lesson.include_in_preview"
|
||||
type="checkbox"
|
||||
label="Include in Preview"
|
||||
/>
|
||||
</div>
|
||||
<div class="border-t mt-4">
|
||||
<div class="w-5/6 mx-auto pt-4">
|
||||
<div
|
||||
class="flex justify-between cursor-pointer"
|
||||
@click="
|
||||
() => {
|
||||
openInstructorEditor = !openInstructorEditor
|
||||
}
|
||||
"
|
||||
>
|
||||
<label class="block font-medium text-gray-600 mb-1">
|
||||
{{ __('Instructor Notes') }}
|
||||
</label>
|
||||
<ChevronRight
|
||||
class="stroke-2 h-5 w-5 text-gray-600"
|
||||
:class="{
|
||||
'rotate-90 transform duration-200': openInstructorEditor,
|
||||
'duration-200': !openInstructorEditor,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="openInstructorEditor"
|
||||
id="instructor-notes"
|
||||
class="py-3"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<FormControl v-model="lesson.title" label="Title" class="mb-4" />
|
||||
<FormControl
|
||||
v-model="lesson.include_in_preview"
|
||||
type="checkbox"
|
||||
label="Include in Preview"
|
||||
/>
|
||||
<div class="mt-4">
|
||||
<label class="block text-xs text-gray-600 mb-1">
|
||||
{{ __('Instructor Notes') }}
|
||||
</label>
|
||||
<div
|
||||
id="instructor-notes"
|
||||
class="border rounded-md px-10 py-3"
|
||||
></div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label class="block text-xs text-gray-600 mb-1">
|
||||
{{ __('Content') }}
|
||||
</label>
|
||||
<div id="content" class="border rounded-md py-3"></div>
|
||||
<div class="border-t mt-4">
|
||||
<div class="w-5/6 mx-auto pt-4">
|
||||
<label class="block font-medium text-gray-600 mb-1">
|
||||
{{ __('Content') }}
|
||||
</label>
|
||||
<div id="content" class="py-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-l px-5 pt-5">
|
||||
<div class="text-lg font-semibold">
|
||||
{{ __('Components') }}
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<div class="flex">
|
||||
<Link
|
||||
v-model="quiz"
|
||||
class="flex-1"
|
||||
doctype="LMS Quiz"
|
||||
:label="__('Select a Quiz')"
|
||||
/>
|
||||
<Button @click="addQuiz()" class="self-end ml-2">
|
||||
<template #icon>
|
||||
<Plus class="h-4 w-4 stroke-1.5" />
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="sticky top-0 p-5">
|
||||
<LessonPlugins :editor="editor" :notesEditor="instructorEditor" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,13 +76,14 @@ import {
|
||||
import { computed, reactive, onMounted, inject, ref } from 'vue'
|
||||
import EditorJS from '@editorjs/editorjs'
|
||||
import { createToast } from '../utils'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import LessonPlugins from '@/components/LessonPlugins.vue'
|
||||
import { getEditorTools } from '../utils'
|
||||
import { ChevronRight } from 'lucide-vue-next'
|
||||
|
||||
let editor
|
||||
const editor = ref(null)
|
||||
const instructorEditor = ref(null)
|
||||
const user = inject('$user')
|
||||
const quiz = ref(null)
|
||||
const openInstructorEditor = ref(false)
|
||||
|
||||
const props = defineProps({
|
||||
courseName: {
|
||||
@@ -100,7 +104,8 @@ onMounted(() => {
|
||||
if (!user.data?.is_moderator || !user.data?.is_instructor) {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
editor = renderEditor('content')
|
||||
editor.value = renderEditor('content')
|
||||
instructorEditor.value = renderEditor('instructor-notes')
|
||||
})
|
||||
|
||||
const renderEditor = (holder) => {
|
||||
@@ -132,8 +137,27 @@ const lessonDetails = createResource({
|
||||
lesson[key] = data.lesson[key]
|
||||
})
|
||||
lesson.include_in_preview = data.include_in_preview ? true : false
|
||||
editor.isReady.then(() => {
|
||||
editor.render(JSON.parse(data.lesson.content))
|
||||
editor.value.isReady.then(() => {
|
||||
if (data.lesson.content) {
|
||||
editor.value.render(JSON.parse(data.lesson.content))
|
||||
} else if (data.lesson.body) {
|
||||
let blocks = convertToJSON(data.lesson)
|
||||
editor.value.render({
|
||||
blocks: blocks,
|
||||
})
|
||||
}
|
||||
})
|
||||
instructorEditor.value.isReady.then(() => {
|
||||
if (data.lesson.instructor_content) {
|
||||
instructorEditor.value.render(
|
||||
JSON.parse(data.lesson.instructor_content)
|
||||
)
|
||||
} else if (data.lesson.instructor_notes) {
|
||||
let blocks = convertToJSON(data.lesson)
|
||||
instructorEditor.value.render({
|
||||
blocks: blocks,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -180,30 +204,106 @@ const lessonReference = createResource({
|
||||
},
|
||||
})
|
||||
|
||||
const saveLesson = () => {
|
||||
editor.save().then((outputData) => {
|
||||
lesson.content = JSON.stringify(outputData)
|
||||
if (lessonDetails.data?.lesson) {
|
||||
editLesson.submit(
|
||||
{
|
||||
lesson: lessonDetails.data.lesson.name,
|
||||
const convertToJSON = (lessonData) => {
|
||||
let blocks = []
|
||||
lessonData.body.split('\n').forEach((block) => {
|
||||
if (block.includes('{{ YouTubeVideo')) {
|
||||
let youtubeID = block.match(/\(["']([^"']+?)["']\)/)[1]
|
||||
if (!youtubeID.includes('https://'))
|
||||
youtubeID = `https://www.youtube.com/embed/${youtubeID}`
|
||||
blocks.push({
|
||||
type: 'embed',
|
||||
data: {
|
||||
service: 'youtube',
|
||||
embed: youtubeID,
|
||||
},
|
||||
{
|
||||
validate() {
|
||||
return validateLesson()
|
||||
},
|
||||
onSuccess() {
|
||||
showToast('Success', 'Lesson updated successfully', 'check')
|
||||
},
|
||||
onError(err) {
|
||||
showToast('Error', err.message, 'x')
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
} else if (block.includes('{{ Quiz')) {
|
||||
let quiz = block.match(/\(["']([^"']+?)["']\)/)[1]
|
||||
blocks.push({
|
||||
type: 'quiz',
|
||||
data: {
|
||||
quiz: quiz,
|
||||
},
|
||||
})
|
||||
} else if (block.includes('{{ Video')) {
|
||||
let video = block.match(/\(["']([^"']+?)["']\)/)[1]
|
||||
blocks.push({
|
||||
type: 'upload',
|
||||
data: {
|
||||
file_url: video,
|
||||
file_type: 'video',
|
||||
},
|
||||
})
|
||||
} else if (block.includes('{{ Audio')) {
|
||||
let audio = block.match(/\(["']([^"']+?)["']\)/)[1]
|
||||
blocks.push({
|
||||
type: 'upload',
|
||||
data: {
|
||||
file_url: audio,
|
||||
file_type: 'audio',
|
||||
},
|
||||
})
|
||||
} else if (block.includes('{{ PDF')) {
|
||||
let pdf = block.match(/\(["']([^"']+?)["']\)/)[1]
|
||||
blocks.push({
|
||||
type: 'upload',
|
||||
data: {
|
||||
file_url: pdf,
|
||||
file_type: 'pdf',
|
||||
},
|
||||
})
|
||||
} else if (block.includes('{{ Embed')) {
|
||||
let embed = block.match(/\(["']([^"']+?)["']\)/)[1]
|
||||
blocks.push({
|
||||
type: 'embed',
|
||||
data: {
|
||||
service: embed.split('|||')[0],
|
||||
embed: embed.split('|||')[1],
|
||||
},
|
||||
})
|
||||
} else if (block.includes('![]')) {
|
||||
let image = block.match(/\((.*?)\)/)[1]
|
||||
blocks.push({
|
||||
type: 'upload',
|
||||
data: {
|
||||
file_url: image,
|
||||
file_type: 'image',
|
||||
},
|
||||
})
|
||||
} else if (block.includes('#')) {
|
||||
let level = (block.match(/#/g) || []).length
|
||||
blocks.push({
|
||||
type: 'header',
|
||||
data: {
|
||||
text: block.replace(/#/g, '').trim(),
|
||||
level: level,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
createNewLesson()
|
||||
blocks.push({
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: block,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
return blocks
|
||||
}
|
||||
|
||||
const saveLesson = () => {
|
||||
editor.value.save().then((outputData) => {
|
||||
lesson.content = JSON.stringify(outputData)
|
||||
instructorEditor.value.save().then((outputData) => {
|
||||
lesson.instructor_content = JSON.stringify(outputData)
|
||||
if (lessonDetails.data?.lesson) {
|
||||
editCurrentLesson()
|
||||
} else {
|
||||
createNewLesson()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const createNewLesson = () => {
|
||||
@@ -230,6 +330,25 @@ const createNewLesson = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const editCurrentLesson = () => {
|
||||
editLesson.submit(
|
||||
{
|
||||
lesson: lessonDetails.data.lesson.name,
|
||||
},
|
||||
{
|
||||
validate() {
|
||||
return validateLesson()
|
||||
},
|
||||
onSuccess() {
|
||||
showToast('Success', 'Lesson updated successfully', 'check')
|
||||
},
|
||||
onError(err) {
|
||||
showToast('Error', err.message, 'x')
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const validateLesson = () => {
|
||||
if (!lesson.title) {
|
||||
return 'Title is required'
|
||||
@@ -239,20 +358,6 @@ const validateLesson = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const addQuiz = () => {
|
||||
if (quiz.value) {
|
||||
editor.blocks.insert(
|
||||
'quiz',
|
||||
{
|
||||
quiz: quiz.value,
|
||||
},
|
||||
{},
|
||||
editor.blocks.getBlocksCount()
|
||||
)
|
||||
quiz.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const showToast = (title, text, icon) => {
|
||||
createToast({
|
||||
title: title,
|
||||
@@ -306,3 +411,16 @@ const breadcrumbs = computed(() => {
|
||||
return crumbs
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.embed-tool__caption {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ce-toolbar__actions {
|
||||
right: 108%;
|
||||
}
|
||||
|
||||
.ce-block__content {
|
||||
max-width: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user