From 81fb664ad95cb276a73b1ec644ea80f751c0cd77 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 20 Apr 2022 17:45:40 +0530 Subject: [PATCH 1/3] feat: image markdown extension --- lms/hooks.py | 3 ++- lms/lms/md.py | 3 ++- lms/lms/web_form/lesson/lesson.json | 6 +++--- lms/plugins.py | 3 +++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lms/hooks.py b/lms/hooks.py index 81783b67..1a4c774a 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -232,7 +232,8 @@ lms_markdown_macro_renderers = { "Quiz": "lms.plugins.quiz_renderer", "YouTubeVideo": "lms.plugins.youtube_video_renderer", "Video": "lms.plugins.video_renderer", - "Assignment": "lms.plugins.assignment_renderer" + "Assignment": "lms.plugins.assignment_renderer", + "Image": "lms.plugin.image_renderer" } # page_renderer to manage profile pages diff --git a/lms/lms/md.py b/lms/lms/md.py index b02ed68f..99bea3d3 100644 --- a/lms/lms/md.py +++ b/lms/lms/md.py @@ -25,6 +25,7 @@ import html as HTML def markdown_to_html(text): """Renders markdown text into html. """ + print(text) return markdown.markdown(text, extensions=['fenced_code', MacroExtension()]) def find_macros(text): @@ -105,7 +106,7 @@ def sanitize_html(html, macro): any broken tags. This makes sures that all those things are fixed before passing to the etree parser. """ - soup = BeautifulSoup(html, features="lxml") + soup = BeautifulSoup(html, features="html5lib") nodes = soup.body.children classname = "" if macro == "YouTubeVideo": diff --git a/lms/lms/web_form/lesson/lesson.json b/lms/lms/web_form/lesson/lesson.json index 2c519587..210a830a 100644 --- a/lms/lms/web_form/lesson/lesson.json +++ b/lms/lms/web_form/lesson/lesson.json @@ -16,12 +16,12 @@ "docstatus": 0, "doctype": "Web Form", "idx": 0, - "introduction_text": "



Create lessons for your course. You can add some 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.


