feat: edit existing lesson

This commit is contained in:
Jannat Patel
2023-05-03 17:41:18 +05:30
parent bbdfaa32e9
commit 4336839932
7 changed files with 168 additions and 44 deletions

View File

@@ -121,8 +121,7 @@ textarea.field-input {
} }
.outline-lesson { .outline-lesson {
border-bottom: 1px solid var(--gray-300); padding: 0.5rem 0;
padding: 1rem 0;
} }
.common-card-style .outline-lesson:last-of-type { .common-card-style .outline-lesson:last-of-type {
@@ -160,6 +159,12 @@ textarea.field-input {
padding-top: 0.5rem; padding-top: 0.5rem;
} }
.lesson-parent .breadcrumb {
border-bottom: 1px solid var(--gray-300);
margin-bottom: 2rem;
padding-bottom: 1rem;
}
body { body {
background-color: #FFFFFF; background-color: #FFFFFF;
} }
@@ -764,15 +769,12 @@ input[type=checkbox] {
} }
.course-content-parent .course-home-headings { .course-content-parent .course-home-headings {
margin: 0 0 1rem; margin: 0 0 0.5rem;
width: 100%; width: 100%;
} }
.lesson-pagination { .lesson-pagination {
display: flex; margin: 2rem 0;
justify-content: space-between;
align-items: center;
margin-top: 2rem
} }
.lesson-video { .lesson-video {
@@ -937,7 +939,7 @@ input[type=checkbox] {
} }
.progress-bar { .progress-bar {
background-color: var(--accent-color); background-color: var(--primary-color);
} }
.progress-percent { .progress-percent {

View File

@@ -46,7 +46,7 @@
</div> </div>
</div> </div>
<div class=""> <div class="">
<input id="lesson-title" type="text" class="field-input" {% if lesson.name %} data-course="{{ lesson.name }}" value="{{ lesson.title }}" {% endif %}> <input id="lesson-title" type="text" class="field-input" data-index="{{ lesson_index }}" data-chapter="{{ chapter }}" data-course="{{ lesson.name }}" {% if lesson.name %} data-lesson="{{ lesson.name }}" value="{{ lesson.title }}" {% endif %}>
</div> </div>
</div> </div>
@@ -60,6 +60,9 @@
</div> </div>
</div> </div>
<div id="lesson-content" class="lesson-editor"></div> <div id="lesson-content" class="lesson-editor"></div>
{% if lesson.body %}
<div id="current-lesson-content" class="hide">{{ lesson.body }}</div>
{% endif %}
</div> </div>
@@ -86,5 +89,7 @@
{% endif %} {% endif %}
{{ include_script('controls.bundle.js') }} {{ include_script('controls.bundle.js') }}
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script> <script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/paragraph@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest"></script>
{% endblock %} {% endblock %}

View File

@@ -1,5 +1,9 @@
frappe.ready(() => { frappe.ready(() => {
let self = this; let self = this;
if ($("#current-lesson-content").length) {
parse_string_to_lesson();
}
setup_editor(); setup_editor();
fetch_quiz_list(); fetch_quiz_list();
@@ -8,19 +12,102 @@ frappe.ready(() => {
}); });
}); });
const parse_string_to_lesson = () => {
let lesson_content = $("#current-lesson-content").html();
let lesson_blocks = [];
lesson_content.split("\n").forEach((block) => {
if (block.includes("{{ YouTubeVideo")) {
let youtube_id = block.match(/'([^']+)'/)[1];
lesson_blocks.push({
type: "youtube",
data: {
youtube: youtube_id,
},
});
} else if (block.includes("{{ Quiz")) {
let quiz = block.match(/'([^']+)'/)[1];
lesson_blocks.push({
type: "quiz",
data: {
quiz: quiz,
},
});
} else {
lesson_blocks.push({
type: "paragraph",
data: {
text: block,
},
});
}
});
this.blocks = lesson_blocks;
};
const setup_editor = () => { const setup_editor = () => {
self.editor = new EditorJS({ self.editor = new EditorJS({
holder: "lesson-content", holder: "lesson-content",
tools: { tools: {
youtube: YouTubeVideo, youtube: YouTubeVideo,
quiz: Quiz, quiz: Quiz,
paragraph: {
class: Paragraph,
inlineToolbar: true,
config: {
preserveBlank: true,
},
},
},
data: {
blocks: self.blocks ? self.blocks : [],
}, },
}); });
}; };
const save_lesson = (e) => { const save_lesson = (e) => {
self.editor.save().then((outputData) => { self.editor.save().then((outputData) => {
parse_lesson(outputData); parse_lesson_to_string(outputData);
});
};
const parse_lesson_to_string = (data) => {
let lesson_content = "";
data.blocks.forEach((block) => {
if (block.type == "youtube") {
lesson_content += `{{ YouTubeVideo("${block.data.youtube}") }}\n`;
} else if (block.type == "quiz") {
lesson_content += `{{ Quiz("${block.data.quiz}") }}\n`;
} else if (block.type == "paragraph") {
lesson_content += `${block.data.text}\n`;
}
});
save(lesson_content);
};
const save = (lesson_content) => {
let lesson = $("#lesson-title").data("lesson");
frappe.call({
method: "lms.lms.doctype.lms_course.lms_course.save_lesson",
args: {
title: $("#lesson-title").val(),
body: lesson_content,
chapter: $("#lesson-title").data("chapter"),
preview: 0,
idx: $("#lesson-title").data("index"),
lesson: lesson ? lesson : "",
},
callback: (data) => {
frappe.show_alert({
message: __("Saved"),
indicator: "green",
});
setTimeout(() => {
window.location.href = window.location.href.split("?")[0];
}, 1000);
},
}); });
}; };
@@ -33,9 +120,11 @@ const fetch_quiz_list = () => {
}); });
}; };
const parse_lesson = (data) => {};
class YouTubeVideo { class YouTubeVideo {
constructor({ data }) {
this.data = data;
}
static get toolbox() { static get toolbox() {
return { return {
title: "YouTube Video", title: "YouTube Video",
@@ -43,8 +132,17 @@ class YouTubeVideo {
} }
render() { render() {
let self = this;
this.wrapper = document.createElement("div"); this.wrapper = document.createElement("div");
if (this.data && this.data.youtube) {
$(this.wrapper).html(this.render_youtube(this.data.youtube));
} else {
this.render_youtube_dialog();
}
return this.wrapper;
}
render_youtube_dialog() {
let self = this;
let youtubedialog = new frappe.ui.Dialog({ let youtubedialog = new frappe.ui.Dialog({
title: __("YouTube Video"), title: __("YouTube Video"),
fields: [ fields: [
@@ -59,23 +157,26 @@ class YouTubeVideo {
primary_action(values) { primary_action(values) {
youtubedialog.hide(); youtubedialog.hide();
self.youtube = values.youtube; self.youtube = values.youtube;
$(self.wrapper).html(` <iframe width="100%" height="400" $(self.wrapper).html(self.render_youtube(values.youtube));
src="https://www.youtube.com/embed/${self.youtube}"
title="YouTube video player"
frameborder="0"
style="border-radius: var(--border-radius-lg); margin: 1rem 0;"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>`);
}, },
}); });
youtubedialog.show(); youtubedialog.show();
return this.wrapper; }
render_youtube(youtube) {
return `<iframe width="100%" height="400"
src="https://www.youtube.com/embed/${youtube}"
title="YouTube video player"
frameborder="0"
style="border-radius: var(--border-radius-lg); margin: 1rem 0;"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>`;
} }
save(block_content) { save(block_content) {
return { return {
youtube: this.youtube, youtube: this.data.youtube || this.youtube,
}; };
} }
} }
@@ -87,8 +188,21 @@ class Quiz {
}; };
} }
constructor({ data }) {
this.data = data;
}
render() { render() {
this.wrapper = document.createElement("div"); this.wrapper = document.createElement("div");
if (this.data && this.data.quiz) {
$(this.wrapper).html(this.render_quiz(this.data.quiz));
} else {
this.render_quiz_dialog();
}
return this.wrapper;
}
render_quiz_dialog() {
let self = this; let self = this;
let quizdialog = new frappe.ui.Dialog({ let quizdialog = new frappe.ui.Dialog({
title: __("Select a Quiz"), title: __("Select a Quiz"),
@@ -105,11 +219,7 @@ class Quiz {
primary_action(values) { primary_action(values) {
self.quiz = values.quiz; self.quiz = values.quiz;
quizdialog.hide(); quizdialog.hide();
$(self.wrapper).html( $(self.wrapper).html(self.render_quiz(self.quiz));
`<div class="common-card-style p-2 my-2 bold-heading">
Quiz: ${self.quiz}
</div>`
);
}, },
}); });
quizdialog.show(); quizdialog.show();
@@ -117,12 +227,17 @@ class Quiz {
$(".modal-body").css("min-height", "300px"); $(".modal-body").css("min-height", "300px");
$(".modal-body input").focus(); $(".modal-body input").focus();
}, 1000); }, 1000);
return this.wrapper; }
render_quiz(quiz) {
return `<div class="common-card-style p-2 my-2 bold-heading">
Quiz: ${quiz}
</div>`;
} }
save(block_content) { save(block_content) {
return { return {
quiz: this.quiz, quiz: this.data.quiz || this.quiz,
}; };
} }
} }
@@ -136,7 +251,6 @@ class Video {
render() { render() {
this.wrapper = document.createElement("div"); this.wrapper = document.createElement("div");
let self = this;
new frappe.ui.FileUploader({ new frappe.ui.FileUploader({
disable_file_browser: true, disable_file_browser: true,
folder: "Home/Attachments", folder: "Home/Attachments",

View File

@@ -9,6 +9,10 @@ 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_index = lesson_index
context.chapter = frappe.db.get_value(
"Chapter Reference", {"idx": chapter_index, "parent": context.course.name}, "chapter"
)
context.lesson = get_current_lesson_details(lesson_number, context, True) context.lesson = get_current_lesson_details(lesson_number, context, True)
context.is_moderator = has_course_moderator_role() context.is_moderator = has_course_moderator_role()
instructor = is_instructor(context.course.name) instructor = is_instructor(context.course.name)

View File

@@ -42,7 +42,7 @@
{{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True) }} {{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True) }}
</div> </div>
</div> </div>
<div class="lesson-pagination-parent"> <div class="lesson-parent">
{{ BreadCrumb(course, lesson) }} {{ BreadCrumb(course, lesson) }}
{{ LessonContent(lesson) }} {{ LessonContent(lesson) }}
{% if course.status == "Approved" and not course.upcoming %} {% if course.status == "Approved" and not course.upcoming %}
@@ -85,7 +85,7 @@
{% endif %} {% endif %}
</div> </div>
<div class="page-title title {% if membership %} is-member {% endif %} <div class="course-home-headings title {% if membership %} is-member {% endif %}
{% if membership or is_instructor %} eligible-for-submission {% endif %}" id="title" {% if membership or is_instructor %} eligible-for-submission {% endif %}" id="title"
data-index="{{ lesson_index }}" data-course="{{ course.name }}" data-chapter="{{ chapter }}" data-index="{{ lesson_index }}" data-course="{{ course.name }}" data-chapter="{{ chapter }}"
{% if lesson.name %} data-lesson="{{ lesson.name }}" {% endif %} {% if lesson.name %} data-lesson="{{ lesson.name }}" {% endif %}
@@ -155,19 +155,15 @@
{% if prev_url or next_url %} {% if prev_url or next_url %}
<div class="lesson-pagination"> <div class="lesson-pagination">
{% if prev_url %} {% if prev_url %}
<div> <a class="btn btn-secondary btn-sm prev" href="{{ prev_url }}">
<a class="btn btn-secondary btn-sm prev" href="{{ prev_url }}"> {{ _("Previous Lesson") }}
{{ _("Previous") }} </a>
</a>
</div>
{% endif %} {% endif %}
{% if next_url %} {% if next_url %}
<div> <a class="btn btn-primary btn-sm next pull-right" href="{{ next_url }}">
<a class="btn btn-primary btn-sm next ml-2 " href="{{ next_url }}"> {{ _("Next Lesson") }}
{{ _("Next") }} </a>
</a>
</div>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}

View File

@@ -75,7 +75,7 @@ const save_course = (e) => {
indicator: "green", indicator: "green",
}); });
setTimeout(() => { setTimeout(() => {
window.location.href = `/courses/${data.message}`; window.location.href = `/courses/${data.message}/outline`;
}, 1000); }, 1000);
}, },
}); });

View File

@@ -52,9 +52,12 @@
{{ chapter.title }} {{ chapter.title }}
</div> </div>
</div> </div>
{% if chapter.description %}
<div class="mb-2 ml-5 chapter-description"> <div class="mb-2 ml-5 chapter-description">
{{ chapter.description }} {{ chapter.description }}
</div> </div>
{% endif %}
{% for lesson in lessons %} {% for lesson in lessons %}
<div class="outline-lesson level"> <div class="outline-lesson level">