Merge branch 'develop' of https://github.com/frappe/lms into issues-115

This commit is contained in:
Jannat Patel
2025-06-16 13:07:26 +05:30
5 changed files with 120 additions and 25 deletions

View File

@@ -0,0 +1,61 @@
<template>
<div v-if="relatedCourses.data?.length" class="mt-10">
<div class="flex items-center justify-between mb-6">
<div class="text-2xl font-semibold text-ink-gray-9">
{{ __('Related Courses') }}
</div>
<div class="text-sm text-ink-gray-7">
{{ relatedCourses.data.length }} {{ __('courses') }}
</div>
</div>
<div
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-4"
>
<router-link
v-for="course in relatedCourses.data"
:to="{ name: 'CourseDetail', params: { courseName: course.name } }"
class="cursor-pointer"
>
<CourseCard :course="course" />
</router-link>
</div>
</div>
</template>
<script setup>
import { createResource } from 'frappe-ui'
import CourseCard from '@/components/CourseCard.vue'
import { useRoute } from 'vue-router'
import { watch } from 'vue'
const route = useRoute()
const props = defineProps({
courseName: {
type: String,
required: true,
},
})
const relatedCourses = createResource({
url: 'lms.lms.utils.get_related_courses',
cache: ['related_courses', props.courseName],
params: {
course: props.courseName,
},
auto: true,
})
watch(
() => route.params.courseName,
(newCourseName, oldCourseName) => {
if (newCourseName && newCourseName !== oldCourseName) {
relatedCourses.update({
cache: ['related_courses', newCourseName],
params: { course: newCourseName },
})
relatedCourses.reload()
}
}
)
</script>

View File

