Compare commits

..

53 Commits

Author SHA1 Message Date
Frappe PR Bot
4b3a71e424 chore(release): Bumped to Version 2.10.0 2024-11-06 05:17:44 +00:00
Jannat Patel
5499e7294d Merge pull request #1095 from pateljannat/issues-46
fix: misc issues
2024-11-06 10:34:17 +05:30
Jannat Patel
fe1f78f8aa Merge pull request #1093 from frappe/l10n_develop2
chore: sync translations from crowdin
2024-11-05 16:13:22 +05:30
Jannat Patel
1709c6b658 Merge pull request #1094 from frappe/pot_develop_2024-11-01
chore: update POT file
2024-11-05 16:13:05 +05:30
Jannat Patel
d3583a2cfb fix: set event in live class 2024-11-04 12:01:58 +05:30
Jannat Patel
634035fbc0 fix: misc issues 2024-11-04 09:54:53 +05:30
Jannat Patel
3c5b18411b chore: Swedish translations 2024-11-03 20:08:50 +05:30
frappe-pr-bot
82bb45a9ef chore: update POT file 2024-11-01 16:04:24 +00:00
Jannat Patel
373f3df196 chore: Turkish translations 2024-11-01 19:00:24 +05:30
Jannat Patel
6021f15bac chore: Turkish translations 2024-10-31 18:59:42 +05:30
Jannat Patel
8f6f35d7c1 Merge pull request #1090 from iamejaaz/add-required-attribute
feat: add required indicator on the course add page
2024-10-31 11:48:06 +05:30
Jannat Patel
7aa5f4d20b Merge pull request #1086 from 0xflotus/patch-1
fix: small bug in course_progress_summary.py
2024-10-31 11:39:08 +05:30
Jannat Patel
64b54b05a6 Merge pull request #1085 from pateljannat/new-onboarding
feat: onboarding
2024-10-31 11:35:45 +05:30
Jannat Patel
22b1f22df4 fix: empty state conditions 2024-10-31 11:16:39 +05:30
Jannat Patel
ae4e5539d7 fix: removed chapter description when fetching outline 2024-10-31 09:51:50 +05:30
Ejaaz Khan
dbd96329b5 style: format code with precommit 2024-10-31 00:22:28 +05:30
Ejaaz Khan
c118ec7c4a feat: add required indicator on the course add page 2024-10-30 23:59:26 +05:30
0xflotus
7aab449502 fix: changed ranges 2024-10-30 18:47:36 +01:00
Jannat Patel
cf166b3a57 Merge pull request #1089 from 0xflotus/patch-2
chore: add some german translations
2024-10-30 23:12:33 +05:30
Jannat Patel
da5910d40d test: changed labels as per new onboarding 2024-10-30 23:11:47 +05:30
Jannat Patel
8640ecf9be refactor: course list data 2024-10-30 22:12:59 +05:30
0xflotus
c4faceff30 chore: add some german translations 2024-10-30 14:55:25 +01:00
0xflotus
01bd017bda fix: fixed labels 2024-10-29 19:33:22 +01:00
0xflotus
d76357981b fix: small bug in course_progress_summary.py
This is a small logical fix.

Otherwise if `row.progress == 10 or row.progress == 40 or row.progress == 70` wouldn't have an effect.
2024-10-29 19:28:14 +01:00
Jannat Patel
19b759e9fb feat: onboarding 2024-10-29 23:00:38 +05:30
Jannat Patel
df3bca6405 Merge pull request #1081 from frappe/l10n_develop2
chore: sync translations from crowdin
2024-10-28 09:34:34 +05:30
Jannat Patel
5cde79b5eb chore: Persian translations 2024-10-27 17:10:17 +05:30
Jannat Patel
9b35cdbddc chore: Bosnian translations 2024-10-26 16:54:33 +05:30
Jannat Patel
70ec22004a chore: Persian translations 2024-10-26 16:54:32 +05:30
Jannat Patel
95ed77421a chore: Chinese Simplified translations 2024-10-26 16:54:31 +05:30
Jannat Patel
d64ec9817c chore: Turkish translations 2024-10-26 16:54:29 +05:30
Jannat Patel
ce01b7634f chore: Swedish translations 2024-10-26 16:54:28 +05:30
Jannat Patel
e0819f83bc chore: Russian translations 2024-10-26 16:54:27 +05:30
Jannat Patel
f87d28c2f5 chore: Polish translations 2024-10-26 16:54:25 +05:30
Jannat Patel
544b59744b chore: Hungarian translations 2024-10-26 16:54:24 +05:30
Jannat Patel
467dfb831d chore: German translations 2024-10-26 16:54:23 +05:30
Jannat Patel
4c4b4eaf55 chore: Arabic translations 2024-10-26 16:54:21 +05:30
Jannat Patel
227e5d00e5 chore: Spanish translations 2024-10-26 16:54:20 +05:30
Jannat Patel
73e9e384c8 chore: French translations 2024-10-26 16:54:18 +05:30
Jannat Patel
5bebdcba68 Merge pull request #1080 from frappe/l10n_develop2
chore: sync translations from crowdin
2024-10-25 16:48:48 +05:30
Jannat Patel
1c2e52ae4b chore: Esperanto translations 2024-10-25 16:35:56 +05:30
Jannat Patel
9377e89561 chore: Bosnian translations 2024-10-25 16:35:54 +05:30
Jannat Patel
4cae05ecbe chore: Persian translations 2024-10-25 16:35:53 +05:30
Jannat Patel
909dcfd51e chore: Chinese Simplified translations 2024-10-25 16:35:51 +05:30
Jannat Patel
2bd96a1f2a chore: Turkish translations 2024-10-25 16:35:49 +05:30
Jannat Patel
aca41080ee chore: Swedish translations 2024-10-25 16:35:48 +05:30
Jannat Patel
1c351696a9 chore: Russian translations 2024-10-25 16:35:46 +05:30
Jannat Patel
51a8958aa6 chore: Polish translations 2024-10-25 16:35:45 +05:30
Jannat Patel
777b8aed02 chore: Hungarian translations 2024-10-25 16:35:43 +05:30
Jannat Patel
3672b90075 chore: German translations 2024-10-25 16:35:42 +05:30
Jannat Patel
92c7e613db chore: Arabic translations 2024-10-25 16:35:40 +05:30
Jannat Patel
5c58b85a00 chore: Spanish translations 2024-10-25 16:35:39 +05:30
Jannat Patel
8af82daa37 chore: French translations 2024-10-25 16:35:36 +05:30
54 changed files with 68404 additions and 4849 deletions

View File

@@ -13,6 +13,6 @@ module.exports = defineConfig({
openMode: 0,
},
e2e: {
baseUrl: "http://test_site_ui:8000",
baseUrl: "http://test:8000",
},
});

View File

