feat: lesson edit page
This commit is contained in:
@@ -5,148 +5,156 @@
|
||||
|
||||
{% block content %}
|
||||
<main class="common-page-style">
|
||||
|
||||
<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>
|
||||
|
||||
{{ Header() }}
|
||||
<div class="container w-75">
|
||||
|
||||
<div class="field-parent">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Title") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Something Short and Concise") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="title" type="text" class="field-input" data-course="{{ course.name }}" {% if course.title %} value="{{ course.title }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Preview Video") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("A feature video that provides a preview of the course") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="video-link" type="text" class="field-input" {% if course.video_link %} value="{{ course.video_link }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Short Introduction") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("A one line breif description") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="intro" type="text" class="field-input" {% if course.short_introduction %} value="{{ course.short_introduction }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Tags") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Add suitable tags") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tags field-input">
|
||||
{% for tag in get_tags(course.name) %}
|
||||
<button class="btn btn-secondary btn-sm mr-2 text-uppercase">
|
||||
{{ tag }}
|
||||
<span class="btn-remove">
|
||||
<svg class="icon icon-sm">
|
||||
<use class="" href="#icon-close"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
{% endfor %}
|
||||
<input type="text" class="invisible-input" id="tags-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<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>
|
||||
|
||||
<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 class="mt-2">
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-medium") }}
|
||||
<span class="ml-2">
|
||||
{{ member.full_name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{ CreateCourse() }}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% macro Header() %}
|
||||
<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>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro CreateCourse() %}
|
||||
<div class="field-parent">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Title") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Something Short and Concise") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="title" type="text" class="field-input" {% if course.title %} data-course="{{ course.name }}" value="{{ course.title }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Preview Video") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("A feature video that provides a preview of the course") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="video-link" type="text" class="field-input" {% if course.video_link %} value="{{ course.video_link }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Short Introduction") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("A one line breif description") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="intro" type="text" class="field-input" {% if course.short_introduction %} value="{{ course.short_introduction }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Tags") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Add suitable tags") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tags field-input">
|
||||
{% for tag in get_tags(course.name) %}
|
||||
<button class="btn btn-secondary btn-sm mr-2 text-uppercase">
|
||||
{{ tag }}
|
||||
<span class="btn-remove">
|
||||
<svg class="icon icon-sm">
|
||||
<use class="" href="#icon-close"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
{% endfor %}
|
||||
<input type="text" class="invisible-input" id="tags-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<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>
|
||||
|
||||
<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 class="mt-2">
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-medium") }}
|
||||
<span class="ml-2">
|
||||
{{ member.full_name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{%- block script %}
|
||||
{{ super() }}
|
||||
{{ include_script('controls.bundle.js') }}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
frappe.ready(() => {
|
||||
pin_header();
|
||||
|
||||
$(".tags").click((e) => {
|
||||
e.preventDefault();
|
||||
$("#tags-input").focus();
|
||||
@@ -94,23 +92,14 @@ const make_editor = () => {
|
||||
],
|
||||
body: $("#description").get(0),
|
||||
});
|
||||
console.log(this.description);
|
||||
this.description.make();
|
||||
console.log(this.description);
|
||||
$("#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,
|
||||
|
||||
@@ -7,102 +7,150 @@
|
||||
|
||||
{% block page_content %}
|
||||
<main class="common-page-style">
|
||||
|
||||
<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 Outline") }} </div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container w-75">
|
||||
{{ Outline(chapters) }}
|
||||
{{ Header() }}
|
||||
<div class="container w-75" id="course-outline" {% if course.name %} data-course="{{ course.name }}" {% endif %}>
|
||||
{% if chapters | length %}
|
||||
{{ Outline(chapters) }}
|
||||
{% else %}
|
||||
{{ EmptyState() }}
|
||||
{% endif %}
|
||||
{{ CreateChapter() }}
|
||||
{{ EmptyState() }}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% macro Outline(chapters) %}
|
||||
{% if chapters %}
|
||||
{% for chapter in chapters %}
|
||||
<article>
|
||||
<div class="common-card-style column-card p-4 mb-5">
|
||||
<div class="level">
|
||||
<svg class="icon icon-xs level-item mr-2">
|
||||
<use class="" href="#icon-drag"></use>
|
||||
</svg>
|
||||
<div class="bold-heading">
|
||||
{{ chapter.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for lesson in get_lessons(course.name, chapter) %}
|
||||
<div class="outline-lesson level">
|
||||
<svg class="icon icon-xs level-item mr-2">
|
||||
<use class="" href="#icon-drag"></use>
|
||||
</svg>
|
||||
<!-- <div class="icon-bg">
|
||||
<svg class="icon icon-sm level-item">
|
||||
<use class="" href="#{{ lesson.icon }}">
|
||||
</svg>
|
||||
</div> -->
|
||||
<div class="">
|
||||
{{ lesson.title }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<button class="btn btn-default btn-sm align-self-start">
|
||||
<svg class="icon icon-xs">
|
||||
<use class="" href="#icon-add"></use>
|
||||
</svg>
|
||||
{% macro Header() %}
|
||||
<header class="sticky">
|
||||
<div class="container w-75">
|
||||
<button class="btn btn-primary btn-sm pull-right mt-1" id="add-chapter">
|
||||
<span>
|
||||
{{ _("Add Lesson") }}
|
||||
{{ _("Add Chapter") }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="page-title">
|
||||
{{ course.title if course.name else _("Course Outline") }}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</header>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro Outline(chapters) %}
|
||||
{% if chapters %}
|
||||
{% for chapter in chapters %}
|
||||
{% set chapter_index = loop.index %}
|
||||
{% set lessons = get_lessons(course.name, chapter) %}
|
||||
<article>
|
||||
<div class="common-card-style column-card chapter-container p-4 my-5" data-chapter="{{ chapter.name }}" data-idx="{{ loop.index }}">
|
||||
<div class="level">
|
||||
<svg class="icon icon-xs level-item mr-2">
|
||||
<use class="" href="#icon-drag"></use>
|
||||
</svg>
|
||||
<div class="bold-heading chapters-title">
|
||||
{{ chapter.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2 ml-5 chapter-description">
|
||||
{{ chapter.description }}
|
||||
</div>
|
||||
|
||||
{% for lesson in lessons %}
|
||||
<div class="outline-lesson level">
|
||||
<svg class="icon icon-xs level-item mr-2">
|
||||
<use class="" href="#icon-drag"></use>
|
||||
</svg>
|
||||
<!-- <div class="icon-bg">
|
||||
<svg class="icon icon-sm level-item">
|
||||
<use class="" href="#{{ lesson.icon }}">
|
||||
</svg>
|
||||
</div> -->
|
||||
<a class="button-links" href="/courses/{{ course.name }}/learn/{{ chapter_index }}.{{ loop.index }}/edit">
|
||||
{{ lesson.title }}
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="align-self-start mt-4">
|
||||
<a class="btn btn-secondary btn-sm" href="/courses/{{ course.name }}/learn/{{ loop.index }}.{{ lessons | length + 1 }}/edit">
|
||||
<span>
|
||||
{{ _("Add Lesson") }}
|
||||
</span>
|
||||
</a>
|
||||
<button class="btn btn-secondary btn-sm ml-2 edit-chapter">
|
||||
<span>
|
||||
{{ _("Edit") }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro CreateChapter() %}
|
||||
<article>
|
||||
<div class="common-card-style column-card p-4">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Chapter Title") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Something Short and Concise") }}
|
||||
</div>
|
||||
<div class="modal fade chapter-modal" id="chapter-modal" tabindex="-1" role="dialog"
|
||||
aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">{{ _("New Chapter") }}</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="chapter-title" type="text" class="field-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Short Description") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("A breif description about this chapter.") }}
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<article id="create-chapter">
|
||||
<div class="chapter-container">
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Chapter Title") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Something Short and Concise") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="chapter-title" type="text" class="field-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Short Description") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("A breif description about this chapter.") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="chapter-description" type="text" class="field-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="">
|
||||
<input id="chapter-description" type="text" class="field-input">
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary btn-sm mr-2" data-dismiss="modal" aria-label="Close">
|
||||
{{ _("Discard") }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-primary btn-sm align-self-start" id="save-chapter">
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,51 @@
|
||||
frappe.ready(() => {
|
||||
pin_header();
|
||||
$("#add-chapter").click((e) => {
|
||||
show_chapter_modal(e);
|
||||
});
|
||||
|
||||
$(".edit-chapter").click((e) => {
|
||||
show_chapter_modal(e);
|
||||
});
|
||||
|
||||
$("#save-chapter").click((e) => {
|
||||
save_chapter(e);
|
||||
});
|
||||
});
|
||||
|
||||
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 show_chapter_modal = (e) => {
|
||||
e.preventDefault();
|
||||
$("#chapter-modal").modal("show");
|
||||
let parent = $(e.currentTarget).closest(".chapter-container");
|
||||
if (parent) {
|
||||
$("#chapter-title").val($.trim(parent.find(".chapters-title").text()));
|
||||
$("#chapter-description").val(
|
||||
$.trim(parent.find(".chapter-description").text())
|
||||
);
|
||||
$("#chapter-modal").data("chapter", parent.data("chapter"));
|
||||
$("#chapter-modal").data("idx", parent.data("idx"));
|
||||
}
|
||||
};
|
||||
|
||||
const save_chapter = (e) => {
|
||||
let parent = $("#chapter-modal");
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_course.lms_course.save_chapter",
|
||||
args: {
|
||||
course: $("#course-outline").data("course"),
|
||||
title: $("#chapter-title").val(),
|
||||
chapter_description: $("#chapter-description").val(),
|
||||
idx: parent.data("idx") || $(".chapter-container").length + 1,
|
||||
chapter: parent.data("chapter") || null,
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert({
|
||||
message: __("Saved"),
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user