@@ -83,6 +83,7 @@
:avg_rating="course.data.rating" :avg_rating="course.data.rating"
:membership="course.data.membership" :membership="course.data.membership"
/> />
<RelatedCourses :courseName="course.data.name" />
</div> </div>
<div class="hidden md:block"> <div class="hidden md:block">
<CourseCardOverlay :course="course" /> <CourseCardOverlay :course="course" />
@@ -99,7 +100,7 @@ import {
Tooltip, Tooltip,
usePageMeta, usePageMeta,
} from 'frappe-ui' } from 'frappe-ui'
import { computed } from 'vue' import { computed, watch } from 'vue'
import { Users, Star } from 'lucide-vue-next' import { Users, Star } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import CourseCardOverlay from '@/components/CourseCardOverlay.vue' import CourseCardOverlay from '@/components/CourseCardOverlay.vue'
@@ -107,8 +108,11 @@ import CourseOutline from '@/components/CourseOutline.vue'
import CourseReviews from '@/components/CourseReviews.vue' import CourseReviews from '@/components/CourseReviews.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import CourseInstructors from '@/components/CourseInstructors.vue' import CourseInstructors from '@/components/CourseInstructors.vue'
import RelatedCourses from '@/components/RelatedCourses.vue'
import { useRoute } from 'vue-router'
const { brand } = sessionStore() const { brand } = sessionStore()
const route = useRoute()
const props = defineProps({ const props = defineProps({
courseName: { courseName: {
@@ -126,6 +130,19 @@ const course = createResource({
auto: true, auto: true,
}) })
watch(
() => route.params.courseName,
(newCourseName, oldCourseName) => {
if (newCourseName && newCourseName !== oldCourseName) {
course.update({
cache: ['course', newCourseName],
params: { course: newCourseName },
})
course.reload()
}
}
)
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: 'Courses', route: { name: 'Courses' } }] let items = [{ label: 'Courses', route: { name: 'Courses' } }]
items.push({ items.push({

View File

@@ -197,6 +197,14 @@ export function getEditorTools() {
window.innerWidth < 640 ? '15rem' : '30rem' window.innerWidth < 640 ? '15rem' : '30rem'
};" frameborder="0" allowfullscreen></iframe>`, };" frameborder="0" allowfullscreen></iframe>`,
}, },
bunnyStream: {
regex: /https:\/\/(?:iframe\.mediadelivery\.net|video\.bunnycdn\.com)\/play\/([a-zA-Z0-9]+\/[a-zA-Z0-9-]+)/,
embedUrl:
'https://iframe.mediadelivery.net/embed/<%= remote_id %>',
html: `<iframe style="width:100%; height: ${
window.innerWidth < 640 ? '15rem' : '30rem'
};" frameborder="0" allowfullscreen></iframe>`,
},
codepen: true, codepen: true,
aparat: { aparat: {
regex: /(?:http[s]?:\/\/)?(?:www.)?aparat\.com\/v\/([^\/\?\&]+)\/?/, regex: /(?:http[s]?:\/\/)?(?:www.)?aparat\.com\/v\/([^\/\?\&]+)\/?/,

View File

@@ -182,6 +182,7 @@ def get_lesson_icon(body, content):
"youtube", "youtube",
"vimeo", "vimeo",
"cloudflareStream", "cloudflareStream",
"bunnyStream",
]: ]:
return "icon-youtube" return "icon-youtube"
@@ -2171,5 +2172,17 @@ def get_palette(full_name):
return palette[idx % 8] return palette[idx % 8]
@frappe.whitelist(allow_guest=True)
def get_related_courses(course):
related_course_details = []
related_courses = frappe.get_all(
"Related Courses", {"parent": course}, order_by="idx", pluck="course"
)
for related_course in related_courses:
related_course_details.append(get_course_details(related_course))
return related_course_details
def persona_captured(): def persona_captured():
frappe.db.set_single_value("LMS Settings", "persona_captured", 1) frappe.db.set_single_value("LMS Settings", "persona_captured", 1)

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Frappe LMS VERSION\n" "Project-Id-Version: Frappe LMS VERSION\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n" "Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2025-06-06 16:04+0000\n" "POT-Creation-Date: 2025-06-13 16:04+0000\n"
"PO-Revision-Date: 2025-06-06 16:04+0000\n" "PO-Revision-Date: 2025-06-13 16:04+0000\n"
"Last-Translator: jannat@frappe.io\n" "Last-Translator: jannat@frappe.io\n"
"Language-Team: jannat@frappe.io\n" "Language-Team: jannat@frappe.io\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@@ -410,7 +410,7 @@ msgstr ""
msgid "Archived" msgid "Archived"
msgstr "" msgstr ""
#: frontend/src/components/UpcomingEvaluations.vue:169 #: frontend/src/components/UpcomingEvaluations.vue:172
msgid "Are you sure you want to cancel this evaluation? This action cannot be undone." msgid "Are you sure you want to cancel this evaluation? This action cannot be undone."
msgstr "" msgstr ""
@@ -760,12 +760,12 @@ msgstr ""
msgid "CGPA/4" msgid "CGPA/4"
msgstr "" msgstr ""
#: frontend/src/components/UpcomingEvaluations.vue:60 #: frontend/src/components/UpcomingEvaluations.vue:57
#: frontend/src/components/UpcomingEvaluations.vue:174 #: frontend/src/components/UpcomingEvaluations.vue:177
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: frontend/src/components/UpcomingEvaluations.vue:168 #: frontend/src/components/UpcomingEvaluations.vue:171
msgid "Cancel this evaluation?" msgid "Cancel this evaluation?"
msgstr "" msgstr ""
@@ -1274,7 +1274,7 @@ msgid "Continue Learning"
msgstr "" msgstr ""
#. Option for the 'Type' (Select) field in DocType 'Job Opportunity' #. Option for the 'Type' (Select) field in DocType 'Job Opportunity'
#: frontend/src/pages/Jobs.vue:177 #: frontend/src/pages/Jobs.vue:178
#: lms/job/doctype/job_opportunity/job_opportunity.json #: lms/job/doctype/job_opportunity/job_opportunity.json
msgid "Contract" msgid "Contract"
msgstr "" msgstr ""
@@ -2309,7 +2309,7 @@ msgid "Free"
msgstr "" msgstr ""
#. Option for the 'Type' (Select) field in DocType 'Job Opportunity' #. Option for the 'Type' (Select) field in DocType 'Job Opportunity'
#: frontend/src/pages/Jobs.vue:178 #: frontend/src/pages/Jobs.vue:179
#: lms/job/doctype/job_opportunity/job_opportunity.json #: lms/job/doctype/job_opportunity/job_opportunity.json
msgid "Freelance" msgid "Freelance"
msgstr "" msgstr ""
@@ -2353,7 +2353,7 @@ msgid "Full Name"
msgstr "" msgstr ""
#. Option for the 'Type' (Select) field in DocType 'Job Opportunity' #. Option for the 'Type' (Select) field in DocType 'Job Opportunity'
#: frontend/src/pages/Jobs.vue:175 #: frontend/src/pages/Jobs.vue:176
#: lms/job/doctype/job_opportunity/job_opportunity.json #: lms/job/doctype/job_opportunity/job_opportunity.json
msgid "Full Time" msgid "Full Time"
msgstr "" msgstr ""
@@ -2853,7 +2853,7 @@ msgstr ""
#. Label of the jobs (Check) field in DocType 'LMS Settings' #. Label of the jobs (Check) field in DocType 'LMS Settings'
#: frontend/src/pages/JobDetail.vue:10 frontend/src/pages/Jobs.vue:8 #: frontend/src/pages/JobDetail.vue:10 frontend/src/pages/Jobs.vue:8
#: frontend/src/pages/Jobs.vue:184 #: frontend/src/pages/Jobs.vue:185
#: lms/lms/doctype/lms_settings/lms_settings.json #: lms/lms/doctype/lms_settings/lms_settings.json
msgid "Jobs" msgid "Jobs"
msgstr "" msgstr ""
@@ -2863,7 +2863,7 @@ msgstr ""
msgid "Join" msgid "Join"
msgstr "" msgstr ""
#: frontend/src/components/UpcomingEvaluations.vue:93 #: frontend/src/components/UpcomingEvaluations.vue:90
msgid "Join Call" msgid "Join Call"
msgstr "" msgstr ""
@@ -3350,10 +3350,6 @@ msgstr ""
msgid "Mark all as read" msgid "Mark all as read"
msgstr "" msgstr ""
#: frontend/src/pages/Notifications.vue:40
msgid "Mark as read"
msgstr ""
#. Label of the marks (Int) field in DocType 'LMS Quiz Question' #. Label of the marks (Int) field in DocType 'LMS Quiz Question'
#. Label of the marks (Int) field in DocType 'LMS Quiz Result' #. Label of the marks (Int) field in DocType 'LMS Quiz Result'
#: frontend/src/components/Modals/Question.vue:40 #: frontend/src/components/Modals/Question.vue:40
@@ -3888,7 +3884,7 @@ msgstr ""
msgid "Not Saved" msgid "Not Saved"
msgstr "" msgstr ""
#: frontend/src/pages/Notifications.vue:54 #: frontend/src/pages/Notifications.vue:53
msgid "Nothing to see here." msgid "Nothing to see here."
msgstr "" msgstr ""
@@ -4075,7 +4071,7 @@ msgid "Pan Number"
msgstr "" msgstr ""
#. Option for the 'Type' (Select) field in DocType 'Job Opportunity' #. Option for the 'Type' (Select) field in DocType 'Job Opportunity'
#: frontend/src/pages/Jobs.vue:176 #: frontend/src/pages/Jobs.vue:177
#: lms/job/doctype/job_opportunity/job_opportunity.json #: lms/job/doctype/job_opportunity/job_opportunity.json
msgid "Part Time" msgid "Part Time"
msgstr "" msgstr ""
@@ -4312,7 +4308,7 @@ msgstr ""
msgid "Please prepare well and be on time for the evaluations." msgid "Please prepare well and be on time for the evaluations."
msgstr "" msgstr ""
#: frontend/src/components/UpcomingEvaluations.vue:101 #: frontend/src/components/UpcomingEvaluations.vue:98
msgid "Please schedule an evaluation to get certified." msgid "Please schedule an evaluation to get certified."
msgstr "" msgstr ""
@@ -4927,7 +4923,7 @@ msgid "Schedule"
msgstr "" msgstr ""
#: frontend/src/components/Modals/EvaluationModal.vue:5 #: frontend/src/components/Modals/EvaluationModal.vue:5
#: frontend/src/components/UpcomingEvaluations.vue:14 #: frontend/src/components/UpcomingEvaluations.vue:11
msgid "Schedule Evaluation" msgid "Schedule Evaluation"
msgstr "" msgstr ""
@@ -6073,7 +6069,7 @@ msgstr ""
msgid "Video Embed Link" msgid "Video Embed Link"
msgstr "" msgstr ""
#: frontend/src/pages/Notifications.vue:38 #: frontend/src/pages/Notifications.vue:39
msgid "View" msgid "View"
msgstr "" msgstr ""
@@ -6208,7 +6204,7 @@ msgstr ""
msgid "Write your answer here" msgid "Write your answer here"
msgstr "" msgstr ""
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:96 #: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:95
msgid "You already have an evaluation on {0} at {1} for the course {2}." msgid "You already have an evaluation on {0} at {1} for the course {2}."
msgstr "" msgstr ""
@@ -6261,11 +6257,11 @@ msgstr ""
msgid "You cannot change the roles in read-only mode." msgid "You cannot change the roles in read-only mode."
msgstr "" msgstr ""
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:116 #: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:115
msgid "You cannot schedule evaluations after {0}." msgid "You cannot schedule evaluations after {0}."
msgstr "" msgstr ""
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:105 #: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:104
msgid "You cannot schedule evaluations for past slots." msgid "You cannot schedule evaluations for past slots."
msgstr "" msgstr ""
@@ -6387,7 +6383,7 @@ msgstr ""
msgid "Your evaluation for the course {0} has been scheduled on {1} at {2} {3}." msgid "Your evaluation for the course {0} has been scheduled on {1} at {2} {3}."
msgstr "" msgstr ""
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:126 #: lms/lms/doctype/lms_certificate_request/lms_certificate_request.py:125
msgid "Your evaluation slot has been booked" msgid "Your evaluation slot has been booked"
msgstr "" msgstr ""