Merge pull request #1576 from frappe/develop
chore: merge 'develop' into 'main'
This commit is contained in:
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -83,6 +83,7 @@ declare module 'vue' {
|
|||||||
QuizBlock: typeof import('./src/components/QuizBlock.vue')['default']
|
QuizBlock: typeof import('./src/components/QuizBlock.vue')['default']
|
||||||
QuizInVideo: typeof import('./src/components/Modals/QuizInVideo.vue')['default']
|
QuizInVideo: typeof import('./src/components/Modals/QuizInVideo.vue')['default']
|
||||||
Rating: typeof import('./src/components/Controls/Rating.vue')['default']
|
Rating: typeof import('./src/components/Controls/Rating.vue')['default']
|
||||||
|
RelatedCourses: typeof import('./src/components/RelatedCourses.vue')['default']
|
||||||
ReviewModal: typeof import('./src/components/Modals/ReviewModal.vue')['default']
|
ReviewModal: typeof import('./src/components/Modals/ReviewModal.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<FrappeUIProvider>
|
<FrappeUIProvider>
|
||||||
<Layout>
|
<Layout>
|
||||||
<router-view />
|
<div class="text-base">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Dialogs />
|
<Dialogs />
|
||||||
</FrappeUIProvider>
|
</FrappeUIProvider>
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ import {
|
|||||||
h,
|
h,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
} from 'vue'
|
} 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 { useSidebar } from '@/stores/sidebar'
|
import { useSidebar } from '@/stores/sidebar'
|
||||||
|
|||||||
@@ -70,9 +70,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Badge } from 'frappe-ui'
|
import { formatTime } from '@/utils'
|
||||||
import { formatTime } from '../utils'
|
import { Clock, Globe } from 'lucide-vue-next'
|
||||||
import { Clock, BookOpen, Globe } from 'lucide-vue-next'
|
|
||||||
import DateRange from '@/components/Common/DateRange.vue'
|
import DateRange from '@/components/Common/DateRange.vue'
|
||||||
import CourseInstructors from '@/components/CourseInstructors.vue'
|
import CourseInstructors from '@/components/CourseInstructors.vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Button, createResource, Tooltip, toast } from 'frappe-ui'
|
import { Button, createResource, Tooltip, toast } from 'frappe-ui'
|
||||||
import { getCurrentInstance, inject, ref } from 'vue'
|
import { getCurrentInstance, inject, ref, watch } from 'vue'
|
||||||
import Draggable from 'vuedraggable'
|
import Draggable from 'vuedraggable'
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
||||||
import {
|
import {
|
||||||
@@ -197,13 +197,22 @@ const props = defineProps({
|
|||||||
const outline = createResource({
|
const outline = createResource({
|
||||||
url: 'lms.lms.utils.get_course_outline',
|
url: 'lms.lms.utils.get_course_outline',
|
||||||
cache: ['course_outline', props.courseName],
|
cache: ['course_outline', props.courseName],
|
||||||
params: {
|
makeParams() {
|
||||||
course: props.courseName,
|
return {
|
||||||
progress: props.getProgress,
|
course: props.courseName,
|
||||||
|
progress: props.getProgress,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.courseName,
|
||||||
|
() => {
|
||||||
|
outline.reload()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const deleteLesson = createResource({
|
const deleteLesson = createResource({
|
||||||
url: 'lms.lms.api.delete_lesson',
|
url: 'lms.lms.api.delete_lesson',
|
||||||
makeParams(values) {
|
makeParams(values) {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Star } from 'lucide-vue-next'
|
import { Star } from 'lucide-vue-next'
|
||||||
import { createResource, Button } from 'frappe-ui'
|
import { createResource, Button } from 'frappe-ui'
|
||||||
import { computed, ref, inject } from 'vue'
|
import { watch, ref, inject } from 'vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import ReviewModal from '@/components/Modals/ReviewModal.vue'
|
import ReviewModal from '@/components/Modals/ReviewModal.vue'
|
||||||
|
|
||||||
@@ -101,12 +101,21 @@ const hasReviewed = createResource({
|
|||||||
const reviews = createResource({
|
const reviews = createResource({
|
||||||
url: 'lms.lms.utils.get_reviews',
|
url: 'lms.lms.utils.get_reviews',
|
||||||
cache: ['course_reviews', props.courseName],
|
cache: ['course_reviews', props.courseName],
|
||||||
params: {
|
makeParams() {
|
||||||
course: props.courseName,
|
return {
|
||||||
|
course: props.courseName,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.courseName,
|
||||||
|
() => {
|
||||||
|
reviews.reload()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const showReviewModal = ref(false)
|
const showReviewModal = ref(false)
|
||||||
|
|
||||||
function openReviewModal() {
|
function openReviewModal() {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { createResource, TextEditor, Button, Dropdown, toast } from 'frappe-ui'
|
import { createResource, TextEditor, Button, Dropdown, toast } from 'frappe-ui'
|
||||||
import { timeAgo } from '../utils'
|
import { timeAgo } from '@/utils'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import { ChevronLeft, MoreHorizontal } from 'lucide-vue-next'
|
import { ChevronLeft, MoreHorizontal } from 'lucide-vue-next'
|
||||||
import { ref, inject, onMounted, onUnmounted } from 'vue'
|
import { ref, inject, onMounted, onUnmounted } from 'vue'
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { createResource, Button } from 'frappe-ui'
|
import { createResource, Button } from 'frappe-ui'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
import { singularize, timeAgo } from '../utils'
|
import { singularize, timeAgo } from '@/utils'
|
||||||
import { ref, onMounted, inject, onUnmounted } from 'vue'
|
import { ref, onMounted, inject, onUnmounted } from 'vue'
|
||||||
import DiscussionReplies from '@/components/DiscussionReplies.vue'
|
import DiscussionReplies from '@/components/DiscussionReplies.vue'
|
||||||
import DiscussionModal from '@/components/Modals/DiscussionModal.vue'
|
import DiscussionModal from '@/components/Modals/DiscussionModal.vue'
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getSidebarLinks } from '../utils'
|
import { getSidebarLinks } from '@/utils'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { watch, ref, onMounted } from 'vue'
|
import { watch, ref, onMounted } from 'vue'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
|
|||||||
@@ -3,44 +3,59 @@
|
|||||||
v-model="show"
|
v-model="show"
|
||||||
:options="{
|
:options="{
|
||||||
title: __('Attendance for Class - {0}').format(live_class?.title),
|
title: __('Attendance for Class - {0}').format(live_class?.title),
|
||||||
size: 'xl',
|
size: '4xl',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
<div class="space-y-5">
|
<div
|
||||||
|
class="grid grid-cols-2 gap-12 text-sm font-semibold text-ink-gray-5 pb-2"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{{ __('Member') }}
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-3 gap-20">
|
||||||
|
<div>
|
||||||
|
{{ __('Joined at') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
{{ __('Left at') }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ __('Attended for') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divide-y text-base">
|
||||||
<div
|
<div
|
||||||
v-for="participant in participants.data"
|
v-for="participant in participants.data"
|
||||||
@click="redirectToProfile(participant.member_username)"
|
@click="redirectToProfile(participant.member_username)"
|
||||||
class="cursor-pointer text-base w-fit"
|
class="grid grid-cols-2 items-center w-full text-base w-fit py-2"
|
||||||
>
|
>
|
||||||
<Tooltip placement="right">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="flex items-center space-x-2">
|
<Avatar
|
||||||
<Avatar
|
:image="participant.member_image"
|
||||||
:image="participant.member_image"
|
:label="participant.member_name"
|
||||||
:label="participant.member_name"
|
size="xl"
|
||||||
size="xl"
|
/>
|
||||||
/>
|
<div class="space-y-1">
|
||||||
<div class="space-y-1">
|
<div class="font-medium">
|
||||||
<div class="font-medium">
|
{{ participant.member_name }}
|
||||||
{{ participant.member_name }}
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
{{ participant.member }}
|
||||||
{{ participant.member }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #body>
|
</div>
|
||||||
<div
|
|
||||||
class="max-w-[30ch] rounded bg-surface-gray-7 px-2 py-1 text-p-xs text-ink-white leading-5 shadow-xl"
|
<div class="grid grid-cols-3 gap-20 text-right">
|
||||||
>
|
<div>
|
||||||
{{ dayjs(participant.joined_at).format('HH:mm a') }} -
|
{{ dayjs(participant.joined_at).format('HH:mm a') }}
|
||||||
{{ dayjs(participant.left_at).format('HH:mm a') }}
|
</div>
|
||||||
<br />
|
<div>
|
||||||
{{ __('attended for') }} {{ participant.duration }}
|
{{ dayjs(participant.left_at).format('HH:mm a') }}
|
||||||
{{ __('minutes') }}
|
</div>
|
||||||
</div>
|
<div>{{ participant.duration }} {{ __('minutes') }}</div>
|
||||||
</template>
|
</div>
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
52
frontend/src/components/RelatedCourses.vue
Normal file
52
frontend/src/components/RelatedCourses.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<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>
|
||||||
|
<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"
|
||||||
|
:key="course.name"
|
||||||
|
: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 { watch } from 'vue'
|
||||||
|
import CourseCard from '@/components/CourseCard.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
courseName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const relatedCourses = createResource({
|
||||||
|
url: 'lms.lms.utils.get_related_courses',
|
||||||
|
cache: ['related_courses', props.courseName],
|
||||||
|
makeParams() {
|
||||||
|
return {
|
||||||
|
course: props.courseName,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.courseName,
|
||||||
|
() => {
|
||||||
|
relatedCourses.reload()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
@@ -116,7 +116,7 @@ import {
|
|||||||
EllipsisVertical,
|
EllipsisVertical,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { inject, ref, getCurrentInstance, computed } from 'vue'
|
import { inject, ref, getCurrentInstance, computed } from 'vue'
|
||||||
import { formatTime } from '../utils'
|
import { formatTime } from '@/utils'
|
||||||
import { Button, createResource, call } from 'frappe-ui'
|
import { Button, createResource, call } from 'frappe-ui'
|
||||||
import EvaluationModal from '@/components/Modals/EvaluationModal.vue'
|
import EvaluationModal from '@/components/Modals/EvaluationModal.vue'
|
||||||
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div v-if="quizzes.length && !showQuiz && readOnly" class="leading-5">
|
||||||
v-if="quizzes.length && !showQuiz && readOnly"
|
|
||||||
class="bg-surface-blue-2 space-y-1 py-3 px-4 rounded-md text-sm text-ink-blue-3 leading-5"
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
__('This video contains {0} {1}:').format(
|
__('This video contains {0} {1}:').format(
|
||||||
quizzes.length,
|
quizzes.length,
|
||||||
@@ -12,8 +9,10 @@
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
<div v-for="(quiz, index) in quizzes" class="pl-3 mt-1">
|
<div v-for="(quiz, index) in quizzes" class="pl-3 mt-1">
|
||||||
<span> {{ index + 1 }}. {{ quiz.quiz }} </span>
|
<span>
|
||||||
{{ __('at {0}').format(formatTimestamp(quiz.time)) }}
|
{{ index + 1 }}. <span class="font-semibold"> {{ quiz.quiz }} </span>
|
||||||
|
</span>
|
||||||
|
{{ __('at {0} minutes').format(formatTimestamp(quiz.time)) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -65,15 +64,28 @@
|
|||||||
<Pause v-else @click="pauseVideo" class="size-5 text-ink-white" />
|
<Pause v-else @click="pauseVideo" class="size-5 text-ink-white" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
|
||||||
type="range"
|
<div class="relative flex items-center w-full flex-1">
|
||||||
min="0"
|
<input
|
||||||
:max="duration"
|
type="range"
|
||||||
step="0.1"
|
min="0"
|
||||||
v-model="currentTime"
|
:max="duration"
|
||||||
@input="changeCurrentTime"
|
step="0.1"
|
||||||
class="duration-slider w-full h-1"
|
v-model="currentTime"
|
||||||
/>
|
@input="changeCurrentTime"
|
||||||
|
class="duration-slider h-1"
|
||||||
|
/>
|
||||||
|
<!-- QUIZ MARKERS -->
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
||||||
|
<div
|
||||||
|
v-for="(quiz, index) in quizzes"
|
||||||
|
:key="index"
|
||||||
|
:style="getQuizMarkerStyle(quiz.time)"
|
||||||
|
class="absolute top-0 h-full w-2 bg-surface-amber-3"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span class="text-sm font-medium">
|
<span class="text-sm font-medium">
|
||||||
{{ formatSeconds(currentTime) }} / {{ formatSeconds(duration) }}
|
{{ formatSeconds(currentTime) }} / {{ formatSeconds(duration) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -116,11 +128,27 @@
|
|||||||
:saveQuizzes="saveQuizzes"
|
:saveQuizzes="saveQuizzes"
|
||||||
:duration="duration"
|
:duration="duration"
|
||||||
/>
|
/>
|
||||||
|
<Dialog
|
||||||
|
v-model="showQuizLoader"
|
||||||
|
:options="{
|
||||||
|
size: 'sm',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<div class="p-5 text-base">
|
||||||
|
{{
|
||||||
|
__(
|
||||||
|
'Complete the upcoming quiz to continue watching the video. The quiz will open in {0} {1}.'
|
||||||
|
).format(quizLoadTimer, quizLoadTimer === 1 ? 'second' : 'seconds')
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed, watch } from 'vue'
|
||||||
import { Pause, Maximize, Volume2, VolumeX } from 'lucide-vue-next'
|
import { Pause, Maximize, Volume2, VolumeX } from 'lucide-vue-next'
|
||||||
import { Button } from 'frappe-ui'
|
import { Button, Dialog } from 'frappe-ui'
|
||||||
import { formatSeconds, formatTimestamp } from '@/utils'
|
import { formatSeconds, formatTimestamp } from '@/utils'
|
||||||
import Play from '@/components/Icons/Play.vue'
|
import Play from '@/components/Icons/Play.vue'
|
||||||
import QuizInVideo from '@/components/Modals/QuizInVideo.vue'
|
import QuizInVideo from '@/components/Modals/QuizInVideo.vue'
|
||||||
@@ -133,6 +161,8 @@ let duration = ref(0)
|
|||||||
let muted = ref(false)
|
let muted = ref(false)
|
||||||
const showQuizModal = ref(false)
|
const showQuizModal = ref(false)
|
||||||
const showQuiz = ref(false)
|
const showQuiz = ref(false)
|
||||||
|
const showQuizLoader = ref(false)
|
||||||
|
const quizLoadTimer = ref(0)
|
||||||
const currentQuiz = ref(null)
|
const currentQuiz = ref(null)
|
||||||
const nextQuiz = ref({})
|
const nextQuiz = ref({})
|
||||||
|
|
||||||
@@ -175,12 +205,24 @@ const updateCurrentTime = () => {
|
|||||||
playing.value = false
|
playing.value = false
|
||||||
videoRef.value.onTimeupdate = null
|
videoRef.value.onTimeupdate = null
|
||||||
currentQuiz.value = nextQuiz.value.quiz
|
currentQuiz.value = nextQuiz.value.quiz
|
||||||
showQuiz.value = true
|
quizLoadTimer.value = 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(quizLoadTimer, () => {
|
||||||
|
if (quizLoadTimer.value > 0) {
|
||||||
|
showQuizLoader.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
quizLoadTimer.value -= 1
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
showQuizLoader.value = false
|
||||||
|
showQuiz.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const resumeVideo = (restart = false) => {
|
const resumeVideo = (restart = false) => {
|
||||||
showQuiz.value = false
|
showQuiz.value = false
|
||||||
currentQuiz.value = null
|
currentQuiz.value = null
|
||||||
@@ -259,6 +301,13 @@ const toggleFullscreen = () => {
|
|||||||
videoContainer.value.requestFullscreen()
|
videoContainer.value.requestFullscreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getQuizMarkerStyle = (time) => {
|
||||||
|
const percentage = ((time - 7) / Math.ceil(duration.value)) * 100
|
||||||
|
return {
|
||||||
|
left: `${percentage}%`,
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -278,11 +327,10 @@ iframe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.duration-slider {
|
.duration-slider {
|
||||||
flex: 1;
|
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background-color: theme('colors.gray.100');
|
background-color: theme('colors.gray.600');
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,20 +338,20 @@ iframe {
|
|||||||
width: 2px;
|
width: 2px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
background-color: theme('colors.gray.500');
|
background-color: theme('colors.white');
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
||||||
input[type='range'] {
|
input[type='range'] {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 150px;
|
width: 100%;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='range']::-webkit-slider-thumb {
|
input[type='range']::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: -500px 0 0 500px theme('colors.gray.600');
|
box-shadow: -500px 0 0 500px theme('colors.white');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
<CourseCardOverlay :course="course" />
|
<CourseCardOverlay :course="course" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<RelatedCourses :courseName="course.data.name" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -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,6 +108,7 @@ 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'
|
||||||
|
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
|
|
||||||
@@ -120,12 +122,21 @@ const props = defineProps({
|
|||||||
const course = createResource({
|
const course = createResource({
|
||||||
url: 'lms.lms.utils.get_course_details',
|
url: 'lms.lms.utils.get_course_details',
|
||||||
cache: ['course', props.courseName],
|
cache: ['course', props.courseName],
|
||||||
params: {
|
makeParams() {
|
||||||
course: props.courseName,
|
return {
|
||||||
|
course: props.courseName,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.courseName,
|
||||||
|
() => {
|
||||||
|
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({
|
||||||
|
|||||||
@@ -199,6 +199,21 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MultiSelect
|
||||||
|
v-model="related_courses"
|
||||||
|
doctype="LMS Course"
|
||||||
|
:label="__('Related Courses')"
|
||||||
|
:filters="{ name: ['!=', courseResource.data?.name] }"
|
||||||
|
:onCreate="
|
||||||
|
(close) => {
|
||||||
|
router.push({
|
||||||
|
name: 'CourseForm',
|
||||||
|
params: { courseName: 'new' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-10 pb-5 space-y-5 border-b">
|
<div class="px-10 pb-5 space-y-5 border-b">
|
||||||
@@ -319,6 +334,7 @@ const newTag = ref('')
|
|||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const instructors = ref([])
|
const instructors = ref([])
|
||||||
|
const related_courses = ref([])
|
||||||
const app = getCurrentInstance()
|
const app = getCurrentInstance()
|
||||||
const { updateOnboardingStep } = useOnboarding('learning')
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
const { $dialog } = app.appContext.config.globalProperties
|
const { $dialog } = app.appContext.config.globalProperties
|
||||||
@@ -400,6 +416,9 @@ const courseCreationResource = createResource({
|
|||||||
instructors: instructors.value.map((instructor) => ({
|
instructors: instructors.value.map((instructor) => ({
|
||||||
instructor: instructor,
|
instructor: instructor,
|
||||||
})),
|
})),
|
||||||
|
related_courses: related_courses.value.map((course) => ({
|
||||||
|
course: course,
|
||||||
|
})),
|
||||||
...values,
|
...values,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -418,6 +437,9 @@ const courseEditResource = createResource({
|
|||||||
instructors: instructors.value.map((instructor) => ({
|
instructors: instructors.value.map((instructor) => ({
|
||||||
instructor: instructor,
|
instructor: instructor,
|
||||||
})),
|
})),
|
||||||
|
related_courses: related_courses.value.map((course) => ({
|
||||||
|
course: course,
|
||||||
|
})),
|
||||||
...course,
|
...course,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -440,6 +462,11 @@ const courseResource = createResource({
|
|||||||
data.instructors.forEach((instructor) => {
|
data.instructors.forEach((instructor) => {
|
||||||
instructors.value.push(instructor.instructor)
|
instructors.value.push(instructor.instructor)
|
||||||
})
|
})
|
||||||
|
} else if (key == 'related_courses') {
|
||||||
|
related_courses.value = []
|
||||||
|
data.related_courses.forEach((course) => {
|
||||||
|
related_courses.value.push(course.course)
|
||||||
|
})
|
||||||
} else if (Object.hasOwn(course, key)) course[key] = data[key]
|
} else if (Object.hasOwn(course, key)) course[key] = data[key]
|
||||||
})
|
})
|
||||||
let checkboxes = [
|
let checkboxes = [
|
||||||
|
|||||||
@@ -122,9 +122,6 @@ onMounted(() => {
|
|||||||
const jobs = createResource({
|
const jobs = createResource({
|
||||||
url: 'lms.lms.api.get_job_opportunities',
|
url: 'lms.lms.api.get_job_opportunities',
|
||||||
cache: ['jobs'],
|
cache: ['jobs'],
|
||||||
onSuccess(data) {
|
|
||||||
jobCount.value = data.length
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateJobs = () => {
|
const updateJobs = () => {
|
||||||
@@ -169,6 +166,10 @@ watch(country, (val) => {
|
|||||||
updateJobs()
|
updateJobs()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(jobs, () => {
|
||||||
|
jobCount.value = jobs.data?.length || 0
|
||||||
|
})
|
||||||
|
|
||||||
const jobTypes = computed(() => {
|
const jobTypes = computed(() => {
|
||||||
return [
|
return [
|
||||||
'',
|
'',
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const persona = reactive({
|
|||||||
const submitPersona = () => {
|
const submitPersona = () => {
|
||||||
let responses = {
|
let responses = {
|
||||||
site: user.data?.sitename,
|
site: user.data?.sitename,
|
||||||
no_of_students: persona.noOfStudents,
|
role: persona.role,
|
||||||
use_case: persona.useCase,
|
use_case: persona.useCase,
|
||||||
}
|
}
|
||||||
call('lms.lms.api.capture_user_persona', {
|
call('lms.lms.api.capture_user_persona', {
|
||||||
|
|||||||
@@ -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\/([^\/\?\&]+)\/?/,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import VideoBlock from '@/components/VideoBlock.vue'
|
|||||||
import UploadPlugin from '@/components/UploadPlugin.vue'
|
import UploadPlugin from '@/components/UploadPlugin.vue'
|
||||||
import { h, createApp } from 'vue'
|
import { h, createApp } from 'vue'
|
||||||
import { Upload as UploadIcon } from 'lucide-vue-next'
|
import { Upload as UploadIcon } from 'lucide-vue-next'
|
||||||
|
import { createDialog } from '@/utils/dialogs'
|
||||||
import translationPlugin from '../translation'
|
import translationPlugin from '../translation'
|
||||||
|
|
||||||
export class Upload {
|
export class Upload {
|
||||||
@@ -54,6 +55,7 @@ export class Upload {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
app.use(translationPlugin)
|
app.use(translationPlugin)
|
||||||
|
app.config.globalProperties.$dialog = createDialog
|
||||||
app.mount(this.wrapper)
|
app.mount(this.wrapper)
|
||||||
return
|
return
|
||||||
} else if (this.isAudio(file.file_type)) {
|
} else if (this.isAudio(file.file_type)) {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "2.29.0"
|
__version__ = "2.30.0"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: frappe\n"
|
"Project-Id-Version: frappe\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-06 16:04+0000\n"
|
||||||
"PO-Revision-Date: 2025-06-09 22:05\n"
|
"PO-Revision-Date: 2025-06-10 22:03\n"
|
||||||
"Last-Translator: jannat@frappe.io\n"
|
"Last-Translator: jannat@frappe.io\n"
|
||||||
"Language-Team: Serbian (Latin)\n"
|
"Language-Team: Serbian (Latin)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@@ -153,7 +153,7 @@ msgstr "Dodaj lekciju"
|
|||||||
|
|
||||||
#: frontend/src/components/VideoBlock.vue:109
|
#: frontend/src/components/VideoBlock.vue:109
|
||||||
msgid "Add Quiz to Video"
|
msgid "Add Quiz to Video"
|
||||||
msgstr ""
|
msgstr "Dodaj kviz u video-snimak"
|
||||||
|
|
||||||
#: frontend/src/pages/ProfileEvaluator.vue:89
|
#: frontend/src/pages/ProfileEvaluator.vue:89
|
||||||
msgid "Add Slot"
|
msgid "Add Slot"
|
||||||
@@ -214,7 +214,7 @@ msgstr "Dodaj obuke u Vašu grupu"
|
|||||||
|
|
||||||
#: frontend/src/components/Modals/QuizInVideo.vue:5
|
#: frontend/src/components/Modals/QuizInVideo.vue:5
|
||||||
msgid "Add quiz to this video"
|
msgid "Add quiz to this video"
|
||||||
msgstr ""
|
msgstr "Dodaj kviz u ovaj video-snimak"
|
||||||
|
|
||||||
#: frontend/src/components/AppSidebar.vue:510
|
#: frontend/src/components/AppSidebar.vue:510
|
||||||
msgid "Add students to your batch"
|
msgid "Add students to your batch"
|
||||||
@@ -3832,7 +3832,7 @@ msgstr "Nema zakazanih onlajn predavanja"
|
|||||||
|
|
||||||
#: frontend/src/components/Modals/QuizInVideo.vue:93
|
#: frontend/src/components/Modals/QuizInVideo.vue:93
|
||||||
msgid "No quizzes added yet."
|
msgid "No quizzes added yet."
|
||||||
msgstr ""
|
msgstr "Još uvek nisu dodati kvizovi."
|
||||||
|
|
||||||
#: frontend/src/components/Modals/EvaluationModal.vue:62
|
#: frontend/src/components/Modals/EvaluationModal.vue:62
|
||||||
msgid "No slots available for this date."
|
msgid "No slots available for this date."
|
||||||
@@ -4275,7 +4275,7 @@ msgstr "Molimo Vas da unesete ispravno vreme u formatu HH:mm."
|
|||||||
|
|
||||||
#: frontend/src/components/Modals/QuizInVideo.vue:181
|
#: frontend/src/components/Modals/QuizInVideo.vue:181
|
||||||
msgid "Please enter a valid timestamp"
|
msgid "Please enter a valid timestamp"
|
||||||
msgstr ""
|
msgstr "Molimo Vas da unesete važeći vremenski žig"
|
||||||
|
|
||||||
#: lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py:78
|
#: lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py:78
|
||||||
msgid "Please enter the URL for assignment submission."
|
msgid "Please enter the URL for assignment submission."
|
||||||
@@ -4332,7 +4332,7 @@ msgstr "Molimo Vas da izaberete budući datum i vreme."
|
|||||||
|
|
||||||
#: frontend/src/components/Modals/QuizInVideo.vue:186
|
#: frontend/src/components/Modals/QuizInVideo.vue:186
|
||||||
msgid "Please select a quiz"
|
msgid "Please select a quiz"
|
||||||
msgstr ""
|
msgstr "Molimo Vas da izaberete kviz"
|
||||||
|
|
||||||
#: frontend/src/components/Modals/LiveClassModal.vue:192
|
#: frontend/src/components/Modals/LiveClassModal.vue:192
|
||||||
msgid "Please select a time."
|
msgid "Please select a time."
|
||||||
@@ -4694,7 +4694,7 @@ msgstr "Kvizovi"
|
|||||||
|
|
||||||
#: frontend/src/components/Modals/QuizInVideo.vue:35
|
#: frontend/src/components/Modals/QuizInVideo.vue:35
|
||||||
msgid "Quizzes in this video"
|
msgid "Quizzes in this video"
|
||||||
msgstr ""
|
msgstr "Kvizovi u ovom video-snimku"
|
||||||
|
|
||||||
#. Label of the rating (Rating) field in DocType 'LMS Certificate Evaluation'
|
#. 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 (Data) field in DocType 'LMS Course'
|
||||||
@@ -4808,7 +4808,7 @@ msgstr "CV"
|
|||||||
|
|
||||||
#: frontend/src/components/Quiz.vue:75 frontend/src/components/Quiz.vue:278
|
#: frontend/src/components/Quiz.vue:75 frontend/src/components/Quiz.vue:278
|
||||||
msgid "Resume Video"
|
msgid "Resume Video"
|
||||||
msgstr ""
|
msgstr "Nastavi video-snimak"
|
||||||
|
|
||||||
#. Label of the review (Small Text) field in DocType 'LMS Course Review'
|
#. Label of the review (Small Text) field in DocType 'LMS Course Review'
|
||||||
#. Label of a Link in the LMS Workspace
|
#. Label of a Link in the LMS Workspace
|
||||||
@@ -5277,7 +5277,7 @@ msgstr "Početni URL"
|
|||||||
|
|
||||||
#: frontend/src/components/Quiz.vue:71
|
#: frontend/src/components/Quiz.vue:71
|
||||||
msgid "Start the Quiz"
|
msgid "Start the Quiz"
|
||||||
msgstr ""
|
msgstr "Započni kviz"
|
||||||
|
|
||||||
#. Option for the 'Company Type' (Select) field in DocType 'User'
|
#. Option for the 'Company Type' (Select) field in DocType 'User'
|
||||||
#: lms/fixtures/custom_field.json
|
#: lms/fixtures/custom_field.json
|
||||||
@@ -5686,7 +5686,7 @@ msgstr "Sajt se ažurira. Trenutno nisu moguće izmene. Pun pristup će uskoro b
|
|||||||
|
|
||||||
#: frontend/src/components/VideoBlock.vue:8
|
#: frontend/src/components/VideoBlock.vue:8
|
||||||
msgid "This video contains {0} {1}:"
|
msgid "This video contains {0} {1}:"
|
||||||
msgstr ""
|
msgstr "Ovaj video-snimak sadrži {0} {1}:"
|
||||||
|
|
||||||
#. Option for the 'Day' (Select) field in DocType 'Evaluator Schedule'
|
#. Option for the 'Day' (Select) field in DocType 'Evaluator Schedule'
|
||||||
#. Option for the 'Day' (Select) field in DocType 'LMS Certificate Request'
|
#. Option for the 'Day' (Select) field in DocType 'LMS Certificate Request'
|
||||||
@@ -5710,15 +5710,15 @@ msgstr "Vremenska preferencija"
|
|||||||
|
|
||||||
#: frontend/src/components/Modals/QuizInVideo.vue:13
|
#: frontend/src/components/Modals/QuizInVideo.vue:13
|
||||||
msgid "Time in Video"
|
msgid "Time in Video"
|
||||||
msgstr ""
|
msgstr "Vreme u video-snimku"
|
||||||
|
|
||||||
#: frontend/src/components/Modals/QuizInVideo.vue:220
|
#: frontend/src/components/Modals/QuizInVideo.vue:220
|
||||||
msgid "Time in Video (minutes)"
|
msgid "Time in Video (minutes)"
|
||||||
msgstr ""
|
msgstr "Vreme u video-snimku (u minutima)"
|
||||||
|
|
||||||
#: frontend/src/components/Modals/QuizInVideo.vue:173
|
#: frontend/src/components/Modals/QuizInVideo.vue:173
|
||||||
msgid "Time in video exceeds the total duration of the video."
|
msgid "Time in video exceeds the total duration of the video."
|
||||||
msgstr ""
|
msgstr "Vreme u video-snimku prelazi ukupno trajanje video-snimka."
|
||||||
|
|
||||||
#: frontend/src/components/Modals/LiveClassModal.vue:44
|
#: frontend/src/components/Modals/LiveClassModal.vue:44
|
||||||
msgid "Time must be in 24 hour format (HH:mm). Example 11:30 or 22:00"
|
msgid "Time must be in 24 hour format (HH:mm). Example 11:30 or 22:00"
|
||||||
@@ -6338,7 +6338,7 @@ msgstr "Neophodno je da se prvo prijavite da biste se upisali na ovu obuku"
|
|||||||
|
|
||||||
#: frontend/src/components/Quiz.vue:7
|
#: frontend/src/components/Quiz.vue:7
|
||||||
msgid "You will have to complete the quiz to continue the video"
|
msgid "You will have to complete the quiz to continue the video"
|
||||||
msgstr ""
|
msgstr "Neophodno je da završite kviz kako biste nastavili video-snimak"
|
||||||
|
|
||||||
#: frontend/src/components/Quiz.vue:30 lms/templates/quiz/quiz.html:11
|
#: frontend/src/components/Quiz.vue:30 lms/templates/quiz/quiz.html:11
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -6452,7 +6452,7 @@ msgstr "kandidati"
|
|||||||
|
|
||||||
#: frontend/src/components/VideoBlock.vue:16
|
#: frontend/src/components/VideoBlock.vue:16
|
||||||
msgid "at {0}"
|
msgid "at {0}"
|
||||||
msgstr ""
|
msgstr "u {0}"
|
||||||
|
|
||||||
#: frontend/src/components/Modals/LiveClassAttendance.vue:39
|
#: frontend/src/components/Modals/LiveClassAttendance.vue:39
|
||||||
msgid "attended for"
|
msgid "attended for"
|
||||||
|
|||||||
Reference in New Issue
Block a user