Merge pull request #1414 from pateljannat/seo-improvements
fix: seo improvements
This commit is contained in:
32
.github/workflows/linters.yml
vendored
32
.github/workflows/linters.yml
vendored
@@ -7,8 +7,27 @@ on:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
commit-lint:
|
||||
name: 'Semantic Commits'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 200
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
check-latest: true
|
||||
|
||||
- name: Check commit titles
|
||||
run: |
|
||||
npm install @commitlint/cli @commitlint/config-conventional
|
||||
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
linters:
|
||||
name: Semantic Commits
|
||||
name: Semgrep Rules
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
@@ -20,8 +39,17 @@ jobs:
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Install and Run Pre-commit
|
||||
uses: pre-commit/action@v2.0.3
|
||||
uses: pre-commit/action@v3.0.1
|
||||
|
||||
- name: Download Semgrep rules
|
||||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
||||
|
||||
4
.github/workflows/ui-tests.yml
vendored
4
.github/workflows/ui-tests.yml
vendored
@@ -70,7 +70,7 @@ jobs:
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
${{ runner.os }}-yarn-ui-
|
||||
|
||||
- name: Cache cypress binary
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress
|
||||
|
||||
26
commitlint.config.js
Normal file
26
commitlint.config.js
Normal file
@@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
parserPreset: "conventional-changelog-conventionalcommits",
|
||||
rules: {
|
||||
"subject-empty": [2, "never"],
|
||||
"type-case": [2, "always", "lower-case"],
|
||||
"type-empty": [2, "never"],
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"build",
|
||||
"chore",
|
||||
"ci",
|
||||
"docs",
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"refactor",
|
||||
"revert",
|
||||
"style",
|
||||
"test",
|
||||
"deprecate", // deprecation decision
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="{{ favicon or '/assets/lms/frontend/favicon.png' }}" />
|
||||
<link rel="icon" href="{{ favicon }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Frappe Learning</title>
|
||||
<title>{{ title }}</title>
|
||||
<meta name="title" content="{{ meta.title }}" />
|
||||
<meta name="image" content="{{ meta.image }}" />
|
||||
<meta name="description" content="{{ meta.description }}" />
|
||||
@@ -23,17 +23,6 @@
|
||||
<p>
|
||||
{{ meta.description }}
|
||||
</p>
|
||||
<p>
|
||||
The content here is just for seo purposes. The actual content will be loaded in a few seconds.
|
||||
</p>
|
||||
<p>
|
||||
Seo checks if a page has more than 300 words. So, here are some more words to make it more than 300 words.
|
||||
Page descriptions are the HTML meta tags that provide a brief summary of a web page.
|
||||
Search engines use meta descriptions to help identify the page's topic - they don't use them to rank the page, but they do use them to determine whether or not to display the page in search results.
|
||||
Meta descriptions are important because they're often the first thing people see when they're deciding which search result to click on.
|
||||
They're also important because they can help improve your click-through rate (CTR) from search results.
|
||||
A good meta description can entice people to click on your page instead of someone else's.
|
||||
</p>
|
||||
<a href="{{ meta.link }}">Know More</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,8 +30,6 @@
|
||||
<div id="popovers"></div>
|
||||
|
||||
<script>
|
||||
window.csrf_token = '{{ csrf_token }}'
|
||||
window.setup_complete = '{{ setup_complete }}'
|
||||
document.getElementById('seo-content').style.display = 'none';
|
||||
</script>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<span v-else> Learning </span>
|
||||
</div>
|
||||
<div
|
||||
v-if="userResource"
|
||||
v-if="userResource.data"
|
||||
class="mt-1 text-sm text-ink-gray-7 leading-none"
|
||||
>
|
||||
{{ convertToTitleCase(userResource.data?.full_name) }}
|
||||
|
||||
152
lms/www/lms.py
152
lms/www/lms.py
@@ -10,25 +10,55 @@ no_cache = 1
|
||||
|
||||
def get_context():
|
||||
app_path = frappe.form_dict.get("app_path")
|
||||
favicon = (
|
||||
frappe.db.get_single_value("Website Settings", "favicon")
|
||||
or "/assets/lms/frontend/favicon.png"
|
||||
)
|
||||
title = frappe.db.get_single_value("Website Settings", "app_name") or "Frappe Learning"
|
||||
|
||||
context = frappe._dict()
|
||||
if app_path:
|
||||
context.meta = get_meta(app_path)
|
||||
else:
|
||||
context.meta = {}
|
||||
csrf_token = frappe.sessions.get_csrf_token()
|
||||
frappe.db.commit() # nosemgrep
|
||||
context.csrf_token = csrf_token
|
||||
context.setup_complete = cint(frappe.get_system_settings("setup_complete"))
|
||||
context.meta = get_meta(app_path, title, favicon)
|
||||
capture("active_site", "lms")
|
||||
context.favicon = frappe.db.get_single_value("Website Settings", "favicon")
|
||||
context.title = title
|
||||
context.favicon = favicon
|
||||
return context
|
||||
|
||||
|
||||
def get_meta(app_path):
|
||||
def get_meta(app_path, title, favicon):
|
||||
meta = {}
|
||||
if app_path:
|
||||
meta = get_meta_from_document(app_path, favicon)
|
||||
|
||||
route_meta = frappe.get_all("Website Meta Tag", {"parent": app_path}, ["key", "value"])
|
||||
|
||||
if len(route_meta) > 0:
|
||||
for row in route_meta:
|
||||
if row.key == "title":
|
||||
meta["title"] = row.value
|
||||
elif row.key == "image":
|
||||
meta["image"] = row.value
|
||||
elif row.key == "description":
|
||||
meta["description"] = f"{meta.get('description', '')} {row.value}"
|
||||
elif row.key == "keywords":
|
||||
meta["keywords"] = f"{meta.get('keywords', '')} {row.value}"
|
||||
elif row.key == "link":
|
||||
meta["link"] = row.value
|
||||
|
||||
if not meta:
|
||||
meta = {
|
||||
"title": title,
|
||||
"image": favicon,
|
||||
"description": "Easy to use Learning Management System",
|
||||
}
|
||||
|
||||
return meta
|
||||
|
||||
|
||||
def get_meta_from_document(app_path, favicon):
|
||||
if app_path == "courses":
|
||||
return {
|
||||
"title": _("Course List"),
|
||||
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
|
||||
"image": favicon,
|
||||
"description": "This page lists all the courses published on our website",
|
||||
"keywords": "All Courses, Courses, Learn",
|
||||
"link": "/courses",
|
||||
@@ -47,13 +77,18 @@ def get_meta(app_path):
|
||||
course = frappe.db.get_value(
|
||||
"LMS Course",
|
||||
course_name,
|
||||
["title", "image", "short_introduction", "tags"],
|
||||
["title", "image", "description", "tags"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if course.description:
|
||||
soup = BeautifulSoup(course.description, "html.parser")
|
||||
course.description = soup.get_text()
|
||||
|
||||
return {
|
||||
"title": course.title,
|
||||
"image": course.image,
|
||||
"description": course.short_introduction,
|
||||
"description": course.description,
|
||||
"keywords": course.tags,
|
||||
"link": f"/courses/{course_name}",
|
||||
}
|
||||
@@ -61,7 +96,7 @@ def get_meta(app_path):
|
||||
if app_path == "batches":
|
||||
return {
|
||||
"title": _("Batches"),
|
||||
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
|
||||
"image": favicon,
|
||||
"description": "This page lists all the batches published on our website",
|
||||
"keywords": "All Batches, Batches, Learn",
|
||||
"link": "/batches",
|
||||
@@ -71,13 +106,18 @@ def get_meta(app_path):
|
||||
batch = frappe.db.get_value(
|
||||
"LMS Batch",
|
||||
batch_name,
|
||||
["title", "meta_image", "description", "category", "medium"],
|
||||
["title", "meta_image", "batch_details", "category", "medium"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if batch.batch_details:
|
||||
soup = BeautifulSoup(batch.batch_details, "html.parser")
|
||||
batch.batch_details = soup.get_text()
|
||||
|
||||
return {
|
||||
"title": batch.title,
|
||||
"image": batch.meta_image,
|
||||
"description": batch.description,
|
||||
"description": batch.batch_details,
|
||||
"keywords": f"{batch.category} {batch.medium}",
|
||||
"link": f"/batches/details/{batch_name}",
|
||||
}
|
||||
@@ -87,7 +127,7 @@ def get_meta(app_path):
|
||||
if "new/edit" in app_path:
|
||||
return {
|
||||
"title": _("New Batch"),
|
||||
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
|
||||
"image": favicon,
|
||||
"description": "Create a new batch",
|
||||
"keywords": "New Batch, Create Batch",
|
||||
"link": "/lms/batches/new/edit",
|
||||
@@ -95,13 +135,18 @@ def get_meta(app_path):
|
||||
batch = frappe.db.get_value(
|
||||
"LMS Batch",
|
||||
batch_name,
|
||||
["title", "meta_image", "description", "category", "medium"],
|
||||
["title", "meta_image", "batch_details", "category", "medium"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if batch.batch_details:
|
||||
soup = BeautifulSoup(batch.batch_details, "html.parser")
|
||||
batch.batch_details = soup.get_text()
|
||||
|
||||
return {
|
||||
"title": batch.title,
|
||||
"image": batch.meta_image,
|
||||
"description": batch.description,
|
||||
"description": batch.batch_details,
|
||||
"keywords": f"{batch.category} {batch.medium}",
|
||||
"link": f"/batches/{batch_name}",
|
||||
}
|
||||
@@ -109,7 +154,7 @@ def get_meta(app_path):
|
||||
if app_path == "job-openings":
|
||||
return {
|
||||
"title": _("Job Openings"),
|
||||
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
|
||||
"image": favicon,
|
||||
"description": "This page lists all the job openings published on our website",
|
||||
"keywords": "Job Openings, Jobs, Vacancies",
|
||||
"link": "/job-openings",
|
||||
@@ -120,13 +165,13 @@ def get_meta(app_path):
|
||||
job_opening = frappe.db.get_value(
|
||||
"Job Opportunity",
|
||||
job_opening_name,
|
||||
["job_title", "company_logo", "company_name"],
|
||||
["job_title", "company_logo", "description"],
|
||||
as_dict=True,
|
||||
)
|
||||
return {
|
||||
"title": job_opening.job_title,
|
||||
"image": job_opening.company_logo,
|
||||
"description": job_opening.company_name,
|
||||
"description": job_opening.description,
|
||||
"keywords": "Job Openings, Jobs, Vacancies",
|
||||
"link": f"/job-openings/{job_opening_name}",
|
||||
}
|
||||
@@ -134,7 +179,7 @@ def get_meta(app_path):
|
||||
if app_path == "statistics":
|
||||
return {
|
||||
"title": _("Statistics"),
|
||||
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
|
||||
"image": favicon,
|
||||
"description": "This page lists all the statistics of this platform",
|
||||
"keywords": "Enrollment Count, Completion, Signups",
|
||||
"link": "/statistics",
|
||||
@@ -179,3 +224,64 @@ def get_meta(app_path):
|
||||
"keywords": f"{badge.title}, {badge.description}",
|
||||
"link": f"/badges/{badgeName}/{email}",
|
||||
}
|
||||
|
||||
if app_path == "quizzes":
|
||||
return {
|
||||
"title": _("Quizzes"),
|
||||
"image": favicon,
|
||||
"description": _("Test your knowledge with interactive quizzes and more."),
|
||||
"keywords": "Quizzes, interactive quizzes, online quizzes",
|
||||
"link": "/quizzes",
|
||||
}
|
||||
|
||||
if re.match(r"^quizzes/[^/]+$", app_path):
|
||||
quiz_name = app_path.split("/")[1]
|
||||
quiz = frappe.db.get_value(
|
||||
"LMS Quiz",
|
||||
quiz_name,
|
||||
["title"],
|
||||
as_dict=True,
|
||||
)
|
||||
if quiz:
|
||||
return {
|
||||
"title": quiz.title,
|
||||
"image": favicon,
|
||||
"description": "Test your knowledge with interactive quizzes.",
|
||||
"keywords": quiz.title,
|
||||
"link": f"/quizzes/{quiz_name}",
|
||||
}
|
||||
|
||||
if app_path == "assignments":
|
||||
return {
|
||||
"title": _("Assignments"),
|
||||
"image": favicon,
|
||||
"description": _("Test your knowledge with interactive assignments and more."),
|
||||
"keywords": "Assignments, interactive assignments, online assignments",
|
||||
"link": "/assignments",
|
||||
}
|
||||
|
||||
if re.match(r"^assignments/[^/]+$", app_path):
|
||||
assignment_name = app_path.split("/")[1]
|
||||
assignment = frappe.db.get_value(
|
||||
"LMS Assignment",
|
||||
assignment_name,
|
||||
["title"],
|
||||
as_dict=True,
|
||||
)
|
||||
if assignment:
|
||||
return {
|
||||
"title": assignment.title,
|
||||
"image": favicon,
|
||||
"description": "Test your knowledge with interactive assignments.",
|
||||
"keywords": assignment.title,
|
||||
"link": f"/assignments/{assignment_name}",
|
||||
}
|
||||
|
||||
if app_path == "programs":
|
||||
return {
|
||||
"title": _("Programs"),
|
||||
"image": favicon,
|
||||
"description": "This page lists all the programs published on our website",
|
||||
"keywords": "All Programs, Programs, Learn",
|
||||
"link": "/programs",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user