feat: redesign lesson page
This commit is contained in:
@@ -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
|
||||
)
|
||||
Reference in New Issue
Block a user