diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 48f3c5dd..2be9a71c 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -33,19 +33,17 @@ describe("Course Creation", () => { 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(".collapse-section.collapsed:first").click(); + cy.get("#lesson-content .ce-block") + .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. {enter}" + ); + cy.get("#lesson-content .ce-toolbar__plus").click(); + cy.get('#lesson-content [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 diff --git a/lms/lms/doctype/course_lesson/course_lesson.json b/lms/lms/doctype/course_lesson/course_lesson.json index 3fd75f37..4bd3c65d 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.json +++ b/lms/lms/doctype/course_lesson/course_lesson.json @@ -135,13 +135,13 @@ }, { "fieldname": "instructor_notes", - "fieldtype": "Text", + "fieldtype": "Markdown Editor", "label": "Instructor Notes" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-08-31 21:47:06.314995", + "modified": "2023-09-27 15:45:54.738573", "modified_by": "Administrator", "module": "LMS", "name": "Course Lesson", diff --git a/lms/lms/utils.py b/lms/lms/utils.py index f66db678..dfefe26f 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -152,11 +152,6 @@ def get_lesson_details(chapter): ) lesson_details.number = flt(f"{chapter.idx}.{row.idx}") lesson_details.icon = get_lesson_icon(lesson_details.body) - if lesson_details.instructor_notes: - lesson_details.instructor_notes_html = markdown_to_html( - lesson_details.instructor_notes - ) - lessons.append(lesson_details) return lessons diff --git a/lms/public/css/style.css b/lms/public/css/style.css index a6ea778b..5cd1fb3f 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -160,6 +160,10 @@ textarea.field-input { position: unset; } +.codex-editor--narrow .ce-toolbar__actions { + right: 100%; +} + .lesson-editor { border: 1px solid var(--gray-300); border-radius: var(--border-radius-md); @@ -2453,4 +2457,16 @@ select { .batch-details { width: 100%; } +} + +.collapse-section { + font-size: var(--text-lg); + cursor: pointer; +} + +.collapse-section.collapsed .icon { + transition: all 0.5s; + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + transform: rotate(180deg); } \ No newline at end of file diff --git a/lms/www/batch/edit.html b/lms/www/batch/edit.html index 641f5256..5f47b3a0 100644 --- a/lms/www/batch/edit.html +++ b/lms/www/batch/edit.html @@ -75,41 +75,54 @@
-
-
- {{ _("Instructor Notes") }} + -
- {{ _("These notes will only be visible to the Course Creator, Course Evaluaor and Moderator.") }} + +
+
-
- {% if lesson.instructor_notes %} -
{{ lesson.instructor_notes }}
+ + {% if lesson.body %} +
{{ lesson.body }}
{% endif %}
-
+ +
+
+
+ {% if lesson.instructor_notes %} +
{{ lesson.instructor_notes }}
{% endif %}
- {% endmacro %} diff --git a/lms/www/batch/edit.js b/lms/www/batch/edit.js index 69f4c463..9b124812 100644 --- a/lms/www/batch/edit.js +++ b/lms/www/batch/edit.js @@ -1,110 +1,129 @@ frappe.ready(() => { let self = this; - + frappe.require("controls.bundle.js"); frappe.telemetry.capture("on_lesson_creation_page", "lms"); - if ($("#instructor-notes").length) { - frappe.require("controls.bundle.js", () => { - make_instructor_notes_component(); - }); - } - if ($("#current-lesson-content").length) { - parse_string_to_lesson(); + parse_string_to_lesson("lesson"); } - setup_editor(); + if ($("#current-instructor-notes").length) { + parse_string_to_lesson("notes"); + } + + setup_editor_for_lesson_content(); + setup_editor_for_instructor_notes(); $("#save-lesson").click((e) => { save_lesson(e); }); }); -const setup_editor = () => { +const setup_editor_for_lesson_content = () => { self.editor = new EditorJS({ holder: "lesson-content", - tools: { - embed: { - class: Embed, - config: { - services: { - youtube: true, - vimeo: true, - codepen: true, - slides: { - regex: /https:\/\/docs\.google\.com\/presentation\/d\/e\/([A-Za-z0-9_-]+)\/pub/, - embedUrl: - "https://docs.google.com/presentation/d/e/<%= remote_id %>/embed", - html: "", - }, - pdf: { - regex: /(https?:\/\/.*\.pdf)/, - embedUrl: "<%= remote_id %>", - html: "", - }, - }, - }, - }, - header: { - class: Header, - inlineToolbar: ["bold", "italic", "link"], - config: { - levels: [4, 5, 6], - defaultLevel: 5, - }, - icon: ` - - `, - }, - paragraph: { - class: Paragraph, - inlineToolbar: true, - config: { - preserveBlank: true, - }, - }, - youtube: YouTubeVideo, - quiz: Quiz, - upload: Upload, - }, + tools: get_tools(), data: { - blocks: self.blocks ? self.blocks : [], + blocks: self.lesson_blocks || [], }, }); }; -const parse_string_to_lesson = () => { - let lesson_content = $("#current-lesson-content").html(); - let lesson_blocks = []; +const setup_editor_for_instructor_notes = () => { + self.instructor_notes_editor = new EditorJS({ + holder: "instructor-notes", + tools: get_tools(), + data: { + blocks: self.notes_blocks || [], + }, + }); +}; - lesson_content.split("\n").forEach((block) => { +const get_tools = () => { + return { + embed: { + class: Embed, + config: { + services: { + youtube: true, + vimeo: true, + codepen: true, + slides: { + regex: /https:\/\/docs\.google\.com\/presentation\/d\/e\/([A-Za-z0-9_-]+)\/pub/, + embedUrl: + "https://docs.google.com/presentation/d/e/<%= remote_id %>/embed", + html: "", + }, + pdf: { + regex: /(https?:\/\/.*\.pdf)/, + embedUrl: "<%= remote_id %>", + html: "", + }, + }, + }, + }, + header: { + class: Header, + inlineToolbar: ["bold", "italic", "link"], + config: { + levels: [4, 5, 6], + defaultLevel: 5, + }, + icon: ` + + `, + }, + paragraph: { + class: Paragraph, + inlineToolbar: true, + config: { + preserveBlank: true, + }, + }, + youtube: YouTubeVideo, + quiz: Quiz, + upload: Upload, + }; +}; + +const parse_string_to_lesson = (type) => { + let content; + let blocks = []; + + if (type == "lesson") { + content = $("#current-lesson-content").html(); + } else if (type == "notes") { + content = $("#current-instructor-notes").html(); + } + + content.split("\n").forEach((block) => { if (block.includes("{{ YouTubeVideo")) { - let youtube_id = block.match(/'([^']+)'/)[1]; - lesson_blocks.push({ + let youtube_id = block.match(/\(["']([^"']+?)["']\)/)[1]; + blocks.push({ type: "youtube", data: { youtube: youtube_id, }, }); } else if (block.includes("{{ Quiz")) { - let quiz = block.match(/'([^']+)'/)[1]; - lesson_blocks.push({ + let quiz = block.match(/\(["']([^"']+?)["']\)/)[1]; + blocks.push({ type: "quiz", data: { quiz: quiz, }, }); } else if (block.includes("{{ Video")) { - let video = block.match(/'([^']+)'/)[1]; - lesson_blocks.push({ + let video = block.match(/\(["']([^"']+?)["']\)/)[1]; + blocks.push({ type: "upload", data: { file_url: video, }, }); } else if (block.includes("{{ Embed")) { - let embed = block.match(/'([^']+)'/)[1]; - lesson_blocks.push({ + let embed = block.match(/\(["']([^"']+?)["']\)/)[1]; + blocks.push({ type: "embed", data: { service: embed.split("|||")[0], @@ -113,7 +132,7 @@ const parse_string_to_lesson = () => { }); } else if (block.includes("![]")) { let image = block.match(/\((.*?)\)/)[1]; - lesson_blocks.push({ + blocks.push({ type: "upload", data: { file_url: image, @@ -121,7 +140,7 @@ const parse_string_to_lesson = () => { }); } else if (block.includes("#")) { let level = (block.match(/#/g) || []).length; - lesson_blocks.push({ + blocks.push({ type: "header", data: { text: block.replace(/#/g, "").trim(), @@ -129,7 +148,7 @@ const parse_string_to_lesson = () => { }, }); } else { - lesson_blocks.push({ + blocks.push({ type: "paragraph", data: { text: block, @@ -138,16 +157,25 @@ const parse_string_to_lesson = () => { } }); - this.blocks = lesson_blocks; + if (type == "lesson") { + this.lesson_blocks = blocks; + } else if (type == "notes") { + this.notes_blocks = blocks; + } }; const save_lesson = (e) => { self.editor.save().then((outputData) => { - parse_lesson_to_string(outputData); + parse_content_to_string(outputData, "lesson"); + + self.instructor_notes_editor.save().then((outputData) => { + parse_content_to_string(outputData, "notes"); + save(); + }); }); }; -const parse_lesson_to_string = (data) => { +const parse_content_to_string = (data, type) => { let lesson_content = ""; data.blocks.forEach((block) => { if (block.type == "youtube") { @@ -175,24 +203,28 @@ const parse_lesson_to_string = (data) => { }|||${block.data.embed.replace(/&/g, "&")}") }}\n`; } }); - save(lesson_content); + if (type == "lesson") { + this.lesson_content_data = lesson_content; + } else if (type == "notes") { + this.instructor_notes_data = lesson_content; + } }; -const save = (lesson_content) => { - validate_mandatory(lesson_content); +const save = () => { + console.log(this.instructor_notes_data); + console.log(this.lesson_content_data); + validate_mandatory(this.lesson_content_data); 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, + body: this.lesson_content_data, chapter: $("#lesson-title").data("chapter"), preview: $("#preview").prop("checked") ? 1 : 0, idx: $("#lesson-title").data("index"), lesson: lesson ? lesson : "", - instructor_notes: - this.instructor_notes.get_values().instructor_notes, + instructor_notes: this.instructor_notes_data, }, callback: (data) => { frappe.show_alert({ diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index a4720d3a..60279848 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -155,13 +155,18 @@
{% endif %} - {% if lesson.instructor_notes and (is_moderator or instructor or is_evaluator) %} -
-
- {{ _("Instructor Notes") }} + {% if instructor_notes and (is_moderator or instructor or is_evaluator) %} +
+ -
- {{ lesson.instructor_notes_html }} +
+ {{ instructor_notes }}
{% endif %} @@ -227,36 +232,6 @@
{% endmacro %} - - -{% macro HelpArticle() %} -
-

{{ _("Embed Components") }}

-

- {{ _("You can add additional content to the lesson using a special syntax. The table below mentions - all types of dynamic content that you can add to the lessons and the syntax for the same.") }} -

-
    -
  1. - {{ _("YouTube Video") }} -

    To get the YouTube Video ID, follow the steps mentioned below.

    -
      -
    • - {{ _("Upload the video on youtube.") }} -
    • -
    • - {{ _("When you share a youtube video, it shows a URL") }} -
    • -
    • - {{ _("Copy the last parameter of the URL and paste it here.") }} -
    • -
    -
  2. -
-
-{% endmacro %} - - {% macro Discussions() %} {% set topics_count = frappe.db.count("Discussion Topic", { diff --git a/lms/www/batch/learn.py b/lms/www/batch/learn.py index c1fde031..c77233b7 100644 --- a/lms/www/batch/learn.py +++ b/lms/www/batch/learn.py @@ -1,6 +1,7 @@ import frappe from frappe import _ from frappe.utils import cstr, flt +from lms.lms.md import markdown_to_html from lms.lms.utils import ( get_lesson_url, @@ -46,6 +47,9 @@ def get_context(context): context.is_moderator = has_course_moderator_role() context.is_evaluator = has_course_evaluator_role() + if context.lesson.instructor_notes: + context.instructor_notes = markdown_to_html(context.lesson.instructor_notes) + context.show_lesson = ( context.membership or (context.lesson and context.lesson.include_in_preview) diff --git a/lms/www/batches/batch_details.html b/lms/www/batches/batch_details.html index d543bc88..9fd5c93e 100644 --- a/lms/www/batches/batch_details.html +++ b/lms/www/batches/batch_details.html @@ -172,6 +172,7 @@ {% macro CourseList(courses) %} +{% if courses | length or is_moderator %}
@@ -215,6 +216,7 @@
{% endif %}
+{% endif %} {% endmacro %}