feat: lesson edit page
This commit is contained in:
@@ -69,7 +69,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-15 15:36:39.585488",
|
"modified": "2023-04-26 17:48:29.664013",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Quiz",
|
"name": "LMS Quiz",
|
||||||
@@ -86,6 +86,21 @@
|
|||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"if_owner": 1,
|
||||||
|
"permlevel": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "All",
|
||||||
|
"select": 1,
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"show_title_field_in_link": 1,
|
"show_title_field_in_link": 1,
|
||||||
|
|||||||
@@ -192,3 +192,10 @@ def check_input_answers(question, answer):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_user_quizzes():
|
||||||
|
return frappe.get_all(
|
||||||
|
"LMS Quiz", filters={"owner": frappe.session.user}, fields=["name", "title"]
|
||||||
|
)
|
||||||
|
|||||||
@@ -275,7 +275,6 @@ def get_progress(course, lesson):
|
|||||||
|
|
||||||
|
|
||||||
def render_html(lesson):
|
def render_html(lesson):
|
||||||
print(lesson)
|
|
||||||
youtube = lesson.youtube
|
youtube = lesson.youtube
|
||||||
quiz_id = lesson.quiz_id
|
quiz_id = lesson.quiz_id
|
||||||
body = lesson.body
|
body = lesson.body
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
font-size: 1.5rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--gray-900);
|
color: var(--gray-900);
|
||||||
line-height: 160%;
|
line-height: 160%;
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.field-group {
|
.field-group {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-description {
|
.field-description {
|
||||||
@@ -125,9 +125,8 @@ textarea.field-input {
|
|||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.common-card-style .outline-lesson:last-child {
|
.common-card-style .outline-lesson:last-of-type {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.level {
|
.level {
|
||||||
@@ -364,12 +363,12 @@ input[type=checkbox] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button-links {
|
.button-links {
|
||||||
color: var(--gray-900);
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-links:hover {
|
.button-links:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--gray-900);
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-background {
|
.icon-background {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
frappe.ready(() => {
|
frappe.ready(() => {
|
||||||
setup_file_size();
|
setup_file_size();
|
||||||
|
pin_header();
|
||||||
|
|
||||||
$(".join-batch").click((e) => {
|
$(".join-batch").click((e) => {
|
||||||
join_course(e);
|
join_course(e);
|
||||||
@@ -57,6 +58,18 @@ frappe.ready(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pin_header = () => {
|
||||||
|
const el = document.querySelector(".sticky");
|
||||||
|
if (el) {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([e]) =>
|
||||||
|
e.target.classList.toggle("is-pinned", e.intersectionRatio < 1),
|
||||||
|
{ threshold: [1] }
|
||||||
|
);
|
||||||
|
observer.observe(el);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const setSortable = (el) => {
|
const setSortable = (el) => {
|
||||||
new Sortable(el, {
|
new Sortable(el, {
|
||||||
group: {
|
group: {
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import EditorJS from "@editorjs/editorjs";
|
|||||||
import Header from "@editorjs/header";
|
import Header from "@editorjs/header";
|
||||||
import List from "@editorjs/list";
|
import List from "@editorjs/list";
|
||||||
|
|
||||||
const create_editor_for_short_description = () => {
|
let self = this;
|
||||||
let editor = new EditorJS({
|
const create_editor_for_lesson_content = () => {
|
||||||
holder: "course-description",
|
self.editor = new EditorJS({
|
||||||
|
holder: "lesson-content",
|
||||||
tools: {
|
tools: {
|
||||||
header: {
|
header: {
|
||||||
class: Header,
|
class: Header,
|
||||||
@@ -13,3 +14,6 @@ const create_editor_for_short_description = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
create_editor_for_lesson_content();
|
||||||
|
console.log(self.editor);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import "./profile.js";
|
import "./profile.js";
|
||||||
import "./common_functions.js";
|
import "./common_functions.js";
|
||||||
import "./editor.js";
|
|
||||||
import "../../../../frappe/frappe/public/js/frappe/ui/chart.js";
|
import "../../../../frappe/frappe/public/js/frappe/ui/chart.js";
|
||||||
|
|||||||
@@ -9,17 +9,56 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<main class="common-page-style">
|
||||||
<div>
|
{{ Header() }}
|
||||||
<div class="">
|
<div class="container w-75" id="course-outline" {% if course.name %} data-course="{{ course.name }}" {% endif %}>
|
||||||
<div>
|
{{ CreateLesson() }}
|
||||||
{{ _("Title") }}
|
</div>
|
||||||
</div>
|
</main>
|
||||||
<div>
|
{% endblock %}
|
||||||
{{ _("Something short and concise.") }}
|
|
||||||
</div>
|
|
||||||
<input class="form-control">
|
{% macro Header() %}
|
||||||
|
<header class="sticky">
|
||||||
|
<div class="container w-75">
|
||||||
|
<button class="btn btn-primary btn-sm pull-right mt-1" id="save-lesson">
|
||||||
|
<span>
|
||||||
|
{{ _("Save") }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="page-title">
|
||||||
|
{{ course.title if course.name else _("Course Outline") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro CreateLesson() %}
|
||||||
|
<article 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="lesson-title" type="text" class="field-input" {% if lesson.name %} data-course="{{ lesson.name }}" value="{{ lesson.title }}" {% endif %}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="lesson-content"></div>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{%- block script %}
|
||||||
|
{{ super() }}
|
||||||
|
{{ include_script('controls.bundle.js') }}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
84
lms/www/batch/edit.js
Normal file
84
lms/www/batch/edit.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
frappe.ready(() => {
|
||||||
|
let self = this;
|
||||||
|
setup_editor();
|
||||||
|
fetch_quiz_list();
|
||||||
|
|
||||||
|
$("#save-lesson").click((e) => {
|
||||||
|
save_lesson(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const setup_editor = () => {
|
||||||
|
self.editor = new EditorJS({
|
||||||
|
holder: "lesson-content",
|
||||||
|
tools: {
|
||||||
|
youtube: YouTubeVideo,
|
||||||
|
quiz: Quiz,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const save_lesson = (e) => {
|
||||||
|
self.editor.save().then((outputData) => {
|
||||||
|
console.log(outputData);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
class YouTubeVideo {
|
||||||
|
static get toolbox() {
|
||||||
|
return {
|
||||||
|
title: "YouTube Video",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.wrapper = document.createElement("div");
|
||||||
|
$(this.wrapper).html(`<div class="field-group">
|
||||||
|
<div class="">
|
||||||
|
<input id="youtube" type="text" class="field-input">
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
return this.wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
save(block_content) {
|
||||||
|
return {
|
||||||
|
youtube: $("#youtube").val(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Quiz {
|
||||||
|
static get toolbox() {
|
||||||
|
return {
|
||||||
|
title: "Quiz",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.wrapper = document.createElement("div");
|
||||||
|
$(this.wrapper).html(
|
||||||
|
`<div>
|
||||||
|
<select id="quiz" class="field-input">
|
||||||
|
<option value="">Select Quiz</option>
|
||||||
|
</select>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
self.quiz_list.forEach((quiz) => {
|
||||||
|
$(this.wrapper)
|
||||||
|
.find("#quiz")
|
||||||
|
.append(`<option value="${quiz.name}">${quiz.title}</option>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.wrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetch_quiz_list = () => {
|
||||||
|
frappe.call({
|
||||||
|
method: "lms.lms.doctype.lms_quiz.lms_quiz.get_user_quizzes",
|
||||||
|
callback: (r) => {
|
||||||
|
self.quiz_list = r.message;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -7,4 +7,4 @@ def get_context(context):
|
|||||||
chapter_index = frappe.form_dict.get("chapter")
|
chapter_index = frappe.form_dict.get("chapter")
|
||||||
lesson_index = frappe.form_dict.get("lesson")
|
lesson_index = frappe.form_dict.get("lesson")
|
||||||
lesson_number = f"{chapter_index}.{lesson_index}"
|
lesson_number = f"{chapter_index}.{lesson_index}"
|
||||||
context.lesson = get_current_lesson_details(lesson_number, context)
|
context.lesson = get_current_lesson_details(lesson_number, context, True)
|
||||||
|
|||||||
@@ -5,148 +5,156 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="common-page-style">
|
<main class="common-page-style">
|
||||||
|
{{ 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>
|
|
||||||
|
|
||||||
<div class="container w-75">
|
<div class="container w-75">
|
||||||
|
{{ 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" 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>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% 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 %}
|
{%- block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{{ include_script('controls.bundle.js') }}
|
{{ include_script('controls.bundle.js') }}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
frappe.ready(() => {
|
frappe.ready(() => {
|
||||||
pin_header();
|
|
||||||
|
|
||||||
$(".tags").click((e) => {
|
$(".tags").click((e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$("#tags-input").focus();
|
$("#tags-input").focus();
|
||||||
@@ -94,23 +92,14 @@ const make_editor = () => {
|
|||||||
],
|
],
|
||||||
body: $("#description").get(0),
|
body: $("#description").get(0),
|
||||||
});
|
});
|
||||||
|
console.log(this.description);
|
||||||
this.description.make();
|
this.description.make();
|
||||||
|
console.log(this.description);
|
||||||
$("#description .form-section:last").removeClass("empty-section");
|
$("#description .form-section:last").removeClass("empty-section");
|
||||||
$("#description .frappe-control").removeClass("hide-control");
|
$("#description .frappe-control").removeClass("hide-control");
|
||||||
$("#description .form-column").addClass("p-0");
|
$("#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) => {
|
const upload_file = (e) => {
|
||||||
new frappe.ui.FileUploader({
|
new frappe.ui.FileUploader({
|
||||||
disable_file_browser: true,
|
disable_file_browser: true,
|
||||||
|
|||||||
@@ -7,102 +7,150 @@
|
|||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<main class="common-page-style">
|
<main class="common-page-style">
|
||||||
|
{{ Header() }}
|
||||||
<header class="sticky">
|
<div class="container w-75" id="course-outline" {% if course.name %} data-course="{{ course.name }}" {% endif %}>
|
||||||
<div class="container w-75">
|
{% if chapters | length %}
|
||||||
<button class="btn btn-primary btn-sm btn-save-course pull-right mt-1">
|
{{ Outline(chapters) }}
|
||||||
{{ _("Save") }}
|
{% else %}
|
||||||
</button>
|
{{ EmptyState() }}
|
||||||
|
{% endif %}
|
||||||
<div class="page-title"> {{ _("Course Outline") }} </div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="container w-75">
|
|
||||||
{{ Outline(chapters) }}
|
|
||||||
{{ CreateChapter() }}
|
{{ CreateChapter() }}
|
||||||
{{ EmptyState() }}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% 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) %}
|
{% macro Header() %}
|
||||||
<div class="outline-lesson level">
|
<header class="sticky">
|
||||||
<svg class="icon icon-xs level-item mr-2">
|
<div class="container w-75">
|
||||||
<use class="" href="#icon-drag"></use>
|
<button class="btn btn-primary btn-sm pull-right mt-1" id="add-chapter">
|
||||||
</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>
|
|
||||||
<span>
|
<span>
|
||||||
{{ _("Add Lesson") }}
|
{{ _("Add Chapter") }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="page-title">
|
||||||
|
{{ course.title if course.name else _("Course Outline") }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</header>
|
||||||
{% endfor %}
|
{% endmacro %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
{% 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 %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
{% macro CreateChapter() %}
|
{% macro CreateChapter() %}
|
||||||
<article>
|
<div class="modal fade chapter-modal" id="chapter-modal" tabindex="-1" role="dialog"
|
||||||
<div class="common-card-style column-card p-4">
|
aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
<div class="field-group">
|
<div class="modal-dialog" role="document">
|
||||||
<div>
|
<div class="modal-content">
|
||||||
<div class="field-label">
|
<div class="modal-header">
|
||||||
{{ _("Chapter Title") }}
|
<div class="modal-title">{{ _("New Chapter") }}</div>
|
||||||
</div>
|
|
||||||
<div class="field-description">
|
|
||||||
{{ _("Something Short and Concise") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="">
|
|
||||||
<input id="chapter-title" type="text" class="field-input">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group">
|
|
||||||
<div>
|
<div class="modal-body">
|
||||||
<div class="field-label">
|
<article id="create-chapter">
|
||||||
{{ _("Short Description") }}
|
<div class="chapter-container">
|
||||||
</div>
|
|
||||||
<div class="field-description">
|
<div class="field-group">
|
||||||
{{ _("A breif description about this chapter.") }}
|
<div>
|
||||||
</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>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,51 @@
|
|||||||
frappe.ready(() => {
|
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 show_chapter_modal = (e) => {
|
||||||
const el = document.querySelector(".sticky");
|
e.preventDefault();
|
||||||
const observer = new IntersectionObserver(
|
$("#chapter-modal").modal("show");
|
||||||
([e]) =>
|
let parent = $(e.currentTarget).closest(".chapter-container");
|
||||||
e.target.classList.toggle("is-pinned", e.intersectionRatio < 1),
|
if (parent) {
|
||||||
{ threshold: [1] }
|
$("#chapter-title").val($.trim(parent.find(".chapters-title").text()));
|
||||||
);
|
$("#chapter-description").val(
|
||||||
observer.observe(el);
|
$.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);
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ def redirect_to_lesson(course, index_="1.1"):
|
|||||||
raise frappe.Redirect
|
raise frappe.Redirect
|
||||||
|
|
||||||
|
|
||||||
def get_current_lesson_details(lesson_number, context):
|
def get_current_lesson_details(lesson_number, context, is_edit=False):
|
||||||
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
|
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
|
||||||
|
|
||||||
if not len(details_list):
|
if not len(details_list):
|
||||||
if frappe.form_dict.get("edit"):
|
if is_edit:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
redirect_to_lesson(context.course)
|
redirect_to_lesson(context.course)
|
||||||
|
|||||||
Reference in New Issue
Block a user