test: fixed course creation test
This commit is contained in:
@@ -5,39 +5,50 @@ describe("Course Creation", () => {
|
||||
// Create a course
|
||||
cy.get("a.btn").contains("Create a Course").click();
|
||||
cy.wait(1000);
|
||||
cy.url().should("include", "/courses/new-course");
|
||||
cy.button("Add Tag").click();
|
||||
cy.get(".course-card-pills").type("Test");
|
||||
cy.url().should("include", "/courses/new-course/edit");
|
||||
cy.get("#title").type("Test Course");
|
||||
cy.get("#intro").type("Test Course Short Introduction");
|
||||
cy.get("#video-link").type("-LPmw2Znl2c");
|
||||
cy.get("#published").check();
|
||||
cy.get("#description").type("Test Course Description");
|
||||
cy.get("#video-link").type("-LPmw2Znl2c");
|
||||
cy.get("#tags-input").type("Test");
|
||||
cy.get("#published").check();
|
||||
cy.wait(1000);
|
||||
cy.button("Save Course Details").click();
|
||||
cy.button("Save").click();
|
||||
|
||||
// Add Chapter
|
||||
cy.wait(3000);
|
||||
cy.button("New Chapter").click();
|
||||
cy.get(".new-chapter .chapter-title-main").type("Test Chapter");
|
||||
cy.get(".new-chapter .chapter-description").type(
|
||||
"Test Chapter Description"
|
||||
);
|
||||
cy.get(".new-chapter .btn-save-chapter").click();
|
||||
cy.wait(1000);
|
||||
cy.link("Course Outline").click();
|
||||
|
||||
cy.wait(1000);
|
||||
cy.get(".edit-header .btn-add-chapter").click();
|
||||
cy.get("#chapter-title").type("Test Chapter");
|
||||
cy.get("#chapter-description").type("Test Chapter Description");
|
||||
cy.button("Save").click();
|
||||
|
||||
// Add Lesson
|
||||
cy.wait(3000);
|
||||
cy.get(".chapter-parent .btn-lesson").click();
|
||||
|
||||
cy.wait(3000);
|
||||
cy.get("#title").type("Test Lesson");
|
||||
cy.get("#youtube").type("GoDtyItReto");
|
||||
cy.get("#body").type("Test Lesson Content");
|
||||
cy.wait(1000);
|
||||
cy.get(".btn-lesson").click();
|
||||
cy.link("Add Lesson").click();
|
||||
cy.wait(1000);
|
||||
cy.get("#lesson-title").type("Test Lesson");
|
||||
|
||||
// Content
|
||||
cy.get(".ce-block").click().type("{enter}");
|
||||
cy.get(".ce-toolbar__plus").click();
|
||||
cy.get('[data-item-name="youtube"]').click();
|
||||
cy.get('input[data-fieldname="youtube"]').type("GoDtyItReto");
|
||||
cy.button("Insert").click();
|
||||
cy.wait(1000);
|
||||
|
||||
cy.get(".ce-block:last").click().type("{enter}");
|
||||
cy.get(".ce-block:last")
|
||||
.click()
|
||||
.type(
|
||||
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
|
||||
);
|
||||
cy.button("Save").click();
|
||||
|
||||
// View Course
|
||||
cy.wait(3000);
|
||||
cy.wait(1000);
|
||||
cy.visit("/courses");
|
||||
cy.get(".course-card-title:first").contains("Test Course");
|
||||
cy.get(".course-card:first").click();
|
||||
@@ -59,7 +70,7 @@ describe("Course Creation", () => {
|
||||
cy.get(".lesson-info:first").click();
|
||||
|
||||
// View Lesson
|
||||
cy.wait(3000);
|
||||
cy.wait(1000);
|
||||
cy.url().should("include", "learn/1.1");
|
||||
cy.get("#title").contains("Test Lesson");
|
||||
cy.get(".lesson-video iframe").should(
|
||||
@@ -67,7 +78,9 @@ describe("Course Creation", () => {
|
||||
"src",
|
||||
"https://www.youtube.com/embed/GoDtyItReto"
|
||||
);
|
||||
cy.get(".lesson-content-card").contains("Test Lesson Content");
|
||||
cy.get(".lesson-content-card").contains(
|
||||
"This is an extremely big paragraph that is meant to test the UI. This is a very long paragraph. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
|
||||
);
|
||||
|
||||
// Add Discussion
|
||||
cy.get(".reply").click();
|
||||
@@ -79,7 +92,7 @@ describe("Course Creation", () => {
|
||||
cy.get(".submit-discussion").click();
|
||||
|
||||
// View Discussion
|
||||
cy.wait(3000);
|
||||
cy.wait(1000);
|
||||
cy.get(".discussion-topic-title:first").contains("Question Title");
|
||||
cy.get(".sidebar-parent:first").click();
|
||||
cy.get(".reply-text").contains(
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"documentation_url": "https://frappe.school",
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
@@ -260,7 +261,7 @@
|
||||
}
|
||||
],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2023-05-02 09:45:54.826328",
|
||||
"modified": "2023-05-09 17:08:19.763405",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import json
|
||||
|
||||
import random
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
@@ -72,7 +72,10 @@ class LMSCourse(Document):
|
||||
|
||||
def autoname(self):
|
||||
if not self.name:
|
||||
self.name = generate_slug(self.title, "LMS Course")
|
||||
title = self.title
|
||||
if self.title == "New Course":
|
||||
title = self.title + str(random.randint(0, 99))
|
||||
self.name = generate_slug(title, "LMS Course")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Course#{self.name}>"
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
{{ _("Course Content") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<!-- <div class="mb-2">
|
||||
<span>
|
||||
{{ chapters | length }} chapters
|
||||
</span>
|
||||
<span>
|
||||
. {{ get_lessons(course.name, None, False) }} lessons
|
||||
</span>
|
||||
</div>
|
||||
</div> -->
|
||||
{% endif %}
|
||||
|
||||
{% if chapters | length %}
|
||||
<div {% if not lesson_page %} class="common-card-style column-card p-4" {% endif %}>
|
||||
<div>
|
||||
{% for chapter in chapters %}
|
||||
{% set lessons = get_lessons(course.name, chapter) %}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}"
|
||||
{% endif %}>
|
||||
|
||||
<svg class="icon icon-md mr-2">
|
||||
<svg class="icon icon-sm mr-2">
|
||||
<use class="" href="#{{ lesson.icon }}">
|
||||
</svg>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
--text-4xl: 36px;
|
||||
--primary-color: var(--gray-900);
|
||||
--primary: var(--gray-900);
|
||||
--checkbox-gradient: linear-gradient(180deg, #3d4142 -124.51%, var(--primary) 100%);
|
||||
}
|
||||
|
||||
.nav-link .course-list-count {
|
||||
@@ -28,7 +29,7 @@
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
z-index: 1020;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.is-pinned {
|
||||
@@ -118,6 +119,7 @@ textarea.field-input {
|
||||
|
||||
.outline-lesson {
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid var(--gray-300);
|
||||
}
|
||||
|
||||
.common-card-style .outline-lesson:last-of-type {
|
||||
@@ -186,7 +188,7 @@ textarea.field-input {
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
cursor: grabbing;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.edit-header {
|
||||
@@ -201,6 +203,16 @@ textarea.field-input {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.field-label.reqd::after {
|
||||
content: " *";
|
||||
color: var(--red-400);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--red-500);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
@@ -1763,14 +1775,6 @@ li {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.quiz-card {
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.25rem;
|
||||
margin-top: 1.25rem;
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.option-input {
|
||||
width: 45%;
|
||||
margin-right: 1rem;
|
||||
|
||||
@@ -5,19 +5,39 @@
|
||||
<svg id="icon-video-blue" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 9C1 4.58172 4.58172 1 9 1C11.1217 1 13.1566 1.84286 14.6569 3.34315C16.1571 4.84343 17 6.87827 17 9C17 13.4182 13.4182 17 9 17C4.58172 17 1 13.4182 1 9ZM8.00636 12.0679L11.8766 9.51133C12.0614 9.40191 12.174 9.2084 12.174 9C12.174 8.79161 12.0614 8.59809 11.8766 8.48867L8.00636 5.932C7.79102 5.78453 7.51 5.75869 7.2694 5.86422C7.0288 5.96977 6.86529 6.1906 6.84063 6.44334V11.5567C6.86529 11.8094 7.0288 12.0302 7.2694 12.1358C7.51 12.2413 7.79102 12.2155 8.00636 12.0679Z" fill="#2D95F0"/>
|
||||
</svg>
|
||||
<svg id="icon-youtube" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_779_38008)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 13.625H14.875C15.4273 13.625 15.875 13.1773 15.875 12.625V3.125C15.875 2.57272 15.4273 2.125 14.875 2.125H3.125C2.57272 2.125 2.125 2.57272 2.125 3.125V12.625C2.125 13.1773 2.57272 13.625 3.125 13.625ZM14.875 14.625C15.9796 14.625 16.875 13.7296 16.875 12.625V3.125C16.875 2.02043 15.9796 1.125 14.875 1.125H3.125C2.02043 1.125 1.125 2.02043 1.125 3.125V12.625C1.125 13.7296 2.02043 14.625 3.125 14.625H14.875ZM6.74988 5.35507C6.74988 4.74831 7.43289 4.39269 7.92997 4.74065L11.619 7.32298C12.0456 7.62156 12.0456 8.25325 11.619 8.55183L7.92998 11.1342C7.43289 11.4821 6.74988 11.1265 6.74988 10.5197V5.35507ZM7.74988 5.83524V10.0396L10.753 7.93741L7.74988 5.83524ZM5.625 15.25C5.34886 15.25 5.125 15.4739 5.125 15.75C5.125 16.0261 5.34886 16.25 5.625 16.25H12.375C12.6511 16.25 12.875 16.0261 12.875 15.75C12.875 15.4739 12.6511 15.25 12.375 15.25H5.625Z" fill="#171717"/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" id="icon-youtube" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3613_984)">
|
||||
<mask id="mask0_3613_984" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||
<path d="M16 0H0V16H16V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3613_984)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.77778 12.1111H13.2222C13.7132 12.1111 14.1111 11.7132 14.1111 11.2222V2.77778C14.1111 2.28686 13.7132 1.88889 13.2222 1.88889H2.77778C2.28686 1.88889 1.88889 2.28686 1.88889 2.77778V11.2222C1.88889 11.7132 2.28686 12.1111 2.77778 12.1111ZM13.2222 13C14.2041 13 15 12.2041 15 11.2222V2.77778C15 1.79594 14.2041 1 13.2222 1H2.77778C1.79594 1 1 1.79594 1 2.77778V11.2222C1 12.2041 1.79594 13 2.77778 13H13.2222ZM5.99989 4.76006C5.99989 4.22072 6.60701 3.90461 7.04886 4.21391L10.328 6.50932C10.7072 6.77472 10.7072 7.33622 10.328 7.60163L7.04887 9.89707C6.60701 10.2063 5.99989 9.89022 5.99989 9.35084V4.76006ZM6.88878 5.18688V8.92409L9.55822 7.05548L6.88878 5.18688ZM5 13.5556C4.75454 13.5556 4.55556 13.7546 4.55556 14C4.55556 14.2454 4.75454 14.4444 5 14.4444H11C11.2454 14.4444 11.4444 14.2454 11.4444 14C11.4444 13.7546 11.2454 13.5556 11 13.5556H5Z" fill="#525252"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_779_38008">
|
||||
<rect width="18" height="18" fill="white"/>
|
||||
<clipPath id="clip0_3613_984">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="icon-quiz" viewBox="0 0 1024 1024" stroke="#1F272E">
|
||||
<path d="M512 0C229.232 0 0 229.232 0 512c0 282.784 229.232 512 512 512 282.784 0 512.017-229.216 512.017-512C1024.017 229.232 794.785 0 512 0zm0 961.008c-247.024 0-448-201.984-448-449.01 0-247.024 200.976-448 448-448s448.017 200.977 448.017 448S759.025 961.009 512 961.009zm-47.056-160.529h80.512v-81.248h-80.512zm46.112-576.944c-46.88 0-85.503 12.64-115.839 37.889-30.336 25.263-45.088 75.855-44.336 117.775l1.184 2.336h73.44c0-25.008 8.336-60.944 25.008-73.84 16.656-12.88 36.848-19.328 60.56-19.328 27.328 0 48.336 7.424 63.073 22.271 14.72 14.848 22.063 36.08 22.063 63.664 0 23.184-5.44 42.976-16.368 59.376-10.96 16.4-29.328 39.841-55.088 70.322-26.576 23.967-42.992 43.231-49.232 57.807-6.256 14.592-9.504 40.768-9.744 78.512h76.96c0-23.68 1.503-41.136 4.496-52.336 2.975-11.184 11.504-23.823 25.568-37.888 30.224-29.152 54.496-57.664 72.88-85.551 18.336-27.857 27.52-58.593 27.52-92.193 0-46.88-14.176-83.408-42.577-109.568-28.416-26.176-68.272-39.248-119.568-39.248z" fill="#1F272E"/>
|
||||
|
||||
<svg width="16" height="16" id="icon-quiz" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_3613_978)">
|
||||
<mask id="mask0_3613_978" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||
<path d="M16 0H0V16H16V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3613_978)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1111 8C14.1111 11.3751 11.3751 14.1111 8 14.1111C4.62492 14.1111 1.88889 11.3751 1.88889 8C1.88889 4.62492 4.62492 1.88889 8 1.88889C11.3751 1.88889 14.1111 4.62492 14.1111 8ZM15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM7.37988 9.72462V9.79298H8.39062V9.72462C8.39062 9.4512 8.41992 9.23307 8.47852 9.07031C8.53711 8.90427 8.62826 8.76269 8.75196 8.64551C8.87891 8.52832 9.04329 8.4095 9.24516 8.28907C9.56089 8.09375 9.80987 7.85775 9.99218 7.58106C10.1745 7.30111 10.2656 6.96093 10.2656 6.56055C10.2656 6.17644 10.1745 5.83952 9.99218 5.5498C9.81316 5.26009 9.56089 5.03386 9.23538 4.87109C8.90987 4.70834 8.52734 4.62695 8.08789 4.62695C7.69401 4.62695 7.33268 4.70345 7.0039 4.85644C6.67513 5.00619 6.41146 5.22916 6.21289 5.52539C6.01432 5.81836 5.90852 6.17644 5.89551 6.59961H6.96972C6.986 6.34896 7.04948 6.14551 7.16016 5.98926C7.27084 5.82975 7.40756 5.71257 7.57031 5.6377C7.73633 5.56283 7.90885 5.52539 8.08789 5.52539C8.28972 5.52539 8.47364 5.56771 8.63964 5.65235C8.80892 5.73698 8.94071 5.8558 9.0352 6.00879C9.1328 6.16179 9.1816 6.34244 9.1816 6.55078C9.1816 6.81771 9.11004 7.0472 8.96676 7.23926C8.82356 7.42806 8.64453 7.58594 8.42969 7.71289C8.21159 7.84961 8.02278 7.98958 7.86328 8.13281C7.70703 8.27278 7.58659 8.46322 7.50196 8.7041C7.42057 8.94169 7.37988 9.28187 7.37988 9.72462ZM7.37988 11.8682C7.51986 12.0016 7.69076 12.0684 7.89258 12.0684C8.09765 12.0684 8.26855 12.0016 8.40527 11.8682C8.54524 11.7315 8.61524 11.5638 8.61524 11.3652C8.61524 11.1634 8.54524 10.9957 8.40527 10.8623C8.26855 10.7256 8.09765 10.6572 7.89258 10.6572C7.69076 10.6572 7.51986 10.7256 7.37988 10.8623C7.24316 10.9957 7.17481 11.1634 7.17481 11.3652C7.17481 11.5638 7.24316 11.7315 7.37988 11.8682Z" fill="#525252"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3613_978">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
||||
<svg id="icon-quiz-blue" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1511_749)">
|
||||
<path d="M512 0C229.232 0 0 229.232 0 512C0 794.784 229.232 1024 512 1024C794.784 1024 1024.02 794.784 1024.02 512C1024.02 229.232 794.785 0 512 0ZM512 961.008C264.976 961.008 64 759.024 64 511.998C64 264.974 264.976 63.998 512 63.998C759.024 63.998 960.017 264.975 960.017 511.998C960.017 759.021 759.025 961.009 512 961.009V961.008ZM464.944 800.479H545.456V719.231H464.944V800.479ZM511.056 223.535C464.176 223.535 425.553 236.175 395.217 261.424C364.881 286.687 350.129 337.279 350.881 379.199L352.065 381.535H425.505C425.505 356.527 433.841 320.591 450.513 307.695C467.169 294.815 487.361 288.367 511.073 288.367C538.401 288.367 559.409 295.791 574.146 310.638C588.866 325.486 596.209 346.718 596.209 374.302C596.209 397.486 590.769 417.278 579.841 433.678C568.881 450.078 550.513 473.519 524.753 504C498.177 527.967 481.761 547.231 475.521 561.807C469.265 576.399 466.017 602.575 465.777 640.319H542.737C542.737 616.639 544.24 599.183 547.233 587.983C550.208 576.799 558.737 564.16 572.801 550.095C603.025 520.943 627.297 492.431 645.681 464.544C664.017 436.687 673.201 405.951 673.201 372.351C673.201 325.471 659.025 288.943 630.624 262.783C602.208 236.607 562.352 223.535 511.056 223.535V223.535Z" fill="#2D95F0" stroke="#2D95F0"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
@@ -10,14 +10,6 @@ frappe.ready(() => {
|
||||
notify_user(e);
|
||||
});
|
||||
|
||||
$(".btn-chapter").click((e) => {
|
||||
add_chapter(e);
|
||||
});
|
||||
|
||||
$(document).on("click", ".btn-save-chapter", (e) => {
|
||||
save_chapter(e);
|
||||
});
|
||||
|
||||
$(".nav-link").click((e) => {
|
||||
change_hash(e);
|
||||
});
|
||||
@@ -62,32 +54,6 @@ const pin_header = () => {
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -167,65 +133,6 @@ const notify_user = (e) => {
|
||||
});
|
||||
};
|
||||
|
||||
const add_chapter = (e) => {
|
||||
if ($(".new-chapter").length) {
|
||||
scroll_to_chapter_container();
|
||||
return;
|
||||
}
|
||||
|
||||
let next_index = $("[data-index]").last().data("index") + 1 || 1;
|
||||
let add_after = $(`.chapter-parent:last`).length
|
||||
? $(`.chapter-dropzone`)
|
||||
: $("#outline-heading");
|
||||
|
||||
$(`<div class="chapter-parent chapter-edit new-chapter">
|
||||
<div contenteditable="true" data-placeholder="${__(
|
||||
"Chapter Name"
|
||||
)}" class="chapter-title-main"></div>
|
||||
<div class="chapter-description small my-2" contenteditable="true"
|
||||
data-placeholder="${__("Short Description")}"></div>
|
||||
<button class="btn btn-sm btn-secondary d-block btn-save-chapter"
|
||||
data-index="${next_index}"> ${__("Save")} </button>
|
||||
</div>`).insertAfter(add_after);
|
||||
|
||||
scroll_to_chapter_container();
|
||||
};
|
||||
|
||||
const scroll_to_chapter_container = () => {
|
||||
$([document.documentElement, document.body]).animate(
|
||||
{
|
||||
scrollTop: $(".new-chapter").offset().top,
|
||||
},
|
||||
1000
|
||||
);
|
||||
$(".new-chapter").find(".chapter-title-main").focus();
|
||||
};
|
||||
|
||||
const save_chapter = (e) => {
|
||||
let target = $(e.currentTarget);
|
||||
let parent = target.closest(".chapter-parent");
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_course.lms_course.save_chapter",
|
||||
args: {
|
||||
course: $("#title").data("course"),
|
||||
title: parent.find(".chapter-title-main").text(),
|
||||
chapter_description: parent.find(".chapter-description").text(),
|
||||
idx: target.data("index"),
|
||||
chapter: parent.data("chapter") ? parent.data("chapter") : "",
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert({
|
||||
message: __("Saved"),
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const generate_graph = (chart_name, element, type = "line") => {
|
||||
let date = frappe.datetime;
|
||||
|
||||
@@ -351,50 +258,6 @@ 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();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const open_class_dialog = (e) => {
|
||||
this.class_dialog = new frappe.ui.Dialog({
|
||||
title: __("New Class"),
|
||||
|
||||
@@ -59,12 +59,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</header>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -122,7 +122,6 @@ const parse_lesson_to_string = (data) => {
|
||||
? `{{ Video("${url}") }}\n`
|
||||
: ``;
|
||||
} else if (block.type == "header") {
|
||||
console.log(block);
|
||||
lesson_content +=
|
||||
"#".repeat(block.data.level) + ` ${block.data.text}\n`;
|
||||
} else if (block.type == "paragraph") {
|
||||
@@ -133,6 +132,7 @@ const parse_lesson_to_string = (data) => {
|
||||
};
|
||||
|
||||
const save = (lesson_content) => {
|
||||
validate_mandatory(lesson_content);
|
||||
let lesson = $("#lesson-title").data("lesson");
|
||||
|
||||
frappe.call({
|
||||
@@ -157,6 +157,28 @@ const save = (lesson_content) => {
|
||||
});
|
||||
};
|
||||
|
||||
const validate_mandatory = (lesson_content) => {
|
||||
if (!$("#lesson-title").val()) {
|
||||
let error = $("p")
|
||||
.addClass("error-message")
|
||||
.text(__("Please enter a Lesson Title"));
|
||||
$(error).insertAfter("#lesson-title");
|
||||
$("#lesson-title").focus();
|
||||
throw "Title is mandatory";
|
||||
}
|
||||
|
||||
if (!lesson_content.trim()) {
|
||||
let error = $("p")
|
||||
.addClass("error-message")
|
||||
.text(__("Please enter some content for the lesson"));
|
||||
$(error).insertAfter("#lesson-content");
|
||||
document
|
||||
.getElementById("lesson-content")
|
||||
.scrollIntoView({ block: "start" });
|
||||
throw "Lesson Content is mandatory";
|
||||
}
|
||||
};
|
||||
|
||||
const fetch_quiz_list = () => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_quiz.lms_quiz.get_user_quizzes",
|
||||
@@ -205,6 +227,19 @@ class YouTubeVideo {
|
||||
label: __("YouTube Video ID"),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "instructions_section_break",
|
||||
fieldtype: "Section Break",
|
||||
label: __("Instructions:"),
|
||||
},
|
||||
{
|
||||
fieldname: "instructions",
|
||||
fieldtype: "HTML",
|
||||
label: __("Instructions"),
|
||||
options: __(
|
||||
"Enter the YouTube Video ID. The ID is the part of the URL after <code>watch?v=</code>. For example, if the URL is <code>https://www.youtube.com/watch?v=QH2-TGUlwu4</code>, the ID is <code>QH2-TGUlwu4</code>"
|
||||
),
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Insert"),
|
||||
primary_action(values) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
frappe.ready(() => {
|
||||
this.marked_as_complete = false;
|
||||
this.quiz_submitted = false;
|
||||
this.file_type;
|
||||
this.answer = [];
|
||||
this.is_correct = [];
|
||||
let self = this;
|
||||
@@ -12,8 +11,6 @@ frappe.ready(() => {
|
||||
|
||||
save_current_lesson();
|
||||
|
||||
set_file_type();
|
||||
|
||||
$(".option").click((e) => {
|
||||
enable_check(e);
|
||||
});
|
||||
@@ -64,14 +61,6 @@ frappe.ready(() => {
|
||||
clear_work(e);
|
||||
});
|
||||
|
||||
$(".btn-lesson").click((e) => {
|
||||
save_lesson(e);
|
||||
});
|
||||
|
||||
$(".add-attachment").click((e) => {
|
||||
show_upload_modal();
|
||||
});
|
||||
|
||||
$(".btn-start-quiz").click((e) => {
|
||||
$("#start-banner").addClass("hide");
|
||||
$("#quiz-form").removeClass("hide");
|
||||
@@ -95,16 +84,6 @@ frappe.ready(() => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ($("#body").length) {
|
||||
make_editor();
|
||||
}
|
||||
|
||||
$("#file-type").change((e) => {
|
||||
$("#file-type option:selected").each(function () {
|
||||
self.file_type = $(this).val();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const save_current_lesson = () => {
|
||||
@@ -522,98 +501,3 @@ const calculate_and_display_time = (percent_time) => {
|
||||
let progress_color = percent_time < 20 ? "red" : "var(--primary-color)";
|
||||
$(".timer .progress-bar").css("background-color", progress_color);
|
||||
};
|
||||
|
||||
const save_lesson = (e) => {
|
||||
let lesson = $("#title").data("lesson");
|
||||
let self = this;
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_course.lms_course.save_lesson",
|
||||
args: {
|
||||
title: $("#title").text(),
|
||||
body: this.code_field_group.fields_dict["code_md"].value,
|
||||
youtube: $("#youtube").text(),
|
||||
quiz_id: $("#quiz-id").text(),
|
||||
chapter: $("#title").data("chapter"),
|
||||
preview: $("#preview").prop("checked") ? 1 : 0,
|
||||
idx: $("#title").data("index"),
|
||||
lesson: lesson ? lesson : "",
|
||||
question: $("#assignment-question").text(),
|
||||
file_type: self.file_type,
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert({
|
||||
message: __("Saved"),
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.href = window.location.href.split("?")[0];
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const show_upload_modal = () => {
|
||||
new frappe.ui.FileUploader({
|
||||
folder: "Home/Attachments",
|
||||
restrictions: {
|
||||
allowed_file_types: ["image/*", "video/*"],
|
||||
},
|
||||
on_success: (file_doc) => {
|
||||
$(".attachments").append(build_attachment_table(file_doc));
|
||||
let count = $(".attachment-count").data("count") + 1;
|
||||
$(".attachment-count").data("count", count);
|
||||
$(".attachment-count").html(__(`${count} attachments`));
|
||||
$(".attachments").removeClass("hide");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const build_attachment_table = (file_doc) => {
|
||||
let video_types = ["mov", "mp4", "mkv"];
|
||||
let video_extension = file_doc.file_url.split(".").pop();
|
||||
let is_video = video_types.indexOf(video_extension) >= 0;
|
||||
let link = is_video
|
||||
? `{{ Video('${file_doc.file_url}') }}`
|
||||
: ``;
|
||||
|
||||
return $(`
|
||||
<tr class="attachment-row">
|
||||
<td>${file_doc.file_name}</td>
|
||||
<td class="">
|
||||
<a class="button is-secondary button-links copy-link" data-link="${link}"
|
||||
data-name="${file_doc.file_name}" > ${__("Copy Link")}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
};
|
||||
|
||||
const make_editor = () => {
|
||||
/* this.code_field_group = new frappe.ui.FieldGroup({
|
||||
fields: [
|
||||
{
|
||||
fieldname: "code_md",
|
||||
fieldtype: "Text Editor",
|
||||
default: $(".body-data").html(),
|
||||
},
|
||||
],
|
||||
body: $("#body").get(0),
|
||||
});
|
||||
this.code_field_group.make();
|
||||
$("#body .form-section:last").removeClass("empty-section");
|
||||
$("#body .frappe-control").removeClass("hide-control");
|
||||
$("#body .form-column").addClass("p-0"); */
|
||||
};
|
||||
|
||||
const set_file_type = () => {
|
||||
let self = this;
|
||||
let file_type = $("#file-type").data("type");
|
||||
if (file_type) {
|
||||
$("#file-type option").each((i, elem) => {
|
||||
if ($(elem).val() == file_type) {
|
||||
$(elem).attr("selected", true);
|
||||
self.file_type = file_type;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "templates/base.html" %}
|
||||
{% block title %}
|
||||
{{ _("Quiz List") }}
|
||||
{{ quiz.title if quiz.name else _("Quiz Details") }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -8,11 +8,25 @@
|
||||
<div class="common-page-style">
|
||||
{{ Header() }}
|
||||
<div class="container form-width">
|
||||
{{ QuizCard(quiz) }}
|
||||
{{ QuizForm(quiz) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro QuizForm(quiz) %}
|
||||
<div>
|
||||
{{ QuizDetails(quiz) }}
|
||||
{% if quiz.questions %}
|
||||
{% for question in quiz.questions %}
|
||||
{{ Question(question, loop.index) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<div id="question-template" class="hide">
|
||||
{{ Question({}, 0) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro Header() %}
|
||||
<header class="sticky">
|
||||
<div class="container form-width">
|
||||
@@ -30,104 +44,101 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-sm btn-save-question align-self-center">
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
<div class="align-self-center">
|
||||
<button class="btn btn-default btn-sm btn-add-question">
|
||||
{{ _("Add Question") }}
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm btn-save-question">
|
||||
{{ _("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro QuizCard(quiz) %}
|
||||
<div>
|
||||
|
||||
<div class="field-parent">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Title") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Give your quiz a title") }}
|
||||
</div>
|
||||
{% macro QuizDetails(quiz) %}
|
||||
<div class="field-parent">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Title") }}
|
||||
</div>
|
||||
<div class="">
|
||||
<input type="text" class="field-input" id="quiz-title" {% if quiz.name %} value="{{ quiz.title }}" data-name="{{ quiz.name }}" {% endif %}>
|
||||
<div class="field-description">
|
||||
{{ _("Give your quiz a title") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if quiz.questions %}
|
||||
{% for question in quiz.questions %}
|
||||
<div class="common-card-style column-card field-parent">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
{{ _("Question") }} {{ loop.index }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input type="text" class="field-input question" {% if question.name %} value="{{ question.question }}" data-question="{{ question.name }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div class="vertically-center justify-content-between">
|
||||
<div class="field-label">
|
||||
{{ _("Question Type") }}
|
||||
</div>
|
||||
<div class="btn-group btn-group-toggle type align-self-center" data-toggle="buttons">
|
||||
<label class="btn btn-default btn-sm active question-type">
|
||||
<input type="radio" name="type-{{ loop.index }}" data-type="Choices" {% if question.type == "Choices" %} checked {% endif %}>
|
||||
{{ _("Choices") }}
|
||||
</label>
|
||||
<label class="btn btn-default btn-sm question-type">
|
||||
<input type="radio" name="type-{{ loop.index }}" data-type="User Input" {% if question.type == "User Input" %} checked {% endif %}>
|
||||
{{ _("User Input") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
|
||||
{% for i in range(1,5) %}
|
||||
{% set num = frappe.utils.cstr(i) %}
|
||||
|
||||
{% set option = question["option_" + num] %}
|
||||
{% set explanation = question["explanation_" + num] %}
|
||||
{% set possible_answer = question["possibility_" + num] %}
|
||||
|
||||
<div class="field-group">
|
||||
|
||||
<div class="options-group {% if question.type == 'User Input' %} hide {% endif %}">
|
||||
<input type="text" placeholder="Option" class="field-input option-{{ num }}" {% if option %} value="{{ option }}" {% endif %}>
|
||||
<input type="text" placeholder="Explanation" class="field-input explanation-{{ num }}" {% if explanation %} value="{{ explanation }}" {% endif %}>
|
||||
<label class="vertically-center mt-1">
|
||||
<input type="checkbox" class="correct-{{ num }}" {% if question['is_correct_' + num] %} checked {% endif %}>
|
||||
{{ _("Is Correct") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="answers-group {% if question.type == 'Choices' %} hide {% endif %}">
|
||||
<div class="field-label">
|
||||
{{ _("Possible Answers") }} {{ num }}
|
||||
</div>
|
||||
<textarea class="field-input possibility-{{ num }}"
|
||||
style="height: 100px;">{% if possible_answer %}{{ possible_answer }}{% endif %}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
<input type="text" class="field-input" id="quiz-title" {% if quiz.name %} value="{{ quiz.title }}" data-name="{{ quiz.name }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro Question(question, index) %}
|
||||
{% set type = question.type if question.type else "Choices" %}
|
||||
<div class="common-card-style column-card field-parent question-card" data-index="{{ index }}">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label question-label">
|
||||
{{ _("Question") }} {{ index }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<input type="text" class="field-input question" {% if question.name %} value="{{ question.question }}" data-question="{{ question.name }}" {% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div class="vertically-center justify-content-between">
|
||||
<div class="field-label">
|
||||
{{ _("Question Type") }}
|
||||
</div>
|
||||
<div class="btn-group btn-group-toggle type align-self-center" data-toggle="buttons">
|
||||
<label class="btn btn-default btn-sm active question-type">
|
||||
<input type="radio" name="type-{{ index }}" data-type="Choices" {% if type == "Choices" %} checked {% endif %}>
|
||||
{{ _("Choices") }}
|
||||
</label>
|
||||
<label class="btn btn-default btn-sm question-type">
|
||||
<input type="radio" name="type-{{ index }}" data-type="User Input" {% if type == "User Input" %} checked {% endif %}>
|
||||
{{ _("User Input") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
|
||||
{% for i in range(1,5) %}
|
||||
{% set num = frappe.utils.cstr(i) %}
|
||||
|
||||
{% set option = question["option_" + num] %}
|
||||
{% set explanation = question["explanation_" + num] %}
|
||||
{% set possible_answer = question["possibility_" + num] %}
|
||||
|
||||
<div class="field-group">
|
||||
|
||||
<div class="options-group {% if type == 'User Input' %} hide {% endif %}">
|
||||
<input type="text" placeholder="Option" class="field-input option-{{ num }}" {% if option %} value="{{ option }}" {% endif %}>
|
||||
<input type="text" placeholder="Explanation" class="field-input explanation-{{ num }}" {% if explanation %} value="{{ explanation }}" {% endif %}>
|
||||
<label class="vertically-center mt-1">
|
||||
<input type="checkbox" class="correct-{{ num }}" {% if question['is_correct_' + num] %} checked {% endif %}>
|
||||
{{ _("Is Correct") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="answers-group {% if type == 'Choices' %} hide {% endif %}">
|
||||
<div class="field-label">
|
||||
{{ _("Possible Answers") }} {{ num }}
|
||||
</div>
|
||||
<textarea class="field-input possibility-{{ num }}"
|
||||
style="height: 100px;">{% if possible_answer %}{{ possible_answer }}{% endif %}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,6 +1,10 @@
|
||||
frappe.ready(() => {
|
||||
$(".btn-question").click((e) => {
|
||||
if ($(".question-card").length <= 1) {
|
||||
add_question();
|
||||
}
|
||||
|
||||
$(".btn-add-question").click((e) => {
|
||||
add_question(true);
|
||||
});
|
||||
|
||||
$(".btn-save-question").click((e) => {
|
||||
@@ -11,7 +15,7 @@ frappe.ready(() => {
|
||||
frappe.utils.copy_to_clipboard($(e.currentTarget).data("name"));
|
||||
});
|
||||
|
||||
$(".question-type").click((e) => {
|
||||
$(document).on("click", ".question-type", (e) => {
|
||||
toggle_form($(e.currentTarget));
|
||||
});
|
||||
|
||||
@@ -43,72 +47,21 @@ const toggle_form = (el) => {
|
||||
}
|
||||
};
|
||||
|
||||
const add_question = () => {
|
||||
let add_after = $(".quiz-card").length
|
||||
? $(".quiz-card:last")
|
||||
: $("#quiz-title");
|
||||
let question_template = `<div class="quiz-card new-quiz-card">
|
||||
<div contenteditable="true" data-placeholder="${__(
|
||||
"Question"
|
||||
)}" class="question mb-4"></div>
|
||||
<select value="{{ question.type }}" class="input-with-feedback form-control ellipsis type" maxlength="140" data-fieldtype="Select" data-fieldname="type" placeholder="" data-doctype="LMS Quiz Question">
|
||||
<option value="Choices"> ${__("Choices")} </option>
|
||||
<option value="User Input"> ${__("User Input")} </option>
|
||||
</select>
|
||||
</div>`;
|
||||
$(question_template).insertAfter(add_after);
|
||||
get_question_template();
|
||||
$(".btn-save-question").removeClass("hide");
|
||||
const add_question = (scroll = false) => {
|
||||
let template = $("#question-template").html();
|
||||
let index = $(".question-card:nth-last-child(2)").data("index") + 1 || 1;
|
||||
template = update_index(template, index);
|
||||
|
||||
$(template).insertBefore($("#question-template"));
|
||||
scroll && scroll_to_question_container();
|
||||
};
|
||||
|
||||
const get_question_template = () => {
|
||||
Array.from({ length: 4 }, (x, num) => {
|
||||
let option_template = get_option_template(num + 1);
|
||||
|
||||
let add_after = $(".quiz-card:last .option-group").length
|
||||
? $(".quiz-card:last .option-group").last()
|
||||
: $(".type:last");
|
||||
question_template = $(option_template).insertAfter(add_after);
|
||||
});
|
||||
|
||||
Array.from({ length: 4 }, (x, num) => {
|
||||
let possibility_template = get_possibility_template(num + 1);
|
||||
let add_after = $(".quiz-card:last .possibility-group").length
|
||||
? $(".quiz-card:last .possibility-group").last()
|
||||
: $(".quiz-card:last .option-group:last");
|
||||
question_template = $(possibility_template).insertAfter(add_after);
|
||||
});
|
||||
};
|
||||
|
||||
const get_possibility_template = (num) => {
|
||||
return `<div class="possibility-group mt-4 hide">
|
||||
<label class=""> ${__("Possible Answer")} ${num} </label>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<div contenteditable="true" class="input-with-feedback form-control bold possibility-{{ num }}" style="height: 100px;" spellcheck="false"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
const get_option_template = (num) => {
|
||||
return `<div class="option-group mt-4">
|
||||
<label class="">${__("Option")} ${num}</label>
|
||||
<div class="d-flex justify-content-between option-${num}">
|
||||
<div contenteditable="true" data-placeholder="${__(
|
||||
"Option"
|
||||
)}"
|
||||
class="option-input"></div>
|
||||
<div contenteditable="true" data-placeholder="${__(
|
||||
"Explanation"
|
||||
)}"
|
||||
class="option-input"></div>
|
||||
<div class="option-checkbox">
|
||||
<input type="checkbox">
|
||||
<label class="mb-0"> ${__("Is Correct")} </label>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
const update_index = (template, index) => {
|
||||
const $template = $(template);
|
||||
$template.attr("data-index", index);
|
||||
$template.find(".question-label").text("Question " + index);
|
||||
$template.find(".question-type input").attr("name", "type-" + index);
|
||||
return $template.prop("outerHTML");
|
||||
};
|
||||
|
||||
const save_question = (e) => {
|
||||
@@ -124,7 +77,7 @@ const save_question = (e) => {
|
||||
quiz: $("#quiz-title").data("name") || "",
|
||||
},
|
||||
callback: (data) => {
|
||||
window.location.reload();
|
||||
window.location.href = `/quizzes/${data.message}`;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -203,15 +156,15 @@ const validate_mandatory = (details, correct_options, possibilities) => {
|
||||
};
|
||||
|
||||
const scroll_to_question_container = () => {
|
||||
scroll_to_element(".new-quiz-card:last");
|
||||
$(".new-quiz-card").find(".question").focus();
|
||||
scroll_to_element(".question-card:nth-last-child(2)");
|
||||
$(".question-card:nth-last-child(2)").find(".question").focus();
|
||||
};
|
||||
|
||||
const scroll_to_element = (element) => {
|
||||
if ($(element).length)
|
||||
$([document.documentElement, document.body]).animate(
|
||||
{
|
||||
scrollTop: $(element).offset().top,
|
||||
scrollTop: $(element).offset().top - 100,
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<div class="mt-5">
|
||||
<ul class="list-unstyled">
|
||||
{% for quiz in quiz_list %}
|
||||
<li>
|
||||
<li class="mt-2">
|
||||
<a class="clickable" href="/quizzes/{{ quiz.name }}">
|
||||
{{ quiz.title }}
|
||||
</a>
|
||||
@@ -58,22 +58,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
|
||||
$(".copy-quiz-id").click((e) => {
|
||||
e.preventDefault();
|
||||
frappe.utils.copy_to_clipboard($(e.currentTarget).data("name"));
|
||||
});
|
||||
|
||||
$(".quiz-row").click((e) => {
|
||||
if (!$(e.target).hasClass("copy-quiz-id")) {
|
||||
window.location.href = `/quizzes/${$(e.currentTarget).data('name')}`;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
frappe.ready(() => {
|
||||
hide_wrapped_mentor_cards();
|
||||
|
||||
$("#cancel-request").click((e) => {
|
||||
cancel_mentor_request(e);
|
||||
});
|
||||
|
||||
$(".view-all-mentors").click((e) => {
|
||||
view_all_mentors(e);
|
||||
});
|
||||
|
||||
$(".review-link").click((e) => {
|
||||
show_review_dialog(e);
|
||||
});
|
||||
@@ -48,30 +40,6 @@ frappe.ready(() => {
|
||||
$(document).on("click", ".slot", (e) => {
|
||||
select_slot(e);
|
||||
});
|
||||
|
||||
$(".btn-attach").click((e) => {
|
||||
show_upload_modal(e);
|
||||
});
|
||||
|
||||
$(".btn-clear").click((e) => {
|
||||
clear_image(e);
|
||||
});
|
||||
|
||||
$(".btn-tag").click((e) => {
|
||||
add_tag(e);
|
||||
});
|
||||
|
||||
$(".btn-save-course").click((e) => {
|
||||
save_course(e);
|
||||
});
|
||||
|
||||
$(".btn-delete-tag").click((e) => {
|
||||
remove_tag(e);
|
||||
});
|
||||
|
||||
if ($("#description").length) {
|
||||
make_editor();
|
||||
}
|
||||
});
|
||||
|
||||
const hide_wrapped_mentor_cards = () => {
|
||||
@@ -92,42 +60,6 @@ const hide_wrapped_mentor_cards = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const cancel_mentor_request = (e) => {
|
||||
e.preventDefault();
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_mentor_request.lms_mentor_request.cancel_request",
|
||||
args: {
|
||||
course: decodeURIComponent($(e.currentTarget).attr("data-course")),
|
||||
},
|
||||
callback: (data) => {
|
||||
if (data.message == "OK") {
|
||||
$("#mentor-request").removeClass("hide");
|
||||
$("#already-applied").addClass("hide");
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const view_all_mentors = (e) => {
|
||||
$(".wrapped").each((i, element) => {
|
||||
$(element).slideToggle("slow");
|
||||
});
|
||||
var text_element = $(
|
||||
".view-all-mentors .course-instructor .all-mentors-text"
|
||||
);
|
||||
var text =
|
||||
text_element.text() == "View all mentors"
|
||||
? "View less"
|
||||
: "View all mentors";
|
||||
text_element.text(text);
|
||||
|
||||
if ($(".mentor-icon").css("transform") == "none") {
|
||||
$(".mentor-icon").css("transform", "rotate(180deg)");
|
||||
} else {
|
||||
$(".mentor-icon").css("transform", "");
|
||||
}
|
||||
};
|
||||
|
||||
const show_review_dialog = (e) => {
|
||||
e.preventDefault();
|
||||
$("#review-modal").modal("show");
|
||||
@@ -326,84 +258,3 @@ const close_slot_modal = (e) => {
|
||||
$("#slot-date").val("");
|
||||
$(".slot-label").addClass("hide");
|
||||
};
|
||||
|
||||
const show_upload_modal = () => {
|
||||
new frappe.ui.FileUploader({
|
||||
folder: "Home/Attachments",
|
||||
restrictions: {
|
||||
allowed_file_types: ["image/*"],
|
||||
},
|
||||
on_success: (file_doc) => {
|
||||
$(".course-image-attachment").removeClass("hide");
|
||||
$(".course-image-attachment a")
|
||||
.attr("href", file_doc.file_url)
|
||||
.text(file_doc.file_url);
|
||||
$(".btn-attach").addClass("hide");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const clear_image = () => {
|
||||
$(".course-image-attachment").addClass("hide");
|
||||
$(".course-image-attachment a").removeAttr("href");
|
||||
$(".btn-attach").removeClass("hide");
|
||||
};
|
||||
|
||||
const add_tag = (e) => {
|
||||
$(`<div class="course-card-pills" contenteditable="true"
|
||||
data-placeholder="${__("Tag")}"></div>`).insertBefore(`.btn-tag`);
|
||||
};
|
||||
|
||||
const save_course = (e) => {
|
||||
let tags = $(".course-card-pills")
|
||||
.map((i, el) => $(el).text().trim())
|
||||
.get();
|
||||
tags = tags.filter((word) => word.trim().length > 0);
|
||||
|
||||
frappe.call({
|
||||
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,
|
||||
course: $("#title").data("course")
|
||||
? $("#title").data("course")
|
||||
: "",
|
||||
published: $("#published").prop("checked") ? 1 : 0,
|
||||
upcoming: $("#upcoming").prop("checked") ? 1 : 0,
|
||||
},
|
||||
callback: (data) => {
|
||||
frappe.show_alert({
|
||||
message: __("Saved"),
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.href = `/courses/${data.message}?edit=1`;
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const remove_tag = (e) => {
|
||||
$(e.currentTarget).closest(".course-card-pills").remove();
|
||||
};
|
||||
|
||||
const make_editor = () => {
|
||||
this.code_field_group = new frappe.ui.FieldGroup({
|
||||
fields: [
|
||||
{
|
||||
fieldname: "code_md",
|
||||
fieldtype: "Text Editor",
|
||||
default: $(".description-data").html(),
|
||||
},
|
||||
],
|
||||
body: $("#description").get(0),
|
||||
});
|
||||
this.code_field_group.make();
|
||||
$("#description .form-section:last").removeClass("empty-section");
|
||||
$("#description .frappe-control").removeClass("hide-control");
|
||||
$("#description .form-column").addClass("p-0");
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<div class="field-parent">
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
<div class="field-label reqd">
|
||||
{{ _("Title") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
@@ -60,21 +60,7 @@
|
||||
|
||||
<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">
|
||||
<div class="field-label reqd">
|
||||
{{ _("Short Introduction") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
@@ -86,6 +72,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label reqd">
|
||||
{{ _("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>
|
||||
<div class="field-label">
|
||||
{{ _("Preview Video ID") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
{{ _("Enter the Preview Video ID. The ID is the part of the URL after <code>watch?v=</code>. For example, if the URL is <code>https://www.youtube.com/watch?v=QH2-TGUlwu4</code>, the ID is <code>QH2-TGUlwu4</code>") }}
|
||||
</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">
|
||||
@@ -110,12 +127,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<label for="published" class="mb-0">
|
||||
<div class="field-group vertically-center">
|
||||
<label for="published" class="vertically-center mb-0">
|
||||
<input type="checkbox" id="published" {% if course.published %} checked {% endif %}>
|
||||
{{ _("Published") }}
|
||||
</label>
|
||||
<label for="upcoming" class="mb-0 ml-20">
|
||||
<label for="upcoming" class="vertically-center mb-0 ml-20">
|
||||
<input type="checkbox" id="upcoming" {% if course.upcoming %} checked {% endif %}>
|
||||
{{ _("Upcoming") }}
|
||||
</label>
|
||||
@@ -138,23 +155,6 @@
|
||||
<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") }}
|
||||
|
||||
@@ -20,6 +20,12 @@ frappe.ready(() => {
|
||||
make_editor();
|
||||
}
|
||||
|
||||
$(".field-input").focusout((e) => {
|
||||
if ($(e.currentTarget).siblings(".error-message")) {
|
||||
$(e.currentTarget).siblings(".error-message").remove();
|
||||
}
|
||||
});
|
||||
|
||||
$("#tags-input").focus((e) => {
|
||||
$(e.target).keypress((e) => {
|
||||
if (e.which == 13) {
|
||||
@@ -50,6 +56,7 @@ const create_tag = (e) => {
|
||||
};
|
||||
|
||||
const save_course = (e) => {
|
||||
validate_mandatory();
|
||||
let tags = $(".tags button")
|
||||
.map((i, el) => $(el).text().trim())
|
||||
.get();
|
||||
@@ -75,12 +82,45 @@ const save_course = (e) => {
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
window.location.href = `/courses/${data.message}/edit`;
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const validate_mandatory = () => {
|
||||
let fields = $(".field-label.reqd");
|
||||
fields.each((i, el) => {
|
||||
let input = $(el).closest(".field-group").find(".field-input");
|
||||
if (input.length && input.val().trim() == "") {
|
||||
if (input.siblings(".error-message").length == 0) {
|
||||
let error = document.createElement("p");
|
||||
error.classList.add("error-message");
|
||||
error.innerText = `Please enter a ${$(el).text().trim()}`;
|
||||
$(error).insertAfter($(input));
|
||||
}
|
||||
scroll_to_element(input);
|
||||
throw "Mandatory field missing";
|
||||
}
|
||||
});
|
||||
console.log(this.description.fields_dict["description"].value);
|
||||
if (!this.description.fields_dict["description"].value) {
|
||||
scroll_to_element("#description");
|
||||
frappe.throw(__(`Please enter a description`));
|
||||
}
|
||||
};
|
||||
|
||||
const scroll_to_element = (element) => {
|
||||
if ($(element).length) {
|
||||
$([document.documentElement, document.body]).animate(
|
||||
{
|
||||
scrollTop: $(element).offset().top - 100,
|
||||
},
|
||||
1000
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const make_editor = () => {
|
||||
this.description = new frappe.ui.FieldGroup({
|
||||
fields: [
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
|
||||
<div class="field-group">
|
||||
<div>
|
||||
<div class="field-label">
|
||||
<div class="field-label reqd">
|
||||
{{ _("Chapter Title") }}
|
||||
</div>
|
||||
<div class="field-description">
|
||||
|
||||
@@ -35,6 +35,7 @@ const show_chapter_modal = (e) => {
|
||||
};
|
||||
|
||||
const save_chapter = (e) => {
|
||||
validate_mandatory();
|
||||
let parent = $("#chapter-modal");
|
||||
|
||||
frappe.call({
|
||||
@@ -58,6 +59,16 @@ const save_chapter = (e) => {
|
||||
});
|
||||
};
|
||||
|
||||
const validate_mandatory = () => {
|
||||
if (!$("#chapter-title").val()) {
|
||||
let error = $("p")
|
||||
.addClass("error-message")
|
||||
.text("Chapter title is required");
|
||||
$(error).insertAfter("#chapter-title");
|
||||
throw __("Chapter title is required");
|
||||
}
|
||||
};
|
||||
|
||||
const setSortable = (el) => {
|
||||
new Sortable(el, {
|
||||
group: "drag",
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import frappe
|
||||
from lms.lms.utils import get_chapters
|
||||
from frappe import _
|
||||
from lms.lms.utils import get_chapters, can_create_courses
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
|
||||
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.db.get_value(
|
||||
"LMS Course", frappe.form_dict["course"], ["name", "title"], as_dict=True
|
||||
)
|
||||
|
||||
24
yarn.lock
24
yarn.lock
@@ -203,6 +203,16 @@
|
||||
"@babel/helper-validator-identifier" "^7.19.1"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@codexteam/icons@^0.0.4":
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.4.tgz#8b72dcd3f3a1b0d880bdceb2abebd74b46d3ae13"
|
||||
integrity sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q==
|
||||
|
||||
"@codexteam/icons@^0.0.5":
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.5.tgz#d17f39b6a0497c6439f57dd42711817a3dd3679c"
|
||||
integrity sha512-s6H2KXhLz2rgbMZSkRm8dsMJvyUNZsEjxobBEg9ztdrb1B2H3pEzY6iTwI4XUPJWJ3c3qRKwV4TrO3J5jUdoQA==
|
||||
|
||||
"@colors/colors@1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||
@@ -264,6 +274,20 @@
|
||||
debug "^3.1.0"
|
||||
lodash.once "^4.1.1"
|
||||
|
||||
"@editorjs/header@^2.7.0":
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@editorjs/header/-/header-2.7.0.tgz#755d104a9210a8e2d9ccf22b175b2a93bdbb2330"
|
||||
integrity sha512-4fGKGe2ZYblVqR/P/iw5ieG00uXInFgNMftBMqJRYcB2hUPD30kuu7Sn6eJDcLXoKUMOeqi8Z2AlUxYAmvw7zQ==
|
||||
dependencies:
|
||||
"@codexteam/icons" "^0.0.5"
|
||||
|
||||
"@editorjs/list@^1.8.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@editorjs/list/-/list-1.8.0.tgz#c64b88679f23c0129ffac589004300832c345d3b"
|
||||
integrity sha512-Vq6cjyTXBzgegYv/MtTfuDdiz59yGhDEc/yAVXr6lmvoWAFs9cJ4TLuh4/9SbrbhIptcQLDvUjMDKmRrV6v2NQ==
|
||||
dependencies:
|
||||
"@codexteam/icons" "^0.0.4"
|
||||
|
||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||
|
||||
Reference in New Issue
Block a user