feat: redesign lesson page
This commit is contained in:
@@ -139,6 +139,7 @@ website_route_rules = [
|
||||
{"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"},
|
||||
{"from_route": "/courses/<course>", "to_route": "courses/course"},
|
||||
{"from_route": "/courses/<course>/edit", "to_route": "courses/create"},
|
||||
{"from_route": "/courses/<course>/outline", "to_route": "courses/outline"},
|
||||
{"from_route": "/courses/<course>/<certificate>", "to_route": "courses/certificate"},
|
||||
{"from_route": "/courses/<course>/learn", "to_route": "batch/learn"},
|
||||
{
|
||||
|
||||
@@ -502,12 +502,17 @@ def can_create_courses(member=None):
|
||||
if not member:
|
||||
member = frappe.session.user
|
||||
|
||||
if frappe.session.user == "Guest":
|
||||
return False
|
||||
|
||||
if has_course_instructor_role(member) or has_course_moderator_role(member):
|
||||
return True
|
||||
|
||||
portal_course_creation = frappe.db.get_single_value(
|
||||
"LMS Settings", "portal_course_creation"
|
||||
)
|
||||
return frappe.session.user != "Guest" and (
|
||||
portal_course_creation == "Anyone" or has_course_instructor_role(member)
|
||||
)
|
||||
|
||||
return portal_course_creation == "Anyone"
|
||||
|
||||
|
||||
def has_course_moderator_role(member=None):
|
||||
@@ -618,15 +623,17 @@ def get_filtered_membership(course, memberships):
|
||||
|
||||
|
||||
def show_start_learing_cta(course, membership):
|
||||
return (
|
||||
not course.disable_self_learning
|
||||
and not membership
|
||||
and not course.upcoming
|
||||
and not check_profile_restriction()
|
||||
and not is_instructor(course.name)
|
||||
and course.status == "Approved"
|
||||
and has_lessons(course)
|
||||
)
|
||||
|
||||
if course.disable_self_learning or course.upcoming:
|
||||
return False
|
||||
if is_instructor(course.name):
|
||||
return False
|
||||
if course.status != "Approved":
|
||||
return False
|
||||
if not has_lessons(course):
|
||||
return False
|
||||
if not membership:
|
||||
return True
|
||||
|
||||
|
||||
def has_lessons(course):
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{% if chapters | length %}
|
||||
<div class="course-home-outline">
|
||||
|
||||
{% if not lesson_page %}
|
||||
<div class="page-title mb-8" id="outline-heading" data-course="{{ course.name }}">
|
||||
{{ _("Course Content") }}
|
||||
</div>
|
||||
@@ -16,9 +17,10 @@
|
||||
. {{ get_lessons(course.name, None, False) }} lessons
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if chapters | length %}
|
||||
<div class="common-card-style column-card p-4">
|
||||
<div {% if not lesson_page %} class="common-card-style column-card p-4" {% endif %}>
|
||||
{% for chapter in chapters %}
|
||||
{% set lessons = get_lessons(course.name, chapter) %}
|
||||
|
||||
@@ -41,7 +43,7 @@
|
||||
<div class="chapter-content collapse navbar-collapse" id="{{ get_slugified_chapter_title(chapter.title) }}">
|
||||
|
||||
{% if chapter.description %}
|
||||
<div class="chapter-description ml-2 mb-2">
|
||||
<div class="chapter-description">
|
||||
{{ chapter.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -67,8 +69,8 @@
|
||||
<span>{{ lesson.title }}</span>
|
||||
|
||||
{% if membership %}
|
||||
<svg class="icon icon-sm lesson-progress-tick {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}">
|
||||
<use class="" href="#icon-green-check">
|
||||
<svg class="icon icon-md lesson-progress-tick ml-auto {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}">
|
||||
<use class="" href="#icon-success">
|
||||
</svg>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -29,6 +29,30 @@
|
||||
letter-spacing: 0.005em;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
z-index: 1020;
|
||||
}
|
||||
|
||||
.is-pinned {
|
||||
background: #FFFFFF;
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid var(--gray-300);
|
||||
}
|
||||
|
||||
.field-parent {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.frappe-control .ql-editor:not(.read-mode) {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.ql-toolbar.ql-snow, .ql-container.ql-snow {
|
||||
border: 1px solid var(--gray-300);
|
||||
}
|
||||
|
||||
.rating .icon {
|
||||
background: var(--gray-200);
|
||||
border-radius: var(--border-radius-md);
|
||||
@@ -60,16 +84,20 @@
|
||||
border: 1px solid var(--gray-300);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: 0.5rem;
|
||||
width: 75%;
|
||||
margin: 0.25rem 0 1rem;
|
||||
width: 100%;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.field-input:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.field-group {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.field-description {
|
||||
font-size: var(--text-sm);
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.invisible-input {
|
||||
@@ -480,7 +508,8 @@ input[type=checkbox] {
|
||||
color: var(--gray-900);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-bottom: 0.25rem;
|
||||
padding-right: 0.5rem;
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
@@ -491,6 +520,8 @@ input[type=checkbox] {
|
||||
.chapter-description {
|
||||
color: var(--gray-900);
|
||||
font-size: var(--text-sm);
|
||||
margin-left: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.course-content-parent .chapter-description {
|
||||
@@ -663,7 +694,7 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.course-details-outline {
|
||||
margin-top: 1rem;
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.lesson-content {
|
||||
@@ -672,7 +703,7 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.lesson-content-card {
|
||||
margin-top: 2rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.lesson-content-card .alert-dismissible .close {
|
||||
@@ -682,7 +713,7 @@ input[type=checkbox] {
|
||||
.course-content-parent {
|
||||
display: grid;
|
||||
grid-gap: 2rem;
|
||||
grid-template-columns: 2fr minmax(600px, 5fr);
|
||||
grid-template-columns: 1fr 3fr;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
@@ -704,16 +735,6 @@ input[type=checkbox] {
|
||||
margin-top: 2rem
|
||||
}
|
||||
|
||||
.lesson-pagination-parent {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.lesson-pagination-parent {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.lesson-video {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -723,12 +744,6 @@ input[type=checkbox] {
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.lesson-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.profile-page {
|
||||
padding-top: 0;
|
||||
}
|
||||
@@ -993,12 +1008,12 @@ pre {
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
background: var(--gray-200);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 4rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid var(--gray-300);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 4rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-state-text {
|
||||
@@ -1568,14 +1583,6 @@ li {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.course-card-wide .avatar .standard-image {
|
||||
border: 1px solid var(--gray-400);
|
||||
}
|
||||
|
||||
.lesson-progress-tick {
|
||||
margin: 0 0.5rem
|
||||
}
|
||||
|
||||
.no-preview {
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
@@ -74,4 +74,7 @@
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</svg>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" id="icon-success" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18.75C14.8325 18.75 18.75 14.8325 18.75 10C18.75 5.16751 14.8325 1.25 10 1.25C5.16751 1.25 1.25 5.16751 1.25 10C1.25 14.8325 5.16751 18.75 10 18.75ZM13.966 7.48104C14.1856 7.21471 14.1477 6.8208 13.8813 6.60122C13.615 6.38164 13.2211 6.41954 13.0015 6.68587L8.68984 11.9155L7.01289 9.74823C6.80165 9.47524 6.40911 9.42517 6.13611 9.6364C5.86311 9.84764 5.81304 10.2402 6.02428 10.5132L8.18004 13.2993C8.29633 13.4495 8.47467 13.5388 8.66468 13.5417C8.85468 13.5447 9.0357 13.461 9.15658 13.3144L13.966 7.48104Z" fill="#171717"/>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
@@ -3,11 +3,7 @@
|
||||
|
||||
|
||||
{% block title %}
|
||||
{% if lesson.title %}
|
||||
{{ lesson.title }} - {{ course.title }}
|
||||
{% else %}
|
||||
{{ _("New Lesson") }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -21,16 +17,35 @@
|
||||
|
||||
|
||||
{% block page_content %}
|
||||
<div class="common-page-style lesson-page">
|
||||
|
||||
<div class="common-page-style">
|
||||
<div class="container course-details-page">
|
||||
{{ BreadCrumb(course, lesson) }}
|
||||
|
||||
<div class="course-content-parent">
|
||||
<div class="course-details-outline">
|
||||
{{ widgets.CourseOutline(course=course, membership=membership) }}
|
||||
<div>
|
||||
<div class="bold-heading mb-4">
|
||||
{{ course.title }}
|
||||
</div>
|
||||
|
||||
{% if membership %}
|
||||
<div class="">
|
||||
<div class="progress-percent m-0">{{ progress }}% {{ _("Completed") }}</div>
|
||||
<div class="progress" title="{{ progress }}% Completed">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="{{ progress }}"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width:{{ progress }}%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="course-details-outline">
|
||||
{{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="lesson-pagination-parent">
|
||||
{{ BreadCrumb(course, lesson) }}
|
||||
{{ LessonContent(lesson) }}
|
||||
{% if not lesson.edit_mode and course.status == "Approved" and not course.upcoming %}
|
||||
{% if course.status == "Approved" and not course.upcoming %}
|
||||
{{ Discussions() }}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -57,25 +72,24 @@
|
||||
{% set instructors = get_instructors(course.name) %}
|
||||
{% set is_instructor = is_instructor(course.name) %}
|
||||
|
||||
<div class="common-card-style lesson-content">
|
||||
<div class="lesson-title">
|
||||
<div>
|
||||
<div>
|
||||
<div class="pull-right">
|
||||
{% if get_progress(course.name, lesson.name) == 'Complete' %}
|
||||
<span id="status-indicator" class="indicator-pill green">{{ _("COMPLETED") }}</span>
|
||||
{% endif %}
|
||||
|
||||
<!-- Title -->
|
||||
<div class="course-home-headings title mb-0 {% if membership %} is-member {% endif %}
|
||||
<!-- Edit Button -->
|
||||
{% if (is_instructor or has_course_moderator_role()) %}
|
||||
<button class="btn btn-secondary btn-sm ml-2 btn-edit"> {{ _("Edit") }} </button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="page-title title {% if membership %} is-member {% endif %}
|
||||
{% if membership or is_instructor %} eligible-for-submission {% endif %}" id="title"
|
||||
{% if lesson.edit_mode %} data-placeholder="{{ _('Title') }}" contenteditable="true" {% endif %}
|
||||
data-index="{{ lesson_index }}" data-course="{{ course.name }}" data-chapter="{{ chapter }}"
|
||||
{% if lesson.name %} data-lesson="{{ lesson.name }}" {% endif %}
|
||||
>{% if lesson.title %}{{ lesson.title }}{% endif %}</div>
|
||||
|
||||
{% if get_progress(course.name, lesson.name) == 'Complete' %}
|
||||
<span id="status-indicator" class="indicator-pill green">{{ _("COMPLETED") }}</span>
|
||||
{% endif %}
|
||||
|
||||
<!-- Edit Button -->
|
||||
{% if (is_instructor or has_course_moderator_role()) and not lesson.edit_mode %}
|
||||
<button class="btn btn-secondary btn-sm ml-2 btn-edit"> {{ _("Edit") }} </button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Instructors -->
|
||||
@@ -107,21 +121,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Lesson Content -->
|
||||
<div class="markdown-source lesson-content-card {% if lesson.edit_mode %} mb-0 mt-2 {% endif %} ">
|
||||
<div class="markdown-source lesson-content-card">
|
||||
{% if show_lesson %}
|
||||
|
||||
{% if is_instructor and not lesson.include_in_preview and not lesson.edit_mode %}
|
||||
{% if is_instructor and not lesson.include_in_preview %}
|
||||
<div class="medium alert alert-info alert-dismissible mb-4">
|
||||
{{ _("This lesson is not available for preview. As you are the Instructor of the course only you can see it.") }}
|
||||
<a href="#" class="close" data-dismiss="alert" aria-label="close">×</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if lesson.edit_mode %}
|
||||
{{ EditLesson(lesson) }}
|
||||
{% else %}
|
||||
{{ render_html(lesson) }}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{% set course_link = "<a class='join-batch' data-course=" + course.name | urlencode + " href=''>" + _('here') + "</a>" %}
|
||||
@@ -133,9 +143,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not lesson.edit_mode %}
|
||||
{{ pagination(prev_url, next_url) }}
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -164,56 +173,6 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Edit Lesson -->
|
||||
{% macro EditLesson(lesson) %}
|
||||
|
||||
<div class="d-flex mt-2 medium">
|
||||
<div class="flex-grow-1" contenteditable="true" data-placeholder="{{ _('YouTube Video ID') }}"
|
||||
id="youtube">{% if lesson.youtube %}{{ lesson.youtube }}{% endif %}</div>
|
||||
|
||||
<div class="flex-grow-1 ml-2" contenteditable="true" data-placeholder="{{ _('Quiz ID') }}"
|
||||
id="quiz-id">{% if lesson.quiz_id %}{{ lesson.quiz_id }}{% endif %}</div>
|
||||
</div>
|
||||
|
||||
{% if lesson.body %}
|
||||
<div class="body-data hide"> {{ lesson.body }} </div>
|
||||
{% endif %}
|
||||
<div id="body"></div>
|
||||
|
||||
<div class="d-flex medium mx-0 mb-4">
|
||||
<div class="flex-grow-1" contenteditable="true" data-placeholder="{{ _('Assignment Question') }}"
|
||||
id="assignment-question">{% if lesson.question %}{{ lesson.question }}{% endif %}</div>
|
||||
|
||||
<select class="btn btn-default ml-2" id="file-type" data-type="{{ lesson.file_type }}">
|
||||
<option selected> {{ _("File Type") }} </option>
|
||||
<option value="Image"> {{ _("Image") }} </option>
|
||||
<option value="Document"> {{ _("Document") }} </option>
|
||||
<option value="PDF"> {{ _("PDF") }} </option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<label class="preview" for="preview">
|
||||
<input {% if lesson.include_in_preview %} checked {% endif %} type="checkbox" id="preview">
|
||||
{{ _("Show preview of this lesson to Guest users.") }}
|
||||
</label>
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-primary btn-sm btn-lesson pull-right ml-2"> {{ _("Save") }} </button>
|
||||
{% if lesson.name %}
|
||||
<button class="btn btn-secondary btn-sm pull-right btn-back ml-2"> {{ _("Back to Lesson") }} </button>
|
||||
<a class="btn btn-secondary btn-sm pull-right" href="/quizzes"> {{ _("Create Quiz") }} </a>
|
||||
{% endif %}
|
||||
|
||||
{{ UploadAttachments() }}
|
||||
|
||||
</div>
|
||||
|
||||
{{ HelpArticle() }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro UploadAttachments() %}
|
||||
<div class="attachments-parent">
|
||||
<div class="attachment-controls">
|
||||
|
||||
@@ -27,6 +27,7 @@ def get_context(context):
|
||||
|
||||
context.lesson = get_current_lesson_details(lesson_number, context)
|
||||
instructor = is_instructor(context.course.name)
|
||||
|
||||
context.show_lesson = (
|
||||
context.membership
|
||||
or (context.lesson and context.lesson.include_in_preview)
|
||||
|
||||
@@ -12,11 +12,9 @@
|
||||
<div class="container">
|
||||
<div class="course-body-container">
|
||||
{{ CourseHeaderOverlay(course) }}
|
||||
{{ CourseSettings(course) }}
|
||||
{{ Description(course) }}
|
||||
{{ Save(course) }}
|
||||
{{ widgets.CourseOutline(course=course, membership=membership, is_user_interested=is_user_interested) }}
|
||||
{% if not course.edit_mode and course.status == "Approved" and not frappe.utils.cint(course.upcoming) %}
|
||||
{% if course.status == "Approved" and not frappe.utils.cint(course.upcoming) %}
|
||||
{% include "lms/templates/reviews.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -69,6 +67,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not course.upcoming %}
|
||||
<div class="avg-rating-stars">
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
@@ -78,10 +77,9 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="mt-2">
|
||||
<div class="bold-heading">{{ _("Instructors") }}:</div>
|
||||
{% for instructor in get_instructors(course.name) %}
|
||||
<div class="mt-1">
|
||||
@@ -149,8 +147,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{ SlotModal(course) }}
|
||||
@@ -160,54 +156,12 @@
|
||||
|
||||
<!-- Description -->
|
||||
{% macro Description(course) %}
|
||||
{% if course.edit_mode %}
|
||||
{% if course.description %}
|
||||
<div class="description-data hide">{{ course.description }}</div>
|
||||
{% endif %}
|
||||
<div id="description"></div>
|
||||
{% else %}
|
||||
<div class="course-description-section">
|
||||
{{ frappe.utils.md_to_html(course.description) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="course-description-section">
|
||||
{{ course.description }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Course Settings -->
|
||||
{% macro CourseSettings(course) %}
|
||||
|
||||
{% if course.edit_mode and has_course_moderator_role() %}
|
||||
<div class="mb-4">
|
||||
<label for="published" class="mb-0">
|
||||
<input type="checkbox" id="published" {% if course.published %} checked {% endif %}>
|
||||
{{ _("Published") }}
|
||||
</label>
|
||||
<label for="upcoming" class="mb-0 ml-20">
|
||||
<input type="checkbox" id="upcoming" {% if course.upcoming %} checked {% endif %}>
|
||||
{{ _("Upcoming") }}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Save -->
|
||||
{% macro Save(course) %}
|
||||
{% if course.edit_mode %}
|
||||
<div class="mb-16">
|
||||
<button class="btn btn-primary btn-sm btn-save-course">
|
||||
{{ _("Save Course Details") }}
|
||||
</button>
|
||||
{% if course.name %}
|
||||
<a class="btn btn-secondary btn-sm btn-exit-edit ml-2" href="/courses/{{ course.name }}">
|
||||
{{ _("Back to Course") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
<!-- Related Courses Section -->
|
||||
{% macro RelatedCourses(course) %}
|
||||
@@ -251,7 +205,6 @@
|
||||
{% set lesson_index = get_lesson_index(membership.current_lesson) if membership and
|
||||
membership.current_lesson else "1.1" if first_lesson_exists(course.name) else None %}
|
||||
|
||||
|
||||
<div class="all-cta">
|
||||
{% if is_instructor(course.name) and not course.published and course.status != "Under Review" %}
|
||||
<div class="btn btn-primary wide-button" id="submit-for-review" data-course="{{ course.name | urlencode }}">
|
||||
@@ -310,7 +263,7 @@
|
||||
|
||||
<div>
|
||||
{% if is_instructor(course.name) or has_course_moderator_role() %}
|
||||
<a class="btn btn-default btn-sm pull-right ml-2" title="Edit Course" href="/courses/{{ course.name }}?edit=1">
|
||||
<a class="btn btn-default btn-sm pull-right ml-2" title="Edit Course" href="/courses/{{ course.name }}/edit">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-edit"></use>
|
||||
</svg>
|
||||
|
||||
@@ -5,10 +5,21 @@
|
||||
|
||||
{% block content %}
|
||||
<main class="common-page-style">
|
||||
<div class="container">
|
||||
<div class="page-title"> {{ _("Course Details") }} </div>
|
||||
<div class="mt-10">
|
||||
<div>
|
||||
|
||||
<header class="sticky">
|
||||
<div class="container w-75">
|
||||
<button class="btn btn-primary btn-sm btn-save-course pull-right mt-1">
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
|
||||
<div class="page-title"> {{ _("Course Details") }} </div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container w-75">
|
||||
|
||||
<div class="field-parent">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Title") }}
|
||||
@@ -18,11 +29,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input type="text" class="field-input" value="{{ course.title }}">
|
||||
<input id="title" type="text" class="field-input" data-course="{{ course.name }}" {% if course.title %} value="{{ course.title }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Preview Video") }}
|
||||
@@ -32,11 +43,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input type="text" class="field-input" value="{{ course.video_link }}">
|
||||
<input id="video-link" type="text" class="field-input" {% if course.video_link %} value="{{ course.video_link }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Short Introduction") }}
|
||||
@@ -46,11 +57,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input type="text" class="field-input" value="{{ course.short_introduction }}">
|
||||
<input id="intro" type="text" class="field-input" {% if course.short_introduction %} value="{{ course.short_introduction }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Tags") }}
|
||||
@@ -74,34 +85,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Course Image") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Add an appropriate image") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input type="file" class="field-input" id="image">
|
||||
</div>
|
||||
<img {% if course.image %} class="image-preview" src="{{ course.image }}" {% endif %}>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Course Description") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Add a detailed description") }}
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="course-description" class="field-input">{{ course.description }}</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="field-group">
|
||||
<label for="published" class="mb-0">
|
||||
<input type="checkbox" id="published" {% if course.published %} checked {% endif %}>
|
||||
{{ _("Published") }}
|
||||
@@ -112,20 +96,58 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Course Image") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Add an appropriate image") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<button class="btn btn-secondary btn-sm btn-upload mt-2">
|
||||
{{ _("Upload Image") }}
|
||||
</button>
|
||||
</div>
|
||||
<img {% if course.image %} class="image-preview" src="{{ course.image }}" {% endif %}>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Course Description") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Add a detailed description") }}
|
||||
</div>
|
||||
</div>
|
||||
<div id="description" class=""></div>
|
||||
{% if course.description %}
|
||||
<div id="description-data" class="hide">
|
||||
{{ course.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div class="field-label">
|
||||
{{ _("Instructor") }}
|
||||
</div>
|
||||
<div>
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-medium") }} {{ member.full_name }}
|
||||
<div class="mt-2">
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-medium") }}
|
||||
<span class="ml-2">
|
||||
{{ member.full_name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-sm btn-save-course">
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{%- block script %}
|
||||
{{ super() }}
|
||||
{{ include_script('controls.bundle.js') }}
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,6 @@
|
||||
frappe.ready(() => {
|
||||
pin_header();
|
||||
|
||||
$(".tags").click((e) => {
|
||||
e.preventDefault();
|
||||
$("#tags-input").focus();
|
||||
@@ -12,17 +14,25 @@ frappe.ready(() => {
|
||||
$(e.target).parent().parent().remove();
|
||||
});
|
||||
|
||||
$("#image").change((e) => {
|
||||
$(e.target)
|
||||
.parent()
|
||||
.siblings("img")
|
||||
.addClass("image-preview")
|
||||
.attr("src", URL.createObjectURL(e.target.files[0]));
|
||||
});
|
||||
|
||||
$(".btn-save-course").click((e) => {
|
||||
save_course(e);
|
||||
});
|
||||
|
||||
if ($("#description").length) {
|
||||
make_editor();
|
||||
}
|
||||
|
||||
$("#tags-input").focus((e) => {
|
||||
$(e.target).keypress((e) => {
|
||||
if (e.which == 13) {
|
||||
create_tag(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(".btn-upload").click((e) => {
|
||||
upload_file(e);
|
||||
});
|
||||
});
|
||||
|
||||
const create_tag = (e) => {
|
||||
@@ -50,11 +60,11 @@ const save_course = (e) => {
|
||||
method: "lms.lms.doctype.lms_course.lms_course.save_course",
|
||||
args: {
|
||||
tags: tags.join(", "),
|
||||
title: $("#title").text(),
|
||||
short_introduction: $("#intro").text(),
|
||||
video_link: $("#video-link").text(),
|
||||
image: $("#image").attr("href"),
|
||||
description: this.code_field_group.fields_dict["code_md"].value,
|
||||
title: $("#title").val(),
|
||||
short_introduction: $("#intro").val(),
|
||||
video_link: $("#video-link").val(),
|
||||
image: $(".image-preview").attr("src"),
|
||||
description: this.description.fields_dict["description"].value,
|
||||
course: $("#title").data("course")
|
||||
? $("#title").data("course")
|
||||
: "",
|
||||
@@ -67,8 +77,54 @@ const save_course = (e) => {
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.href = `/courses/${data.message}?edit=1`;
|
||||
window.location.href = `/courses/${data.message}`;
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const make_editor = () => {
|
||||
this.description = new frappe.ui.FieldGroup({
|
||||
fields: [
|
||||
{
|
||||
fieldname: "description",
|
||||
fieldtype: "Text Editor",
|
||||
default: $("#description-data").html(),
|
||||
},
|
||||
],
|
||||
body: $("#description").get(0),
|
||||
});
|
||||
this.description.make();
|
||||
$("#description .form-section:last").removeClass("empty-section");
|
||||
$("#description .frappe-control").removeClass("hide-control");
|
||||
$("#description .form-column").addClass("p-0");
|
||||
};
|
||||
|
||||
const pin_header = () => {
|
||||
const el = document.querySelector(".sticky");
|
||||
const observer = new IntersectionObserver(
|
||||
([e]) =>
|
||||
e.target.classList.toggle("is-pinned", e.intersectionRatio < 1),
|
||||
{ threshold: [1] }
|
||||
);
|
||||
|
||||
observer.observe(el);
|
||||
};
|
||||
|
||||
const upload_file = (e) => {
|
||||
new frappe.ui.FileUploader({
|
||||
disable_file_browser: true,
|
||||
folder: "Home/Attachments",
|
||||
make_attachments_public: true,
|
||||
restrictions: {
|
||||
allowed_file_types: ["image/*"],
|
||||
},
|
||||
on_success: (file_doc) => {
|
||||
$(e.target)
|
||||
.parent()
|
||||
.siblings("img")
|
||||
.addClass("image-preview")
|
||||
.attr("src", file_doc.file_url);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,24 +5,26 @@ from frappe import _
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
|
||||
try:
|
||||
course_name = frappe.form_dict["course"]
|
||||
except KeyError:
|
||||
redirect_to_courses_list()
|
||||
|
||||
if not can_create_courses():
|
||||
message = "You do not have permission to access this page."
|
||||
if frappe.session.user == "Guest":
|
||||
message = "Please login to access this page."
|
||||
|
||||
raise frappe.PermissionError(_(message))
|
||||
|
||||
if course_name == "new-course":
|
||||
if not can_create_courses():
|
||||
message = "You do not have permission to access this page."
|
||||
if frappe.session.user == "Guest":
|
||||
message = "Please login to access this page."
|
||||
|
||||
raise frappe.PermissionError(_(message))
|
||||
|
||||
context.course = frappe._dict()
|
||||
context.course.edit_mode = True
|
||||
context.membership = None
|
||||
else:
|
||||
set_course_context(context, course_name)
|
||||
|
||||
context.member = frappe.db.get_value(
|
||||
"User", frappe.session.user, ["full_name", "username"], as_dict=True
|
||||
)
|
||||
|
||||
37
lms/www/courses/outline.html
Normal file
37
lms/www/courses/outline.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "lms/templates/lms_base.html" %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
{{ _("Outline") }} - {{ course.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<main class="common-page-style">
|
||||
<div class="container">
|
||||
{{ EmptyState() }}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% macro EmptyState() %}
|
||||
<article class="empty-state">
|
||||
<div class="text-center">
|
||||
<div class="bold-heading">
|
||||
{{ _("You have not added any chapter yet") }}
|
||||
</div>
|
||||
<div>
|
||||
{{ _("Create and manage your chapters from here.") }}
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-default btn-sm">
|
||||
<svg class="icon icon-xs">
|
||||
<use class="" href="#icon-add"></use>
|
||||
</svg>
|
||||
<span>
|
||||
{{ _("Add Chapter") }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endmacro %}
|
||||
8
lms/www/courses/outline.py
Normal file
8
lms/www/courses/outline.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
context.course = frappe.db.get_value(
|
||||
"LMS Course", frappe.form_dict["course"], ["name", "title"], as_dict=True
|
||||
)
|
||||
@@ -25,6 +25,7 @@ def get_common_context(context):
|
||||
context.lessons = get_lessons(course.name)
|
||||
membership = get_membership(course.name, frappe.session.user, batch_name)
|
||||
context.membership = membership
|
||||
context.progress = frappe.utils.cint(membership.progress) if membership else 0
|
||||
context.batch = membership.batch if membership and membership.batch else None
|
||||
context.course.query_parameter = (
|
||||
"?batch=" + membership.batch if membership and membership.batch else ""
|
||||
|
||||
Reference in New Issue
Block a user