Merge pull request #1424 from pateljannat/seo-description
feat: SEO Meta Description
This commit is contained in:
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@@ -68,7 +68,6 @@ declare module 'vue' {
|
|||||||
NoPermission: typeof import('./src/components/NoPermission.vue')['default']
|
NoPermission: typeof import('./src/components/NoPermission.vue')['default']
|
||||||
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
|
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
|
||||||
NotPermitted: typeof import('./src/components/NotPermitted.vue')['default']
|
NotPermitted: typeof import('./src/components/NotPermitted.vue')['default']
|
||||||
OnboardingBanner: typeof import('./src/components/OnboardingBanner.vue')['default']
|
|
||||||
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
|
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
|
||||||
PaymentSettings: typeof import('./src/components/PaymentSettings.vue')['default']
|
PaymentSettings: typeof import('./src/components/PaymentSettings.vue')['default']
|
||||||
ProgressBar: typeof import('./src/components/ProgressBar.vue')['default']
|
ProgressBar: typeof import('./src/components/ProgressBar.vue')['default']
|
||||||
|
|||||||
@@ -350,6 +350,20 @@ const tabsStructure = computed(() => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'SEO',
|
||||||
|
icon: 'Search',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Meta Description',
|
||||||
|
name: 'meta_description',
|
||||||
|
type: 'textarea',
|
||||||
|
rows: 5,
|
||||||
|
description:
|
||||||
|
"This description will be shown on lists and pages that don't have meta description",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="showOnboardingBanner && onboardingDetails.data">
|
|
||||||
<Tooltip :text="__('Skip Onboarding')" placement="left">
|
|
||||||
<X
|
|
||||||
class="w-4 h-4 stroke-1 absolute top-2 right-2 cursor-pointer mr-1"
|
|
||||||
@click="skipOnboarding.reload()"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<div class="flex items-center justify-evenly bg-surface-gray-2 p-10">
|
|
||||||
<div
|
|
||||||
@click="redirectToCourseForm()"
|
|
||||||
class="flex items-center space-x-2"
|
|
||||||
:class="{
|
|
||||||
'cursor-pointer': !onboardingDetails.data.course_created?.length,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="onboardingDetails.data.course_created?.length"
|
|
||||||
class="py-1 px-1 bg-surface-white rounded-full"
|
|
||||||
>
|
|
||||||
<Check class="h-4 w-4 stroke-2 text-ink-green-3" />
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="font-semibold bg-surface-white px-2 py-1 rounded-full"
|
|
||||||
>
|
|
||||||
1
|
|
||||||
</span>
|
|
||||||
<span class="text-lg font-semibold">
|
|
||||||
{{ __('Create a course') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
@click="redirectToChapterForm()"
|
|
||||||
class="flex items-center space-x-2"
|
|
||||||
:class="{
|
|
||||||
'cursor-pointer':
|
|
||||||
onboardingDetails.data.course_created?.length &&
|
|
||||||
!onboardingDetails.data.chapter_created?.length,
|
|
||||||
'text-ink-gray-3': !onboardingDetails.data.course_created?.length,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="onboardingDetails.data.chapter_created?.length"
|
|
||||||
class="py-1 px-1 bg-surface-white rounded-full"
|
|
||||||
>
|
|
||||||
<Check class="h-4 w-4 stroke-2 text-ink-green-3" />
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="font-semibold bg-surface-white px-2 py-1 rounded-full"
|
|
||||||
>
|
|
||||||
2
|
|
||||||
</span>
|
|
||||||
<span class="text-lg font-semibold">
|
|
||||||
{{ __('Add a chapter') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
@click="redirectToLessonForm()"
|
|
||||||
class="flex items-center space-x-2"
|
|
||||||
:class="{
|
|
||||||
'cursor-pointer':
|
|
||||||
onboardingDetails.data.course_created?.length &&
|
|
||||||
onboardingDetails.data.chapter_created?.length,
|
|
||||||
'text-ink-gray-3':
|
|
||||||
!onboardingDetails.data.course_created?.length ||
|
|
||||||
!onboardingDetails.data.chapter_created?.length,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="onboardingDetails.data.lesson_created?.length"
|
|
||||||
class="py-1 px-1 bg-surface-white rounded-full"
|
|
||||||
>
|
|
||||||
<Check class="h-4 w-4 stroke-2 text-ink-green-3" />
|
|
||||||
</span>
|
|
||||||
<span class="font-semibold bg-surface-white px-2 py-1 rounded-full">
|
|
||||||
3
|
|
||||||
</span>
|
|
||||||
<span class="text-lg font-semibold">
|
|
||||||
{{ __('Add a lesson') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
import { Check, X } from 'lucide-vue-next'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useSettings } from '@/stores/settings'
|
|
||||||
import { createResource, Tooltip } from 'frappe-ui'
|
|
||||||
|
|
||||||
const showOnboardingBanner = ref(false)
|
|
||||||
const settings = useSettings()
|
|
||||||
const onboardingDetails = settings.onboardingDetails
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
watch(onboardingDetails, () => {
|
|
||||||
if (!onboardingDetails.data?.is_onboarded) {
|
|
||||||
showOnboardingBanner.value = true
|
|
||||||
} else {
|
|
||||||
showOnboardingBanner.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const redirectToCourseForm = () => {
|
|
||||||
if (onboardingDetails.data?.course_created.length) {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
router.push({ name: 'CourseForm', params: { courseName: 'new' } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirectToChapterForm = () => {
|
|
||||||
if (!onboardingDetails.data?.course_created.length) {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
router.push({
|
|
||||||
name: 'CourseForm',
|
|
||||||
params: {
|
|
||||||
courseName: onboardingDetails.data?.first_course,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirectToLessonForm = () => {
|
|
||||||
if (!onboardingDetails.data?.course_created.length) {
|
|
||||||
return
|
|
||||||
} else if (!onboardingDetails.data?.chapter_created.length) {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
router.push({
|
|
||||||
name: 'LessonForm',
|
|
||||||
params: {
|
|
||||||
courseName: onboardingDetails.data?.first_course,
|
|
||||||
chapterNumber: 1,
|
|
||||||
lessonNumber: 1,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const skipOnboarding = createResource({
|
|
||||||
url: 'frappe.client.set_value',
|
|
||||||
makeParams() {
|
|
||||||
return {
|
|
||||||
doctype: 'LMS Settings',
|
|
||||||
name: 'LMS Settings',
|
|
||||||
fieldname: 'is_onboarding_complete',
|
|
||||||
value: 1,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSuccess(data) {
|
|
||||||
onboardingDetails.reload()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -310,11 +310,7 @@ const course = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (
|
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
||||||
props.courseName == 'new' &&
|
|
||||||
!user.data?.is_moderator &&
|
|
||||||
!user.data?.is_instructor
|
|
||||||
) {
|
|
||||||
router.push({ name: 'Courses' })
|
router.push({ name: 'Courses' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
"general_tab",
|
"general_tab",
|
||||||
"default_home",
|
"default_home",
|
||||||
"send_calendar_invite_for_evaluations",
|
"send_calendar_invite_for_evaluations",
|
||||||
"is_onboarding_complete",
|
|
||||||
"column_break_zdel",
|
"column_break_zdel",
|
||||||
"allow_guest_access",
|
"allow_guest_access",
|
||||||
"enable_learning_paths",
|
"enable_learning_paths",
|
||||||
@@ -60,7 +59,9 @@
|
|||||||
"batch_confirmation_template",
|
"batch_confirmation_template",
|
||||||
"column_break_uwsp",
|
"column_break_uwsp",
|
||||||
"assignment_submission_template",
|
"assignment_submission_template",
|
||||||
"payment_reminder_template"
|
"payment_reminder_template",
|
||||||
|
"seo_tab",
|
||||||
|
"meta_description"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -107,13 +108,6 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Identify User Persona"
|
"label": "Identify User Persona"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "is_onboarding_complete",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Onboarding Complete",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "default_home",
|
"fieldname": "default_home",
|
||||||
@@ -372,14 +366,25 @@
|
|||||||
"fieldname": "disable_signup",
|
"fieldname": "disable_signup",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Disable Signup"
|
"label": "Disable Signup"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "seo_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "SEO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This description will be shown on lists and pages without meta description",
|
||||||
|
"fieldname": "meta_description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Meta Description"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-04-07 18:05:52.000651",
|
"modified": "2025-04-10 16:17:00.658698",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Settings",
|
"name": "LMS Settings",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import re
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils.telemetry import capture
|
from frappe.utils.telemetry import capture
|
||||||
from frappe.utils import cint
|
|
||||||
|
|
||||||
no_cache = 1
|
no_cache = 1
|
||||||
|
|
||||||
@@ -15,22 +14,23 @@ def get_context():
|
|||||||
or "/assets/lms/frontend/favicon.png"
|
or "/assets/lms/frontend/favicon.png"
|
||||||
)
|
)
|
||||||
title = frappe.db.get_single_value("Website Settings", "app_name") or "Frappe Learning"
|
title = frappe.db.get_single_value("Website Settings", "app_name") or "Frappe Learning"
|
||||||
|
description = frappe.db.get_single_value("LMS Settings", "meta_description")
|
||||||
csrf_token = frappe.sessions.get_csrf_token()
|
csrf_token = frappe.sessions.get_csrf_token()
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
context = frappe._dict()
|
context = frappe._dict()
|
||||||
context.csrf_token = csrf_token
|
context.csrf_token = csrf_token
|
||||||
context.meta = get_meta(app_path, title, favicon)
|
context.meta = get_meta(app_path, title, favicon, description)
|
||||||
capture("active_site", "lms")
|
capture("active_site", "lms")
|
||||||
context.title = title
|
context.title = title
|
||||||
context.favicon = favicon
|
context.favicon = favicon
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
def get_meta(app_path, title, favicon):
|
def get_meta(app_path, title, favicon, description):
|
||||||
meta = {}
|
meta = {}
|
||||||
if app_path:
|
if app_path:
|
||||||
meta = get_meta_from_document(app_path, favicon)
|
meta = get_meta_from_document(app_path)
|
||||||
|
|
||||||
route_meta = frappe.get_all("Website Meta Tag", {"parent": app_path}, ["key", "value"])
|
route_meta = frappe.get_all("Website Meta Tag", {"parent": app_path}, ["key", "value"])
|
||||||
|
|
||||||
@@ -47,22 +47,26 @@ def get_meta(app_path, title, favicon):
|
|||||||
elif row.key == "link":
|
elif row.key == "link":
|
||||||
meta["link"] = row.value
|
meta["link"] = row.value
|
||||||
|
|
||||||
|
if not meta.get("description"):
|
||||||
|
meta["description"] = description
|
||||||
|
|
||||||
|
if not meta.get("image"):
|
||||||
|
meta["image"] = favicon
|
||||||
|
|
||||||
if not meta:
|
if not meta:
|
||||||
meta = {
|
meta = {
|
||||||
"title": title,
|
"title": title,
|
||||||
"image": favicon,
|
"image": favicon,
|
||||||
"description": "Easy to use Learning Management System",
|
"description": description,
|
||||||
}
|
}
|
||||||
|
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
|
|
||||||
def get_meta_from_document(app_path, favicon):
|
def get_meta_from_document(app_path):
|
||||||
if app_path == "courses":
|
if app_path == "courses":
|
||||||
return {
|
return {
|
||||||
"title": _("Course List"),
|
"title": _("Course List"),
|
||||||
"image": favicon,
|
|
||||||
"description": "This page lists all the courses published on our website",
|
|
||||||
"keywords": "All Courses, Courses, Learn",
|
"keywords": "All Courses, Courses, Learn",
|
||||||
"link": "/courses",
|
"link": "/courses",
|
||||||
}
|
}
|
||||||
@@ -72,7 +76,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
return {
|
return {
|
||||||
"title": _("New Course"),
|
"title": _("New Course"),
|
||||||
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
|
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
|
||||||
"description": "Create a new course",
|
|
||||||
"keywords": "New Course, Create Course",
|
"keywords": "New Course, Create Course",
|
||||||
"link": "/lms/courses/new/edit",
|
"link": "/lms/courses/new/edit",
|
||||||
}
|
}
|
||||||
@@ -99,8 +102,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if app_path == "batches":
|
if app_path == "batches":
|
||||||
return {
|
return {
|
||||||
"title": _("Batches"),
|
"title": _("Batches"),
|
||||||
"image": favicon,
|
|
||||||
"description": "This page lists all the batches published on our website",
|
|
||||||
"keywords": "All Batches, Batches, Learn",
|
"keywords": "All Batches, Batches, Learn",
|
||||||
"link": "/batches",
|
"link": "/batches",
|
||||||
}
|
}
|
||||||
@@ -130,8 +131,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if "new/edit" in app_path:
|
if "new/edit" in app_path:
|
||||||
return {
|
return {
|
||||||
"title": _("New Batch"),
|
"title": _("New Batch"),
|
||||||
"image": favicon,
|
|
||||||
"description": "Create a new batch",
|
|
||||||
"keywords": "New Batch, Create Batch",
|
"keywords": "New Batch, Create Batch",
|
||||||
"link": "/lms/batches/new/edit",
|
"link": "/lms/batches/new/edit",
|
||||||
}
|
}
|
||||||
@@ -157,8 +156,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if app_path == "job-openings":
|
if app_path == "job-openings":
|
||||||
return {
|
return {
|
||||||
"title": _("Job Openings"),
|
"title": _("Job Openings"),
|
||||||
"image": favicon,
|
|
||||||
"description": "This page lists all the job openings published on our website",
|
|
||||||
"keywords": "Job Openings, Jobs, Vacancies",
|
"keywords": "Job Openings, Jobs, Vacancies",
|
||||||
"link": "/job-openings",
|
"link": "/job-openings",
|
||||||
}
|
}
|
||||||
@@ -182,8 +179,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if app_path == "statistics":
|
if app_path == "statistics":
|
||||||
return {
|
return {
|
||||||
"title": _("Statistics"),
|
"title": _("Statistics"),
|
||||||
"image": favicon,
|
|
||||||
"description": "This page lists all the statistics of this platform",
|
|
||||||
"keywords": "Enrollment Count, Completion, Signups",
|
"keywords": "Enrollment Count, Completion, Signups",
|
||||||
"link": "/statistics",
|
"link": "/statistics",
|
||||||
}
|
}
|
||||||
@@ -231,8 +226,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if app_path == "quizzes":
|
if app_path == "quizzes":
|
||||||
return {
|
return {
|
||||||
"title": _("Quizzes"),
|
"title": _("Quizzes"),
|
||||||
"image": favicon,
|
|
||||||
"description": _("Test your knowledge with interactive quizzes and more."),
|
|
||||||
"keywords": "Quizzes, interactive quizzes, online quizzes",
|
"keywords": "Quizzes, interactive quizzes, online quizzes",
|
||||||
"link": "/quizzes",
|
"link": "/quizzes",
|
||||||
}
|
}
|
||||||
@@ -248,8 +241,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if quiz:
|
if quiz:
|
||||||
return {
|
return {
|
||||||
"title": quiz.title,
|
"title": quiz.title,
|
||||||
"image": favicon,
|
|
||||||
"description": "Test your knowledge with interactive quizzes.",
|
|
||||||
"keywords": quiz.title,
|
"keywords": quiz.title,
|
||||||
"link": f"/quizzes/{quiz_name}",
|
"link": f"/quizzes/{quiz_name}",
|
||||||
}
|
}
|
||||||
@@ -257,8 +248,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if app_path == "assignments":
|
if app_path == "assignments":
|
||||||
return {
|
return {
|
||||||
"title": _("Assignments"),
|
"title": _("Assignments"),
|
||||||
"image": favicon,
|
|
||||||
"description": _("Test your knowledge with interactive assignments and more."),
|
|
||||||
"keywords": "Assignments, interactive assignments, online assignments",
|
"keywords": "Assignments, interactive assignments, online assignments",
|
||||||
"link": "/assignments",
|
"link": "/assignments",
|
||||||
}
|
}
|
||||||
@@ -274,8 +263,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if assignment:
|
if assignment:
|
||||||
return {
|
return {
|
||||||
"title": assignment.title,
|
"title": assignment.title,
|
||||||
"image": favicon,
|
|
||||||
"description": "Test your knowledge with interactive assignments.",
|
|
||||||
"keywords": assignment.title,
|
"keywords": assignment.title,
|
||||||
"link": f"/assignments/{assignment_name}",
|
"link": f"/assignments/{assignment_name}",
|
||||||
}
|
}
|
||||||
@@ -283,8 +270,6 @@ def get_meta_from_document(app_path, favicon):
|
|||||||
if app_path == "programs":
|
if app_path == "programs":
|
||||||
return {
|
return {
|
||||||
"title": _("Programs"),
|
"title": _("Programs"),
|
||||||
"image": favicon,
|
|
||||||
"description": "This page lists all the programs published on our website",
|
|
||||||
"keywords": "All Programs, Programs, Learn",
|
"keywords": "All Programs, Programs, Learn",
|
||||||
"link": "/programs",
|
"link": "/programs",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user