Merge pull request #494 from pateljannat/move-lessons
feat: move chapters and lessons
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ lms/public/dist
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
node_modules
|
||||
package-lock.json
|
||||
@@ -306,3 +306,43 @@ def save_lesson(
|
||||
lesson_reference.save(ignore_permissions=True)
|
||||
|
||||
return doc.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def reorder_lesson(old_chapter, old_lesson_array, new_chapter, new_lesson_array):
|
||||
if old_chapter == new_chapter:
|
||||
sort_lessons(new_chapter, new_lesson_array)
|
||||
else:
|
||||
sort_lessons(old_chapter, old_lesson_array)
|
||||
sort_lessons(new_chapter, new_lesson_array)
|
||||
|
||||
|
||||
def sort_lessons(chapter, lesson_array):
|
||||
lesson_array = json.loads(lesson_array)
|
||||
for les in lesson_array:
|
||||
ref = frappe.get_all("Lesson Reference", {"lesson": les}, ["name", "idx"])
|
||||
if ref:
|
||||
frappe.db.set_value(
|
||||
"Lesson Reference",
|
||||
ref[0].name,
|
||||
{
|
||||
"parent": chapter,
|
||||
"idx": lesson_array.index(les) + 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def reorder_chapter(chapter_array):
|
||||
chapter_array = json.loads(chapter_array)
|
||||
|
||||
for chap in chapter_array:
|
||||
ref = frappe.get_all("Chapter Reference", {"chapter": chap}, ["name", "idx"])
|
||||
if ref:
|
||||
frappe.db.set_value(
|
||||
"Chapter Reference",
|
||||
ref[0].name,
|
||||
{
|
||||
"idx": chapter_array.index(chap) + 1,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if course.name and (course.edit_mode or chapters | length) %}
|
||||
<div class="course-home-headings" id="outline-heading">
|
||||
<div class="course-home-headings" id="outline-heading" data-course="{{ course.name }}">
|
||||
{{ _("Course Content") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -23,9 +23,9 @@
|
||||
{% endif %}
|
||||
|
||||
{% if chapters | length %}
|
||||
|
||||
<div class="chapter-dropzone">
|
||||
{% for chapter in chapters %}
|
||||
<div class="chapter-parent {% if course.edit_mode %} chapter-edit {% endif %} ">
|
||||
<div class="chapter-parent {% if course.edit_mode %} chapter-edit {% endif %}" data-chapter="{{ chapter.name }}">
|
||||
<div class="chapter-title" {% if not course.edit_mode %} data-toggle="collapse" aria-expanded="false"
|
||||
data-target="#{{ get_slugified_chapter_title(chapter.title) }}" {% endif %} >
|
||||
{% if not course.edit_mode %}
|
||||
@@ -42,13 +42,13 @@
|
||||
{% if chapter.description or course.edit_mode %}
|
||||
<div {% if course.edit_mode %} contenteditable="true" {% endif %} class="chapter-description
|
||||
{% if not course.edit_mode %} mx-8 mb-2 {% endif %} "
|
||||
data-placeholder="{{ _('Short Description') }}">{{ chapter.description }}</div>
|
||||
data-placeholder="{{ _('Short Description') }}">{% if chapter.description %}{{ chapter.description }}{% endif %}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if course.edit_mode %}
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-secondary btn-save-chapter"
|
||||
data-index="{{ loop.index }}" data-chapter="{{ chapter.name }}"> {{ _('Save') }} </button>
|
||||
data-index="{{ loop.index }}"> {{ _('Save') }} </button>
|
||||
<a class="btn btn-sm btn-secondary btn-lesson ml-2"
|
||||
href="/courses/{{ course.name }}/learn/{{loop.index}}.{{ lessons | length + 1 }}?edit=1"> {{ _("New Lesson") }} </a>
|
||||
</div>
|
||||
@@ -56,19 +56,22 @@
|
||||
|
||||
{% set is_instructor = is_instructor(course.name) %}
|
||||
|
||||
{% if lessons | length %}
|
||||
<div class="lessons">
|
||||
{% if course.edit_mode %}
|
||||
<div class="course-meta mt-8 font-weight-bold"> {{ _("Lessons") }}: </div>
|
||||
{% endif %}
|
||||
|
||||
{% if course.edit_mode %}
|
||||
<b class="course-meta"> {{ _("Lessons") }}: </b>
|
||||
{% endif %}
|
||||
<div class="lessons {% if course.edit_mode %} lesson-dropzone {% endif %}">
|
||||
|
||||
|
||||
|
||||
{% if lessons | length %}
|
||||
|
||||
{% for lesson in lessons %}
|
||||
{% set active = membership.current_lesson == lesson.name %}
|
||||
<div class="lesson-info {% if active and not course.edit_mode %} active-lesson {% endif %}">
|
||||
<div data-lesson="{{ lesson.name }}" class="lesson-info {% if active and not course.edit_mode %} active-lesson {% endif %}">
|
||||
|
||||
{% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %}
|
||||
<a class="lesson-links" data-course="{{ course.name }}"
|
||||
<a class="lesson-links"
|
||||
{% if is_instructor and not lesson.include_in_preview %}
|
||||
title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}"
|
||||
{% endif %}
|
||||
@@ -89,7 +92,7 @@
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<div class="no-preview" title="This lesson is not available for preview" data-course="{{ course.name }}">
|
||||
<div class="no-preview" title="This lesson is not available for preview">
|
||||
<div class="lesson-links">
|
||||
<svg class="icon icon-sm mr-2">
|
||||
<use class="" href="#icon-lock-gray">
|
||||
@@ -102,12 +105,13 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
@@ -119,87 +123,3 @@
|
||||
{{ widgets.NoPreviewModal(course=course, membership=membership) }}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
|
||||
frappe.ready(() => {
|
||||
|
||||
expand_the_active_chapter();
|
||||
|
||||
$(".chapter-title").unbind().click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
$(".no-preview").click((e) => {
|
||||
show_no_preview_dialog(e);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
const expand_the_first_chapter = () => {
|
||||
let elements = $(".course-home-outline .collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i < 1) {
|
||||
show_section(element);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const expand_the_active_chapter = () => {
|
||||
|
||||
/* Find anchor matching the URL for course details page */
|
||||
let selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
if (!selector.length) {
|
||||
selector = $(`a[href^="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
}
|
||||
if (selector.length && $(".course-details-page").length) {
|
||||
$(".lesson-info").removeClass("active-lesson");
|
||||
$(".lesson-info").each((i, elem) => {
|
||||
let href = $(elem).find("use").attr("href");
|
||||
href.endsWith("blue") && $(elem).find("use").attr("href", href.substring(0, href.length - 5));
|
||||
})
|
||||
|
||||
selector.addClass("active-lesson");
|
||||
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* For course home page */
|
||||
else if ($(".active-lesson").length) {
|
||||
selector = $(".active-lesson")
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* If no active chapter then exapand the first chapter */
|
||||
else {
|
||||
expand_the_first_chapter();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const show_section = (element) => {
|
||||
$(element).addClass("show");
|
||||
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
|
||||
$(element).siblings(".chapter-title").attr("aria-expanded", true);
|
||||
};
|
||||
|
||||
|
||||
const rotate_chapter_icon = (e) => {
|
||||
let icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const show_no_preview_dialog = (e) => {
|
||||
$("#no-preview-modal").modal("show");
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1593,7 +1593,6 @@ li {
|
||||
|
||||
.chapter-edit .lessons {
|
||||
margin-left: 0;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.chapter-parent {
|
||||
|
||||
@@ -31,8 +31,54 @@ frappe.ready(() => {
|
||||
generate_graph("Lesson Completion", "#lesson-completion");
|
||||
generate_course_completion_graph();
|
||||
}
|
||||
|
||||
expand_the_active_chapter();
|
||||
|
||||
$(".chapter-title")
|
||||
.unbind()
|
||||
.click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
$(".no-preview").click((e) => {
|
||||
show_no_preview_dialog(e);
|
||||
});
|
||||
|
||||
$(".lesson-dropzone").each((i, el) => {
|
||||
setSortable(el);
|
||||
});
|
||||
|
||||
$(".chapter-dropzone").each((i, el) => {
|
||||
setSortable(el);
|
||||
});
|
||||
});
|
||||
|
||||
const setSortable = (el) => {
|
||||
new Sortable(el, {
|
||||
group: {
|
||||
name: "les",
|
||||
pull: "les",
|
||||
put: "les",
|
||||
},
|
||||
onEnd: (e) => {
|
||||
if ($(e.item).hasClass("lesson-info")) reorder_lesson(e);
|
||||
else reorder_chapter(e);
|
||||
},
|
||||
onMove: (e) => {
|
||||
if (
|
||||
$(e.dragged).hasClass("lesson-info") &&
|
||||
$(e.to).hasClass("chapter-dropzone")
|
||||
)
|
||||
return false;
|
||||
if (
|
||||
$(e.dragged).hasClass("chapter-edit") &&
|
||||
$(e.to).hasClass("lesson-dropzone")
|
||||
)
|
||||
return false;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const setup_file_size = () => {
|
||||
frappe.provide("frappe.form.formatters");
|
||||
frappe.form.formatters.FileSize = file_size;
|
||||
@@ -49,7 +95,7 @@ const file_size = (value) => {
|
||||
|
||||
const join_course = (e) => {
|
||||
e.preventDefault();
|
||||
let course = $(e.currentTarget).attr("data-course");
|
||||
let course = $("#outline-heading").attr("data-course");
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/courses/${course}`;
|
||||
return;
|
||||
@@ -83,7 +129,7 @@ const join_course = (e) => {
|
||||
|
||||
const notify_user = (e) => {
|
||||
e.preventDefault();
|
||||
var course = decodeURIComponent($(e.currentTarget).attr("data-course"));
|
||||
var course = decodeURIComponent($("#outline-heading").attr("data-course"));
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/courses/${course}`;
|
||||
return;
|
||||
@@ -120,7 +166,7 @@ const add_chapter = (e) => {
|
||||
|
||||
let next_index = $("[data-index]").last().data("index") + 1 || 1;
|
||||
let add_after = $(`.chapter-parent:last`).length
|
||||
? $(`.chapter-parent:last`)
|
||||
? $(`.chapter-dropzone`)
|
||||
: $("#outline-heading");
|
||||
|
||||
$(`<div class="chapter-parent chapter-edit new-chapter">
|
||||
@@ -157,7 +203,7 @@ const save_chapter = (e) => {
|
||||
title: parent.find(".chapter-title-main").text(),
|
||||
chapter_description: parent.find(".chapter-description").text(),
|
||||
idx: target.data("index"),
|
||||
chapter: target.data("chapter") ? target.data("chapter") : "",
|
||||
chapter: parent.data("chapter") ? parent.data("chapter") : "",
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert({
|
||||
@@ -226,3 +272,116 @@ const change_hash = (e) => {
|
||||
const open_tab = () => {
|
||||
$(`a[href="${window.location.hash}"]`).click();
|
||||
};
|
||||
|
||||
const expand_the_first_chapter = () => {
|
||||
let elements = $(".course-home-outline .collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i < 1) {
|
||||
show_section(element);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const expand_the_active_chapter = () => {
|
||||
/* Find anchor matching the URL for course details page */
|
||||
let selector = $(
|
||||
`a[href="${decodeURIComponent(window.location.pathname)}"]`
|
||||
).parent();
|
||||
|
||||
if (!selector.length) {
|
||||
selector = $(
|
||||
`a[href^="${decodeURIComponent(window.location.pathname)}"]`
|
||||
).parent();
|
||||
}
|
||||
if (selector.length && $(".course-details-page").length) {
|
||||
expand_for_course_details(selector);
|
||||
} else if ($(".active-lesson").length) {
|
||||
/* For course home page */
|
||||
selector = $(".active-lesson");
|
||||
show_section(selector.parent().parent());
|
||||
} else {
|
||||
/* If no active chapter then exapand the first chapter */
|
||||
expand_the_first_chapter();
|
||||
}
|
||||
};
|
||||
|
||||
const expand_for_course_details = (selector) => {
|
||||
$(".lesson-info").removeClass("active-lesson");
|
||||
$(".lesson-info").each((i, elem) => {
|
||||
let href = $(elem).find("use").attr("href");
|
||||
href.endsWith("blue") &&
|
||||
$(elem)
|
||||
.find("use")
|
||||
.attr("href", href.substring(0, href.length - 5));
|
||||
});
|
||||
selector.addClass("active-lesson");
|
||||
|
||||
show_section(selector.parent().parent());
|
||||
};
|
||||
|
||||
const show_section = (element) => {
|
||||
$(element).addClass("show");
|
||||
$(element)
|
||||
.siblings(".chapter-title")
|
||||
.children(".chapter-icon")
|
||||
.css("transform", "rotate(90deg)");
|
||||
$(element).siblings(".chapter-title").attr("aria-expanded", true);
|
||||
};
|
||||
|
||||
const rotate_chapter_icon = (e) => {
|
||||
let icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
};
|
||||
|
||||
const show_no_preview_dialog = (e) => {
|
||||
$("#no-preview-modal").modal("show");
|
||||
};
|
||||
|
||||
const reorder_lesson = (e) => {
|
||||
let old_chapter = $(e.from).closest(".chapter-edit").data("chapter");
|
||||
let new_chapter = $(e.to).closest(".chapter-edit").data("chapter");
|
||||
|
||||
if (old_chapter == new_chapter && e.oldIndex == e.newIndex) return;
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_course.lms_course.reorder_lesson",
|
||||
args: {
|
||||
old_chapter: old_chapter,
|
||||
old_lesson_array: $(e.from)
|
||||
.children()
|
||||
.map((i, e) => $(e).data("lesson"))
|
||||
.get(),
|
||||
new_chapter: new_chapter,
|
||||
new_lesson_array: $(e.to)
|
||||
.children()
|
||||
.map((i, e) => $(e).data("lesson"))
|
||||
.get(),
|
||||
},
|
||||
callback: (data) => {
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const reorder_chapter = (e) => {
|
||||
if (e.oldIndex == e.newIndex) return;
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_course.lms_course.reorder_chapter",
|
||||
args: {
|
||||
new_index: e.newIndex + 1,
|
||||
chapter_array: $(e.to)
|
||||
.children()
|
||||
.map((i, e) => $(e).data("chapter"))
|
||||
.get(),
|
||||
},
|
||||
callback: (data) => {
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user