Content TypeSyntax
Video{{ Video(\"url_of_source\") }}
YouTube Video{{ YouTubeVideo(\"unique_embed_id\") }}
Exercise{{ Exercise(\"exercise_name\") }}
Quiz{{ Quiz(\"lms_quiz_name\") }}
Assignment{{ Assignment(\"id-filetype\") }}
", + "introduction_text": "



Create lessons for your course. For adding content, use markdown syntax. You can add some 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.


Content TypeSyntax
Video{{ Video(\"url_of_source\") }}
YouTube Video{{ YouTubeVideo(\"unique_embed_id\") }}
Exercise{{ Exercise(\"exercise_name\") }}
Quiz{{ Quiz(\"lms_quiz_name\") }}
Assignment{{ Assignment(\"id-filetype\") }}
", "is_multi_step_form": 0, "is_standard": 1, "login_required": 1, "max_attachment_size": 0, - "modified": "2022-03-14 18:49:33.526455", + "modified": "2022-04-20 15:45:55.567759", "modified_by": "Administrator", "module": "LMS", "name": "lesson", @@ -80,7 +80,7 @@ "fieldname": "body", "fieldtype": "Text", "hidden": 0, - "label": "Body", + "label": "Content", "max_length": 0, "max_value": 0, "read_only": 0, diff --git a/lms/plugins.py b/lms/plugins.py index cd03d2a6..479d45e7 100644 --- a/lms/plugins.py +++ b/lms/plugins.py @@ -142,3 +142,6 @@ def show_custom_signup(): or frappe.db.get_single_value("LMS Settings", "privacy_policy")): return "lms/templates/signup-form.html" return "frappe/templates/signup.html" + +def image_renderer(src); + return f"" From f34519e3ffa281a83b4a68a9742fe94b44816f2a Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 22 Apr 2022 12:22:25 +0530 Subject: [PATCH 2/3] fix: upload image component in lesson web form --- lms/hooks.py | 3 +- lms/lms/web_form/lesson/lesson.js | 82 +++++++++++++++++++++++++++++-- lms/plugins.py | 3 -- lms/public/css/style.css | 29 +++++++++++ lms/public/icons/symbol-defs.svg | 13 +++++ 5 files changed, 120 insertions(+), 10 deletions(-) diff --git a/lms/hooks.py b/lms/hooks.py index 1a4c774a..81783b67 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -232,8 +232,7 @@ lms_markdown_macro_renderers = { "Quiz": "lms.plugins.quiz_renderer", "YouTubeVideo": "lms.plugins.youtube_video_renderer", "Video": "lms.plugins.video_renderer", - "Assignment": "lms.plugins.assignment_renderer", - "Image": "lms.plugin.image_renderer" + "Assignment": "lms.plugins.assignment_renderer" } # page_renderer to manage profile pages diff --git a/lms/lms/web_form/lesson/lesson.js b/lms/lms/web_form/lesson/lesson.js index 4347f730..e0467118 100644 --- a/lms/lms/web_form/lesson/lesson.js +++ b/lms/lms/web_form/lesson/lesson.js @@ -1,9 +1,44 @@ frappe.ready(function() { - frappe.web_form.after_save = () => { - frappe.call({ + + frappe.web_form.after_load = () => { + add_file_upload_component(); + }; + + frappe.web_form.after_save = () => { + show_success_message(); + }; + + $(document).on("click", ".add-attachment", (e) => { + show_upload_modal(); + }); + + $(document).on("click", ".copy-link", (e) => { + frappe.utils.copy_to_clipboard($(e.currentTarget).data("link")); + }); + +}); + +const show_upload_modal = () => { + new frappe.ui.FileUploader({ + folder: "Home/Attachments", + restrictions: { + allowed_file_types: ['image/*'] + }, + 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 show_success_message = () => { + frappe.call({ method: "lms.lms.doctype.course_lesson.course_lesson.get_lesson_info", args: { - "chapter": frappe.web_form.doc.chapter + "chapter": frappe.web_form.doc.chapter }, callback: (data) => { frappe.msgprint(__(`Lesson has been saved successfully. Go back to the chapter and add this lesson to the lessons table.`)); @@ -12,5 +47,42 @@ frappe.ready(function() { }, 3000); } }); - }; -}); +}; + +const add_file_upload_component = () => { + $(get_attachment_controls_html()).insertBefore($(`[data-fieldname="include_in_preview"]`).first()); +}; + +const get_attachment_controls_html = () => { + return ` +
+
+ +
+ + + + + {{ _("Upload Image") }} + +
+
+
+
+ `; +}; + +const build_attachment_table = (file_doc) => { + return $(` + + ${file_doc.file_name} + {{ _("Copy Link") }} + + `); +}; diff --git a/lms/plugins.py b/lms/plugins.py index 479d45e7..cd03d2a6 100644 --- a/lms/plugins.py +++ b/lms/plugins.py @@ -142,6 +142,3 @@ def show_custom_signup(): or frappe.db.get_single_value("LMS Settings", "privacy_policy")): return "lms/templates/signup-form.html" return "frappe/templates/signup.html" - -def image_renderer(src); - return f"" diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 9c1b0986..3fa90c9e 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -1414,3 +1414,32 @@ pre { .btn-outline-primary { border: 1px solid var(--primary-color); } + +.show-attachments { + padding-right: 0.5rem; + display: flex; + align-items: center; +} + +.attachment-controls { + display: flex; + align-items: center; + width: fit-content; + cursor: pointer; +} + +.attachments { + flex-direction: column; + padding: 0.5rem 0; + margin-top: 1rem; + position: absolute; + z-index: 1; + width: fit-content; + border-collapse: separate; + border-spacing: 1rem 0.5rem; +} + +.attachments-parent { + float: right; + font-size: var(--text-sm); +} diff --git a/lms/public/icons/symbol-defs.svg b/lms/public/icons/symbol-defs.svg index 0090c5c8..53708e2d 100644 --- a/lms/public/icons/symbol-defs.svg +++ b/lms/public/icons/symbol-defs.svg @@ -28,4 +28,17 @@ + + + + + + + + From 674c6a1684a0f5272a0981e19f478d2ea78fc983 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 25 Apr 2022 10:42:07 +0530 Subject: [PATCH 3/3] fix: web form description and redirection --- .../doctype/course_lesson/course_lesson.json | 5 ++- lms/lms/md.py | 3 +- lms/lms/web_form/lesson/lesson.js | 34 +++++++++++-------- lms/lms/web_form/lesson/lesson.json | 8 ++--- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/lms/lms/doctype/course_lesson/course_lesson.json b/lms/lms/doctype/course_lesson/course_lesson.json index c38059ac..5d19f852 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.json +++ b/lms/lms/doctype/course_lesson/course_lesson.json @@ -10,9 +10,9 @@ "field_order": [ "chapter", "course", - "include_in_preview", "column_break_4", "title", + "include_in_preview", "index_label", "section_break_6", "body", @@ -75,7 +75,6 @@ "fetch_from": "chapter.course", "fieldname": "course", "fieldtype": "Link", - "hidden": 1, "label": "Course", "options": "LMS Course", "read_only": 1 @@ -83,7 +82,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-03-14 18:56:31.969801", + "modified": "2022-04-22 12:59:23.641915", "modified_by": "Administrator", "module": "LMS", "name": "Course Lesson", diff --git a/lms/lms/md.py b/lms/lms/md.py index 99bea3d3..b02ed68f 100644 --- a/lms/lms/md.py +++ b/lms/lms/md.py @@ -25,7 +25,6 @@ import html as HTML def markdown_to_html(text): """Renders markdown text into html. """ - print(text) return markdown.markdown(text, extensions=['fenced_code', MacroExtension()]) def find_macros(text): @@ -106,7 +105,7 @@ def sanitize_html(html, macro): any broken tags. This makes sures that all those things are fixed before passing to the etree parser. """ - soup = BeautifulSoup(html, features="html5lib") + soup = BeautifulSoup(html, features="lxml") nodes = soup.body.children classname = "" if macro == "YouTubeVideo": diff --git a/lms/lms/web_form/lesson/lesson.js b/lms/lms/web_form/lesson/lesson.js index e0467118..ad73a38c 100644 --- a/lms/lms/web_form/lesson/lesson.js +++ b/lms/lms/web_form/lesson/lesson.js @@ -2,10 +2,11 @@ frappe.ready(function() { frappe.web_form.after_load = () => { add_file_upload_component(); + fetch_course(); }; frappe.web_form.after_save = () => { - show_success_message(); + show_success_message(); }; $(document).on("click", ".add-attachment", (e) => { @@ -14,10 +15,23 @@ frappe.ready(function() { $(document).on("click", ".copy-link", (e) => { frappe.utils.copy_to_clipboard($(e.currentTarget).data("link")); + $(".attachments").collapse("hide"); }); }); +const fetch_course = () => { + frappe.call({ + method: "lms.lms.doctype.course_lesson.course_lesson.get_lesson_info", + args: { + "chapter": frappe.web_form.doc.chapter + }, + callback: (data) => { + this.course = data.message; + } + }); +} + const show_upload_modal = () => { new frappe.ui.FileUploader({ folder: "Home/Attachments", @@ -35,18 +49,10 @@ const show_upload_modal = () => { }; const show_success_message = () => { - frappe.call({ - method: "lms.lms.doctype.course_lesson.course_lesson.get_lesson_info", - args: { - "chapter": frappe.web_form.doc.chapter - }, - callback: (data) => { - frappe.msgprint(__(`Lesson has been saved successfully. Go back to the chapter and add this lesson to the lessons table.`)); - setTimeout(() => { - window.location.href = `/courses/${data.message}`; - }, 3000); - } - }); + frappe.msgprint(__(`Lesson has been saved successfully. Go back to the chapter and add this lesson to the lessons table.`)); + setTimeout(() => { + window.location.href = `/courses/${this.course}`; + }, 2000); }; const add_file_upload_component = () => { @@ -68,7 +74,7 @@ const get_attachment_controls_html = () => { - {{ _("Upload Image") }} + {{ _("Upload Attachments") }} diff --git a/lms/lms/web_form/lesson/lesson.json b/lms/lms/web_form/lesson/lesson.json index 210a830a..346c4459 100644 --- a/lms/lms/web_form/lesson/lesson.json +++ b/lms/lms/web_form/lesson/lesson.json @@ -11,17 +11,17 @@ "apply_document_permissions": 1, "button_label": "Save", "creation": "2022-03-07 18:41:42.549831", - "custom_css": "", + "custom_css": "#introduction {\n font-size: var(--text-base);\n}", "doc_type": "Course Lesson", "docstatus": 0, "doctype": "Web Form", "idx": 0, - "introduction_text": "



Create lessons for your course. For adding content, use markdown syntax. You can add some 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.


Content TypeSyntax
Video{{ Video(\"url_of_source\") }}
YouTube Video{{ YouTubeVideo(\"unique_embed_id\") }}
Exercise{{ Exercise(\"exercise_name\") }}
Quiz{{ Quiz(\"lms_quiz_name\") }}
Assignment{{ Assignment(\"id-filetype\") }}
", + "introduction_text": "
\n

Create lessons for your course. For adding content, use markdown syntax. You can add some 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.

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Content Type\n \n Syntax \n
Assignment {{ Assignment(\"id-filetype\") }}
Quiz {{ Quiz(\"lms_quiz_name\") }}
YouTube Video {{ YouTubeVideo(\"unique_embed_id\") }}
\n

Note: You can also attach videos from Vimeo by including the iframe embed of the video to the lesson.

\n
", "is_multi_step_form": 0, "is_standard": 1, "login_required": 1, "max_attachment_size": 0, - "modified": "2022-04-20 15:45:55.567759", + "modified": "2022-04-25 10:26:12.691050", "modified_by": "Administrator", "module": "LMS", "name": "lesson", @@ -34,7 +34,7 @@ "show_in_grid": 0, "show_sidebar": 0, "sidebar_items": [], - "success_url": "/lesson", + "success_url": "", "title": "Lesson", "web_form_fields": [ {