feat: seo tags and keywords for courses and batches
This commit is contained in:
@@ -273,6 +273,27 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-20 pb-5 space-y-5 border-b">
|
||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||
{{ __('Meta Tags') }}
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="meta.description"
|
||||
:label="__('Meta Description')"
|
||||
type="textarea"
|
||||
:rows="7"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="meta.keywords"
|
||||
:label="__('Meta Keywords')"
|
||||
type="textarea"
|
||||
:rows="7"
|
||||
:placeholder="__('Comma separated keywords for SEO')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -302,12 +323,13 @@ import { useOnboarding } from 'frappe-ui/frappe'
|
||||
import { sessionStore } from '../stores/session'
|
||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import { openSettings } from '@/utils'
|
||||
import { openSettings, getMetaInfo, updateMetaInfo } from '@/utils'
|
||||
|
||||
const router = useRouter()
|
||||
const user = inject('$user')
|
||||
const { brand } = sessionStore()
|
||||
const { updateOnboardingStep } = useOnboarding('learning')
|
||||
const instructors = ref([])
|
||||
|
||||
const props = defineProps({
|
||||
batchName: {
|
||||
@@ -340,18 +362,26 @@ const batch = reactive({
|
||||
zoom_account: '',
|
||||
})
|
||||
|
||||
const instructors = ref([])
|
||||
const meta = reactive({
|
||||
description: '',
|
||||
keywords: '',
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!user.data) window.location.href = '/login'
|
||||
if (props.batchName != 'new') {
|
||||
batchDetail.reload()
|
||||
fetchBatchInfo()
|
||||
} else {
|
||||
capture('batch_form_opened')
|
||||
}
|
||||
window.addEventListener('keydown', keyboardShortcut)
|
||||
})
|
||||
|
||||
const fetchBatchInfo = () => {
|
||||
batchDetail.reload()
|
||||
getMetaInfo('batches', props.batchName, meta)
|
||||
}
|
||||
|
||||
const keyboardShortcut = (e) => {
|
||||
if (
|
||||
e.key === 's' &&
|
||||
@@ -465,7 +495,7 @@ const createNewBatch = () => {
|
||||
localStorage.setItem('firstBatch', data.name)
|
||||
})
|
||||
}
|
||||
|
||||
updateMetaInfo('batches', data.name, meta)
|
||||
capture('batch_created')
|
||||
router.push({
|
||||
name: 'BatchDetail',
|
||||
@@ -486,6 +516,7 @@ const editBatchDetails = () => {
|
||||
{},
|
||||
{
|
||||
onSuccess(data) {
|
||||
updateMetaInfo('batches', data.name, meta)
|
||||
router.push({
|
||||
name: 'BatchDetail',
|
||||
params: {
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<FormControl
|
||||
v-model="course.short_introduction"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:rows="5"
|
||||
:label="__('Short Introduction')"
|
||||
:placeholder="
|
||||
__(
|
||||
@@ -201,7 +201,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="px-10 pb-5 space-y-5">
|
||||
<div class="px-10 pb-5 space-y-5 border-b">
|
||||
<div class="text-lg font-semibold mt-5">
|
||||
{{ __('Pricing and Certification') }}
|
||||
</div>
|
||||
@@ -248,6 +248,27 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-10 pb-5 space-y-5">
|
||||
<div class="text-lg font-semibold mt-5">
|
||||
{{ __('Meta Tags') }}
|
||||
</div>
|
||||
<div class="space-y-5">
|
||||
<FormControl
|
||||
v-model="meta.description"
|
||||
:label="__('Meta Description')"
|
||||
type="textarea"
|
||||
:rows="7"
|
||||
/>
|
||||
<FormControl
|
||||
v-model="meta.keywords"
|
||||
:label="__('Meta Keywords')"
|
||||
type="textarea"
|
||||
:rows="7"
|
||||
:placeholder="__('Comma separated keywords for SEO')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-l">
|
||||
@@ -264,6 +285,7 @@
|
||||
<script setup>
|
||||
import {
|
||||
Breadcrumbs,
|
||||
call,
|
||||
TextEditor,
|
||||
Button,
|
||||
createResource,
|
||||
@@ -287,7 +309,7 @@ import { useRouter } from 'vue-router'
|
||||
import { capture, startRecording, stopRecording } from '@/telemetry'
|
||||
import { useOnboarding } from 'frappe-ui/frappe'
|
||||
import { sessionStore } from '../stores/session'
|
||||
import { openSettings } from '@/utils'
|
||||
import { openSettings, getMetaInfo, updateMetaInfo } from '@/utils'
|
||||
import Link from '@/components/Controls/Link.vue'
|
||||
import CourseOutline from '@/components/CourseOutline.vue'
|
||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||
@@ -328,13 +350,18 @@ const course = reactive({
|
||||
evaluator: '',
|
||||
})
|
||||
|
||||
const meta = reactive({
|
||||
description: '',
|
||||
keywords: '',
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!user.data?.is_moderator && !user.data?.is_instructor) {
|
||||
router.push({ name: 'Courses' })
|
||||
}
|
||||
|
||||
if (props.courseName !== 'new') {
|
||||
courseResource.reload()
|
||||
fetchCourseInfo()
|
||||
} else {
|
||||
capture('course_form_opened')
|
||||
startRecording()
|
||||
@@ -342,6 +369,11 @@ onMounted(() => {
|
||||
window.addEventListener('keydown', keyboardShortcut)
|
||||
})
|
||||
|
||||
const fetchCourseInfo = () => {
|
||||
courseResource.reload()
|
||||
getMetaInfo('courses', props.courseName, meta)
|
||||
}
|
||||
|
||||
const keyboardShortcut = (e) => {
|
||||
if (
|
||||
e.key === 's' &&
|
||||
@@ -444,40 +476,50 @@ const imageResource = createResource({
|
||||
|
||||
const submitCourse = () => {
|
||||
if (courseResource.data) {
|
||||
courseEditResource.submit(
|
||||
{
|
||||
course: courseResource.data.name,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
toast.success(__('Course updated successfully'))
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
},
|
||||
}
|
||||
)
|
||||
editCourse()
|
||||
} else {
|
||||
courseCreationResource.submit(course, {
|
||||
onSuccess(data) {
|
||||
if (user.data?.is_system_manager) {
|
||||
updateOnboardingStep('create_first_course', true, false, () => {
|
||||
localStorage.setItem('firstCourse', data.name)
|
||||
})
|
||||
}
|
||||
createCourse()
|
||||
}
|
||||
}
|
||||
|
||||
capture('course_created')
|
||||
toast.success(__('Course created successfully'))
|
||||
router.push({
|
||||
name: 'CourseForm',
|
||||
params: { courseName: data.name },
|
||||
const createCourse = () => {
|
||||
courseCreationResource.submit(course, {
|
||||
onSuccess(data) {
|
||||
updateMetaInfo('courses', data.name, meta)
|
||||
if (user.data?.is_system_manager) {
|
||||
updateOnboardingStep('create_first_course', true, false, () => {
|
||||
localStorage.setItem('firstCourse', data.name)
|
||||
})
|
||||
}
|
||||
|
||||
capture('course_created')
|
||||
toast.success(__('Course created successfully'))
|
||||
router.push({
|
||||
name: 'CourseForm',
|
||||
params: { courseName: data.name },
|
||||
})
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const editCourse = () => {
|
||||
courseEditResource.submit(
|
||||
{
|
||||
course: courseResource.data.name,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
updateMetaInfo('courses', props.courseName, meta)
|
||||
toast.success(__('Course updated successfully'))
|
||||
},
|
||||
onError(err) {
|
||||
toast.error(err.messages?.[0] || err)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const deleteCourse = createResource({
|
||||
@@ -517,7 +559,7 @@ watch(
|
||||
() => props.courseName !== 'new',
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
courseResource.reload()
|
||||
fetchCourseInfo()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user