@@ -5,7 +5,7 @@ describe("Course Creation", () => {
cy.visit("/lms/courses");
// Create a course
cy.get("a").contains("New").click();
cy.get("header").children().last().children().last().click();
cy.wait(1000);
cy.url().should("include", "/courses/new/edit");
@@ -73,7 +73,7 @@ describe("Course Creation", () => {
.should("be.visible")
.within(() => {
cy.get("label").contains("Title").type("Test Chapter");
cy.button("Add Chapter").click();
cy.button("Create").click();
});
// Add Lesson

View File

@@ -23,7 +23,7 @@
"codemirror-editor-vue3": "^2.8.0",
"dayjs": "^1.11.6",
"feather-icons": "^4.28.0",
"frappe-ui": "^0.1.69",
"frappe-ui": "^0.1.72",
"lucide-vue-next": "^0.383.0",
"markdown-it": "^14.0.0",
"pinia": "^2.0.33",

View File

@@ -25,7 +25,7 @@
</div>
</template>
<script setup>
import { createListResource, Avatar } from 'frappe-ui'
import { createResource, Avatar } from 'frappe-ui'
import { timeAgo } from '@/utils'
const props = defineProps({
@@ -35,24 +35,15 @@ const props = defineProps({
},
})
const communications = createListResource({
doctype: 'Communication',
fields: [
'subject',
'content',
'recipients',
'cc',
'communication_date',
'sender',
'sender_full_name',
],
filters: {
reference_doctype: 'LMS Batch',
reference_name: props.batch,
const communications = createResource({
url: 'lms.lms.api.get_announcements',
makeParams(value) {
return {
batch: props.batch,
}
},
orderBy: 'communication_date desc',
auto: true,
cache: ['batch', props.batch],
cache: ['announcement', props.batch],
})
</script>
<style>

View File

@@ -2,6 +2,7 @@
<div>
<label class="block mb-1" :class="labelClasses" v-if="label">
{{ label }}
<span class="text-red-500" v-if="required">*</span>
</label>
<div class="grid grid-cols-3 gap-1">
<Button
@@ -115,6 +116,9 @@ const props = defineProps({
type: Function,
default: (value) => `${value} is an Invalid value`,
},
required: {
type: Boolean,
},
})
const values = defineModel()

View File

@@ -30,29 +30,29 @@
</div>
<div class="flex flex-col flex-auto p-4">
<div class="flex items-center justify-between mb-2">
<div v-if="course.lesson_count">
<div v-if="course.lessons">
<Tooltip :text="__('Lessons')">
<span class="flex items-center">
<BookOpen class="h-4 w-4 stroke-1.5 text-gray-700 mr-1" />
{{ course.lesson_count }}
{{ course.lessons }}
</span>
</Tooltip>
</div>
<div v-if="course.enrollment_count">
<div v-if="course.enrollments">
<Tooltip :text="__('Enrolled Students')">
<span class="flex items-center">
<Users class="h-4 w-4 stroke-1.5 text-gray-700 mr-1" />
{{ course.enrollment_count }}
{{ course.enrollments }}
</span>
</Tooltip>
</div>
<div v-if="course.avg_rating">
<div v-if="course.rating">
<Tooltip :text="__('Average Rating')">
<span class="flex items-center">
<Star class="h-4 w-4 stroke-1.5 text-gray-700 mr-1" />
{{ course.avg_rating }}
{{ course.rating }}
</span>
</Tooltip>
</div>

View File

@@ -93,21 +93,19 @@
<div class="flex items-center mb-3">
<BookOpen class="h-5 w-5 stroke-1.5 text-gray-600" />
<span class="ml-2">
{{ course.data.lesson_count }} {{ __('Lessons') }}
{{ course.data.lessons }} {{ __('Lessons') }}
</span>
</div>
<div class="flex items-center mb-3">
<Users class="h-5 w-5 stroke-1.5 text-gray-600" />
<span class="ml-2">
{{ course.data.enrollment_count_formatted }}
{{ formatAmount(course.data.enrollments) }}
{{ __('Enrolled Students') }}
</span>
</div>
<div class="flex items-center">
<Star class="h-5 w-5 stroke-1.5 fill-orange-500 text-gray-50" />
<span class="ml-2">
{{ course.data.avg_rating }} {{ __('Rating') }}
</span>
<span class="ml-2"> {{ course.data.rating }} {{ __('Rating') }} </span>
</div>
</div>
</div>
@@ -116,7 +114,7 @@
import { BookOpen, Users, Star } from 'lucide-vue-next'
import { computed, inject } from 'vue'
import { Button, createResource } from 'frappe-ui'
import { showToast } from '@/utils/'
import { showToast, formatAmount } from '@/utils/'
import { capture } from '@/telemetry'
import { useRouter } from 'vue-router'

View File

@@ -76,7 +76,7 @@ const props = defineProps({
required: true,
},
avg_rating: {
type: Number,
type: String,
required: true,
},
membership: {

View File

@@ -21,7 +21,7 @@
<div class="space-y-2">
<div
class="flex items-center text-sm font-medium space-x-2 cursor-pointer"
class="flex text-sm font-medium space-x-2 cursor-pointer"
@click="openHelpDialog('upload')"
>
<span class="leading-5">
@@ -56,6 +56,21 @@
}}
</div>
</div>
<div class="space-y-2">
<div class="flex items-center text-sm font-medium space-x-2">
<span>
{{ __('What does include in preview mean?') }}
</span>
</div>
<div class="text-xs text-gray-600 mb-1 leading-5">
{{
__(
'If Include in Preview is enabled for a lesson then the lesson will also be accessible to non logged in users.'
)
}}
</div>
</div>
</div>
<ExplanationVideos v-model="showExplanation" :type="type" />
</template>

View File

@@ -48,7 +48,7 @@
<a
:href="cls.join_url"
target="_blank"
class="w-1/2 cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-gray-800 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 focus-visible:ring focus-visible:ring-gray-400 h-7 text-base px-2 rounded"
class="w-full cursor-pointer inline-flex items-center justify-center gap-2 transition-colors focus:outline-none text-gray-800 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 focus-visible:ring focus-visible:ring-gray-400 h-7 text-base px-2 rounded"
>
<Video class="h-4 w-4 stroke-1.5" />
{{ __('Join') }}

View File

@@ -44,7 +44,7 @@
<script setup>
import { Dialog, Input, TextEditor, createResource } from 'frappe-ui'
import { reactive } from 'vue'
import { createToast } from '@/utils/'
import { showToast } from '@/utils/'
const show = defineModel()
@@ -94,22 +94,14 @@ const makeAnnouncement = (close) => {
},
onSuccess() {
close()
createToast({
title: 'Success',
text: 'Announcement has been sent successfully',
icon: 'Check',
iconClasses: 'bg-green-600 text-white rounded-md p-px',
})
showToast(
__('Success'),
__('Announcement has been sent successfully'),
'check'
)
},
onError(err) {
createToast({
title: 'Error',
text: err.messages?.[0] || err,
icon: 'x',
iconClasses: 'bg-red-600 text-white rounded-md p-px',
position: 'top-center',
timeout: 10,
})
showToast(__('Error'), __(err.messages?.[0] || err), 'check')
},
}
)

View File

@@ -2,11 +2,11 @@
<Dialog
v-model="show"
:options="{
title: __('Add Chapter'),
title: chapterDetail ? __('Edit Chapter') : __('Add Chapter'),
size: 'lg',
actions: [
{
label: chapterDetail ? __('Edit Chapter') : __('Add Chapter'),
label: chapterDetail ? __('Edit') : __('Create'),
variant: 'solid',
onClick: (close) =>
chapterDetail ? editChapter(close) : addChapter(close),

View File

@@ -1,7 +1,7 @@
<template>
<div v-if="quiz.data">
<div
class="bg-blue-100 space-y-1 py-2 px-2 rounded-md text-sm text-blue-800"
class="bg-blue-100 space-y-1 py-2 px-2 mb-4 rounded-md text-sm text-blue-800"
>
<div class="leading-5">
{{

View File

@@ -236,7 +236,7 @@ const breadcrumbs = computed(() => {
const isStudent = computed(() => {
return (
user?.data &&
batch.data?.students.length &&
batch.data?.students?.length &&
batch.data?.students.includes(user.data.name)
)
})

View File

@@ -32,57 +32,65 @@
</div>
</div>
<div class="mb-4">
<div>
<FileUploader
v-if="!batch.image"
class="mt-4"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template v-slot="{ file, progress, uploading, openFileSelector }">
<div class="mb-4">
<Button @click="openFileSelector" :loading="uploading">
{{ uploading ? `Uploading ${progress}%` : 'Upload an image' }}
</Button>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="text-xs text-gray-600 mb-1">
{{ __('Meta Image') }}
</div>
<div class="text-xs text-gray-600 mb-2">
{{ __('Meta Image') }}
</div>
<FileUploader
v-if="!batch.image"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template v-slot="{ file, progress, uploading, openFileSelector }">
<div class="flex items-center">
<div class="border rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5 text-gray-700" />
<div class="border rounded-md w-fit py-5 px-20">
<Image class="size-5 stroke-1 text-gray-700" />
</div>
<div class="flex flex-col">
<span>
{{ batch.image.file_name }}
</span>
<span class="text-sm text-gray-500 mt-1">
{{ getFileSize(batch.image.file_size) }}
</span>
<div class="ml-4">
<Button @click="openFileSelector">
{{ __('Upload') }}
</Button>
<div class="mt-2 text-gray-600 text-sm">
{{
__(
'Appears when the batch URL is shared on any online platform'
)
}}
</div>
</div>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img :src="batch.image.file_url" class="border rounded-md w-40" />
<div class="ml-4">
<Button @click="removeImage()">
{{ __('Remove') }}
</Button>
<div class="mt-2 text-gray-600 text-sm">
{{
__(
'Appears when the batch URL is shared on any online platform'
)
}}
</div>
<X
@click="removeImage()"
class="bg-gray-200 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
/>
</div>
</div>
</div>
<MultiSelect
v-model="instructors"
doctype="User"
:label="__('Instructors')"
/>
</div>
<MultiSelect
v-model="instructors"
doctype="User"
:label="__('Instructors')"
/>
<div class="mb-4">
<FormControl
v-model="batch.description"
:label="__('Description')"
type="textarea"
class="my-4"
:placeholder="__('Short description of the batch')"
/>
<div>
<label class="block text-sm text-gray-600 mb-1">
@@ -133,6 +141,7 @@
v-model="batch.timezone"
:label="__('Timezone')"
type="text"
:placeholder="__('Example: IST (+5:30)')"
class="mb-4"
/>
</div>
@@ -149,6 +158,7 @@
:label="__('Seat Count')"
type="number"
class="mb-4"
:placeholder="__('Number of seats available')"
/>
<FormControl
v-model="batch.evaluation_end_date"
@@ -228,11 +238,11 @@ import {
createResource,
} from 'frappe-ui'
import Link from '@/components/Controls/Link.vue'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
import { useRouter } from 'vue-router'
import { getFileSize, showToast } from '../utils'
import { X, FileText } from 'lucide-vue-next'
import { showToast } from '../utils'
import { Image } from 'lucide-vue-next'
import { capture } from '@/telemetry'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
const router = useRouter()
const user = inject('$user')

View File

@@ -40,6 +40,7 @@
{{ __('Loading Batches...') }}
</div>
<Tabs
v-if="hasBatches"
v-model="tabIndex"
:tabs="makeTabs"
tablistClass="overflow-x-visible flex-wrap !gap-3 md:flex-nowrap"
@@ -79,24 +80,63 @@
<BatchCard :batch="batch" />
</router-link>
</div>
<div
v-else
class="grid flex-1 place-items-center text-xl font-medium text-gray-500"
>
<div class="flex flex-col items-center justify-center mt-4">
<div>
{{ __('No {0} batches found').format(tab.label.toLowerCase()) }}
</div>
</div>
<div v-else class="p-5 italic text-gray-500">
{{ __('No {0} batches').format(tab.label.toLowerCase()) }}
</div>
</template>
</Tabs>
<div
v-else-if="
!batches.loading &&
!hasBatches &&
(user.data?.is_instructor || user.data?.is_moderator)
"
class="grid grid-cols-3 p-5"
>
<router-link
:to="{
name: 'BatchForm',
params: {
batchName: 'new',
},
}"
>
<div class="bg-gray-50 py-32 px-5 rounded-md">
<div class="flex flex-col items-center text-center space-y-2">
<Plus
class="size-10 stroke-1 text-gray-800 p-1 rounded-full border bg-white"
/>
<div class="font-medium">
{{ __('Create a Batch') }}
</div>
<span class="text-gray-700 text-sm leading-4">
{{ __('You can link courses and assessments to it.') }}
</span>
</div>
</div>
</router-link>
</div>
<div
v-else-if="!batches.loading && !hasBatches"
class="text-center p-5 text-gray-600 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
>
<BookOpen class="size-10 mx-auto stroke-1 text-gray-500" />
<div class="text-xl font-medium">
{{ __('No batches found') }}
</div>
<div>
{{
__(
'There are no batches available at the moment. Keep an eye out, fresh learning experiences are on the way soon!'
)
}}
</div>
</div>
</div>
</div>
</template>
<script setup>
import {
createListResource,
createResource,
Breadcrumbs,
Button,
@@ -104,13 +144,14 @@ import {
Badge,
Select,
} from 'frappe-ui'
import { Plus } from 'lucide-vue-next'
import { BookOpen, Plus } from 'lucide-vue-next'
import BatchCard from '@/components/BatchCard.vue'
import { inject, ref, computed, onMounted, watch } from 'vue'
import { updateDocumentTitle } from '@/utils'
const user = inject('$user')
const currentCategory = ref(null)
const hasBatches = ref(false)
onMounted(() => {
let queries = new URLSearchParams(location.search)
@@ -119,10 +160,10 @@ onMounted(() => {
}
})
const batches = createListResource({
const batches = createResource({
doctype: 'LMS Batch',
url: 'lms.lms.utils.get_batches',
cache: ['batches', user?.data?.email],
cache: ['batches', user.data?.email],
auto: true,
})
@@ -183,6 +224,14 @@ const addToTabs = (label) => {
})
}
watch(batches, () => {
Object.keys(batches.data).forEach((key) => {
if (batches.data[key].length) {
hasBatches.value = true
}
})
})
watch(
() => currentCategory.value,
() => {

View File

@@ -16,16 +16,16 @@
</div>
<div class="flex items-center">
<Tooltip
v-if="course.data.avg_rating"
v-if="course.data.rating"
:text="__('Average Rating')"
class="flex items-center"
>
<Star class="h-5 w-5 text-gray-100 fill-orange-500" />
<span class="ml-1">
{{ course.data.avg_rating }}
{{ course.data.rating }}
</span>
</Tooltip>
<span v-if="course.data.avg_rating" class="mx-3">&middot;</span>
<span v-if="course.data.rating" class="mx-3">&middot;</span>
<Tooltip
v-if="course.data.enrollment_count"
:text="__('Enrolled Students')"
@@ -74,7 +74,7 @@
</div>
<CourseReviews
:courseName="course.data.name"
:avg_rating="course.data.avg_rating"
:avg_rating="course.data.rating"
:membership="course.data.membership"
/>
</div>
@@ -116,7 +116,7 @@ const breadcrumbs = computed(() => {
let items = [{ label: 'All Courses', route: { name: 'Courses' } }]
items.push({
label: course?.data?.title,
route: { name: 'CourseDetail', params: { course: course?.data?.name } },
route: { name: 'CourseDetail', params: { courseName: course?.data?.name } },
})
return items
})

View File

@@ -23,15 +23,23 @@
v-model="course.title"
:label="__('Title')"
class="mb-4"
:required="true"
/>
<FormControl
v-model="course.short_introduction"
:label="__('Short Introduction')"
:placeholder="
__(
'A one line introduction to the course that appears on the course card'
)
"
class="mb-4"
:required="true"
/>
<div class="mb-4">
<div class="mb-1.5 text-sm text-gray-700">
<div class="mb-1.5 text-sm text-gray-600">
{{ __('Course Description') }}
<span class="text-red-500">*</span>
</div>
<TextEditor
:content="course.description"
@@ -41,49 +49,62 @@
editorClass="prose-sm max-w-none border-b border-x bg-gray-100 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
<FileUploader
v-if="!course.course_image"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template
v-slot="{ file, progress, uploading, openFileSelector }"
>
<div class="mb-4">
<Button @click="openFileSelector" :loading="uploading">
{{
uploading ? `Uploading ${progress}%` : 'Upload an image'
}}
</Button>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="text-xs text-gray-600 mb-1">
<div class="mb-4">
<div class="text-xs text-gray-600 mb-2">
{{ __('Course Image') }}
<span class="text-red-500">*</span>
</div>
<div class="flex items-center">
<div class="border rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5 text-gray-700" />
<FileUploader
v-if="!course.course_image"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template
v-slot="{ file, progress, uploading, openFileSelector }"
>
<div class="flex items-center">
<div class="border rounded-md w-fit py-5 px-20">
<Image class="size-5 stroke-1 text-gray-700" />
</div>
<div class="ml-4">
<Button @click="openFileSelector">
{{ __('Upload') }}
</Button>
<div class="mt-2 text-gray-600 text-sm">
{{
__('Appears on the course card in the course list')
}}
</div>
</div>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img
:src="course.course_image.file_url"
class="border rounded-md w-40"
/>
<div class="ml-4">
<Button @click="removeImage()">
{{ __('Remove') }}
</Button>
<div class="mt-2 text-gray-600 text-sm">
{{ __('Appears on the course card in the course list') }}
</div>
</div>
</div>
<div class="flex flex-col">
<span>
{{ course.course_image.file_name }}
</span>
<span class="text-sm text-gray-500 mt-1">
{{ getFileSize(course.course_image.file_size) }}
</span>
</div>
<X
@click="removeImage()"
class="bg-gray-200 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
/>
</div>
</div>
<FormControl
v-model="course.video_link"
:label="__('Preview Video')"
:placeholder="
__(
'Paste the youtube link of a short video introducing the course'
)
"
class="mb-4"
/>
<div class="mb-4">
@@ -104,6 +125,8 @@
</div>
<FormControl
v-model="newTag"
:placeholder="__('Keywords for the course')"
class="w-52"
@keyup.enter="updateTags()"
id="tags"
/>
@@ -121,6 +144,7 @@
v-model="instructors"
doctype="User"
:label="__('Instructors')"
:required="true"
/>
</div>
<div class="container border-t">
@@ -130,7 +154,7 @@
<div class="grid grid-cols-3 gap-10 mb-4">
<div
v-if="user.data?.is_moderator"
class="flex flex-col space-y-3"
class="flex flex-col space-y-4"
>
<FormControl
type="checkbox"
@@ -224,14 +248,9 @@ import {
reactive,
watch,
} from 'vue'
import {
convertToTitleCase,
showToast,
getFileSize,
updateDocumentTitle,
} from '@/utils'
import { convertToTitleCase, showToast, updateDocumentTitle } from '@/utils'
import Link from '@/components/Controls/Link.vue'
import { FileText, X } from 'lucide-vue-next'
import { FileText, Image, X } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import CourseOutline from '@/components/CourseOutline.vue'
import MultiSelect from '@/components/Controls/MultiSelect.vue'

View File

@@ -8,7 +8,7 @@
:items="[{ label: __('Courses'), route: { name: 'Courses' } }]"
/>
<div class="flex space-x-2 justify-end">
<div class="w-46 md:w-44">
<div class="w-40 md:w-44">
<FormControl
v-if="categories.data?.length"
type="select"
@@ -48,6 +48,7 @@
</header>
<div class="">
<Tabs
v-if="hasCourses"
v-model="tabIndex"
tablistClass="overflow-x-visible flex-wrap !gap-3 md:flex-nowrap"
:tabs="makeTabs"
@@ -101,18 +102,57 @@
<CourseCard :course="course" />
</router-link>
</div>
<div
v-else
class="grid flex-1 place-items-center text-xl font-medium text-gray-500"
>
<div class="flex flex-col items-center justify-center mt-4">
<div>
{{ __('No {0} courses found').format(tab.label.toLowerCase()) }}
</div>
</div>
<div v-else class="p-5 italic text-gray-500">
{{ __('No {0} courses').format(tab.label.toLowerCase()) }}
</div>
</template>
</Tabs>
<div
v-else-if="
!courses.loading &&
(user.data?.is_moderator || user.data?.is_instructor)
"
class="grid grid-cols-3 p-5"
>
<router-link
:to="{
name: 'CourseForm',
params: {
courseName: 'new',
},
}"
>
<div class="bg-gray-50 py-32 px-5 rounded-md">
<div class="flex flex-col items-center text-center space-y-2">
<Plus
class="size-10 stroke-1 text-gray-800 p-1 rounded-full border bg-white"
/>
<div class="font-medium">
{{ __('Create a Course') }}
</div>
<span class="text-gray-700 text-sm leading-4">
{{ __('You can add chapters and lessons to it.') }}
</span>
</div>
</div>
</router-link>
</div>
<div
v-else-if="!courses.loading && !hasCourses"
class="text-center p-5 text-gray-600 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
>
<BookOpen class="size-10 mx-auto stroke-1 text-gray-500" />
<div class="text-xl font-medium">
{{ __('No courses found') }}
</div>
<div>
{{
__(
'There are no courses available at the moment. Keep an eye out, fresh learning experiences are on the way soon!'
)
}}
</div>
</div>
</div>
</div>
</template>
@@ -127,13 +167,14 @@ import {
createResource,
} from 'frappe-ui'
import CourseCard from '@/components/CourseCard.vue'
import { Plus, Search } from 'lucide-vue-next'
import { BookOpen, Plus, Search } from 'lucide-vue-next'
import { ref, computed, inject, onMounted, watch } from 'vue'
import { updateDocumentTitle } from '@/utils'
const user = inject('$user')
const searchQuery = ref('')
const currentCategory = ref(null)
const hasCourses = ref(false)
onMounted(() => {
let queries = new URLSearchParams(location.search)
@@ -223,6 +264,16 @@ const categories = createResource({
},
})
watch(courses, () => {
if (courses.data) {
Object.keys(courses.data).forEach((section) => {
if (courses.data[section].length) {
hasCourses.value = true
}
})
}
})
watch(
() => currentCategory.value,
() => {

View File

@@ -17,14 +17,9 @@
)
}}
</p>
<router-link
v-if="user.data"
:to="{ name: 'CourseDetail', params: { courseName: courseName } }"
>
<Button variant="solid">
{{ __('Start Learning') }}
</Button>
</router-link>
<Button v-if="user.data" @click="enrollStudent()" variant="solid">
{{ __('Start Learning') }}
</Button>
<Button v-else @click="redirectToLogin()">
{{ __('Login') }}
</Button>
@@ -194,7 +189,7 @@ import { createResource, Breadcrumbs, Button } from 'frappe-ui'
import { computed, watch, inject, ref, onMounted, onBeforeUnmount } from 'vue'
import CourseOutline from '@/components/CourseOutline.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import { useRoute } from 'vue-router'
import { useRouter, useRoute } from 'vue-router'
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
import Discussions from '@/components/Discussions.vue'
import { getEditorTools, updateDocumentTitle } from '../utils'
@@ -204,6 +199,7 @@ import CourseInstructors from '@/components/CourseInstructors.vue'
import ProgressBar from '@/components/ProgressBar.vue'
const user = inject('$user')
const router = useRouter()
const route = useRoute()
const allowDiscussions = ref(false)
const editor = ref(null)
@@ -301,14 +297,14 @@ const breadcrumbs = computed(() => {
let items = [{ label: 'All Courses', route: { name: 'Courses' } }]
items.push({
label: lesson?.data?.course_title,
route: { name: 'CourseDetail', params: { course: props.courseName } },
route: { name: 'CourseDetail', params: { courseName: props.courseName } },
})
items.push({
label: lesson?.data?.title,
route: {
name: 'Lesson',
params: {
course: props.courseName,
courseName: props.courseName,
chapterNumber: props.chapterNumber,
lessonNumber: props.lessonNumber,
},
@@ -379,6 +375,30 @@ const allowInstructorContent = () => {
return false
}
const enrollment = createResource({
url: 'frappe.client.insert',
makeParams() {
return {
doc: {
doctype: 'LMS Enrollment',
course: props.courseName,
member: user.data?.name,
},
}
},
})
const enrollStudent = () => {
enrollment.submit(
{},
{
onSuccess() {
window.location.reload()
},
}
)
}
const redirectToLogin = () => {
window.location.href = `/login?redirect-to=/lms/courses/${props.courseName}`
}

View File

@@ -69,7 +69,7 @@
</div>
</template>
<script setup>
import { Breadcrumbs, FormControl, createResource, Button } from 'frappe-ui'
import { Breadcrumbs, Button, createResource, FormControl } from 'frappe-ui'
import {
computed,
reactive,

View File

@@ -57,6 +57,15 @@ export function formatNumberIntoCurrency(number, currency) {
return ''
}
// create a function that formats numbers in thousands to k
export function formatAmount(amount) {
if (amount > 999) {
return (amount / 1000).toFixed(1) + 'k'
}
return amount
}
export function convertToTitleCase(str) {
if (!str) {
return ''

View File

@@ -51,7 +51,7 @@ export class Quiz {
app.mount(this.wrapper)
return
}
this.wrapper.innerHTML = `<div class='border rounded-md p-10 text-center mb-2'>
this.wrapper.innerHTML = `<div class='border rounded-md p-10 text-center bg-gray-50 mb-2'>
<span class="font-medium">
Quiz: ${quiz}
</span>

View File

@@ -1224,10 +1224,10 @@ fraction.js@^4.3.7:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
frappe-ui@^0.1.69:
version "0.1.69"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.69.tgz#bfc6d19dff97d2666c36da63f5de62f819539406"
integrity sha512-MKHYTcRvmccZwTYlIcmf4OCbJQH5eqKXsq3Cj2lbnmoWuuTh9m7T3AoRKEwOIlZ0mSGCH9yzaF2BINBXGpIJdQ==
frappe-ui@^0.1.72:
version "0.1.72"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.72.tgz#f5550056ddee7ad4341f2c1825d046404d221820"
integrity sha512-XWYKmCjw3ViD+/+tZMUiYqwHFlMGMsVuazOYiN5bKlE+aiheJsnHlOOUyQswYX1Y7jNxuC7gGpSLNg2ZpXA7hA==
dependencies:
"@headlessui/vue" "^1.7.14"
"@popperjs/core" "^2.11.2"

View File

@@ -1 +1 @@
__version__ = "2.9.0"
__version__ = "2.10.0"

View File

@@ -110,7 +110,8 @@ doc_events = {
# ---------------
scheduler_events = {
"hourly": [
"lms.lms.doctype.lms_certificate_request.lms_certificate_request.schedule_evals"
"lms.lms.doctype.lms_certificate_request.lms_certificate_request.schedule_evals",
"lms.lms.api.update_course_statistics",
],
"daily": ["lms.job.doctype.job_opportunity.job_opportunity.update_job_openings"],
}

View File

@@ -6,8 +6,9 @@ from frappe.translate import get_all_translations
from frappe import _
from frappe.query_builder import DocType
from frappe.query_builder.functions import Count
from frappe.utils import time_diff, now_datetime, get_datetime
from frappe.utils import time_diff, now_datetime, get_datetime, flt
from typing import Optional
from lms.lms.utils import get_average_rating, get_lesson_count
@frappe.whitelist()
@@ -760,3 +761,44 @@ def get_payment_gateway_details(payment_gateway):
"doctype": doctype,
"docname": docname,
}
def update_course_statistics():
courses = frappe.get_all("LMS Course", fields=["name"])
for course in courses:
lessons = get_lesson_count(course.name)
enrollments = frappe.db.count(
"LMS Enrollment", {"course": course.name, "member_type": "Student"}
)
avg_rating = get_average_rating(course.name) or 0
avg_rating = flt(avg_rating, frappe.get_system_settings("float_precision") or 3)
frappe.db.set_value(
"LMS Course",
course.name,
{"lessons": lessons, "enrollments": enrollments, "rating": avg_rating},
)
@frappe.whitelist()
def get_announcements(batch):
return frappe.get_all(
"Communication",
filters={
"reference_doctype": "LMS Batch",
"reference_name": batch,
},
fields=[
"subject",
"content",
"recipients",
"cc",
"communication_date",
"sender",
"sender_full_name",
],
order_by="communication_date desc",
)

View File

@@ -7,17 +7,3 @@ from frappe.model.document import Document
class BatchStudent(Document):
pass
@frappe.whitelist()
def enroll_batch(batch_name):
if frappe.db.exists(
"Batch Student", {"student": frappe.session.user, "parent": batch_name}
):
frappe.throw("You are already enrolled in this batch")
enrollment = frappe.new_doc("Batch Student")
enrollment.student = frappe.session.user
enrollment.parent = batch_name
enrollment.parentfield = "students"
enrollment.parenttype = "LMS Batch"
enrollment.save(ignore_permissions=True)

View File

@@ -9,9 +9,8 @@
"engine": "InnoDB",
"field_order": [
"course",
"title",
"column_break_3",
"description",
"title",
"section_break_5",
"lessons"
],
@@ -35,11 +34,6 @@
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
@@ -59,7 +53,7 @@
"link_fieldname": "chapter"
}
],
"modified": "2023-09-29 17:03:58.013819",
"modified": "2024-10-29 16:54:20.904683",
"modified_by": "Administrator",
"module": "LMS",
"name": "Course Chapter",

View File

@@ -33,6 +33,7 @@ class LMSBatch(Document):
self.validate_timetable()
self.send_confirmation_mail()
self.validate_evaluation_end_date()
self.add_students_to_live_class()
def validate_batch_end_date(self):
if self.end_date < self.start_date:
@@ -139,6 +140,27 @@ class LMSBatch(Document):
if cint(self.seat_count) < len(self.students):
frappe.throw(_("There are no seats available in this batch."))
def add_students_to_live_class(self):
for student in self.students:
if student.is_new():
live_classes = frappe.get_all(
"LMS Live Class", {"batch_name": self.name}, ["name", "event"]
)
for live_class in live_classes:
if live_class.event:
frappe.get_doc(
{
"doctype": "Event Participants",
"reference_doctype": "User",
"reference_docname": student.student,
"email": student.student,
"parent": live_class.event,
"parenttype": "Event",
"parentfield": "event_participants",
}
).save()
def validate_timetable(self):
for schedule in self.timetable:
if schedule.start_time and schedule.end_time:

View File

@@ -48,7 +48,12 @@
"certification_section",
"enable_certification",
"column_break_rxww",
"expiry"
"expiry",
"tab_4_tab",
"statistics_section",
"enrollments",
"lessons",
"rating"
],
"fields": [
{
@@ -249,6 +254,36 @@
"fieldtype": "Link",
"label": "Category",
"options": "LMS Category"
},
{
"fieldname": "tab_4_tab",
"fieldtype": "Tab Break",
"label": "Statistics"
},
{
"fieldname": "statistics_section",
"fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "enrollments",
"fieldtype": "Data",
"label": "Enrollments",
"read_only": 1
},
{
"default": "0",
"fieldname": "lessons",
"fieldtype": "Data",
"label": "Lessons",
"read_only": 1
},
{
"default": "0",
"fieldname": "rating",
"fieldtype": "Data",
"label": "Rating",
"read_only": 1
}
],
"is_published_field": "published",
@@ -275,7 +310,7 @@
}
],
"make_attachments_public": 1,
"modified": "2024-09-21 10:23:58.633912",
"modified": "2024-10-30 23:08:31.842860",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Course",

View File

@@ -187,192 +187,3 @@ def reindex_exercises(doc):
course = frappe.get_doc("LMS Course", course_data["name"])
course.reindex_exercises()
frappe.msgprint("All exercises in this course have been re-indexed.")
@frappe.whitelist(allow_guest=True)
def search_course(text):
courses = frappe.get_all(
"LMS Course",
filters={"published": True},
or_filters={
"title": ["like", f"%{text}%"],
"tags": ["like", f"%{text}%"],
"short_introduction": ["like", f"%{text}%"],
"description": ["like", f"%{text}%"],
},
fields=["name", "title"],
)
return courses
@frappe.whitelist()
def submit_for_review(course):
chapters = frappe.get_all("Chapter Reference", {"parent": course})
if not len(chapters):
return "No Chp"
frappe.db.set_value("LMS Course", course, "status", "Under Review")
return "OK"
@frappe.whitelist()
def save_course(
tags,
title,
short_introduction,
video_link,
description,
course,
published,
upcoming,
image=None,
paid_course=False,
course_price=None,
currency=None,
):
if not can_create_courses(course):
return
if course:
doc = frappe.get_doc("LMS Course", course)
else:
doc = frappe.get_doc({"doctype": "LMS Course"})
doc.update(
{
"title": title,
"short_introduction": short_introduction,
"video_link": video_link,
"image": image,
"description": description,
"tags": tags,
"published": cint(published),
"upcoming": cint(upcoming),
"paid_course": cint(paid_course),
"course_price": course_price,
"currency": currency,
}
)
doc.save(ignore_permissions=True)
return doc.name
@frappe.whitelist()
def save_chapter(course, title, chapter_description, idx, chapter):
if chapter:
doc = frappe.get_doc("Course Chapter", chapter)
else:
doc = frappe.get_doc({"doctype": "Course Chapter"})
doc.update({"course": course, "title": title, "description": chapter_description})
doc.save(ignore_permissions=True)
if chapter:
chapter_reference = frappe.get_doc("Chapter Reference", {"chapter": chapter})
else:
chapter_reference = frappe.get_doc(
{
"doctype": "Chapter Reference",
"parent": course,
"parenttype": "LMS Course",
"parentfield": "chapters",
"idx": idx,
}
)
chapter_reference.update({"chapter": doc.name})
chapter_reference.save(ignore_permissions=True)
return doc.name
@frappe.whitelist()
def save_lesson(
title,
body,
chapter,
preview,
idx,
lesson,
instructor_notes=None,
youtube=None,
quiz_id=None,
question=None,
file_type=None,
):
if lesson:
doc = frappe.get_doc("Course Lesson", lesson)
else:
doc = frappe.get_doc({"doctype": "Course Lesson"})
doc.update(
{
"chapter": chapter,
"title": title,
"body": body,
"instructor_notes": instructor_notes,
"include_in_preview": preview,
"youtube": youtube,
"quiz_id": quiz_id,
"question": question,
"file_type": file_type,
}
)
doc.save(ignore_permissions=True)
if lesson:
lesson_reference = frappe.get_doc("Lesson Reference", {"lesson": lesson})
else:
lesson_reference = frappe.get_doc(
{
"doctype": "Lesson Reference",
"parent": chapter,
"parenttype": "Course Chapter",
"parentfield": "lessons",
"idx": idx,
}
)
lesson_reference.update({"lesson": doc.name})
lesson_reference.save(ignore_permissions=True)
return doc.name
@frappe.whitelist()
def reorder_lesson(old_chapter, old_lesson_array, new_chapter, new_lesson_array):
if old_chapter == new_chapter:
sort_lessons(new_chapter, new_lesson_array)
else:
sort_lessons(old_chapter, old_lesson_array)
sort_lessons(new_chapter, new_lesson_array)
def sort_lessons(chapter, lesson_array):
lesson_array = json.loads(lesson_array)
for les in lesson_array:
ref = frappe.get_all("Lesson Reference", {"lesson": les}, ["name", "idx"])
if ref:
frappe.db.set_value(
"Lesson Reference",
ref[0].name,
{
"parent": chapter,
"idx": lesson_array.index(les) + 1,
},
)
@frappe.whitelist()
def reorder_chapter(chapter_array):
chapter_array = json.loads(chapter_array)
for chap in chapter_array:
ref = frappe.get_all("Chapter Reference", {"chapter": chap}, ["name", "idx"])
if ref:
frappe.db.set_value(
"Chapter Reference",
ref[0].name,
{
"idx": chapter_array.index(chap) + 1,
},
)

View File

@@ -75,7 +75,8 @@
"in_standard_filter": 1,
"label": "Course",
"options": "LMS Course",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"fieldname": "current_lesson",
@@ -126,7 +127,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-05-14 14:50:08.405033",
"modified": "2024-10-30 12:44:16.103598",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Enrollment",

View File

@@ -10,19 +10,20 @@
"title",
"host",
"batch_name",
"event",
"column_break_astv",
"date",
"time",
"duration",
"section_break_glxh",
"description",
"section_break_glxh",
"date",
"duration",
"column_break_spvt",
"time",
"timezone",
"password",
"auto_recording",
"section_break_yrpq",
"password",
"start_url",
"column_break_yokr",
"auto_recording",
"join_url"
],
"fields": [
@@ -122,11 +123,18 @@
"fieldtype": "Select",
"label": "Auto Recording",
"options": "No Recording\nLocal\nCloud"
},
{
"fieldname": "event",
"fieldtype": "Link",
"label": "Event",
"options": "Event",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-01-09 11:22:33.272341",
"modified": "2024-10-31 15:41:35.540856",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Live Class",

View File

@@ -16,6 +16,7 @@ class LMSLiveClass(Document):
if calendar:
event = self.create_event()
self.add_event_participants(event, calendar)
frappe.db.set_value(self.doctype, self.name, "event", event.name)
def create_event(self):
start = f"{self.date} {self.time}"

View File

@@ -76,6 +76,7 @@
"default": "0",
"fieldname": "payment_received",
"fieldtype": "Check",
"in_standard_filter": 1,
"label": "Payment Received"
},
{
@@ -140,7 +141,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-10-26 16:54:12.408274",
"modified": "2024-10-31 15:33:39.420366",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Payment",

View File

@@ -86,32 +86,32 @@ def get_charts(data):
completed = 0
less_than_hundred = 0
less_than_seventy = 0
less_than_forty = 0
less_than_ten = 0
less_than_seventy_one = 0
less_than_forty_one = 0
less_than_eleven = 0
for row in data:
if row.progress == 100:
completed += 1
elif row.progress < 100 and row.progress > 70:
less_than_hundred += 1
elif row.progress < 70 and row.progress > 40:
less_than_seventy += 1
elif row.progress < 40 and row.progress > 10:
less_than_forty += 1
elif row.progress < 10:
less_than_ten += 1
elif row.progress < 71 and row.progress > 40:
less_than_seventy_one += 1
elif row.progress < 41 and row.progress > 10:
less_than_forty_one += 1
elif row.progress < 11:
less_than_eleven += 1
charts = {
"data": {
"labels": ["0-10", "10-40", "40-70", "70-99", "100"],
"labels": ["0-10", "11-40", "41-70", "71-99", "100"],
"datasets": [
{
"name": "Progress (%)",
"values": [
less_than_ten,
less_than_forty,
less_than_seventy,
less_than_eleven,
less_than_forty_one,
less_than_seventy_one,
less_than_hundred,
completed,
],

View File

@@ -109,7 +109,7 @@ def get_chapters(course):
chapter_details = frappe.db.get_value(
"Course Chapter",
{"name": chapter.chapter},
["name", "title", "description"],
["name", "title"],
as_dict=True,
)
chapter.update(chapter_details)
@@ -157,11 +157,12 @@ def get_lesson_details(chapter, progress=False):
"file_type",
"instructor_notes",
"course",
"content",
],
as_dict=True,
)
lesson_details.number = f"{chapter.idx}.{row.idx}"
lesson_details.icon = get_lesson_icon(lesson_details.body)
lesson_details.icon = get_lesson_icon(lesson_details.body, lesson_details.content)
if progress:
lesson_details.is_complete = get_progress(lesson_details.course, lesson_details.name)
@@ -170,20 +171,38 @@ def get_lesson_details(chapter, progress=False):
return lessons
def get_lesson_icon(content):
icon = None
macros = find_macros(content)
def get_lesson_icon(body, content):
if content:
content = json.loads(content)
for block in content.get("blocks"):
if block.get("type") == "upload" and block.get("data").get("file_type").lower() in [
"mp4",
"webm",
"ogg",
"mov",
]:
return "icon-youtube"
if block.get("type") == "embed" and block.get("data").get("service") in [
"youtube",
"vimeo",
]:
return "icon-youtube"
if block.get("type") == "quiz":
return "icon-quiz"
return "icon-list"
macros = find_macros(body)
for macro in macros:
if macro[0] == "YouTubeVideo" or macro[0] == "Video":
icon = "icon-youtube"
return "icon-youtube"
elif macro[0] == "Quiz":
icon = "icon-quiz"
return "icon-quiz"
if not icon:
icon = "icon-list"
return icon
return "icon-list"
@frappe.whitelist(allow_guest=True)
@@ -1027,23 +1046,13 @@ def get_course_details(course):
"currency",
"amount_usd",
"enable_certification",
"lessons",
"enrollments",
"rating",
],
as_dict=1,
)
course_details.tags = course_details.tags.split(",") if course_details.tags else []
course_details.lesson_count = get_lesson_count(course_details.name)
course_details.enrollment_count = frappe.db.count(
"LMS Enrollment", {"course": course_details.name, "member_type": "Student"}
)
course_details.enrollment_count_formatted = format_number(
course_details.enrollment_count
)
avg_rating = get_average_rating(course_details.name) or 0
course_details.avg_rating = flt(
avg_rating, frappe.get_system_settings("float_precision") or 3
)
course_details.instructors = get_instructors(course_details.name)
if course_details.paid_course:
@@ -1092,14 +1101,14 @@ def get_categorized_courses(courses):
):
new.append(course)
if course.membership and course.published:
if course.membership:
enrolled.append(course)
elif course.is_instructor:
created.append(course)
categories = [live, enrolled, created]
for category in categories:
category.sort(key=lambda x: x.enrollment_count, reverse=True)
category.sort(key=lambda x: x.enrollments, reverse=True)
live.sort(key=lambda x: x.featured, reverse=True)
@@ -1124,7 +1133,7 @@ def get_course_outline(course, progress=False):
chapter_details = frappe.db.get_value(
"Course Chapter",
chapter.chapter,
["name", "title", "description"],
["name", "title"],
as_dict=True,
)
chapter_details["idx"] = chapter.idx

5361
lms/locale/ar.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/bs.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/de.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/eo.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

5361
lms/locale/fa.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/fr.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/hu.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Frappe LMS VERSION\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2024-10-25 10:37+0000\n"
"PO-Revision-Date: 2024-10-25 10:37+0000\n"
"POT-Creation-Date: 2024-11-01 16:04+0000\n"
"PO-Revision-Date: 2024-11-01 16:04+0000\n"
"Last-Translator: jannat@frappe.io\n"
"Language-Team: jannat@frappe.io\n"
"MIME-Version: 1.0\n"
@@ -60,6 +60,10 @@ msgstr ""
msgid "<span style=\"font-size: 18px;\"><b>Statistics</b></span>"
msgstr ""
#: frontend/src/pages/CourseForm.vue:32
msgid "A one line introduction to the course that appears on the course card"
msgstr ""
#: frontend/src/pages/ProfileAbout.vue:4
msgid "About"
msgstr ""
@@ -100,7 +104,6 @@ msgstr ""
#: frontend/src/components/CourseOutline.vue:11
#: frontend/src/components/CreateOutline.vue:18
#: frontend/src/components/Modals/ChapterModal.vue:5
#: frontend/src/components/Modals/ChapterModal.vue:9
msgid "Add Chapter"
msgstr ""
@@ -223,7 +226,7 @@ msgstr ""
#. Label of the amount (Currency) field in DocType 'Web Form'
#. Label of the amount (Currency) field in DocType 'LMS Batch'
#. Label of the amount (Currency) field in DocType 'LMS Payment'
#: frontend/src/pages/BatchForm.vue:198 lms/fixtures/custom_field.json
#: frontend/src/pages/BatchForm.vue:208 lms/fixtures/custom_field.json
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_payment/lms_payment.json
#: lms/public/js/common_functions.js:379
@@ -267,6 +270,14 @@ msgstr ""
msgid "Answer"
msgstr ""
#: frontend/src/pages/CourseForm.vue:76 frontend/src/pages/CourseForm.vue:94
msgid "Appears on the course card in the course list"
msgstr ""
#: frontend/src/pages/BatchForm.vue:55 frontend/src/pages/BatchForm.vue:73
msgid "Appears when the batch URL is shared on any online platform"
msgstr ""
#: frontend/src/pages/JobDetail.vue:131
msgid "Applications Received"
msgstr ""
@@ -465,7 +476,7 @@ msgid "Batch Description"
msgstr ""
#. Label of the batch_details (Text Editor) field in DocType 'LMS Batch'
#: frontend/src/pages/BatchForm.vue:89 lms/lms/doctype/lms_batch/lms_batch.json
#: frontend/src/pages/BatchForm.vue:97 lms/lms/doctype/lms_batch/lms_batch.json
#: lms/public/js/common_functions.js:349
#: lms/templates/emails/batch_confirmation.html:30
msgid "Batch Details"
@@ -632,8 +643,8 @@ msgstr ""
#. Label of the category (Link) field in DocType 'LMS Batch'
#. Label of the category (Data) field in DocType 'LMS Category'
#. Label of the category (Link) field in DocType 'LMS Course'
#: frontend/src/pages/BatchForm.vue:179 frontend/src/pages/Batches.vue:16
#: frontend/src/pages/CourseForm.vue:116 frontend/src/pages/Courses.vue:17
#: frontend/src/pages/BatchForm.vue:189 frontend/src/pages/Batches.vue:16
#: frontend/src/pages/CourseForm.vue:139 frontend/src/pages/Courses.vue:17
#: frontend/src/pages/JobDetail.vue:102
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_category/lms_category.json
@@ -934,7 +945,7 @@ msgstr ""
msgid "Completed"
msgstr ""
#: frontend/src/pages/CourseForm.vue:168
#: frontend/src/pages/CourseForm.vue:192
msgid "Completion Certificate"
msgstr ""
@@ -984,7 +995,7 @@ msgstr ""
msgid "Contract"
msgstr ""
#: lms/lms/utils.py:423
#: lms/lms/utils.py:442
msgid "Cookie Policy"
msgstr ""
@@ -1105,7 +1116,7 @@ msgstr ""
msgid "Course Data"
msgstr ""
#: frontend/src/pages/CourseForm.vue:34
#: frontend/src/pages/CourseForm.vue:41
msgid "Course Description"
msgstr ""
@@ -1114,7 +1125,7 @@ msgstr ""
msgid "Course Evaluator"
msgstr ""
#: frontend/src/pages/CourseForm.vue:64
#: frontend/src/pages/CourseForm.vue:54
msgid "Course Image"
msgstr ""
@@ -1137,7 +1148,7 @@ msgid "Course Name"
msgstr ""
#. Label of the course_price (Currency) field in DocType 'LMS Course'
#: frontend/src/pages/CourseForm.vue:186
#: frontend/src/pages/CourseForm.vue:210
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Course Price"
msgstr ""
@@ -1170,7 +1181,7 @@ msgstr ""
msgid "Course already added to the batch."
msgstr ""
#: frontend/src/pages/CourseForm.vue:433
#: frontend/src/pages/CourseForm.vue:457
msgid "Course price and currency are mandatory for paid courses"
msgstr ""
@@ -1208,6 +1219,10 @@ msgstr ""
msgid "Cover Image"
msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:9
msgid "Create"
msgstr ""
#: lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.js:7
msgid "Create LMS Certificate"
msgstr ""
@@ -1216,7 +1231,11 @@ msgstr ""
msgid "Create LMS Certificate Evaluation"
msgstr ""
#: lms/templates/onboarding_header.html:19
#: frontend/src/pages/Batches.vue:110
msgid "Create a Batch"
msgstr ""
#: frontend/src/pages/Courses.vue:131 lms/templates/onboarding_header.html:19
msgid "Create a Course"
msgstr ""
@@ -1232,7 +1251,7 @@ msgstr ""
#. Label of the currency (Link) field in DocType 'LMS Batch'
#. Label of the currency (Link) field in DocType 'LMS Course'
#. Label of the currency (Link) field in DocType 'LMS Payment'
#: frontend/src/pages/BatchForm.vue:206 frontend/src/pages/CourseForm.vue:193
#: frontend/src/pages/BatchForm.vue:216 frontend/src/pages/CourseForm.vue:217
#: lms/fixtures/custom_field.json lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_course/lms_course.json
#: lms/lms/doctype/lms_payment/lms_payment.json
@@ -1290,7 +1309,7 @@ msgstr ""
#. Label of the section_break_glxh (Section Break) field in DocType 'LMS Live
#. Class'
#: frontend/src/pages/BatchForm.vue:102
#: frontend/src/pages/BatchForm.vue:110
#: lms/lms/doctype/lms_live_class/lms_live_class.json
msgid "Date and Time"
msgstr ""
@@ -1338,7 +1357,6 @@ msgstr ""
#. Label of the description (Markdown Editor) field in DocType 'Cohort'
#. Label of the description (Markdown Editor) field in DocType 'Cohort
#. Subgroup'
#. Label of the description (Small Text) field in DocType 'Course Chapter'
#. Label of the description (Small Text) field in DocType 'LMS Badge'
#. Label of the description (Small Text) field in DocType 'LMS Batch'
#. Label of the description (Markdown Editor) field in DocType 'LMS Batch Old'
@@ -1347,12 +1365,11 @@ msgstr ""
#. Label of the description (Text) field in DocType 'LMS Live Class'
#. Label of the description (Small Text) field in DocType 'Work Experience'
#: frontend/src/components/Modals/LiveClassModal.vue:73
#: frontend/src/pages/BatchForm.vue:83 frontend/src/pages/JobCreation.vue:43
#: frontend/src/pages/BatchForm.vue:90 frontend/src/pages/JobCreation.vue:43
#: lms/job/doctype/job_opportunity/job_opportunity.json
#: lms/lms/doctype/certification/certification.json
#: lms/lms/doctype/cohort/cohort.json
#: lms/lms/doctype/cohort_subgroup/cohort_subgroup.json
#: lms/lms/doctype/course_chapter/course_chapter.json
#: lms/lms/doctype/lms_badge/lms_badge.json
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_batch_old/lms_batch_old.json
@@ -1374,7 +1391,7 @@ msgstr ""
msgid "Details"
msgstr ""
#: frontend/src/pages/CourseForm.vue:163
#: frontend/src/pages/CourseForm.vue:187
msgid "Disable Self Enrollment"
msgstr ""
@@ -1449,13 +1466,14 @@ msgstr ""
#: frontend/src/components/BatchOverlay.vue:93
#: frontend/src/components/CourseCardOverlay.vue:86
#: frontend/src/components/Modals/ChapterModal.vue:9
#: frontend/src/pages/JobDetail.vue:31 frontend/src/pages/Lesson.vue:70
#: frontend/src/pages/Profile.vue:32
msgid "Edit"
msgstr ""
#: frontend/src/components/CourseOutline.vue:106
#: frontend/src/components/Modals/ChapterModal.vue:9
#: frontend/src/components/Modals/ChapterModal.vue:5
msgid "Edit Chapter"
msgstr ""
@@ -1531,7 +1549,7 @@ msgstr ""
#. Label of the end_date (Date) field in DocType 'Cohort'
#. Label of the end_date (Date) field in DocType 'LMS Batch'
#: frontend/src/pages/BatchForm.vue:114 lms/lms/doctype/cohort/cohort.json
#: frontend/src/pages/BatchForm.vue:122 lms/lms/doctype/cohort/cohort.json
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/public/js/common_functions.js:282
msgid "End Date"
@@ -1549,7 +1567,7 @@ msgstr ""
#. Label of the end_time (Time) field in DocType 'LMS Certificate Evaluation'
#. Label of the end_time (Time) field in DocType 'LMS Certificate Request'
#. Label of the end_time (Time) field in DocType 'Scheduled Flow'
#: frontend/src/pages/BatchForm.vue:128
#: frontend/src/pages/BatchForm.vue:136
#: frontend/src/pages/ProfileEvaluator.vue:18
#: lms/lms/doctype/evaluator_schedule/evaluator_schedule.json
#: lms/lms/doctype/lms_batch/lms_batch.json
@@ -1585,13 +1603,15 @@ msgstr ""
msgid "Enrollment Count"
msgstr ""
#: lms/lms/utils.py:1683
#: lms/lms/utils.py:1692
msgid "Enrollment Failed"
msgstr ""
#. Label of the enrollments (Data) field in DocType 'LMS Course'
#. Label of a chart in the LMS Workspace
#. Label of a shortcut in the LMS Workspace
#: frontend/src/pages/Statistics.vue:45 lms/lms/workspace/lms/lms.json
#: frontend/src/pages/Statistics.vue:45
#: lms/lms/doctype/lms_course/lms_course.json lms/lms/workspace/lms/lms.json
msgid "Enrollments"
msgstr ""
@@ -1633,7 +1653,7 @@ msgid "Evaluation Details"
msgstr ""
#. Label of the evaluation_end_date (Date) field in DocType 'LMS Batch'
#: frontend/src/pages/BatchForm.vue:155
#: frontend/src/pages/BatchForm.vue:165
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/public/js/common_functions.js:333
msgid "Evaluation End Date"
@@ -1695,6 +1715,10 @@ msgstr ""
msgid "Event"
msgstr ""
#: frontend/src/pages/BatchForm.vue:144
msgid "Example: IST (+5:30)"
msgstr ""
#. Label of the exercise (Link) field in DocType 'Exercise Latest Submission'
#. Label of the exercise (Link) field in DocType 'Exercise Submission'
#: lms/lms/doctype/exercise_latest_submission/exercise_latest_submission.json
@@ -1765,7 +1789,7 @@ msgstr ""
#. Label of the featured (Check) field in DocType 'LMS Course'
#: frontend/src/components/CourseCard.vue:16
#: frontend/src/pages/CourseForm.vue:156
#: frontend/src/pages/CourseForm.vue:180
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Featured"
msgstr ""
@@ -2028,6 +2052,10 @@ msgstr ""
msgid "Icon"
msgstr ""
#: frontend/src/components/LessonHelp.vue:68
msgid "If Include in Preview is enabled for a lesson then the lesson will also be accessible to non logged in users."
msgstr ""
#: lms/templates/emails/mentor_request_creation_email.html:5
msgid "If you are not any more interested to mentor the course"
msgstr ""
@@ -2164,7 +2192,7 @@ msgstr ""
#. Label of the instructors (Table MultiSelect) field in DocType 'LMS Batch'
#. Label of the instructors (Table MultiSelect) field in DocType 'LMS Course'
#: frontend/src/pages/BatchForm.vue:77 frontend/src/pages/CourseForm.vue:123
#: frontend/src/pages/BatchForm.vue:85 frontend/src/pages/CourseForm.vue:146
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Instructors"
@@ -2323,6 +2351,10 @@ msgstr ""
msgid "Join URL"
msgstr ""
#: frontend/src/pages/CourseForm.vue:128
msgid "Keywords for the course"
msgstr ""
#. Name of a Workspace
#: lms/lms/workspace/lms/lms.json
msgid "LMS"
@@ -2576,9 +2608,11 @@ msgstr ""
#. Label of the lessons (Table) field in DocType 'Course Chapter'
#. Group in Course Chapter's connections
#. Label of the lessons (Data) field in DocType 'LMS Course'
#: frontend/src/components/CourseCard.vue:34
#: frontend/src/components/CourseCardOverlay.vue:96
#: lms/lms/doctype/course_chapter/course_chapter.json
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Lessons"
msgstr ""
@@ -2751,7 +2785,7 @@ msgid "Maximun Attempts"
msgstr ""
#. Label of the medium (Select) field in DocType 'LMS Batch'
#: frontend/src/pages/BatchForm.vue:174
#: frontend/src/pages/BatchForm.vue:184
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/public/js/common_functions.js:309
msgid "Medium"
@@ -2899,7 +2933,7 @@ msgid "Mentors"
msgstr ""
#. Label of the meta_image (Attach Image) field in DocType 'LMS Batch'
#: frontend/src/pages/BatchForm.vue:53 lms/lms/doctype/lms_batch/lms_batch.json
#: frontend/src/pages/BatchForm.vue:36 lms/lms/doctype/lms_batch/lms_batch.json
#: lms/public/js/common_functions.js:362
msgid "Meta Image"
msgstr ""
@@ -2935,11 +2969,11 @@ msgstr ""
msgid "Modified By"
msgstr ""
#: lms/lms/api.py:190
#: lms/lms/api.py:191
msgid "Module Name is incorrect or does not exist."
msgstr ""
#: lms/lms/api.py:186
#: lms/lms/api.py:187
msgid "Module is incorrect."
msgstr ""
@@ -3006,11 +3040,11 @@ msgstr ""
msgid "New Sign Up"
msgstr ""
#: lms/lms/utils.py:613
#: lms/lms/utils.py:632
msgid "New comment in batch {0}"
msgstr ""
#: lms/lms/utils.py:606
#: lms/lms/utils.py:625
msgid "New reply on the topic {0} in course {1}"
msgstr ""
@@ -3048,6 +3082,10 @@ msgstr ""
msgid "No announcements"
msgstr ""
#: frontend/src/pages/Batches.vue:125
msgid "No batches found"
msgstr ""
#: lms/templates/certificates_section.html:23
msgid "No certificates"
msgstr ""
@@ -3056,6 +3094,10 @@ msgstr ""
msgid "No courses created"
msgstr ""
#: frontend/src/pages/Courses.vue:146
msgid "No courses found"
msgstr ""
#: lms/templates/courses_under_review.html:14
msgid "No courses under review"
msgstr ""
@@ -3084,12 +3126,12 @@ msgstr ""
msgid "No {0}"
msgstr ""
#: frontend/src/pages/Batches.vue:88
msgid "No {0} batches found"
#: frontend/src/pages/Batches.vue:84
msgid "No {0} batches"
msgstr ""
#: frontend/src/pages/Courses.vue:110
msgid "No {0} courses found"
#: frontend/src/pages/Courses.vue:106
msgid "No {0} courses"
msgstr ""
#: lms/templates/quiz/quiz.html:147
@@ -3144,6 +3186,10 @@ msgstr ""
msgid "Notify me when available"
msgstr ""
#: frontend/src/pages/BatchForm.vue:161
msgid "Number of seats available"
msgstr ""
#. Label of the sb_00 (Section Break) field in DocType 'Zoom Settings'
#: lms/lms/doctype/zoom_settings/zoom_settings.json
msgid "OAuth Client ID"
@@ -3176,7 +3222,7 @@ msgstr ""
msgid "Only files of type {0} will be accepted."
msgstr ""
#: frontend/src/pages/CourseForm.vue:449 frontend/src/utils/index.js:509
#: frontend/src/pages/CourseForm.vue:473 frontend/src/utils/index.js:518
msgid "Only image file is allowed."
msgstr ""
@@ -3284,14 +3330,14 @@ msgid "Pages"
msgstr ""
#. Label of the paid_batch (Check) field in DocType 'LMS Batch'
#: frontend/src/pages/BatchForm.vue:194
#: frontend/src/pages/BatchForm.vue:204
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/public/js/common_functions.js:373
msgid "Paid Batch"
msgstr ""
#. Label of the paid_course (Check) field in DocType 'LMS Course'
#: frontend/src/pages/CourseForm.vue:181
#: frontend/src/pages/CourseForm.vue:205
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Paid Course"
msgstr ""
@@ -3333,9 +3379,13 @@ msgstr ""
msgid "Password"
msgstr ""
#: frontend/src/pages/CourseForm.vue:104
msgid "Paste the youtube link of a short video introducing the course"
msgstr ""
#. Label of the payment (Link) field in DocType 'Batch Student'
#. Label of the payment (Link) field in DocType 'LMS Enrollment'
#: frontend/src/pages/BatchForm.vue:188
#: frontend/src/pages/BatchForm.vue:198
#: lms/lms/doctype/batch_student/batch_student.json
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
msgid "Payment"
@@ -3427,7 +3477,7 @@ msgstr ""
msgid "Phone Number"
msgstr ""
#: frontend/src/components/CourseCardOverlay.vue:143
#: frontend/src/components/CourseCardOverlay.vue:141
msgid "Please Login"
msgstr ""
@@ -3488,7 +3538,7 @@ msgstr ""
msgid "Please login to access this page."
msgstr ""
#: lms/lms/api.py:182
#: lms/lms/api.py:183
msgid "Please login to continue with payment."
msgstr ""
@@ -3578,7 +3628,7 @@ msgstr ""
msgid "Preview Image"
msgstr ""
#: frontend/src/pages/CourseForm.vue:86
#: frontend/src/pages/CourseForm.vue:102
msgid "Preview Video"
msgstr ""
@@ -3588,7 +3638,7 @@ msgstr ""
#. Label of the pricing_tab (Tab Break) field in DocType 'LMS Batch'
#. Label of the pricing_tab (Tab Break) field in DocType 'LMS Course'
#: frontend/src/pages/CourseForm.vue:175
#: frontend/src/pages/CourseForm.vue:199
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_course/lms_course.json
#: lms/public/js/common_functions.js:368
@@ -3606,7 +3656,7 @@ msgstr ""
msgid "Primary Subgroup"
msgstr ""
#: lms/lms/utils.py:422
#: lms/lms/utils.py:441
msgid "Privacy Policy"
msgstr ""
@@ -3658,7 +3708,7 @@ msgstr ""
#. Label of the published (Check) field in DocType 'LMS Batch'
#. Label of the published (Check) field in DocType 'LMS Course'
#: frontend/src/components/Modals/Event.vue:108
#: frontend/src/pages/BatchForm.vue:24 frontend/src/pages/CourseForm.vue:138
#: frontend/src/pages/BatchForm.vue:24 frontend/src/pages/CourseForm.vue:162
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_course/lms_course.json
#: lms/public/js/common_functions.js:266
@@ -3671,7 +3721,7 @@ msgid "Published Courses"
msgstr ""
#. Label of the published_on (Date) field in DocType 'LMS Course'
#: frontend/src/pages/CourseForm.vue:142
#: frontend/src/pages/CourseForm.vue:166
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Published On"
msgstr ""
@@ -3792,11 +3842,13 @@ msgid "Quizzes"
msgstr ""
#. Label of the rating (Rating) field in DocType 'LMS Certificate Evaluation'
#. Label of the rating (Data) field in DocType 'LMS Course'
#. Label of the rating (Rating) field in DocType 'LMS Course Review'
#: frontend/src/components/CourseCardOverlay.vue:109
#: frontend/src/components/CourseCardOverlay.vue:108
#: frontend/src/components/Modals/Event.vue:86
#: frontend/src/components/Modals/ReviewModal.vue:20
#: lms/lms/doctype/lms_certificate_evaluation/lms_certificate_evaluation.json
#: lms/lms/doctype/lms_course/lms_course.json
#: lms/lms/doctype/lms_course_review/lms_course_review.json
#: lms/templates/reviews.html:125
msgid "Rating"
@@ -3867,6 +3919,10 @@ msgstr ""
msgid "Related Courses"
msgstr ""
#: frontend/src/pages/BatchForm.vue:69 frontend/src/pages/CourseForm.vue:91
msgid "Remove"
msgstr ""
#: frontend/src/components/Modals/AnnouncementModal.vue:26
msgid "Reply To"
msgstr ""
@@ -4022,7 +4078,7 @@ msgid "Search for an icon"
msgstr ""
#. Label of the seat_count (Int) field in DocType 'LMS Batch'
#: frontend/src/pages/BatchForm.vue:149
#: frontend/src/pages/BatchForm.vue:158
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/public/js/common_functions.js:327
msgid "Seat Count"
@@ -4066,7 +4122,7 @@ msgid "Set your Password"
msgstr ""
#: frontend/src/components/Modals/Settings.vue:7
#: frontend/src/pages/BatchForm.vue:143 frontend/src/pages/CourseForm.vue:128
#: frontend/src/pages/BatchForm.vue:152 frontend/src/pages/CourseForm.vue:152
#: frontend/src/pages/ProfileRoles.vue:4 frontend/src/pages/QuizForm.vue:78
msgid "Settings"
msgstr ""
@@ -4076,11 +4132,15 @@ msgid "Share on"
msgstr ""
#. Label of the short_introduction (Small Text) field in DocType 'LMS Course'
#: frontend/src/pages/CourseForm.vue:29
#: frontend/src/pages/CourseForm.vue:30
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Short Introduction"
msgstr ""
#: frontend/src/pages/BatchForm.vue:93
msgid "Short description of the batch"
msgstr ""
#. Label of the show_answer (Check) field in DocType 'LMS Assignment'
#: lms/lms/doctype/lms_assignment/lms_assignment.json
msgid "Show Answer"
@@ -4241,7 +4301,7 @@ msgstr ""
#. Label of the start_date (Date) field in DocType 'Education Detail'
#. Label of the start_date (Date) field in DocType 'LMS Batch'
#. Label of the start_date (Date) field in DocType 'LMS Batch Old'
#: frontend/src/pages/BatchForm.vue:108
#: frontend/src/pages/BatchForm.vue:116
#: lms/lms/doctype/education_detail/education_detail.json
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_batch_old/lms_batch_old.json
@@ -4262,7 +4322,7 @@ msgstr ""
#. Label of the start_time (Time) field in DocType 'LMS Certificate Evaluation'
#. Label of the start_time (Time) field in DocType 'LMS Certificate Request'
#. Label of the start_time (Time) field in DocType 'Scheduled Flow'
#: frontend/src/pages/BatchForm.vue:122
#: frontend/src/pages/BatchForm.vue:130
#: frontend/src/pages/ProfileEvaluator.vue:15
#: lms/lms/doctype/evaluator_schedule/evaluator_schedule.json
#: lms/lms/doctype/lms_batch/lms_batch.json
@@ -4301,7 +4361,9 @@ msgstr ""
msgid "State"
msgstr ""
#. Label of the tab_4_tab (Tab Break) field in DocType 'LMS Course'
#. Label of the statistics (Check) field in DocType 'LMS Settings'
#: lms/lms/doctype/lms_course/lms_course.json
#: lms/lms/doctype/lms_settings/lms_settings.json lms/www/lms.py:133
msgid "Statistics"
msgstr ""
@@ -4431,7 +4493,7 @@ msgstr ""
#: frontend/src/components/BatchCourses.vue:150
#: frontend/src/components/BatchOverlay.vue:135
#: frontend/src/components/BatchStudents.vue:157
#: frontend/src/components/CourseCardOverlay.vue:163
#: frontend/src/components/CourseCardOverlay.vue:161
#: frontend/src/components/Modals/AssessmentModal.vue:73
#: frontend/src/components/Modals/Event.vue:255
#: frontend/src/components/Modals/Event.vue:310
@@ -4505,7 +4567,7 @@ msgid "System Manager"
msgstr ""
#. Label of the tags (Data) field in DocType 'LMS Course'
#: frontend/src/pages/CourseForm.vue:91
#: frontend/src/pages/CourseForm.vue:112
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Tags"
msgstr ""
@@ -4533,7 +4595,7 @@ msgstr ""
msgid "Temporarily Disabled"
msgstr ""
#: lms/lms/utils.py:421
#: lms/lms/utils.py:440
msgid "Terms of Use"
msgstr ""
@@ -4585,10 +4647,18 @@ msgstr ""
msgid "The status of your application has changed."
msgstr ""
#: frontend/src/pages/Batches.vue:129
msgid "There are no batches available at the moment. Keep an eye out, fresh learning experiences are on the way soon!"
msgstr ""
#: frontend/src/components/CreateOutline.vue:12
msgid "There are no chapters in this course. Create and manage chapters from here."
msgstr ""
#: frontend/src/pages/Courses.vue:150
msgid "There are no courses available at the moment. Keep an eye out, fresh learning experiences are on the way soon!"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:140
msgid "There are no seats available in this batch."
msgstr ""
@@ -4620,7 +4690,7 @@ msgstr ""
msgid "This course has:"
msgstr ""
#: lms/lms/utils.py:1563
#: lms/lms/utils.py:1572
msgid "This course is free."
msgstr ""
@@ -4689,7 +4759,7 @@ msgstr ""
#. Label of the timezone (Data) field in DocType 'LMS Certificate Request'
#. Label of the timezone (Data) field in DocType 'LMS Live Class'
#: frontend/src/components/Modals/LiveClassModal.vue:44
#: frontend/src/pages/BatchForm.vue:134
#: frontend/src/pages/BatchForm.vue:142
#: lms/lms/doctype/lms_batch/lms_batch.json
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.json
#: lms/lms/doctype/lms_live_class/lms_live_class.json
@@ -4755,7 +4825,7 @@ msgstr ""
msgid "To Date is mandatory in Work Experience."
msgstr ""
#: lms/lms/utils.py:1574
#: lms/lms/utils.py:1583
msgid "To join this batch, please contact the Administrator."
msgstr ""
@@ -4872,7 +4942,7 @@ msgstr ""
#. Option for the 'Status' (Select) field in DocType 'Cohort'
#. Label of the upcoming (Check) field in DocType 'LMS Course'
#: frontend/src/pages/CourseForm.vue:151 lms/lms/doctype/cohort/cohort.json
#: frontend/src/pages/CourseForm.vue:175 lms/lms/doctype/cohort/cohort.json
#: lms/lms/doctype/lms_course/lms_course.json
msgid "Upcoming"
msgstr ""
@@ -4896,6 +4966,10 @@ msgstr ""
msgid "Update Password"
msgstr ""
#: frontend/src/pages/BatchForm.vue:51 frontend/src/pages/CourseForm.vue:72
msgid "Upload"
msgstr ""
#: frontend/src/pages/AssignmentSubmission.vue:69
msgid "Upload File"
msgstr ""
@@ -5018,6 +5092,10 @@ msgstr ""
msgid "Welcome to {0}!"
msgstr ""
#: frontend/src/components/LessonHelp.vue:63
msgid "What does include in preview mean?"
msgstr ""
#: lms/templates/courses_under_review.html:15
msgid "When a course gets submitted for review, it will be listed here."
msgstr ""
@@ -5071,11 +5149,11 @@ msgstr ""
msgid "You already have an evaluation on {0} at {1} for the course {2}."
msgstr ""
#: lms/lms/api.py:206
#: lms/lms/api.py:207
msgid "You are already enrolled for this batch."
msgstr ""
#: lms/lms/api.py:198
#: lms/lms/api.py:199
msgid "You are already enrolled for this course."
msgstr ""
@@ -5087,6 +5165,10 @@ msgstr ""
msgid "You are not a mentor of the course {0}"
msgstr ""
#: frontend/src/pages/Courses.vue:134
msgid "You can add chapters and lessons to it."
msgstr ""
#: lms/templates/emails/lms_course_interest.html:13
#: lms/templates/emails/lms_invite_request_approved.html:11
msgid "You can also copy-paste following link in your browser"
@@ -5104,6 +5186,10 @@ msgstr ""
msgid "You can find their resume attached to this email."
msgstr ""
#: frontend/src/pages/Batches.vue:113
msgid "You can link courses and assessments to it."
msgstr ""
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:115
msgid "You cannot schedule evaluations after {0}."
msgstr ""
@@ -5145,7 +5231,7 @@ msgstr ""
msgid "You have been enrolled in this batch"
msgstr ""
#: frontend/src/components/CourseCardOverlay.vue:164
#: frontend/src/components/CourseCardOverlay.vue:162
msgid "You have been enrolled in this course"
msgstr ""
@@ -5157,7 +5243,7 @@ msgstr ""
msgid "You haven't enrolled for any courses"
msgstr ""
#: frontend/src/components/CourseCardOverlay.vue:144
#: frontend/src/components/CourseCardOverlay.vue:142
msgid "You need to login first to enroll for this course"
msgstr ""
@@ -5275,7 +5361,7 @@ msgstr ""
msgid "you can"
msgstr ""
#: lms/lms/api.py:731 lms/lms/api.py:739
#: lms/lms/api.py:732 lms/lms/api.py:740
msgid "{0} Settings not found"
msgstr ""
@@ -5311,7 +5397,7 @@ msgstr ""
msgid "{0} is your evaluator"
msgstr ""
#: lms/lms/utils.py:690
#: lms/lms/utils.py:709
msgid "{0} mentioned you in a comment"
msgstr ""
@@ -5319,11 +5405,11 @@ msgstr ""
msgid "{0} mentioned you in a comment in your batch."
msgstr ""
#: lms/lms/utils.py:643 lms/lms/utils.py:649
#: lms/lms/utils.py:662 lms/lms/utils.py:668
msgid "{0} mentioned you in a comment in {1}"
msgstr ""
#: lms/lms/utils.py:461
#: lms/lms/utils.py:480
msgid "{0}k"
msgstr ""

5361
lms/locale/pl.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/ru.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/sv.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/tr.po Normal file

File diff suppressed because it is too large Load Diff

5361
lms/locale/zh.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -90,4 +90,5 @@ lms.patches.v1_0.set_published_on
lms.patches.v2_0.fix_progress_percentage
lms.patches.v2_0.add_discussion_topic_titles
lms.patches.v2_0.sidebar_settings
lms.patches.v2_0.delete_certificate_request_notification #18-09-2024
lms.patches.v2_0.delete_certificate_request_notification #18-09-2024
lms.patches.v2_0.add_course_statistics #21-10-2024

View File

@@ -0,0 +1,6 @@
import frappe
from lms.lms.api import update_course_statistics
def execute():
update_course_statistics()