Merge pull request #924 from pateljannat/reorder-lessons
feat: reorder lessons
This commit is contained in:
@@ -29,7 +29,9 @@
|
|||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
"vue": "^3.4.23",
|
"vue": "^3.4.23",
|
||||||
"vue-chartjs": "^5.3.0",
|
"vue-chartjs": "^5.3.0",
|
||||||
"vue-router": "^4.0.12"
|
"vue-draggable-next": "^2.2.1",
|
||||||
|
"vue-router": "^4.0.12",
|
||||||
|
"vuedraggable": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.3",
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
|
|||||||
@@ -18,20 +18,28 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="sidebarSettings.data?.web_pages?.length || isModerator"
|
v-if="sidebarSettings.data?.web_pages?.length || isModerator"
|
||||||
class="mt-4 pt-1 border-t border-gray-200"
|
class="pt-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isModerator"
|
class="flex items-center justify-between pr-2 cursor-pointer"
|
||||||
class="flex items-center justify-between pr-2"
|
:class="isSidebarCollapsed ? 'pl-3' : 'pl-4'"
|
||||||
:class="isSidebarCollapsed ? 'pl-3' : 'pl-5'"
|
@click="showWebPages = !showWebPages"
|
||||||
>
|
>
|
||||||
<span
|
<div
|
||||||
v-if="!isSidebarCollapsed"
|
v-if="!isSidebarCollapsed"
|
||||||
class="text-sm font-medium text-gray-600"
|
class="flex items-center text-sm font-medium text-gray-600"
|
||||||
>
|
>
|
||||||
{{ __('Web Pages') }}
|
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
|
||||||
</span>
|
<ChevronRight
|
||||||
<Button variant="ghost" @click="openPageModal()">
|
class="h-4 w-4 stroke-1.5 text-gray-900 transition-all duration-300 ease-in-out"
|
||||||
|
:class="{ 'rotate-90': showWebPages }"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span class="ml-2">
|
||||||
|
{{ __('Web Pages') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Button v-if="isModerator" variant="ghost" @click="openPageModal()">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Plus class="h-4 w-4 text-gray-700 stroke-1.5" />
|
<Plus class="h-4 w-4 text-gray-700 stroke-1.5" />
|
||||||
</template>
|
</template>
|
||||||
@@ -39,7 +47,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="sidebarSettings.data?.web_pages?.length"
|
v-if="sidebarSettings.data?.web_pages?.length"
|
||||||
class="flex flex-col"
|
class="flex flex-col transition-all duration-300 ease-in-out"
|
||||||
|
:class="showWebPages ? 'block' : 'hidden'"
|
||||||
>
|
>
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
v-for="link in sidebarSettings.data.web_pages"
|
v-for="link in sidebarSettings.data.web_pages"
|
||||||
@@ -87,7 +96,7 @@ import { ref, onMounted, inject, watch } from 'vue'
|
|||||||
import { getSidebarLinks } from '../utils'
|
import { getSidebarLinks } from '../utils'
|
||||||
import { usersStore } from '@/stores/user'
|
import { usersStore } from '@/stores/user'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { Plus } from 'lucide-vue-next'
|
import { ChevronRight, Plus } from 'lucide-vue-next'
|
||||||
import { createResource, Button } from 'frappe-ui'
|
import { createResource, Button } from 'frappe-ui'
|
||||||
import PageModal from '@/components/Modals/PageModal.vue'
|
import PageModal from '@/components/Modals/PageModal.vue'
|
||||||
|
|
||||||
@@ -99,6 +108,7 @@ const sidebarLinks = ref(getSidebarLinks())
|
|||||||
const showPageModal = ref(false)
|
const showPageModal = ref(false)
|
||||||
const isModerator = ref(false)
|
const isModerator = ref(false)
|
||||||
const pageToEdit = ref(null)
|
const pageToEdit = ref(null)
|
||||||
|
const showWebPages = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
socket.on('publish_lms_notifications', (data) => {
|
socket.on('publish_lms_notifications', (data) => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="user?.data?.is_moderator"
|
v-if="isModerator || isStudent"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'Batch',
|
name: 'Batch',
|
||||||
params: {
|
params: {
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
>
|
>
|
||||||
<Button variant="solid" class="w-full mt-4">
|
<Button variant="solid" class="w-full mt-4">
|
||||||
<span>
|
<span>
|
||||||
{{ __('Manage Batch') }}
|
{{ isModerator ? __('Manage Batch') : __('Visit Batch') }}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
}"
|
}"
|
||||||
v-else-if="batch.data.paid_batch && batch.data.seats_left"
|
v-else-if="batch.data.paid_batch && batch.data.seats_left"
|
||||||
>
|
>
|
||||||
<Button class="w-full mt-4" variant="solid">
|
<Button v-if="!isStudent" class="w-full mt-4" variant="solid">
|
||||||
<span>
|
<span>
|
||||||
{{ __('Register Now') }}
|
{{ __('Register Now') }}
|
||||||
</span>
|
</span>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
{{ __('Enroll Now') }}
|
{{ __('Enroll Now') }}
|
||||||
</Button>
|
</Button>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="user?.data?.is_moderator"
|
v-if="isModerator"
|
||||||
:to="{
|
:to="{
|
||||||
name: 'BatchCreation',
|
name: 'BatchCreation',
|
||||||
params: {
|
params: {
|
||||||
@@ -117,4 +117,12 @@ const seats_left = computed(() => {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isStudent = computed(() => {
|
||||||
|
return props.batch.data?.students?.includes(user.data?.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isModerator = computed(() => {
|
||||||
|
return user.data?.is_moderator
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -39,45 +39,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</DisclosureButton>
|
</DisclosureButton>
|
||||||
<DisclosurePanel>
|
<DisclosurePanel>
|
||||||
<div v-for="lesson in chapter.lessons" :key="lesson.name">
|
<Draggable
|
||||||
<div class="outline-lesson pl-8 py-2 pr-4">
|
:list="chapter.lessons"
|
||||||
<router-link
|
item-key="name"
|
||||||
:to="{
|
group="items"
|
||||||
name: allowEdit ? 'CreateLesson' : 'Lesson',
|
@end="updateOutline"
|
||||||
params: {
|
:data-chapter="chapter.name"
|
||||||
courseName: courseName,
|
>
|
||||||
chapterNumber: lesson.number.split('.')[0],
|
<template #item="{ element: lesson }">
|
||||||
lessonNumber: lesson.number.split('.')[1],
|
<div class="outline-lesson pl-8 py-2 pr-4">
|
||||||
},
|
<router-link
|
||||||
}"
|
:to="{
|
||||||
>
|
name: allowEdit ? 'CreateLesson' : 'Lesson',
|
||||||
<div class="flex items-center text-sm leading-5 group">
|
params: {
|
||||||
<MonitorPlay
|
courseName: courseName,
|
||||||
v-if="lesson.icon === 'icon-youtube'"
|
chapterNumber: lesson.number.split('.')[0],
|
||||||
class="h-4 w-4 text-gray-900 stroke-1 mr-2"
|
lessonNumber: lesson.number.split('.')[1],
|
||||||
/>
|
},
|
||||||
<HelpCircle
|
}"
|
||||||
v-else-if="lesson.icon === 'icon-quiz'"
|
>
|
||||||
class="h-4 w-4 text-gray-900 stroke-1 mr-2"
|
<div class="flex items-center text-sm leading-5 group">
|
||||||
/>
|
<MonitorPlay
|
||||||
<FileText
|
v-if="lesson.icon === 'icon-youtube'"
|
||||||
v-else-if="lesson.icon === 'icon-list'"
|
class="h-4 w-4 text-gray-900 stroke-1 mr-2"
|
||||||
class="h-4 w-4 text-gray-900 stroke-1 mr-2"
|
/>
|
||||||
/>
|
<HelpCircle
|
||||||
{{ lesson.title }}
|
v-else-if="lesson.icon === 'icon-quiz'"
|
||||||
<Trash2
|
class="h-4 w-4 text-gray-900 stroke-1 mr-2"
|
||||||
v-if="allowEdit"
|
/>
|
||||||
@click.prevent="trashLesson(lesson.name, chapter.name)"
|
<FileText
|
||||||
class="h-4 w-4 stroke-1.5 text-gray-700 ml-auto invisible group-hover:visible"
|
v-else-if="lesson.icon === 'icon-list'"
|
||||||
/>
|
class="h-4 w-4 text-gray-900 stroke-1 mr-2"
|
||||||
<Check
|
/>
|
||||||
v-if="lesson.is_complete"
|
{{ lesson.title }}
|
||||||
class="h-4 w-4 text-green-700 ml-2"
|
<Trash2
|
||||||
/>
|
v-if="allowEdit"
|
||||||
</div>
|
@click.prevent="trashLesson(lesson.name, chapter.name)"
|
||||||
</router-link>
|
class="h-4 w-4 stroke-1.5 text-gray-700 ml-auto invisible group-hover:visible"
|
||||||
</div>
|
/>
|
||||||
</div>
|
<Check
|
||||||
|
v-if="lesson.is_complete"
|
||||||
|
class="h-4 w-4 text-green-700 ml-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
<div v-if="allowEdit" class="flex mt-2 mb-4 pl-8">
|
<div v-if="allowEdit" class="flex mt-2 mb-4 pl-8">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
@@ -111,6 +119,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Button, createResource } from 'frappe-ui'
|
import { Button, createResource } from 'frappe-ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import Draggable from 'vuedraggable'
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
||||||
import {
|
import {
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
@@ -172,7 +181,22 @@ const deleteLesson = createResource({
|
|||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
outline.reload()
|
outline.reload()
|
||||||
showToast('Success', 'Lesson deleted', 'check')
|
showToast('Success', 'Lesson deleted successfully', 'check')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateLessonIndex = createResource({
|
||||||
|
url: 'lms.lms.api.update_lesson_index',
|
||||||
|
makeParams(values) {
|
||||||
|
return {
|
||||||
|
lesson: values.lesson,
|
||||||
|
sourceChapter: values.sourceChapter,
|
||||||
|
targetChapter: values.targetChapter,
|
||||||
|
idx: values.idx,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
showToast('Success', 'Lesson moved successfully', 'check')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -195,6 +219,15 @@ const openChapterModal = (chapter = null) => {
|
|||||||
const getCurrentChapter = () => {
|
const getCurrentChapter = () => {
|
||||||
return currentChapter.value
|
return currentChapter.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateOutline = (e) => {
|
||||||
|
updateLessonIndex.submit({
|
||||||
|
lesson: e.item.__draggable_context.element.name,
|
||||||
|
sourceChapter: e.from.dataset.chapter,
|
||||||
|
targetChapter: e.to.dataset.chapter,
|
||||||
|
idx: e.newIndex,
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.outline-lesson:has(.router-link-active) {
|
.outline-lesson:has(.router-link-active) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
</slot>
|
</slot>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span
|
<span
|
||||||
class="flex-shrink-0 text-base duration-300 ease-in-out"
|
class="flex-shrink-0 text-sm duration-300 ease-in-out"
|
||||||
:class="
|
:class="
|
||||||
isCollapsed
|
isCollapsed
|
||||||
? 'ml-0 w-0 overflow-hidden opacity-0'
|
? 'ml-0 w-0 overflow-hidden opacity-0'
|
||||||
@@ -38,12 +38,12 @@
|
|||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="icons['Edit']"
|
:is="icons['Edit']"
|
||||||
class="h-3 w-3 stroke-1.5 text-gray-800"
|
class="h-3 w-3 stroke-1.5 text-gray-700"
|
||||||
@click.stop="openModal(link)"
|
@click.stop="openModal(link)"
|
||||||
/>
|
/>
|
||||||
<component
|
<component
|
||||||
:is="icons['X']"
|
:is="icons['X']"
|
||||||
class="h-3 w-3 stroke-1.5 text-gray-800"
|
class="h-3 w-3 stroke-1.5 text-gray-700"
|
||||||
@click.stop="deletePage(link)"
|
@click.stop="deletePage(link)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ import {
|
|||||||
MessageCircle,
|
MessageCircle,
|
||||||
Globe,
|
Globe,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { formatTime } from '@/utils'
|
import { formatTime, updateDocumentTitle } from '@/utils'
|
||||||
import BatchDashboard from '@/components/BatchDashboard.vue'
|
import BatchDashboard from '@/components/BatchDashboard.vue'
|
||||||
import BatchCourses from '@/components/BatchCourses.vue'
|
import BatchCourses from '@/components/BatchCourses.vue'
|
||||||
import LiveClass from '@/components/LiveClass.vue'
|
import LiveClass from '@/components/LiveClass.vue'
|
||||||
@@ -286,4 +286,13 @@ const redirectToLogin = () => {
|
|||||||
const openAnnouncementModal = () => {
|
const openAnnouncementModal = () => {
|
||||||
showAnnouncementModal.value = true
|
showAnnouncementModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: batch.data?.title,
|
||||||
|
description: batch.data?.description,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
import { computed, inject } from 'vue'
|
import { computed, inject } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { BookOpen, Clock } from 'lucide-vue-next'
|
import { BookOpen, Clock } from 'lucide-vue-next'
|
||||||
import { formatTime } from '@/utils'
|
import { formatTime, updateDocumentTitle } from '@/utils'
|
||||||
import { Breadcrumbs, createResource } from 'frappe-ui'
|
import { Breadcrumbs, createResource } from 'frappe-ui'
|
||||||
import CourseCard from '@/components/CourseCard.vue'
|
import CourseCard from '@/components/CourseCard.vue'
|
||||||
import BatchOverlay from '@/components/BatchOverlay.vue'
|
import BatchOverlay from '@/components/BatchOverlay.vue'
|
||||||
@@ -125,16 +125,6 @@ const batch = createResource({
|
|||||||
batch: props.batchName,
|
batch: props.batchName,
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
onSuccess(data) {
|
|
||||||
if (data.students?.includes(user.data?.name)) {
|
|
||||||
router.push({
|
|
||||||
name: 'Batch',
|
|
||||||
params: {
|
|
||||||
batchName: props.batchName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const courses = createResource({
|
const courses = createResource({
|
||||||
@@ -154,6 +144,15 @@ const breadcrumbs = computed(() => {
|
|||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: batch.data?.title,
|
||||||
|
description: batch.data?.description,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.batch-description p {
|
.batch-description p {
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ import { createListResource, Breadcrumbs, Button, Tabs, Badge } from 'frappe-ui'
|
|||||||
import { Plus } from 'lucide-vue-next'
|
import { Plus } from 'lucide-vue-next'
|
||||||
import BatchCard from '@/components/BatchCard.vue'
|
import BatchCard from '@/components/BatchCard.vue'
|
||||||
import { inject, ref, computed } from 'vue'
|
import { inject, ref, computed } from 'vue'
|
||||||
|
import { updateDocumentTitle } from '@/utils'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
|
|
||||||
@@ -129,4 +130,13 @@ if (user.data) {
|
|||||||
count: computed(() => batches.data?.enrolled?.length),
|
count: computed(() => batches.data?.enrolled?.length),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: 'Batches',
|
||||||
|
description: 'All batches divided by categories',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import { Breadcrumbs, FormControl, createResource } from 'frappe-ui'
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import { Search } from 'lucide-vue-next'
|
import { Search } from 'lucide-vue-next'
|
||||||
|
import { updateDocumentTitle } from '@/utils'
|
||||||
|
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
|
||||||
@@ -70,4 +71,13 @@ const participants = createResource({
|
|||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
return [{ label: 'Certified Participants', to: '/certified-participants' }]
|
return [{ label: 'Certified Participants', to: '/certified-participants' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: 'Certified Participants',
|
||||||
|
description: 'All participants that have been certified.',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,19 +7,6 @@
|
|||||||
>
|
>
|
||||||
<Breadcrumbs class="h-7" :items="breadcrumbs" />
|
<Breadcrumbs class="h-7" :items="breadcrumbs" />
|
||||||
<div class="flex items-center mt-3 md:mt-0">
|
<div class="flex items-center mt-3 md:mt-0">
|
||||||
<router-link
|
|
||||||
v-if="courseResource.data"
|
|
||||||
:to="{
|
|
||||||
name: 'CourseDetail',
|
|
||||||
params: { courseName: courseResource.data.name },
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<Button>
|
|
||||||
<span>
|
|
||||||
{{ __('View Course') }}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
</router-link>
|
|
||||||
<Button variant="solid" @click="submitCourse()" class="ml-2">
|
<Button variant="solid" @click="submitCourse()" class="ml-2">
|
||||||
<span>
|
<span>
|
||||||
{{ __('Save') }}
|
{{ __('Save') }}
|
||||||
@@ -222,7 +209,12 @@ import {
|
|||||||
reactive,
|
reactive,
|
||||||
watch,
|
watch,
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { convertToTitleCase, showToast, getFileSize } from '../utils'
|
import {
|
||||||
|
convertToTitleCase,
|
||||||
|
showToast,
|
||||||
|
getFileSize,
|
||||||
|
updateDocumentTitle,
|
||||||
|
} from '../utils'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { FileText, X } from 'lucide-vue-next'
|
import { FileText, X } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@@ -492,4 +484,13 @@ const breadcrumbs = computed(() => {
|
|||||||
})
|
})
|
||||||
return crumbs
|
return crumbs
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: 'Create a Course',
|
||||||
|
description: 'Create or edit a course for your learning system.',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ import UserAvatar from '@/components/UserAvatar.vue'
|
|||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
||||||
import Discussions from '@/components/Discussions.vue'
|
import Discussions from '@/components/Discussions.vue'
|
||||||
import { getEditorTools } from '../utils'
|
import { getEditorTools, updateDocumentTitle } from '../utils'
|
||||||
import EditorJS from '@editorjs/editorjs'
|
import EditorJS from '@editorjs/editorjs'
|
||||||
import LessonContent from '@/components/LessonContent.vue'
|
import LessonContent from '@/components/LessonContent.vue'
|
||||||
import CourseInstructors from '@/components/CourseInstructors.vue'
|
import CourseInstructors from '@/components/CourseInstructors.vue'
|
||||||
@@ -338,6 +338,15 @@ const allowInstructorContent = () => {
|
|||||||
const redirectToLogin = () => {
|
const redirectToLogin = () => {
|
||||||
window.location.href = `/login?redirect-to=/lms/courses/${props.courseName}`
|
window.location.href = `/login?redirect-to=/lms/courses/${props.courseName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: lesson.data?.title,
|
||||||
|
description: lesson.data?.course,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.avatar-group {
|
.avatar-group {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ import { computed, inject, ref, onMounted } from 'vue'
|
|||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { X } from 'lucide-vue-next'
|
import { X } from 'lucide-vue-next'
|
||||||
|
import { updateDocumentTitle } from '@/utils'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const socket = inject('$socket')
|
const socket = inject('$socket')
|
||||||
@@ -146,6 +147,15 @@ const breadcrumbs = computed(() => {
|
|||||||
]
|
]
|
||||||
return crumbs
|
return crumbs
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: 'Notifications',
|
||||||
|
description: 'All your notifications in one place.',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.notification strong {
|
.notification strong {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ import { Edit } from 'lucide-vue-next'
|
|||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import NoPermission from '@/components/NoPermission.vue'
|
import NoPermission from '@/components/NoPermission.vue'
|
||||||
import { convertToTitleCase } from '@/utils'
|
import { convertToTitleCase, updateDocumentTitle } from '@/utils'
|
||||||
import EditProfile from '@/components/Modals/EditProfile.vue'
|
import EditProfile from '@/components/Modals/EditProfile.vue'
|
||||||
import EditCoverImage from '@/components/Modals/EditCoverImage.vue'
|
import EditCoverImage from '@/components/Modals/EditCoverImage.vue'
|
||||||
|
|
||||||
@@ -208,4 +208,13 @@ const breadcrumbs = computed(() => {
|
|||||||
]
|
]
|
||||||
return crumbs
|
return crumbs
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const pageMeta = computed(() => {
|
||||||
|
return {
|
||||||
|
title: profile.data?.full_name,
|
||||||
|
description: profile.data?.headline,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -357,13 +357,19 @@ export function getSidebarLinks() {
|
|||||||
label: 'Courses',
|
label: 'Courses',
|
||||||
icon: 'BookOpen',
|
icon: 'BookOpen',
|
||||||
to: 'Courses',
|
to: 'Courses',
|
||||||
activeFor: ['Courses', 'CourseDetail', 'Lesson'],
|
activeFor: [
|
||||||
|
'Courses',
|
||||||
|
'CourseDetail',
|
||||||
|
'Lesson',
|
||||||
|
'CreateCourse',
|
||||||
|
'CreateLesson',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Batches',
|
label: 'Batches',
|
||||||
icon: 'Users',
|
icon: 'Users',
|
||||||
to: 'Batches',
|
to: 'Batches',
|
||||||
activeFor: ['Batches', 'BatchDetail', 'Batch'],
|
activeFor: ['Batches', 'BatchDetail', 'Batch', 'BatchCreation'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Certified Participants',
|
label: 'Certified Participants',
|
||||||
|
|||||||
@@ -1865,12 +1865,18 @@ socket.io-parser@~4.2.4:
|
|||||||
"@socket.io/component-emitter" "~3.1.0"
|
"@socket.io/component-emitter" "~3.1.0"
|
||||||
debug "~4.3.1"
|
debug "~4.3.1"
|
||||||
|
|
||||||
|
sortablejs@1.14.0:
|
||||||
|
version "1.14.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
|
||||||
|
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
|
||||||
|
|
||||||
source-map-js@^1.2.0:
|
source-map-js@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
|
||||||
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
||||||
|
name string-width-cjs
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -1889,6 +1895,7 @@ string-width@^5.0.1, string-width@^5.1.2:
|
|||||||
strip-ansi "^7.0.1"
|
strip-ansi "^7.0.1"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
|
name strip-ansi-cjs
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@@ -2025,6 +2032,11 @@ vue-demi@>=0.13.0, vue-demi@>=0.14.5, vue-demi@>=0.14.7:
|
|||||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.8.tgz#00335e9317b45e4a68d3528aaf58e0cec3d5640a"
|
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.8.tgz#00335e9317b45e4a68d3528aaf58e0cec3d5640a"
|
||||||
integrity sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==
|
integrity sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==
|
||||||
|
|
||||||
|
vue-draggable-next@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-draggable-next/-/vue-draggable-next-2.2.1.tgz#adbe98c74610cca8f4eb63f92042681f96920451"
|
||||||
|
integrity sha512-EAMS1IRHF0kZO0o5PMOinsQsXIqsrKT1hKmbICxG3UEtn7zLFkLxlAtajcCcUTisNvQ6TtCB5COjD9a1raNADw==
|
||||||
|
|
||||||
vue-router@^4.0.12:
|
vue-router@^4.0.12:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.3.2.tgz#08096c7765dacc6832f58e35f7a081a8b34116a7"
|
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.3.2.tgz#08096c7765dacc6832f58e35f7a081a8b34116a7"
|
||||||
@@ -2043,6 +2055,13 @@ vue@^3.4.23:
|
|||||||
"@vue/server-renderer" "3.4.27"
|
"@vue/server-renderer" "3.4.27"
|
||||||
"@vue/shared" "3.4.27"
|
"@vue/shared" "3.4.27"
|
||||||
|
|
||||||
|
vuedraggable@4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270"
|
||||||
|
integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==
|
||||||
|
dependencies:
|
||||||
|
sortablejs "1.14.0"
|
||||||
|
|
||||||
w3c-keyname@^2.2.0:
|
w3c-keyname@^2.2.0:
|
||||||
version "2.2.8"
|
version "2.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
||||||
|
|||||||
@@ -483,3 +483,62 @@ def delete_sidebar_item(webpage):
|
|||||||
def delete_lesson(lesson, chapter):
|
def delete_lesson(lesson, chapter):
|
||||||
frappe.db.delete("Lesson Reference", {"parent": chapter, "lesson": lesson})
|
frappe.db.delete("Lesson Reference", {"parent": chapter, "lesson": lesson})
|
||||||
frappe.db.delete("Course Lesson", lesson)
|
frappe.db.delete("Course Lesson", lesson)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def update_lesson_index(lesson, sourceChapter, targetChapter, idx):
|
||||||
|
hasMoved = sourceChapter == targetChapter
|
||||||
|
|
||||||
|
update_source_chapter(lesson, sourceChapter, idx, hasMoved)
|
||||||
|
if not hasMoved:
|
||||||
|
update_target_chapter(lesson, targetChapter, idx)
|
||||||
|
|
||||||
|
|
||||||
|
def update_source_chapter(lesson, chapter, idx, hasMoved=False):
|
||||||
|
lessons = frappe.get_all(
|
||||||
|
"Lesson Reference",
|
||||||
|
{
|
||||||
|
"parent": chapter,
|
||||||
|
},
|
||||||
|
pluck="lesson",
|
||||||
|
order_by="idx",
|
||||||
|
)
|
||||||
|
|
||||||
|
lessons.remove(lesson)
|
||||||
|
if not hasMoved:
|
||||||
|
frappe.db.delete("Lesson Reference", {"parent": chapter, "lesson": lesson})
|
||||||
|
else:
|
||||||
|
lessons.insert(idx, lesson)
|
||||||
|
|
||||||
|
update_index(lessons, chapter)
|
||||||
|
|
||||||
|
|
||||||
|
def update_target_chapter(lesson, chapter, idx):
|
||||||
|
lessons = frappe.get_all(
|
||||||
|
"Lesson Reference",
|
||||||
|
{
|
||||||
|
"parent": chapter,
|
||||||
|
},
|
||||||
|
pluck="lesson",
|
||||||
|
order_by="idx",
|
||||||
|
)
|
||||||
|
|
||||||
|
lessons.insert(idx, lesson)
|
||||||
|
new_lesson_reference = frappe.new_doc("Lesson Reference")
|
||||||
|
new_lesson_reference.update(
|
||||||
|
{
|
||||||
|
"lesson": lesson,
|
||||||
|
"parent": chapter,
|
||||||
|
"parenttype": "Course Chapter",
|
||||||
|
"parentfield": "lessons",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
new_lesson_reference.insert()
|
||||||
|
update_index(lessons, chapter)
|
||||||
|
|
||||||
|
|
||||||
|
def update_index(lessons, chapter):
|
||||||
|
for row in lessons:
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Lesson Reference", {"lesson": row, "parent": chapter}, "idx", lessons.index(row) + 1
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
{% set title = frappe.db.get_value("LMS Course", doc.course, "title") %}
|
{% set title = frappe.db.get_value("LMS Course", doc.course, "title") %}
|
||||||
{% set timezone = frappe.db.get_value("LMS Batch", doc.batch, "timezone") %}
|
{% set timezone = frappe.db.get_value("LMS Batch", doc.batch, "timezone") %}
|
||||||
{% set timezone = timezone if timezone else '' %}
|
{% set timezone = timezone if timezone else '' %}
|
||||||
|
{% set evaluator_name = frappe.db.get_value("User", doc.evaluator, "full_name") %}
|
||||||
|
|
||||||
<p> {{ _("Hey {0}").format(doc.member_name) }} </p>
|
<p> {{ _("Hey {0}").format(doc.member_name) }} </p>
|
||||||
<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, "medium"), frappe.utils.format_time(doc.start_time, "short"), timezone) }}</p>
|
<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, "medium"), frappe.utils.format_time(doc.start_time, "short"), timezone) }}</p>
|
||||||
|
<p> {{ _("Your evaluator is {0}").format(evaluator_name) }}
|
||||||
<p> {{ _("Please prepare well and be on time for the evaluations.") }} </p>
|
<p> {{ _("Please prepare well and be on time for the evaluations.") }} </p>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
"event": "New",
|
"event": "New",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"message": "{% set title = frappe.db.get_value(\"LMS Course\", doc.course, \"title\") %}\n{% set timezone = frappe.db.get_value(\"LMS Batch\", doc.batch, \"timezone\") %}\n{% set timezone = timezone if timezone else '' %}\n\n<p> {{ _(\"Hey {0}\").format(doc.member_name) }} </p>\n<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\"), timezone) }}</p>\n<p> {{ _(\"Please prepare well and be on time for the evaluations.\") }} </p>\n",
|
"message": "{% set title = frappe.db.get_value(\"LMS Course\", doc.course, \"title\") %}\n{% set timezone = frappe.db.get_value(\"LMS Batch\", doc.batch, \"timezone\") %}\n{% set timezone = timezone if timezone else '' %}\n{% set evaluator_name = frappe.db.get_value(\"User\", doc.evaluator, \"full_name\") %}\n\n<p> {{ _(\"Hey {0}\").format(doc.member_name) }} </p>\n<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\"), timezone) }}</p>\n<p> {{ _(\"Your evaluator is {0}\").format(evaluator_name) }}\n<p> {{ _(\"Please prepare well and be on time for the evaluations.\") }} </p>\n",
|
||||||
"message_type": "HTML",
|
"message_type": "HTML",
|
||||||
"modified": "2024-06-21 13:15:38.588768",
|
"modified": "2024-07-10 15:51:03.429317",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "sayali@erpnext.com",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Certificate Request Creation",
|
"name": "Certificate Request Creation",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{% set title = frappe.db.get_value("LMS Course", doc.course, "title") %}
|
{% set title = frappe.db.get_value("LMS Course", doc.course, "title") %}
|
||||||
{% set timezone = frappe.db.get_value("LMS Batch", doc.batch, "timezone") %}
|
{% set timezone = frappe.db.get_value("LMS Batch", doc.batch, "timezone") %}
|
||||||
{% set timezone = timezone if timezone else '' %}
|
{% set timezone = timezone if timezone else '' %}
|
||||||
|
{% set evaluator_name = frappe.db.get_value("User", doc.evaluator, "full_name") %}
|
||||||
|
|
||||||
<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, "medium"), frappe.utils.format_time(doc.start_time, "short"), timezone) }}</p>
|
<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, "medium"), frappe.utils.format_time(doc.start_time, "short"), timezone) }}</p>
|
||||||
|
<p> {{ _("Your evaluator is {0}").format(evaluator_name) }}
|
||||||
<p> {{ _("Please prepare well and be on time for the evaluations.") }} </p>
|
<p> {{ _("Please prepare well and be on time for the evaluations.") }} </p>
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
"event": "Days Before",
|
"event": "Days Before",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"message": "{% set title = frappe.db.get_value(\"LMS Course\", doc.course, \"title\") %}\n{% set timezone = frappe.db.get_value(\"LMS Batch\", doc.batch, \"timezone\") %}\n{% set timezone = timezone if timezone else '' %}\n\n<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\"), timezone) }}</p>\n\n<p> {{ _(\"Please prepare well and be on time for the evaluations.\") }} </p>\n",
|
"message": "{% set title = frappe.db.get_value(\"LMS Course\", doc.course, \"title\") %}\n{% set timezone = frappe.db.get_value(\"LMS Batch\", doc.batch, \"timezone\") %}\n{% set timezone = timezone if timezone else '' %}\n{% set evaluator_name = frappe.db.get_value(\"User\", doc.evaluator, \"full_name\") %}\n\n<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\"), timezone) }}</p>\n<p> {{ _(\"Your evaluator is {0}\").format(evaluator_name) }}\n<p> {{ _(\"Please prepare well and be on time for the evaluations.\") }} </p>\n",
|
||||||
"message_type": "HTML",
|
"message_type": "HTML",
|
||||||
"modified": "2024-06-21 13:17:26.360030",
|
"modified": "2024-07-10 15:51:33.803704",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "sayali@erpnext.com",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Certificate Request Reminder",
|
"name": "Certificate Request Reminder",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
|||||||
Reference in New Issue
Block a user