Merge branch 'frappe:develop' into recent-courses-web-template

This commit is contained in:
Muhammad Kazim
2024-06-17 00:57:16 +03:30
committed by GitHub
6 changed files with 82 additions and 34 deletions

View File

@@ -69,26 +69,18 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { import { Breadcrumbs, FormControl, createResource, Button } from 'frappe-ui'
Breadcrumbs,
FormControl,
createResource,
Button,
createDocumentResource,
} from 'frappe-ui'
import { computed, reactive, onMounted, inject, ref, watch } from 'vue' import { computed, reactive, onMounted, inject, ref, watch } from 'vue'
import EditorJS from '@editorjs/editorjs' import EditorJS from '@editorjs/editorjs'
import { createToast } from '../utils' import { createToast } from '../utils'
import LessonPlugins from '@/components/LessonPlugins.vue' import LessonPlugins from '@/components/LessonPlugins.vue'
import { getEditorTools } from '../utils' import { getEditorTools } from '../utils'
import { ChevronRight } from 'lucide-vue-next' import { ChevronRight } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
const editor = ref(null) const editor = ref(null)
const instructorEditor = ref(null) const instructorEditor = ref(null)
const user = inject('$user') const user = inject('$user')
const openInstructorEditor = ref(false) const openInstructorEditor = ref(false)
const router = useRouter()
const props = defineProps({ const props = defineProps({
courseName: { courseName: {

View File

@@ -146,7 +146,7 @@
</div> </div>
<div class="mt-20"> <div class="mt-20">
<Discussions <Discussions
v-if="allowDiscussions()" v-if="allowDiscussions"
:title="'Questions'" :title="'Questions'"
:doctype="'Course Lesson'" :doctype="'Course Lesson'"
:docname="lesson.data.name" :docname="lesson.data.name"
@@ -185,7 +185,7 @@
</template> </template>
<script setup> <script setup>
import { createResource, Breadcrumbs, Button } from 'frappe-ui' import { createResource, Breadcrumbs, Button } from 'frappe-ui'
import { computed, watch, ref, inject, createApp } from 'vue' import { computed, watch, inject, ref } from 'vue'
import CourseOutline from '@/components/CourseOutline.vue' import CourseOutline from '@/components/CourseOutline.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
@@ -198,7 +198,9 @@ import CourseInstructors from '@/components/CourseInstructors.vue'
const user = inject('$user') const user = inject('$user')
const route = useRoute() const route = useRoute()
let editor, instructorEditor const allowDiscussions = ref(false)
const editor = ref(null)
const instructorEditor = ref(null)
const props = defineProps({ const props = defineProps({
courseName: { courseName: {
@@ -228,13 +230,21 @@ const lesson = createResource({
auto: true, auto: true,
onSuccess(data) { onSuccess(data) {
markProgress(data) markProgress(data)
if (data.content) editor.value = renderEditor('editor', data.content)
if (data.content) editor = renderEditor('editor', data.content)
if (data.instructor_content?.blocks?.length) if (data.instructor_content?.blocks?.length)
instructorEditor = renderEditor( instructorEditor.value = renderEditor(
'instructor-content', 'instructor-content',
data.instructor_content data.instructor_content
) )
editor.value?.isReady.then(() => {
checkIfDiscussionsAllowed()
})
if (!editor.value && data.body) {
const quizRegex = /\{\{ Quiz\(".*"\) \}\}/
const hasQuiz = quizRegex.test(data.body)
if (!hasQuiz) allowDiscussions.value = true
}
}, },
}) })
@@ -292,6 +302,9 @@ watch(
[oldChapterNumber, oldLessonNumber] [oldChapterNumber, oldLessonNumber]
) => { ) => {
if (newChapterNumber || newLessonNumber) { if (newChapterNumber || newLessonNumber) {
editor.value = null
instructorEditor.value = null
allowDiscussions.value = false
lesson.submit({ lesson.submit({
chapter: newChapterNumber, chapter: newChapterNumber,
lesson: newLessonNumber, lesson: newLessonNumber,
@@ -300,12 +313,19 @@ watch(
} }
) )
const allowDiscussions = () => { const checkIfDiscussionsAllowed = () => {
return ( let quizPresent = false
lesson.data?.membership || JSON.parse(lesson.data?.content)?.blocks?.forEach((block) => {
user.data?.is_moderator || if (block.type === 'quiz') quizPresent = true
user.data?.is_instructor })
if (
!quizPresent &&
(lesson.data?.membership ||
user.data?.is_moderator ||
user.data?.is_instructor)
) )
allowDiscussions.value = true
} }
const allowEdit = () => { const allowEdit = () => {

View File

@@ -87,7 +87,7 @@
</template> </template>
<script setup> <script setup>
import { Breadcrumbs, createResource, Button, TabButtons } from 'frappe-ui' import { Breadcrumbs, createResource, Button, TabButtons } from 'frappe-ui'
import { computed, inject, reactive, ref, onMounted, watchEffect } from 'vue' import { computed, inject, watch, ref, onMounted, watchEffect } from 'vue'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { Edit } from 'lucide-vue-next' import { Edit } from 'lucide-vue-next'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
@@ -119,11 +119,13 @@ onMounted(() => {
const profile = createResource({ const profile = createResource({
url: 'frappe.client.get', url: 'frappe.client.get',
params: { makeParams(values) {
doctype: 'User', return {
filters: { doctype: 'User',
username: props.username, filters: {
}, username: props.username,
},
}
}, },
}) })
@@ -165,6 +167,13 @@ watchEffect(() => {
} }
}) })
watch(
() => props.username,
() => {
profile.reload()
}
)
const editProfile = () => { const editProfile = () => {
showProfileModal.value = true showProfileModal.value = true
} }

View File

@@ -12,12 +12,12 @@
{{ __('No introduction') }} {{ __('No introduction') }}
</div> </div>
</div> </div>
<div class="mt-7 mb-10"> <div class="mt-7 mb-10" v-if="badges.data">
<h2 class="mb-3 text-lg font-semibold text-gray-900"> <h2 class="mb-3 text-lg font-semibold text-gray-900">
{{ __('Achievements') }} {{ __('Achievements') }}
</h2> </h2>
<div class="grid grid-cols-5 gap-4"> <div class="grid grid-cols-5 gap-4">
<div v-if="badges.data" v-for="badge in badges.data"> <div v-for="badge in badges.data">
<Popover trigger="hover" :leaveDelay="Number(0.01)"> <Popover trigger="hover" :leaveDelay="Number(0.01)">
<template #target> <template #target>
<div class="relative"> <div class="relative">
@@ -67,18 +67,24 @@
size="sm" size="sm"
@click="shareOnSocial(badge, 'LinkedIn')" @click="shareOnSocial(badge, 'LinkedIn')"
> >
<template #icon> <template #prefix>
<LinkedinIcon <LinkedinIcon class="h-3 w-3 text-gray-700" />
class="h-3 w-3 stroke-1.5 text-gray-700"
/>
</template> </template>
<span class="text-xs">
{{ __('LinkedIn') }}
</span>
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
@click="shareOnSocial(badge, 'Twitter')" @click="shareOnSocial(badge, 'Twitter')"
> >
<Twitter class="h-3 w-3 stroke-1.5 text-gray-700" /> <template #prefix>
<Twitter class="h-3 w-3 text-gray-700" />
</template>
<span class="text-xs">
{{ __('Twitter') }}
</span>
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -15,7 +15,7 @@
<meta name="twitter:title" content="{{ meta.title }}" /> <meta name="twitter:title" content="{{ meta.title }}" />
<meta name="twitter:image" content="{{ meta.image }}" /> <meta name="twitter:image" content="{{ meta.image }}" />
<meta name="twitter:description" content="{{ meta.description }}" /> <meta name="twitter:description" content="{{ meta.description }}" />
<script type="module" crossorigin src="/assets/lms/frontend/assets/index-8W73ALq4.js"></script> <script type="module" crossorigin src="/assets/lms/frontend/assets/index-kSywU9PY.js"></script>
<link rel="modulepreload" crossorigin href="/assets/lms/frontend/assets/frappe-ui-vM9kBbGH.js"> <link rel="modulepreload" crossorigin href="/assets/lms/frontend/assets/frappe-ui-vM9kBbGH.js">
<link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/frappe-ui-DzKBfka9.css"> <link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/frappe-ui-DzKBfka9.css">
<link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/index-CxRhs9Fi.css"> <link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/index-CxRhs9Fi.css">

View File

@@ -1,6 +1,7 @@
import frappe import frappe
from frappe.utils.telemetry import capture from frappe.utils.telemetry import capture
from frappe import _ from frappe import _
from bs4 import BeautifulSoup
import re import re
no_cache = 1 no_cache = 1
@@ -79,6 +80,22 @@ def get_meta(app_path):
"link": f"/batches/details/{batch_name}", "link": f"/batches/details/{batch_name}",
} }
if re.match(r"^batches/.*$", app_path):
batch_name = app_path.split("/")[1]
batch = frappe.db.get_value(
"LMS Batch",
batch_name,
["title", "meta_image", "description", "category", "medium"],
as_dict=True,
)
return {
"title": batch.title,
"image": batch.meta_image,
"description": batch.description,
"keywords": f"{batch.category} {batch.medium}",
"link": f"/batches/{batch_name}",
}
if app_path == "job-openings": if app_path == "job-openings":
return { return {
"title": _("Job Openings"), "title": _("Job Openings"),
@@ -123,6 +140,10 @@ def get_meta(app_path):
["full_name", "user_image", "bio"], ["full_name", "user_image", "bio"],
as_dict=True, as_dict=True,
) )
soup = BeautifulSoup(user.bio, "html.parser")
user.bio = soup.get_text()
return { return {
"title": user.full_name, "title": user.full_name,
"image": user.user_image, "image": user.user_image,