Compare commits

...

44 Commits

Author SHA1 Message Date
pateljannat
037e946bbe fix: mentors cleanup 2021-08-04 19:07:14 +05:30
pateljannat
a51c8de1eb fix: profile progress and review links 2021-08-04 14:01:52 +05:30
Jannat Patel
53dc517180 Merge pull request #165 from fossunited/quiz-cleanup
refactor: Quiz cleanup
2021-08-03 19:04:37 +05:30
pateljannat
44ca940c6b fix: quiz enhancements and tests 2021-08-03 18:30:52 +05:30
pateljannat
c0b688c720 Merge branch 'main' of https://github.com/frappe/community into quiz-cleanup 2021-07-30 18:35:23 +05:30
Jannat Patel
861d5f231d Merge pull request #164 from fossunited/issues
fix: default image, meta, reviews, lesson headers
2021-07-30 17:07:19 +05:30
pateljannat
d14b4f55a6 fix: default image, meta, reviews, lesson headers 2021-07-30 16:24:56 +05:30
Jannat Patel
db9a6c3eda Merge pull request #163 from fossunited/chapter-lesson-patch-fix
fix: chapter lesson patch
2021-07-29 13:48:23 +05:30
pateljannat
a667643681 fix: chapter lesson patch 2021-07-29 13:43:31 +05:30
Jannat Patel
f278e4b6a5 Merge pull request #162 from fossunited/lesson-enhancements
fix: lesson indexing
2021-07-29 12:12:14 +05:30
pateljannat
33a12c2dec fix: lesson indexing 2021-07-29 11:54:30 +05:30
Jannat Patel
508f90f459 Merge pull request #161 from fossunited/upcoming-courses
fix: upcoming course and doctype cleanup
2021-07-26 12:22:02 +05:30
pateljannat
709f0c2274 fix: profile profession section width 2021-07-26 11:42:30 +05:30
pateljannat
be47700e7c Merge branch 'main' of https://github.com/frappe/community into upcoming-courses 2021-07-26 11:14:38 +05:30
pateljannat
40842830a4 fix: profile page social icons position 2021-07-26 11:14:32 +05:30
pateljannat
11d070fa0d fix: condition for chapter description 2021-07-23 19:16:36 +05:30
pateljannat
dd2f830a33 fix: upcoming course and doctype cleanup 2021-07-23 19:07:26 +05:30
pateljannat
5431fcb450 fix: quiz ui change 2021-07-23 17:55:41 +05:30
Jannat Patel
324033e9ee Merge pull request #160 from fossunited/only-show-published-courses
fix: minor issues
2021-07-20 17:35:18 +05:30
pateljannat
86596d0cfe fix: minor issues 2021-07-20 17:19:18 +05:30
Jannat Patel
9323cfd748 Merge pull request #159 from rmehta/fix-global-container
fix(style): max-width on container padding
2021-07-20 13:06:36 +05:30
Jannat Patel
d125b02cec fix: added max width to container padding 2021-07-20 13:05:15 +05:30
pateljannat
282c4c5351 feat: explanation field in quiz 2021-07-20 09:36:22 +05:30
Rushabh Mehta
276d64a66a fix(style): don't mess with global container styles 2021-07-20 09:30:58 +05:30
Rushabh Mehta
79eb381a41 Merge pull request #157 from rmehta/remove-old-styles
fix(cleanup): remove old styles
2021-07-19 17:19:54 +05:30
Rushabh Mehta
44f9c0dfd3 fix(minor): remove old styles 2021-07-19 17:14:52 +05:30
Jannat Patel
0ca4cd724e Merge pull request #156 from fossunited/fix-only-show-published-courses
fix: only show the published courses on All Courses page
2021-07-19 17:11:03 +05:30
Anand Chitipothu
8a3e31f021 fix: only show the published courses on All Courses page
Closes #155
2021-07-19 17:03:38 +05:30
Jannat Patel
9be8a1af0b Merge pull request #154 from fossunited/fix-invite-email
Fix invite email
2021-07-19 13:43:36 +05:30
Anand Chitipothu
b9cac20613 fix: the delay in sending signup email 2021-07-19 13:32:18 +05:30
Anand Chitipothu
e6d5e6d37b fix: "Hello None" in the signup email
We were trying to show the full_name, but invite request only knows the email.
2021-07-19 13:31:44 +05:30
Jannat Patel
0abfcac7da Merge pull request #153 from fossunited/improve-email-templates
fix: fixed the email message sent out on signup
2021-07-19 13:23:55 +05:30
Anand Chitipothu
b70e8b9acc fix: fixed the email message sent out on signup
Currently updated keeping Mon.School in mind.
2021-07-19 13:14:13 +05:30
Jannat Patel
3b1e1aa3c3 Merge pull request #152 from fossunited/cleanup
fix: Cleanup
2021-07-19 12:49:22 +05:30
pateljannat
8f74c74d50 fix: removed unused styles and folders 2021-07-19 10:55:06 +05:30
pateljannat
d2f435016c fix: layout cleanup 2021-07-16 20:24:35 +05:30
pateljannat
389b35802b Merge branch 'main' of https://github.com/frappe/community into cleanup 2021-07-15 17:36:31 +05:30
Jannat Patel
a9192a74f9 Merge pull request #151 from fossunited/redesign-fixes
fix: Profile page, course card ratings, lesson completion tick
2021-07-15 17:36:17 +05:30
pateljannat
1366c7cf75 fix: removed unwanted image 2021-07-15 17:16:49 +05:30
pateljannat
eaa9e8e3ea fix: added linkedin icon 2021-07-15 17:05:54 +05:30
pateljannat
5ecae0df61 fix: removed unused pages 2021-07-15 17:01:15 +05:30
pateljannat
4891be1d8c fix: profile page fixes and course completion tick 2021-07-15 11:16:01 +05:30
pateljannat
ec852fc255 fix: rating on course card and profile page responsive 2021-07-13 16:54:45 +05:30
Jannat Patel
47f2d3cb7b Merge pull request #148 from fossunited/redesign
feat: Redesign
2021-07-13 15:22:59 +05:30
75 changed files with 1440 additions and 1403 deletions

View File

@@ -1,5 +1,5 @@
{% set color = member.get_palette() %} {% set color = member.get_palette() %}
<a href="/{{member.username}}"> <a class="button-links" href="/{{member.username}}">
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}"> <span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
{% if member.user_image %} {% if member.user_image %}
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}" title="{{ member.full_name }}"> <img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}" title="{{ member.full_name }}">

View File

@@ -104,5 +104,270 @@
"translatable": 1, "translatable": 1,
"unique": 0, "unique": 0,
"width": null "width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "User",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "medium",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "github",
"label": "Medium ID",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-06-30 14:46:55.834145",
"name": "User-medium",
"no_copy": 0,
"non_negative": 0,
"options": null,
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "User",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "city",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "mute_sounds",
"label": "City",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-06-30 14:46:55.834145",
"name": "User-city",
"no_copy": 0,
"non_negative": 0,
"options": null,
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "User",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "college",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "city",
"label": "College Name",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-06-30 14:46:55.834145",
"name": "User-college",
"no_copy": 0,
"non_negative": 0,
"options": null,
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "User",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "branch",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "college",
"label": "Branch",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-06-30 14:46:55.834145",
"name": "User-branch",
"no_copy": 0,
"non_negative": 0,
"options": null,
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "User",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "profession",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "medium",
"label": "Profession",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-06-30 14:46:55.834145",
"name": "User-profession",
"no_copy": 0,
"non_negative": 0,
"options": null,
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 1,
"unique": 0,
"width": null
} }
] ]

View File

@@ -136,7 +136,6 @@ primary_rules = [
{"from_route": "/courses/<course>/<topic>", "to_route": "courses/topic"}, {"from_route": "/courses/<course>/<topic>", "to_route": "courses/topic"},
{"from_route": "/hackathons/<hackathon>", "to_route": "hackathons/hackathon"}, {"from_route": "/hackathons/<hackathon>", "to_route": "hackathons/hackathon"},
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"}, {"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
{"from_route": "/dashboard", "to_route": ""},
{"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"}, {"from_route": "/add-a-new-batch", "to_route": "add-a-new-batch"},
{"from_route": "/courses/<course>/home", "to_route": "batch/home"}, {"from_route": "/courses/<course>/home", "to_route": "batch/home"},
{"from_route": "/courses/<course>/learn", "to_route": "batch/learn"}, {"from_route": "/courses/<course>/learn", "to_route": "batch/learn"},
@@ -167,7 +166,8 @@ whitelist = [
"/add-a-new-batch", "/add-a-new-batch",
"/new-sign-up", "/new-sign-up",
"/message", "/message",
"/about" "/about",
"/edit-profile"
] ]
whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist] whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist]
@@ -178,6 +178,10 @@ profile_rules = [
website_route_rules = primary_rules + whitelist_rules + profile_rules website_route_rules = primary_rules + whitelist_rules + profile_rules
website_redirects = [
{"source": "/update-profile", "target": "/edit-profile"},
]
update_website_context = 'community.widgets.update_website_context' update_website_context = 'community.widgets.update_website_context'
## Specify the additional tabs to be included in the user profile page. ## Specify the additional tabs to be included in the user profile page.

View File

@@ -2,7 +2,15 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Chapter', { frappe.ui.form.on('Chapter', {
// refresh: function(frm) {
// } onload: function (frm) {
frm.set_query("lesson", "lessons", function () {
return {
filters: {
"chapter": frm.doc.name,
}
};
});
}
}); });

View File

@@ -9,8 +9,7 @@
"course", "course",
"title", "title",
"description", "description",
"locked", "lessons"
"index_"
], ],
"fields": [ "fields": [
{ {
@@ -24,12 +23,6 @@
"fieldtype": "Markdown Editor", "fieldtype": "Markdown Editor",
"label": "Description" "label": "Description"
}, },
{
"default": "0",
"fieldname": "locked",
"fieldtype": "Check",
"label": "Locked"
},
{ {
"fieldname": "course", "fieldname": "course",
"fieldtype": "Link", "fieldtype": "Link",
@@ -38,10 +31,10 @@
"options": "LMS Course" "options": "LMS Course"
}, },
{ {
"default": "1", "fieldname": "lessons",
"fieldname": "index_", "fieldtype": "Table",
"fieldtype": "Int", "label": "Lessons",
"label": "Index" "options": "Lessons"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
@@ -52,7 +45,7 @@
"link_fieldname": "chapter" "link_fieldname": "chapter"
} }
], ],
"modified": "2021-05-13 21:05:20.531890", "modified": "2021-07-27 16:28:08.667964",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "Chapter", "name": "Chapter",

View File

@@ -5,15 +5,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from ...utils import slugify
class Chapter(Document): class Chapter(Document):
def get_lessons(self): pass
rows = frappe.db.get_all("Lesson",
filters={"chapter": self.name},
fields='name',
order_by="index_")
return [frappe.get_doc('Lesson', row['name']) for row in rows]
def get_slugified_chapter_title(self):
return slugify(self.title)

View File

@@ -0,0 +1,32 @@
{
"actions": [],
"creation": "2021-07-27 16:25:02.903245",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"chapter"
],
"fields": [
{
"fieldname": "chapter",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Chapter",
"options": "Chapter",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-07-27 16:25:02.903245",
"modified_by": "Administrator",
"module": "LMS",
"name": "Chapters",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class Chapters(Document):
pass

View File

@@ -30,10 +30,13 @@ class InviteRequest(Document):
return user return user
def send_email(self): def send_email(self):
subject = _("Your request has been approved.") site_name = "Mon.School"
subject = _("Welcome to {0}!").format(site_name)
args = { args = {
"full_name": self.full_name, "full_name": self.full_name,
"signup_form_link": "/new-sign-up?invite_code={0}".format(self.name), "signup_form_link": "/new-sign-up?invite_code={0}".format(self.name),
"site_name": site_name,
"site_url": frappe.utils.get_url() "site_url": frappe.utils.get_url()
} }
frappe.sendmail( frappe.sendmail(
@@ -42,7 +45,8 @@ class InviteRequest(Document):
subject=subject, subject=subject,
header=[subject, "green"], header=[subject, "green"],
template = "lms_invite_request_approved", template = "lms_invite_request_approved",
args=args) args=args,
now=True)
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def create_invite_request(invite_email): def create_invite_request(invite_email):

View File

@@ -10,7 +10,6 @@
"include_in_preview", "include_in_preview",
"column_break_4", "column_break_4",
"title", "title",
"index_",
"index_label", "index_label",
"section_break_6", "section_break_6",
"body", "body",
@@ -31,13 +30,6 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Title" "label": "Title"
}, },
{
"default": "1",
"fieldname": "index_",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Index"
},
{ {
"fieldname": "body", "fieldname": "body",
"fieldtype": "Markdown Editor", "fieldtype": "Markdown Editor",
@@ -75,7 +67,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-06-29 13:34:49.077363", "modified": "2021-07-27 16:28:29.203624",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "Lesson", "name": "Lesson",

View File

@@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2021-07-27 16:25:48.269536",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"lesson"
],
"fields": [
{
"fieldname": "lesson",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Lesson",
"options": "Lesson"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-07-27 16:53:52.732191",
"modified_by": "Administrator",
"module": "LMS",
"name": "Lessons",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class Lessons(Document):
pass

View File

@@ -37,6 +37,7 @@
"fieldname": "member_type", "fieldname": "member_type",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1,
"label": "Member Type", "label": "Member Type",
"options": "\nStudent\nMentor\nStaff" "options": "\nStudent\nMentor\nStaff"
}, },
@@ -44,7 +45,6 @@
"default": "Member", "default": "Member",
"fieldname": "role", "fieldname": "role",
"fieldtype": "Select", "fieldtype": "Select",
"in_standard_filter": 1,
"label": "Role", "label": "Role",
"options": "\nMember\nAdmin" "options": "\nMember\nAdmin"
}, },
@@ -63,9 +63,10 @@
{ {
"fetch_from": "batch.course", "fetch_from": "batch.course",
"fieldname": "course", "fieldname": "course",
"fieldtype": "Data", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Course" "label": "Course",
"options": "LMS Course"
}, },
{ {
"fieldname": "current_lesson", "fieldname": "current_lesson",
@@ -83,7 +84,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-07-06 20:50:46.885325", "modified": "2021-08-04 17:10:42.708479",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Batch Membership", "name": "LMS Batch Membership",

View File

@@ -2,7 +2,15 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('LMS Course', { frappe.ui.form.on('LMS Course', {
// refresh: function(frm) {
// } onload: function (frm) {
frm.set_query("chapter", "chapters", function () {
return {
filters: {
"course": frm.doc.name,
}
};
});
}
}); });

View File

@@ -1,11 +1,5 @@
{ {
"actions": [ "actions": [
{
"action": "community.lms.doctype.lms_course.lms_course.reindex_lessons",
"action_type": "Server Action",
"group": "Reindex",
"label": "Reindex Lessons"
},
{ {
"action": "community.lms.doctype.lms_course.lms_course.reindex_exercises", "action": "community.lms.doctype.lms_course.lms_course.reindex_exercises",
"action_type": "Server Action", "action_type": "Server Action",
@@ -21,16 +15,17 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"title", "title",
"short_code",
"video_link", "video_link",
"column_break_3",
"is_published",
"disable_self_learning",
"image", "image",
"section_break_5", "column_break_3",
"tags", "tags",
"is_published",
"upcoming",
"disable_self_learning",
"section_break_5",
"short_introduction", "short_introduction",
"description" "description",
"chapters"
], ],
"fields": [ "fields": [
{ {
@@ -53,11 +48,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Published" "label": "Published"
}, },
{
"fieldname": "short_code",
"fieldtype": "Data",
"label": "Short Code"
},
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break" "fieldtype": "Column Break"
@@ -92,6 +82,18 @@
"fieldname": "tags", "fieldname": "tags",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Tags" "label": "Tags"
},
{
"default": "0",
"fieldname": "upcoming",
"fieldtype": "Check",
"label": "Is an Upcoming Course"
},
{
"fieldname": "chapters",
"fieldtype": "Table",
"label": "Chapters",
"options": "Chapters"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
@@ -111,14 +113,9 @@
"group": "Mentors", "group": "Mentors",
"link_doctype": "LMS Course Mentor Mapping", "link_doctype": "LMS Course Mentor Mapping",
"link_fieldname": "course" "link_fieldname": "course"
},
{
"group": "Mentors",
"link_doctype": "LMS Mentor Request",
"link_fieldname": "course"
} }
], ],
"modified": "2021-07-09 15:05:05.372430", "modified": "2021-07-28 19:01:50.677445",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Course", "name": "LMS Course",
@@ -141,6 +138,5 @@
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "title", "title_field": "title",
"track_changes": 1, "track_changes": 1
"track_views": 1
} }

View File

@@ -9,8 +9,10 @@ import json
from ...utils import slugify from ...utils import slugify
from community.query import find, find_all from community.query import find, find_all
from frappe.utils import flt, cint from frappe.utils import flt, cint
from ...utils import slugify
class LMSCourse(Document): class LMSCourse(Document):
@staticmethod @staticmethod
def find(name): def find(name):
"""Returns the course with specified name. """Returns the course with specified name.
@@ -72,8 +74,11 @@ class LMSCourse(Document):
mentors = frappe.get_all("LMS Course Mentor Mapping", {"course": self.name}, ["mentor"]) mentors = frappe.get_all("LMS Course Mentor Mapping", {"course": self.name}, ["mentor"])
for mentor in mentors: for mentor in mentors:
member = frappe.get_doc("User", mentor.mentor) member = frappe.get_doc("User", mentor.mentor)
# TODO: change this to count query member.batch_count = frappe.db.count("LMS Batch Membership",
member.batch_count = len(frappe.get_all("LMS Batch Membership", {"member": member.name, "member_type": "Mentor"})) {
"member": member.name,
"member_type": "Mentor"
})
course_mentors.append(member) course_mentors.append(member)
return course_mentors return course_mentors
@@ -112,17 +117,42 @@ class LMSCourse(Document):
def get_chapters(self): def get_chapters(self):
"""Returns all chapters of this course. """Returns all chapters of this course.
""" """
# TODO: chapters should have a way to specify the order chapters = []
return find_all("Chapter", course=self.name, order_by="index_") for row in self.chapters:
chapter_details = frappe.db.get_value("Chapter", row.chapter,
["name", "title", "description"],
as_dict=True)
chapter_details.idx = row.idx
chapters.append(chapter_details)
return chapters
def get_lessons(self): def get_lessons(self, chapter=None):
""" Returns all lessons of this course """ """ If chapter is passed, returns lessons of only that chapter.
Else returns lessons of all chapters of the course """
lessons = [] lessons = []
chapters = self.get_chapters()
for chapter in chapters: if chapter:
lessons.append(frappe.get_all("Lesson", {"chapter": chapter.name})) return self.get_lesson_details(chapter)
for chapter in self.get_chapters():
lesson = self.get_lesson_details(chapter)
lessons += lesson
return lessons return lessons
def get_lesson_details(self, chapter):
lessons = []
lesson_list = frappe.get_all("Lessons", {"parent": chapter.name},
["lesson", "idx"], order_by="idx")
for row in lesson_list:
lesson_details = frappe.get_doc("Lesson", row.lesson)
lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx))
lessons.append(lesson_details)
return lessons
def get_slugified_chapter_title(self, chapter):
return slugify(chapter)
def get_course_progress(self): def get_course_progress(self):
""" Returns the course progress of the session user """ """ Returns the course progress of the session user """
lesson_count = len(self.get_lessons()) lesson_count = len(self.get_lessons())
@@ -160,38 +190,12 @@ class LMSCourse(Document):
visibility="Public") visibility="Public")
return batches return batches
def get_chapter(self, index):
return find("Chapter", course=self.name, index_=index)
def get_lesson(self, chapter_index, lesson_index):
chapter_name = frappe.get_value(
"Chapter",
{"course": self.name, "index_": chapter_index},
"name")
lesson_name = chapter_name and frappe.get_value(
"Lesson",
{"chapter": chapter_name, "index_": lesson_index},
"name")
return lesson_name and frappe.get_doc("Lesson", lesson_name)
def get_lesson_index(self, lesson_name): def get_lesson_index(self, lesson_name):
"""Returns the {chapter_index}.{lesson_index} for the lesson. """Returns the {chapter_index}.{lesson_index} for the lesson.
""" """
lesson = frappe.get_doc("Lesson", lesson_name) lesson = frappe.db.get_value("Lessons", {"lesson": lesson_name}, ["idx", "parent"], as_dict=True)
chapter = frappe.get_doc("Chapter", lesson.chapter) chapter = frappe.db.get_value("Chapters", {"chapter": lesson.parent}, ["idx"], as_dict=True)
return f"{chapter.index_}.{lesson.index_}" return f"{chapter.idx}.{lesson.idx}"
def reindex_lessons(self):
for i, c in enumerate(self.get_chapters(), start=1):
c.index_ = i
c.save()
self._reindex_lessons_in_chapter(c)
def _reindex_lessons_in_chapter(self, c):
for i, lesson in enumerate(c.get_lessons(), start=1):
lesson.index = i
lesson.index_label = f"{c.index_}.{i}"
lesson.save()
def reindex_exercises(self): def reindex_exercises(self):
for i, c in enumerate(self.get_chapters(), start=1): for i, c in enumerate(self.get_chapters(), start=1):
@@ -202,7 +206,7 @@ class LMSCourse(Document):
def _reindex_exercises_in_chapter(self, c): def _reindex_exercises_in_chapter(self, c):
i = 1 i = 1
for lesson in c.get_lessons(): for lesson in self.get_lessons(c):
for exercise in lesson.get_exercises(): for exercise in lesson.get_exercises():
exercise.index_ = i exercise.index_ = i
exercise.index_label = f"{c.index_}.{i}" exercise.index_label = f"{c.index_}.{i}"
@@ -237,21 +241,6 @@ class LMSCourse(Document):
membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title") membership.batch_title = frappe.db.get_value("LMS Batch", membership.batch, "title")
return all_memberships return all_memberships
def get_mentors(self, batch=None):
filters = {
"course": self.name,
"member_type": "Mentor"
}
if batch:
filters["batch"] = batch
memberships = frappe.get_all(
"LMS Batch Membership",
filters,
["member"])
member_names = [m['member'] for m in memberships]
return find_all("User", name=["IN", member_names])
def get_students(self, batch=None): def get_students(self, batch=None):
"""Returns (email, full_name, username) of all the students of this batch as a list of dict. """Returns (email, full_name, username) of all the students of this batch as a list of dict.
""" """
@@ -302,9 +291,6 @@ class LMSCourse(Document):
return None return None
return sum(ratings)/len(ratings) return sum(ratings)/len(ratings)
def get_outline(self):
return CourseOutline(self)
def get_progress(self, lesson): def get_progress(self, lesson):
return frappe.db.get_value("LMS Course Progress", return frappe.db.get_value("LMS Course Progress",
{ {
@@ -314,55 +300,14 @@ class LMSCourse(Document):
}, },
["status"]) ["status"])
class CourseOutline: def get_neighbours(self, current, lessons):
def __init__(self, course):
self.course = course
self.chapters = self.get_chapters()
self.lessons = self.get_lessons()
def get_next(self, current):
current = flt(current) current = flt(current)
numbers = sorted(lesson['number'] for lesson in self.lessons) numbers = sorted(lesson.number for lesson in lessons)
try:
index = numbers.index(current) index = numbers.index(current)
return numbers[index+1] return {
except IndexError: "prev": numbers[index-1] if index-1 >= 0 else None,
return None "next": numbers[index+1] if index+1 < len(numbers) else None
}
def get_prev(self, current):
current = flt(current)
numbers = sorted(lesson['number'] for lesson in self.lessons)
try:
index = numbers.index(current)
if index == 0:
return None
return numbers[index-1]
except IndexError:
return None
def get_chapters(self):
return frappe.db.get_all("Chapter",
filters={"course": self.course.name},
fields=["name", "title", "index_"],
order_by="index_")
def get_lessons(self):
chapters = [c['name'] for c in self.chapters]
lessons = frappe.db.get_all("Lesson",
filters={"chapter": ["IN", chapters]},
fields=["name", "title", "chapter", "index_"])
chapter_numbers = {c['name']: c['index_'] for c in self.chapters}
for lesson in lessons:
lesson['number'] = flt("{}.{}".format(chapter_numbers[lesson['chapter']], lesson['index_']))
return lessons
@frappe.whitelist()
def reindex_lessons(doc):
course_data = json.loads(doc)
course = frappe.get_doc("LMS Course", course_data['name'])
course.reindex_lessons()
frappe.msgprint("All lessons in this course have been re-indexed.")
@frappe.whitelist() @frappe.whitelist()
def reindex_exercises(doc): def reindex_exercises(doc):

View File

@@ -27,12 +27,13 @@
"fieldname": "lesson", "fieldname": "lesson",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Lesson", "label": "Lesson",
"options": "Lesson" "options": "Lesson",
"read_only": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-06-23 17:58:57.642873", "modified": "2021-07-23 19:06:12.551633",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Quiz", "name": "LMS Quiz",

View File

@@ -46,34 +46,30 @@ class LMSQuiz(Document):
return result[0] return result[0]
@frappe.whitelist() @frappe.whitelist()
def submit(quiz, result): def quiz_summary(quiz, results):
score = 0 score = 0
answer_map = { results = json.loads(results)
"is_correct_1": "option_1",
"is_correct_2": "option_2",
"is_correct_3": "option_3",
"is_correct_4": "option_4"
}
result = json.loads(result)
quiz_details = frappe.get_doc("LMS Quiz", quiz)
for response in result: for result in results:
match = list(filter(lambda x: x.question == response.get("question"), quiz_details.questions))[0] correct = result["is_correct"][0]
correct_options = quiz_details.get_correct_options(match) result["question"] = frappe.db.get_value("LMS Quiz Question",
correct_answers = [ match.get(answer_map[option]) for option in correct_options ] {"parent": quiz, "idx": result["question_index"]},
["question"])
if response.get("answer") == correct_answers: for point in result["is_correct"]:
response["result"] = "Right" correct = correct and point
score += 1
else: result["result"] = "Right" if correct else "Wrong"
response["result"] = "Wrong" score += correct
response["answer"] = ("").join([ ans if idx == len(response.get("answer")) -1 else ans + ", " for idx, ans in enumerate(response.get("answer")) ])
del result["is_correct"]
del result["question_index"]
frappe.get_doc({ frappe.get_doc({
"doctype": "LMS Quiz Submission", "doctype": "LMS Quiz Submission",
"quiz": quiz, "quiz": quiz,
"result": result, "result": results,
"score": score "score": score
}).save(ignore_permissions=True) }).save(ignore_permissions=True)
update_progress(quiz_details.lesson)
return score return score

View File

@@ -3,6 +3,39 @@
# import frappe # import frappe
import unittest import unittest
import frappe
class TestLMSQuiz(unittest.TestCase): class TestLMSQuiz(unittest.TestCase):
pass
@classmethod
def setUpClass(cls) -> None:
frappe.get_doc({
"doctype": "LMS Quiz",
"title": "Test Quiz"
}).save()
def test_with_multiple_options(self):
quiz = frappe.get_doc("LMS Quiz", "Test Quiz")
quiz.append("questions", {
"question": "Question multiple",
"option_1": "Option 1",
"is_correct_1": 1,
"option_2": "Option 2",
"is_correct_2": 1
})
quiz.save()
self.assertTrue(quiz.questions[0].multiple)
def test_with_no_correct_option(self):
quiz = frappe.get_doc("LMS Quiz", "Test Quiz")
quiz.append("questions", {
"question": "Question no correct option",
"option_1": "Option 1",
"option_2": "Option 2",
})
self.assertRaises(frappe.ValidationError, quiz.save)
@classmethod
def tearDownClass(cls) -> None:
frappe.db.delete("LMS Quiz", "Test Quiz")
frappe.db.delete("LMS Quiz Question", {"parent": "Test Quiz"})

View File

@@ -9,15 +9,23 @@
"options_section", "options_section",
"option_1", "option_1",
"is_correct_1", "is_correct_1",
"column_break_5",
"explanation_1",
"section_break_5", "section_break_5",
"option_2", "option_2",
"is_correct_2", "is_correct_2",
"column_break_10",
"explanation_2",
"column_break_4", "column_break_4",
"option_3", "option_3",
"is_correct_3", "is_correct_3",
"column_break_15",
"explanation_3",
"section_break_11", "section_break_11",
"option_4", "option_4",
"is_correct_4", "is_correct_4",
"column_break_20",
"explanation_4",
"multiple" "multiple"
], ],
"fields": [ "fields": [
@@ -101,12 +109,52 @@
{ {
"fieldname": "section_break_11", "fieldname": "section_break_11",
"fieldtype": "Section Break" "fieldtype": "Section Break"
},
{
"depends_on": "option_1",
"fieldname": "explanation_1",
"fieldtype": "Data",
"label": "Explanation"
},
{
"depends_on": "option_2",
"fieldname": "explanation_2",
"fieldtype": "Data",
"label": "Explanation"
},
{
"depends_on": "option_3",
"fieldname": "explanation_3",
"fieldtype": "Data",
"label": "Explanation"
},
{
"depends_on": "option_4",
"fieldname": "explanation_4",
"fieldtype": "Data",
"label": "Explanation"
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_15",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_20",
"fieldtype": "Column Break"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-06-22 16:54:13.133859", "modified": "2021-07-19 19:35:28.446236",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Quiz Question", "name": "LMS Quiz Question",

View File

@@ -1,3 +1,7 @@
frappe.ready(function () { frappe.ready(function () {
// bind events here frappe.web_form.after_load = () => {
if (!frappe.utils.get_url_arg("name")) {
window.location.href = `/edit-profile?name=${frappe.session.user}`;
}
}
}) })

View File

@@ -12,6 +12,7 @@
"breadcrumbs": "", "breadcrumbs": "",
"button_label": "Save", "button_label": "Save",
"creation": "2021-06-30 13:48:13.682851", "creation": "2021-06-30 13:48:13.682851",
"custom_css": "[data-doctype=\"Web Form\"] {\n max-width: 720px;\n margin: 6rem auto;\n}",
"doc_type": "User", "doc_type": "User",
"docstatus": 0, "docstatus": 0,
"doctype": "Web Form", "doctype": "Web Form",
@@ -19,14 +20,14 @@
"is_standard": 1, "is_standard": 1,
"login_required": 1, "login_required": 1,
"max_attachment_size": 0, "max_attachment_size": 0,
"modified": "2021-06-30 15:53:20.967466", "modified": "2021-07-14 17:15:15.424855",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "profile", "name": "profile",
"owner": "Administrator", "owner": "Administrator",
"payment_button_label": "Buy Now", "payment_button_label": "Buy Now",
"published": 1, "published": 1,
"route": "profile", "route": "edit-profile",
"route_to_success_link": 0, "route_to_success_link": 0,
"show_attachments": 0, "show_attachments": 0,
"show_in_grid": 0, "show_in_grid": 0,
@@ -84,18 +85,6 @@
"reqd": 0, "reqd": 0,
"show_in_filter": 0 "show_in_filter": 0
}, },
{
"allow_read_on_all_link_options": 0,
"fieldname": "username",
"fieldtype": "Data",
"hidden": 0,
"label": "Username",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{ {
"allow_read_on_all_link_options": 0, "allow_read_on_all_link_options": 0,
"fieldname": "mobile_no", "fieldname": "mobile_no",
@@ -120,6 +109,91 @@
"read_only": 0, "read_only": 0,
"reqd": 0, "reqd": 0,
"show_in_filter": 0 "show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "linkedin",
"fieldtype": "Data",
"hidden": 0,
"label": "LinkedIn ID",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "github",
"fieldtype": "Data",
"hidden": 0,
"label": "Github ID",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "medium",
"fieldtype": "Data",
"hidden": 0,
"label": "Medium ID",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "city",
"fieldtype": "Data",
"hidden": 0,
"label": "City",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "college",
"fieldtype": "Data",
"hidden": 0,
"label": "College Name",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"depends_on": "college",
"fieldname": "branch",
"fieldtype": "Data",
"hidden": 0,
"label": "Branch",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
},
{
"allow_read_on_all_link_options": 0,
"fieldname": "profession",
"fieldtype": "Data",
"hidden": 0,
"label": "Profession",
"max_length": 0,
"max_value": 0,
"read_only": 0,
"reqd": 0,
"show_in_filter": 0
} }
] ]
} }

View File

@@ -1,67 +0,0 @@
<div class="mt-5">
<a class="anchor_style" href="/courses">Courses</a> /{% if course.is_mentor(frappe.session.user) %} <a
class="anchor_style" href="/courses/{{ course.name }}"> {{ course.title }}</a> {% else %} <span class="text-muted">
{{ course.title }}</span> {% endif %}
{% set all_memberships = course.get_all_memberships(frappe.session.user) %}
{% if membership and membership.batch and all_memberships | length > 1 %}
<a class="pull-right dropdown-item border rounded" style="width: 10rem;" href="#" id="navbarDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ membership.batch_title }}
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
{% for data in all_memberships %}
{% if data.batch != membership.batch %}
<a class="dropdown-item switch-batch"
href="/courses/{{ course.name }}/home?batch={{ data.batch }}">{{ data.batch_title }}</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% if not membership %}
{% set display_class = "hide" %}
{% else %}
{% set display_class = "" %}
{% endif %}
<ul class="nav nav-tabs mt-4">
<li class="nav-item">
<a class="nav-link" id="home" href="/courses/{{course.name}}/home{{ course.query_parameter }}">Home</a>
</li>
<li class="nav-item">
{% set lesson_index = course.get_lesson_index(membership.current_lesson) if membership and membership.current_lesson
else '1.1' %}
<a class="nav-link" id="learn"
href="{{ course.get_learn_url(lesson_index) }}{{ course.query_parameter }}">Lessons</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" id="schedule" href="/courses/{{course.name}}/schedule">Schedule</a>
</li> -->
<li class="nav-item {{ display_class }}">
<a class="nav-link" id="members" href="/courses/{{course.name}}/members{{ course.query_parameter }}">Members</a>
</li>
<!-- <li class="nav-item {{ display_class }}">
<a class="nav-link" id="discussion" href="/courses/{{course.name}}/discuss">Discussion</a>
</li> -->
<!-- <li class="nav-item">
<a class="nav-link" id="about" href="/courses/{{course.name}}/about">About</a>
</li> -->
{% if membership and membership.batch and course.is_mentor(frappe.session.user) %}
<li class="nav-item">
<a class="nav-link" id="progress" href="/courses/{{course.name}}/progress{{ course.query_parameter }}">Progress</a>
</li>
{% endif %}
</ul>
{% block script %}
<script>
frappe.ready(() => {
var selector = document.querySelector(`a[href="${decodeURIComponent(window.location.pathname)}{{ course.query_parameter }}"]`)
if (selector) {
selector.classList.add('active');
}
else {
$("#learn").addClass('active')
}
})
</script>
{% endblock %}

View File

@@ -1,4 +1,4 @@
<div class="course-instructor breadcrumb"> <div class="breadcrumb">
<a class="dark-links" href="/courses">All Courses</a> <a class="dark-links" href="/courses">All Courses</a>
<img class="ml-1 mr-1" src="/assets/community/icons/chevron-right.svg"> <img class="ml-1 mr-1" src="/assets/community/icons/chevron-right.svg">

View File

@@ -1,31 +1,33 @@
<div> <div>
<div class="small-title chapter-title" data-target="#{{ chapter.get_slugified_chapter_title() }}" <div class="small-title chapter-title" data-target="#{{ course.get_slugified_chapter_title(chapter.title) }}"
data-toggle="collapse" aria-expanded="false"> data-toggle="collapse" aria-expanded="false">
<img class="chapter-icon" src="/assets/community/icons/chevron-right.svg"> <img class="chapter-icon" src="/assets/community/icons/chevron-right.svg">
{{ index }}. {{ chapter.title }} {{ index }}. {{ chapter.title }}
</div> </div>
<div class="chapter-content collapse navbar-collapse" id="{{ chapter.get_slugified_chapter_title() }}"> <div class="chapter-content collapse navbar-collapse" id="{{ course.get_slugified_chapter_title(chapter.title) }}">
{% if chapter.description %}
<div class="chapter-description muted-text"> <div class="chapter-description muted-text">
{{ chapter.description }} {{ chapter.description }}
</div> </div>
{% endif %}
<div class="lessons"> <div class="lessons">
{% for lesson in chapter.get_lessons() %} {% for lesson in course.get_lessons(chapter) %}
<div class="lesson-info{% if membership.current_lesson == lesson.name %} active-lesson {% endif %}"> <div class="lesson-info{% if membership.current_lesson == lesson.name %} active-lesson {% endif %}">
{% if membership or lesson.include_in_preview %} {% if membership or lesson.include_in_preview %}
<a class="lesson-links" <a class="lesson-links"
href="{{ course.get_learn_url(course.get_lesson_index(lesson.name)) }}{{course.query_parameter}}" href="{{ course.get_learn_url(lesson.number) }}{{course.query_parameter}}"
data-course="{{ course.name }}"> data-course="{{ course.name }}">
{{ lesson.title }} {{ lesson.title }}
{% if membership %} {% if membership %}
<img class="{{ course.get_progress(lesson.name) != 'Complete' and 'hide' }}" <img class="lesson-progress-tick {{ course.get_progress(lesson.name) != 'Complete' and 'hide' }}"
src="/assets/community/icons/check.svg"> src="/assets/community/icons/check.svg">
{% endif %} {% endif %}

View File

@@ -1,10 +1,14 @@
<div class="common-card-style course-card"> <div class="common-card-style course-card">
<div class="course-image" style="background-image: url({{ course.image }});"> <div class="course-image {% if not course.image %}default-image{% endif %}"
{% if course.image %} style="background-image: url( {{ course.image }} );" {% endif %}>
<div class="course-tags"> <div class="course-tags">
{% for tag in course.get_tags() %} {% for tag in course.get_tags() %}
<div class="course-card-pills">{{ tag }}</div> <div class="course-card-pills">{{ tag }}</div>
{% endfor %} {% endfor %}
</div> </div>
{% if not course.image %}
<div class="default-image-text">{{ course.title[0] }}</div>
{% endif %}
</div> </div>
<div class="course-card-content"> <div class="course-card-content">
<div class="course-card-meta muted-text"> <div class="course-card-meta muted-text">
@@ -29,12 +33,21 @@
<span class="course-instructor"> <span class="course-instructor">
{{ course.get_instructor().full_name }} {{ course.get_instructor().full_name }}
</span> </span>
{% if course.get_students() | length %}
<span class="course-student-count"> <span class="course-student-count">
<img class="icon-background mr-1" src="/assets/community/icons/user.svg" /> {% if course.get_students() | length %}
<span class="mr-4">
<img class="icon-background" src="/assets/community/icons/user.svg" />
{{ course.get_students() | length }} {{ course.get_students() | length }}
</span> </span>
{% endif %} {% endif %}
{% set avg_rating = course.get_average_rating() %}
{% if avg_rating %}
<span>
<img class="icon-background" src="/assets/community/icons/rating.svg" />
{{ avg_rating }}
</span>
{% endif %}
</span>
</div> </div>
{% set membership = course.get_membership(frappe.session.user) %} {% set membership = course.get_membership(frappe.session.user) %}
@@ -44,15 +57,19 @@
{% set query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" %} {% set query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" %}
{% if membership %} {% if course.upcoming %}
<div class="view-course-link is-default">
Upcoming Course <img class="ml-3" src="/assets/community/icons/black-arrow.svg" />
</div>
<a class="stretched-link" href="/courses/{{ course.name }}"></a>
{% elif membership %}
<div class="view-course-link is-primary"> <div class="view-course-link is-primary">
Continue Course <img class="ml-3" src="/assets/community/icons/white-arrow.svg" /> Continue Course <img class="ml-3" src="/assets/community/icons/white-arrow.svg" />
</div> </div>
<a class="stretched-link" href="{{ course.get_learn_url(lesson_index) }}{{ query_parameter }}"></a> <a class="stretched-link" href="{{ course.get_learn_url(lesson_index) }}{{ query_parameter }}"></a>
{% else %} {% else %}
<div class="view-course-link"> <div class="view-course-link">
View Course <img class="ml-3" src="/assets/community/icons/black-arrow.svg" /> View Course <img class="ml-3" src="/assets/community/icons/black-arrow.svg" />
</div> </div>
@@ -61,3 +78,13 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<script>
frappe.ready(() => {
$(".course-card-title").each((i, element) => {
var title = $(element).text();
var length = $(window).width() <= 375 ? 60 : 65;
var suffix = title.length > length ? "..." : "";
$(element).text(title.substring(0, length) + suffix);
})
})
</script>

View File

@@ -1,5 +1,5 @@
<div class="common-card-style member-card {{dimension_class}} "> <div class="common-card-style member-card {{dimension_class}} ">
{% set avatar_class = "avatar-large" if not dimension_class else "avatar-xl"%} {% set avatar_class = "avatar-large" if not dimension_class else "avatar-large"%}
{{ widgets.Avatar(member=member, avatar_class=avatar_class) }} {{ widgets.Avatar(member=member, avatar_class=avatar_class) }}
<div class="small-title member-card-title"> <div class="small-title member-card-title">
{{ member.full_name }} {{ member.full_name }}

View File

@@ -1,30 +0,0 @@
<div class="batch">
<div class="batch-details">
<div class="">Session every {{batch.sessions_on}}</div>
<div>{{frappe.utils.format_time(batch.start_time, "short")}} -
{{frappe.utils.format_time(batch.end_time, "short")}}
</div>
<div>Starting {{frappe.utils.format_date(batch.start_date, "medium")}}</div>
<div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div>
{% for m in course.get_mentors(batch.name) %}
<div>
{{ widgets.Avatar(member=m, avatar_class="avatar-medium" ) }}
<span class="instructor-title">{{m.full_name}}</span>
</div>
{% endfor %}
</div>
{% if can_manage or can_join %}
<div class="cta">
<div class="">
{% if can_manage %}
<a href="/courses/{{ course.name }}/home?batch={{ batch.name }}" class="btn btn-primary manage-batch" data-batch="{{ batch.name | urlencode }}"
data-course="{{ course.name | urlencode }}">Manage</a>
{% elif can_join %}
<button class="join-batch btn btn-secondary" data-batch="{{ batch.name | urlencode }}"
data-course="{{ course.name | urlencode }}">Join this Batch</button>
{% endif %}
</div>
</div>
{% endif %}
</div>

View File

@@ -1,23 +1,28 @@
{% if course.get_reviews() | length %} <div class="reviews-parent">
<div class="reviews-parent col"> {% set reviews = course.get_reviews() %}
<div class="reviews-heading"> {% if reviews | length or course.is_eligible_to_review(membership) %}
<div class="course-home-headings">Student Review</div> <div class="mb-5">
<span class="course-home-headings">Reviews</span>
{% if course.is_eligible_to_review(membership) %} {% if course.is_eligible_to_review(membership) %}
<a class="review-link" href=""> <span class="review-link button is-secondary pull-right">
Provide your Feedback Write a review
</a> </span>
{% endif %} {% endif %}
</div> </div>
{% endif %}
{% if reviews | length %}
<div class="reviews-section"> <div class="reviews-section">
{% for review in course.get_reviews() %} {% for review in reviews %}
<div class="review-card"> <div class="review-card">
<div class="common-card-style review-content small-title"> {{ review.review }} </div> <div class="common-card-style review-content small-title"> {{ review.review }} </div>
<div class="review-card-footer"> <div class="review-card-footer">
<div> <div>
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }} {{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
<a class="button-links" href="/{{review.owner_details.username}}">
<span class="course-instructor"> <span class="course-instructor">
{{ review.owner_details.full_name }} {{ review.owner_details.full_name }}
</span> </span>
</a>
</div> </div>
<div class="rating"> <div class="rating">
{% for i in [1, 2, 3, 4, 5] %} {% for i in [1, 2, 3, 4, 5] %}
@@ -30,6 +35,7 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
</div> </div>
<div class="modal fade review-modal" id="review-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" <div class="modal fade review-modal" id="review-modal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
@@ -67,8 +73,8 @@
</div> </div>
<div class="control-input-wrapper"> <div class="control-input-wrapper">
<div class="control-input"> <div class="control-input">
<textarea type="text" autocomplete="off" class="input-with-feedback form-control review-field" data-fieldtype="Text" <textarea type="text" autocomplete="off" class="input-with-feedback form-control review-field"
data-fieldname="feedback_comments" placeholder="" style="height: 300px;" data-fieldtype="Text" data-fieldname="feedback_comments" placeholder="" style="height: 300px;"
spellcheck="false"></textarea> spellcheck="false"></textarea>
</div> </div>
</div> </div>
@@ -77,9 +83,9 @@
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<div class="button submit-review is-primary" data-course="{{ course.name | urlencode}}" id="submit-review">Submit</div> <div class="button submit-review is-primary" data-course="{{ course.name | urlencode}}" id="submit-review">
Submit</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endif %}

View File

@@ -10,7 +10,8 @@ class CustomUser(User):
""" """
return frappe.get_all( return frappe.get_all(
'LMS Course', { 'LMS Course', {
'owner': self.name 'owner': self.name,
'is_published': True
}) })
def get_palette(self): def get_palette(self):
@@ -59,3 +60,19 @@ class CustomUser(User):
filters["member_type"] = member_type filters["member_type"] = member_type
return frappe.get_all("LMS Batch Membership", filters, ["name", "course"]) return frappe.get_all("LMS Batch Membership", filters, ["name", "course"])
def get_mentored_courses(self):
""" Returns all courses mentored by this user """
mentored_courses = []
mapping = frappe.get_all("LMS Course Mentor Mapping",
{
"mentor": self.name,
},
["name", "course"]
)
for map in mapping:
if frappe.db.get_value("LMS Course", map.course, "is_published"):
mentored_courses.append(map)
return mentored_courses

View File

@@ -6,3 +6,4 @@ community.patches.replace_member_with_user_in_batch_membership
community.patches.replace_member_with_user_in_course_mentor_mapping community.patches.replace_member_with_user_in_course_mentor_mapping
community.patches.replace_member_with_user_in_lms_message community.patches.replace_member_with_user_in_lms_message
community.patches.replace_member_with_user_in_mentor_request community.patches.replace_member_with_user_in_mentor_request
community.patches.v0_0.chapter_lesson_index_table

View File

@@ -0,0 +1,50 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("lms", "doctype", "lms_course")
frappe.reload_doc("lms", "doctype", "chapter")
frappe.reload_doc("lms", "doctype", "lesson")
frappe.reload_doc("lms", "doctype", "lessons")
frappe.reload_doc("lms", "doctype", "chapters")
update_chapters()
update_lessons()
def update_chapters():
courses = frappe.get_all("LMS Course", pluck="name")
for course in courses:
course_details = frappe.get_doc("LMS Course", course)
chapters = frappe.get_all("Chapter",
{
"course": course
},
["name"],
order_by= "index_"
)
for chapter in chapters:
course_details.append("chapters",
{
"chapter": chapter.name
})
course_details.save()
def update_lessons():
chapters = frappe.get_all("Chapter", pluck="name")
for chapter in chapters:
chapter_details = frappe.get_doc("Chapter", chapter)
lessons = frappe.get_all("Lesson",
{
"chapter": chapter
},
["name"],
order_by= "index_"
)
for lesson in lessons:
chapter_details.append("lessons",
{
"lesson": lesson.name
})
chapter_details.save()

View File

@@ -1,4 +1,3 @@
@import "./style.css"; @import "./style.css";
@import "./vars.css"; @import "./vars.css";
@import "./style.less";

View File

@@ -234,6 +234,22 @@ input[type=checkbox] {
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.default-image {
background-color: var(--avatar-frame-bg);
color: var(--avatar-frame-color);
display: flex;
flex-direction: column;
}
.default-image-text {
display: flex;
flex: 1;
align-self: center;
justify-content: normal;
font-size: 7rem;
font-weight: bold;
}
.course-tags { .course-tags {
display: flex; display: flex;
position: relative; position: relative;
@@ -259,11 +275,11 @@ input[type=checkbox] {
.common-page-style { .common-page-style {
background: #F4F5F6; background: #F4F5F6;
padding-bottom: 2rem;
} }
.common-card-style { .common-card-style {
display: flex; display: flex;
align-items: flex-start;
background: #FFFFFF; background: #FFFFFF;
border-radius: 8px; border-radius: 8px;
position: relative; position: relative;
@@ -292,6 +308,12 @@ input[type=checkbox] {
padding: 0px 24px 20px; padding: 0px 24px 20px;
} }
@media (max-width: 350px) {
.course-card-content {
padding: 0px 10px 20px;
}
}
.course-card-title { .course-card-title {
font-weight: 600; font-weight: 600;
font-size: 18px; font-size: 18px;
@@ -303,9 +325,15 @@ input[type=checkbox] {
height: 45px; height: 45px;
} }
@media (max-width: 360px) {
.course-card-title {
font-size: 14px;
}
}
.card-divider { .card-divider {
border: 1px solid #F4F5F6; border: 1px solid #F4F5F6;
margin-bottom: 16px; margin-bottom: 1rem;
} }
.card-divider-dark { .card-divider-dark {
@@ -353,7 +381,7 @@ input[type=checkbox] {
@media (max-width: 767px) { @media (max-width: 767px) {
.cards-parent { .cards-parent {
grid-template-columns: repeat(auto-fill, minmax(300px, 336px)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
-moz-column-gap: 16px; -moz-column-gap: 16px;
column-gap: 16px; column-gap: 16px;
row-gap: 16px; row-gap: 16px;
@@ -362,7 +390,7 @@ input[type=checkbox] {
@media (max-width: 375px) { @media (max-width: 375px) {
.cards-parent { .cards-parent {
grid-template-columns: repeat(auto-fill, minmax(100%, 312px)); grid-template-columns: repeat(auto-fill, minmax(100%, 1fr));
-moz-column-gap: 24px; -moz-column-gap: 24px;
column-gap: 24px; column-gap: 24px;
row-gap: 24px; row-gap: 24px;
@@ -370,7 +398,7 @@ input[type=checkbox] {
} }
.courses-header { .courses-header {
padding: 50px 30px 20px; padding: 50px 20px 20px;
color: var(--text-color); color: var(--text-color);
font-weight: 600; font-weight: 600;
font-size: 22px; font-size: 22px;
@@ -378,22 +406,10 @@ input[type=checkbox] {
letter-spacing: -0.0175em letter-spacing: -0.0175em
} }
.course-top-section { @media (min-width: 576px) and (max-width: 992px) {
float: none; .container {
margin: 0 auto; padding-left: 1rem;
max-width: 1150px; padding-right: 1rem;
padding-bottom: 2rem;
}
@media (max-width: 768px) {
.course-top-section {
max-width: 720px;
}
}
@media (max-width: 600px) {
.course-top-section {
max-width: 342px;
} }
} }
@@ -471,27 +487,33 @@ input[type=checkbox] {
--star-fill: #74808B; --star-fill: #74808B;
} }
div.custom-checkbox>label>input { .custom-checkbox {
display: flex;
align-items: center;
}
.custom-checkbox>label>input {
visibility: hidden; visibility: hidden;
} }
div.custom-checkbox>label>img { .custom-checkbox>label>.empty-checkbox {
height: 20px; height: 1.5rem;
width: 20px; width: 1.5rem;
border: 1px solid black; border: 1px solid black;
border-radius: 5px; border-radius: 5px;
} }
div.custom-checkbox>label>input:checked+img { .custom-checkbox>label>input:checked+.empty-checkbox {
background: url(/assets/community/images/Vector.png); background: url(/assets/community/icons/tick.svg);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center center; background-position: center center;
background-size: 15px 15px; }
object-fit: contain;
.quiz-label {
margin-bottom: 0;
} }
.course-card-wide { .course-card-wide {
height: fit-content;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
padding: 24px; padding: 24px;
@@ -523,6 +545,7 @@ div.custom-checkbox>label>input:checked+img {
margin-right: 32px; margin-right: 32px;
border-radius: 5px; border-radius: 5px;
flex: 1; flex: 1;
align-self: center;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -541,23 +564,10 @@ div.custom-checkbox>label>input:checked+img {
} }
} }
@media (max-width: 500px) {
.course-home-page { .course-home-page {
max-width: 1150px; padding-right: 0;
margin: 0 auto; padding-left: 0;
padding-bottom: 2rem;
}
@media (max-width: 768px) {
.course-home-page {
max-width: 688px;
}
}
@media (max-width: 600px) {
.course-home-page {
width: 100%;
padding-left: 0px;
padding-right: 0px;
} }
} }
@@ -565,6 +575,7 @@ div.custom-checkbox>label>input:checked+img {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 2; flex: 2;
justify-content: space-between;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -613,7 +624,7 @@ div.custom-checkbox>label>input:checked+img {
} }
.button { .button {
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.13), 0px 0px 0.5px rgba(0, 0, 0, 0.5); box-shadow: var(--btn-shadow);
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@@ -665,6 +676,12 @@ div.custom-checkbox>label>input:checked+img {
color: #FFFFFF; color: #FFFFFF;
} }
.is-default {
background-color: white;
border: 1px solid #C8CFD5;
box-sizing: border-box;
}
.course-home-outline { .course-home-outline {
margin-top: 3rem; margin-top: 3rem;
} }
@@ -684,6 +701,7 @@ div.custom-checkbox>label>input:checked+img {
.chapter-description { .chapter-description {
height: fit-content; height: fit-content;
padding-left: 1rem; padding-left: 1rem;
padding-right: 1rem;
} }
.chapter-icon { .chapter-icon {
@@ -691,14 +709,39 @@ div.custom-checkbox>label>input:checked+img {
} }
.course-outline-instructor-parent { .course-outline-instructor-parent {
display: flex; display: grid;
justify-content: space-between; grid-gap: 2rem;
grid-template-columns: 4fr 1fr;
}
@media (max-width: 768px) {
.course-outline-instructor-parent {
grid-gap: 1rem;
}
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.course-outline-instructor-parent { .course-outline-instructor-parent {
flex-direction: column; padding: 0px 24px 0px;
padding: 0px 9px 0px; grid-template-columns: none;
}
}
.profile-parent-section {
display: grid;
grid-gap: 2rem;
grid-template-columns: 4fr 1fr;
}
@media (max-width: 768px) {
.profile-parent-section {
grid-gap: 1rem;
}
}
@media (max-width: 600px) {
.profile-parent-section {
grid-template-columns: none;
} }
} }
@@ -737,7 +780,7 @@ div.custom-checkbox>label>input:checked+img {
} }
.lesson-links { .lesson-links {
display: block; display: flex;
padding: 0 1rem; padding: 0 1rem;
margin-bottom: .25rem; margin-bottom: .25rem;
color: inherit; color: inherit;
@@ -862,20 +905,43 @@ div.custom-checkbox>label>input:checked+img {
margin: 0; margin: 0;
} }
.avatar-xl { .avatar-large {
width: 88px; width: 88px;
height: 88px; height: 88px;
} }
.avatar-xl {
width: 112px;
height: 112px;
}
@media (max-width: 500px) {
.avatar-xl {
width: 88px;
height: 88px;
}
}
@media (max-width: 375px) {
.avatar-xl {
width: 50px;
height: 50px;
}
}
.description-card { .description-card {
padding: 24px; padding: 1.5rem;
flex-direction: column; flex-direction: column;
} }
.overview-card { .overview-card {
padding: 8px 24px 8px; padding: 1.5rem;
width: 256px; width: 256px;
flex-direction: column; flex-direction: column;
display: grid;
-moz-column-gap: 1rem;
column-gap: 1rem;
row-gap: 1rem;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -886,7 +952,7 @@ div.custom-checkbox>label>input:checked+img {
@media (max-width: 600px) { @media (max-width: 600px) {
.overview-card { .overview-card {
padding: 8px 9px 8px; padding: 24px 26px 24px;
width: 100%; width: 100%;
} }
} }
@@ -908,10 +974,6 @@ div.custom-checkbox>label>input:checked+img {
} }
} }
.overtime-item {
margin: 16px 0px 16px;
}
.view-all-mentors { .view-all-mentors {
width: 100%; width: 100%;
display: flex; display: flex;
@@ -958,8 +1020,24 @@ div.custom-checkbox>label>input:checked+img {
color: red; color: red;
} }
.quiz-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.question { .question {
flex-direction: column; flex-direction: column;
width: 688px;
margin: auto;
}
.question p {
margin-bottom: 0;
}
.active-question .card-divider {
margin-top: 1rem;
} }
.dark-links { .dark-links {
@@ -971,20 +1049,16 @@ div.custom-checkbox>label>input:checked+img {
} }
.breadcrumb { .breadcrumb {
padding: 1rem 0.5rem 0; padding: 1rem 0 0;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 12px;
line-height: 135%;
color: var(--text-color);
} }
.course-details-outline { .course-details-outline {
width: 352px; margin-top: 1rem;
margin-top: 16px;
}
@media (max-width: 1025px) {
.course-details-outline {
width: 100%;
}
} }
.lesson-content-card { .lesson-content-card {
@@ -993,19 +1067,20 @@ div.custom-checkbox>label>input:checked+img {
} }
.course-content-parent { .course-content-parent {
display: flex; display: grid;
justify-content: space-between; grid-gap: 2rem;
flex-wrap: wrap-reverse; grid-template-columns: 2fr minmax(600px, 5fr);
} }
@media (max-width: 375px) { @media (max-width: 1024px) {
.course-content-parent { .course-content-parent {
justify-content: center; display: flex;
flex-direction: column-reverse;
} }
} }
.course-content-parent .course-home-headings { .course-content-parent .course-home-headings {
margin: 0px 0px 16px; margin: 0px 0px 1rem;
} }
.lesson-pagination { .lesson-pagination {
@@ -1015,13 +1090,12 @@ div.custom-checkbox>label>input:checked+img {
} }
.lesson-pagination-parent { .lesson-pagination-parent {
width: 736px; margin-top: 1rem;
margin-top: 16px;
} }
@media (max-width: 1025px) { @media (max-width: 768px) {
.lesson-pagination-parent { .lesson-pagination-parent {
width: 100%; margin-left: 0px;
} }
} }
@@ -1029,28 +1103,6 @@ div.custom-checkbox>label>input:checked+img {
width: 100%; width: 100%;
} }
.course-details-page {
padding: 0px 0px 80px;
display: flex;
flex-direction: column;
max-width: 1120px;
margin: 0 auto;
}
@media (max-width: 1025px) {
.course-details-page {
padding: 24px 0px 24px;
width: 80%;
}
}
@media (max-width: 768px) {
.course-details-page {
padding: 24px 0px 24px;
width: 90%;
}
}
.active-lesson { .active-lesson {
background-color: #EBF5FF; background-color: #EBF5FF;
border-radius: 4px; border-radius: 4px;
@@ -1066,25 +1118,152 @@ div.custom-checkbox>label>input:checked+img {
font-weight: bold; font-weight: bold;
} }
.lesson-progress-tick { .profile-page {
width: 16px; padding-top: 1rem;
height: 16px; }
background: #4C5A67;
border-radius: 2px; .profile-banner {
padding: 2px; height: 248px;
margin: 0px 4px 4px; border-radius: 12px 12px 0px 0px;
background-size: cover;
background-position: center;
}
@media (max-width: 500px) {
.profile-banner {
height: 150px;
}
}
.profile-about-section {
flex: 1;
margin-top: 3rem;
}
.profile-info {
height: 68px;
background: #ffffff;
border-radius: 0px 0px 12px 12px;
}
.profile-avatar {
position: relative;
top: 188px;
left: 40px;
display: flex;
}
@media (max-width: 500px) {
.profile-avatar {
top: 95px;
left: 10px;
}
}
@media (max-width: 375px) {
.profile-avatar {
top: 120px;
left: 10px;
}
}
.profile-name {
color: #FFFFFF;
font-weight: 600;
font-size: 22px;
line-height: 156%;
letter-spacing: -0.0175em;
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.64);
padding: 1.5rem;
}
@media (max-width: 375px) {
.profile-name {
font-size: 16px;
padding: 5px;
}
}
.creator-badge {
background: #48BB74;
padding: 4px 6px;
color: #ffffff;
font-style: normal;
font-weight: bold;
font-size: 10px;
line-height: 120%;
text-align: center;
letter-spacing: 0.011em;
text-transform: uppercase;
height: fit-content;
box-shadow: 0px 1px 1px rgb(0 0 0 / 16%);
border-radius: 4px;
margin-top: 2rem;
}
@media (max-width: 375px) {
.creator-badge {
font-size: 8px;
margin: 8px 0px 0px;
}
}
.profile-profession {
position: relative;
top: 16px;
left: 174px;
font-size: 12px;
line-height: 165%;
width: 85%;
}
@media (max-width: 1200px) {
.profile-profession {
width: 75%;
}
}
@media (max-width: 767px) {
.profile-profession {
width: 60%;
}
}
@media (max-width: 500px) {
.profile-profession {
top: 10px;
left: 120px;
}
}
@media (max-width: 375px) {
.profile-profession {
top: 5px;
left: 70px;
width: 75%;
}
}
.social-icons {
margin: 16px;
}
@media (max-width: 500px) {
.social-icons {
margin: 10px;
}
}
@media (max-width: 375px) {
.social-icons {
margin: 5px;
}
} }
.profile-courses { .profile-courses {
margin-top: 3rem; margin-top: 3rem;
} }
@media (max-width: 375px) {
.profile-courses {
padding: 0px 24px 0px;
}
}
.progress-text { .progress-text {
font-size: 12px; font-size: 12px;
line-height: 165%; line-height: 165%;
@@ -1116,9 +1295,9 @@ div.custom-checkbox>label>input:checked+img {
} }
} }
@media (max-width: 375px) { @media (max-width: 600px) {
.progress-card { .progress-card {
width: 312px; width: 100%;
} }
} }
@@ -1144,3 +1323,19 @@ pre {
width: 100%; width: 100%;
overflow-x: auto; overflow-x: auto;
} }
.markdown-source h1 {
font-size: 1.3rem;
}
.markdown-source h2 {
font-size: 1.2rem;
}
.markdown-source h3 {
font-size: 1.1rem;
}
.markdown-source h4 {
font-size: 1rem;
}

View File

@@ -1,307 +0,0 @@
h2 {
margin: 20px 0px;
color: black;
}
.teaser {
background: white;
border-radius: 9px;
border: 1px solid #C4C4C4;
.teaser-body {
padding: 20px;
box-shadow: 0px 5px 10px rgb(0 0 0 / 10%)
}
.teaser-footer {
padding: 20px;
}
}
.sketch-teaser {
.teaser();
width: 220px;
margin-bottom: 30px;
margin-top: 30px;
svg {
width: 200px;
height: 200px;
}
.sketch-image {
padding: 10px;
}
.sketch-footer {
border-top: 1px solid#C4C4C4;
padding: 10px;
background: #F6F6F6;
border-radius: 0px 0px 10px 10px;
}
}
.course-teaser {
.teaser();
color: #444;
margin-bottom: 20px;
margin-top: 20px;
h3, h4 {
color: black;
font-weight: bold;
}
.course-body, .course-footer {
padding: 20px;
}
.course-body {
min-height: 8em;
}
.course-footer {
border-top: 1px solid #ddd;
}
a, a:hover {
color: inherit;
text-decoration: none;
}
}
.anchor_style {
color: inherit;
}
.anchor_style:hover {
text-decoration: none
}
section {
padding: 60px 0px;
}
section h2 {
margin-bottom: 40px;
font-size: 48px;
line-height: 58px;
font-weight: bold;
}
section.lightgray {
background: #F6F6F6;
}
#hero .jumbotron {
background: inherit;
}
.chapter-teaser {
.teaser();
color: #444;
margin: 20px 0px;
h3, h4 {
color: black;
font-weight: bold;
}
}
.field-width {
width: 40%;
display: inline-block;
}
.footer-grouped-links {
display: none;
}
.footer-info {
border-top: 0px;
margin-top: 0px;
.footer-col-right {
padding-top: 1.8rem;
}
}
.web-footer {
border-top: 1px solid #E2E6E9;
padding: 0px;
padding: 2rem 0px;
margin-top: 2rem;
}
.course-type {
text-transform: uppercase;
font-size: 1.0em;
font-weight: bold;
color: var(--tag-color);
}
.instructor-title {
color: black;
}
.instructor-subtitle {
font-size: 0.8em;
color: var(--text-color);
}
// .mentors-wrapper {
// .gray-section();
// }
.chapter-number {
background: var(--text-color);
color: white;
height: 24px;
min-width: 24px;
margin-right: 5px;
}
.sidebar {
background: var(--sidebar-bg);
border: 1px solid var(--sidebar-border);
margin: 20px 0px;
border-radius: 10px;
padding: 1px 20px 20px 20px;
}
.sidebar h3 {
margin-top: 20px;
color: black;
}
.sidebar-batch {
background: var(--sidebar-bg);
color: var(--text-color);
position: fixed;
left: 0;
height: 100%;
}
.sidebar-batch a {
padding: 16px 8px 8px 16px;
display: block;
}
.sidebar .notice {
margin-top: 10px;
padding: 10px;
border-radius: 10px;
border: 1px dashed var(--text-color);
}
.sidebar .notice a {
color: inherit;
text-decoration: underline;
}
// LiveCode editor
.livecode-editor {
.CodeMirror {
border: 1px solid #ddd;
background: #ffe;
height: auto;
}
.CodeMirror-scroll {
max-height: 310px;
min-height: 310px;
}
.controls {
padding: 10px 0px;
}
canvas {
border: 5px solid #ddd;
position: relative;
z-index: 0;
}
.output {
position: absolute;
z-index: 1;
width: 300px;
left: 0px;
top: 0px;
background-color: rgba(255, 255, 255, 0);
max-height: 300px;
white-space: pre-wrap;
word-wrap: break-word;
margin: 0px;
margin-left: 20px;
padding: 4px;
color: #888;
}
@media (max-width: 768px) {
.canvas-wrapper {
padding-top: 10px;
}
.code-wrapper {
min-height: 50px;
}
.CodeMirror {
min-height: 50px;
}
}
}
.sketch-header {
input#sketch-title {
font-weight: bold;
}
}
.lesson-teaser {
line-height: 40px;
}
#hero h1 {
color: black !important;
}
.lesson-page {
margin: 20px 0px;
}
.exercise-image svg {
width: 200px;
height: 200px;
border: 1px solid #ddd;
margin-bottom: 20px;
}
.svg-200 svg {
width: 200px;
height: 200px;
}
.livecode-editor-small .livecode-editor {
.CodeMirror-scroll {
max-height: 160px;
min-height: 160px;
}
canvas {
width: 150px;
height: 150px;
}
}
.mentor-dashboard {
margin-top: 20px;
.submission {
margin: 40px 0px 0px 20px;
}
}
.no-preview-message {
width: fit-content;
margin: 50px 0px 50px;
color: black;
}

View File

@@ -0,0 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="2" y="2" width="20" height="20" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0" transform="scale(0.05)"/>
</pattern>
<image id="image0" width="20" height="20" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABiUlEQVQ4EZWU4VUCMQzHOwIjMAIb6AhuIBvgArb9QkLDh3MD3EA3wA10A9wAN0D/PdLLld5DeI+XuzT95Z9cU+cavxi7maf0GCi9BU6HwHI6//eBUhd5e9/Y1nZFSk+B5GggCqvtPq5l0aY456AqsOz+ARrAJMdI6aEJ9ZReb4INbThdtCCXOQrgeWRZepJvTeI5/ZjniHKLCCiN3SwrzaVWPdMSsFYCz07bN88p2iQ9kGWpTrU1RBPUtihEdSTHvO5Z3hUE6zl91Run3q1C7M0fyPapOKcIDX/dW2fVZaA2t7G55fIkH8qA4kvgLVPgnLMVZiB6phlgPctLS0nLh/Nn9+L4OQCsM0M3smoBrK+Hjeb8lI9UnWWAp4PnbXgmuVNQZJ77jaz6S6NcGHkUR6fDNHZ3MTXVBWBih5nWI1Myr2WhyqAY5wkNxvhpjFqUpbFqkUTXi8XmHEDyeW1SFASLUifjoc4c1Mn7ToFQNglTqQjAlwcYCdRvbVY1dQ/awPr5avZ6w9/7L3HVAW99VdWzAAAAAElFTkSuQmCC"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,9 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="2" y="3" width="20.7" height="18" fill="url(#pattern0)"/>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0" transform="scale(0.0434783 0.05)"/>
</pattern>
<image id="image0" width="23" height="20" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAUCAYAAABmvqYOAAABtUlEQVRIDa2UTU7CQBSAOYJH4CgcxSNwAIPoAgSEqAmIbohR/iQIgiK0nU75q4BiKz0ALnStxoULTMZMzWvGoUBrbPLy5jUz3zd5M61nKxJ7iR0kjz0uH1HEPhErH93+7eK1m+EoobEd3Z25kbSQrAtIJjQkrMxsJQCH7EQiIuQHMJvnJADlM5UkUukNvls3GHsFJL+yUH5MJe2eGvbwULaOp9KfmXzeywpaklzlYXb1cDTWl8ITqSOSyRYqAKeHaAfi33V6KtEmhuIETjKneR/GeK2F5CkP4mtJxkTTH13As4VpQ5B2eJBdPRiNiD4xXMFJ+bJuXjs7ILxTOl0T7BqeyRZIQxCXCu4ftL/Dc+cXC+H9wcACO9p5PHn4TnfMRvXqek6AFMU8RAqFWHlbQnv72bNi6Y2Fn+SKpCmiX4Lh3diCOoYHwpEgveOlcs3PSkqVmgVvd3tzYEdtATh8RKyk3mgSQUJfY03/HzgrKZarz11VfYI28NnseTAc8wVCUYX9p8CY3znAIWuG4aMQHmy1BSbaSVbBYa2dxNw5TIDMSpzCYS0rsYXDRCqhAbWb/COZrH8Ddw70agzSigAAAAAASUVORK5CYII="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" fill="#68D391" stroke="#68D391" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 12H16" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 425 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 309 B

View File

@@ -1,3 +1 @@
<svg class="icon"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
<use href="#icon-tick"></use>
</svg>

Before

Width:  |  Height:  |  Size: 58 B

After

Width:  |  Height:  |  Size: 262 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20ZM6.0429 6.04289C6.43342 5.65237 7.06659 5.65237 7.45711 6.04289L10.0011 8.58692L12.5451 6.04294C12.9356 5.65242 13.5688 5.65242 13.9593 6.04294C14.3499 6.43347 14.3499 7.06663 13.9593 7.45716L11.4154 10.0011L13.9593 12.5451C14.3499 12.9356 14.3499 13.5688 13.9593 13.9593C13.5688 14.3499 12.9357 14.3499 12.5451 13.9593L10.0011 11.4154L7.45711 13.9594C7.06659 14.3499 6.43342 14.3499 6.0429 13.9594C5.65237 13.5689 5.65237 12.9357 6.0429 12.5452L8.58693 10.0011L6.0429 7.45711C5.65237 7.06658 5.65237 6.43342 6.0429 6.04289Z" fill="#F56B6B"/>
</svg>

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,9 +1,8 @@
<div> <div>
{% set site_link = "<a href='" + site_url + "'>" + site_url + "</a>" %} {% set site_link = "<a href='" + site_url + "'>" + site_name + "</a>" %}
<p>{{_("Dear Community Member,")}}</p> <p>{{_("Hi,")}}</p>
<p>{{_("Your Invite Request to be a part of {0} has <p>{{_("Welcome to {0}!").format(site_name)}}</p>
been approved.").format(site_link)}}</p> <p>Click on the link below to complete your sign up and set a new password</p>
<p>Click on the link below to complete your Sign up and set a new password</p>
<p style="margin: 15px 0px;"> <p style="margin: 15px 0px;">
<a href="{{ signup_form_link }}" rel="nofollow" class="btn btn-primary">{{ _("Complete Sign Up") }}</a> <a href="{{ signup_form_link }}" rel="nofollow" class="btn btn-primary">{{ _("Complete Sign Up") }}</a>
</p> </p>
@@ -14,5 +13,5 @@
</p> </p>
<br> <br>
<p>Thanks and Regards,</p> <p>Thanks and Regards,</p>
<p>Your Community.</p> <p>{{site_name}}</p>
</div> </div>

View File

@@ -1,33 +1,59 @@
{% set last_submission = quiz.get_last_submission_details() %} <div id="quiz-title" class="course-home-headings">{{ quiz.title }}</div>
{% if last_submission %}
<div class="mb-5 pull-right"> <div class="card-divider"></div>
<div class="text-muted">Last Submitted On: {{ frappe.utils.pretty_date(last_submission.creation) }}</div>
<div class="text-muted">Last Submission Score: {{ last_submission.score }}</div> <div class="mt-5">
</div>
{% endif %}
<h3 id="title" class="mb-5">{{ quiz.title }}</h3>
<form id="quiz-form"> <form id="quiz-form">
<div class="questions">
{% for question in quiz.questions %} {% for question in quiz.questions %}
<div class="question mb-5" data-question="{{ question.question }}" <div class="question {% if loop.index == 1 %} active-question {% else %} hide {% endif %}"
data-multi="{{ question.multiple_correct_answers}}"> data-question="{{ question.question }}" data-multi="{{ question.multiple}}" data-qt-index="{{ loop.index }}">
<p> {{ loop.index }}. {{ question.question }}</p> <p>{{ frappe.utils.md_to_html(question.question) }}</p>
{% if question.multiple %}
<small class="font-weight-bold">Choose all answers that apply:</small>
{% else %}
<small class="font-weight-bold">Choose 1 answer:</small>
{% endif %}
<div class="card-divider"></div>
{% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %} {% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %}
{% for option in options %} {% for option in options %}
{% if option %} {% if option %}
<div class="custom-checkbox mb-2">
<label> <div class="custom-checkbox">
<input {% if question.multiple %} type="checkbox" {% else %} type="radio" <label class="quiz-label">
name="{{ question.question | urlencode }}" {% endif %} class="option" value="{{ option | urlencode }}"> <input class="option" value="{{ option | urlencode }}"
<img /> data-correct="{{ question['is_correct_' + loop.index | string] }}"
{% if question.multiple %} type="checkbox"
{% else %} type="radio" name="{{ question.question | urlencode }}" {% endif %}>
<img class="empty-checkbox mr-3"/>
</label> </label>
<span class="label-area">{{ option }}</span> <span class="label-area">{{ frappe.utils.md_to_html(option) }}</span>
</div> </div>
{% set explanation = question['explanation_' + loop.index | string] %}
{% if explanation %}
<small class="explanation muted-text hide">{{ explanation }}</small>
{% endif %}
<div class="card-divider"></div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}
<button class="btn btn-secondary hide mb-5" id="try-again">Try Again</button> </div>
<button class="btn btn-primary" id="submit-quiz">Submit</button> <div class="quiz-footer">
<span class="font-weight-bold"> <span class="current-question">1</span> of {{ quiz.questions | length }}</span>
<button class="btn btn-primary pull-right" id="check" disabled>Check</button>
<button class="btn btn-primary hide" id="next">Next</button>
<button class="btn btn-primary hide" id="summary">Summary</button>
</div>
<div class="button is-secondary pull-right hide" id="try-again">Try Again</div>
<h4 class="success-message"></h4> <h4 class="success-message"></h4>
<h5 class="score text-muted"></h5> <h5 class="score text-muted"></h5>
</form> </form>
</div>

View File

@@ -1,40 +0,0 @@
{% extends "templates/base.html" %}
{% block title %}About{% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
<meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %}
{% block content %}
<div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<div class="tab-content" id="about">
{{ CourseBasicDetail(course)}}
<div class="d-flex align-items-center">
<div class="col-lg-4 col-md-12">
<div class="sidebar">
{{ widgets.InstructorSection(instructor=course.get_instructor()) }}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% macro CourseBasicDetail(course) %}
<h2>{{course.title}}</h2>
<div class="course-description">
{{course.short_introduction}}
</div>
{% if course.video_link %}
<div class="preview-video">
<iframe width="560" height="315" src="{{course.video_link}}" title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
{% endif %}
<h2>About the Course</h2>
<div>{{frappe.utils.md_to_html(course.description)}}</div>
{% endmacro %}

View File

@@ -1,5 +0,0 @@
import frappe
from . import utils
def get_context(context):
utils.get_common_context(context)

View File

@@ -11,7 +11,6 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
{{ widgets.BatchTabs(course=course, membership=membership) }}
<div class="messages-container mt-5"> <div class="messages-container mt-5">
{{ widgets.BatchHeader(batch_name=batch.title, member_count=member_count)}} {{ widgets.BatchHeader(batch_name=batch.title, member_count=member_count)}}
<ol class="messages"> <ol class="messages">

View File

@@ -1,61 +0,0 @@
{% extends "templates/base.html" %}
{% block title %} Batch {% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
<meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %}
{% block content %}
<div class="container mt-5">
{{ widgets.BatchTabs(course=course, membership=membership) }}
<div class="course-details mt-5">
{{ widgets.CourseOutline(course=course, batch=batch, show_link=membership, show_progress=True) }}
</div>
{% if batch %}
<div class="w-25">
<h3>Batch Schedule</h3>
{{ widgets.RenderBatch(course=course, batch=batch) }}
</div>
{% if batch.description %}
<div class="mt-5">
<h3>Batch Details</h3>
{{ frappe.utils.md_to_html(batch.description) }}
</div>
{% endif %}
{% endif %}
{% if course.is_mentor(frappe.session.user) %}
{% set invite_link = frappe.utils.get_url() + "/courses/" + course.name + "/join?batch=" + batch.name %}
<div class="">
<h3> Invite Members </h3>
<a href="" class="" id="invite-link" data-link="{{ invite_link }}">Get Batch Invitation
Link</a>
<small id="copy-message" class="text-muted" style="display: none;">Copied to Clipboard.</small>
</div>
{% endif %}
</div>
<script>
frappe.ready(() => {
$("#invite-link").click((e) => {
e.preventDefault();
var link_element = $("#invite-link");
var input_element = document.createElement("input");
input_element.value = link_element.attr("data-link")
document.body.appendChild(input_element);
input_element.select();
document.execCommand("copy");
input_element.remove();
$("#copy-message").slideDown(function () {
setTimeout(function () {
$("#copy-message").slideUp();
}, 2000);
});
})
})
</script>
{% endblock %}

View File

@@ -1,5 +0,0 @@
import frappe
from . import utils
def get_context(context):
utils.get_common_context(context)

View File

@@ -1,13 +1,8 @@
{% extends "templates/base.html" %} {% extends "templates/base.html" %}
{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %} {% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
{% block title %}{{ lesson.title }}{% endblock %} {% block title %} {{ lesson.title }} - {{ course.title }} {% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="{{lesson.title}} - {{course.title}}" />
<meta name="keywords" content="{{lesson.title}} - {{course.title}}" />
<style>
</style>
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css"> <link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
<link rel="stylesheet" href="/assets/css/lms.css"> <link rel="stylesheet" href="/assets/css/lms.css">
<link rel="stylesheet" href="/assets/frappe/css/hljs-night-owl.css"> <link rel="stylesheet" href="/assets/frappe/css/hljs-night-owl.css">
@@ -21,7 +16,7 @@
{% block content %} {% block content %}
<div class="common-page-style"> <div class="common-page-style">
<div class="course-details-page"> <div class="container course-details-page">
{{ widgets.BreadCrumb(course=course, lesson=lesson) }} {{ widgets.BreadCrumb(course=course, lesson=lesson) }}
<div class="course-content-parent"> <div class="course-content-parent">
<div class="course-details-outline"> <div class="course-details-outline">
@@ -30,7 +25,7 @@
<div class="lesson-pagination-parent"> <div class="lesson-pagination-parent">
{{ LessonContent(lesson) }} {{ LessonContent(lesson) }}
{% if membership %} {% if membership %}
{{ pagination(prev_chap, prev_url, next_chap, next_url) }} {{ pagination(prev_url, next_url) }}
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -47,18 +42,17 @@
</div> </div>
{% if membership or lesson.include_in_preview %} {% if membership or lesson.include_in_preview %}
<div class="common-card-style lesson-content-card">{{ lesson.render_html() }}</div> <div class="common-card-style lesson-content-card markdown-source">{{ lesson.render_html() }}</div>
{% else %} {% else %}
<div class="no-preview-message"> <div class="common-card-style lesson-content-card">
<span>This lesson is not available for Preview. Please join the course to access this lesson.</span> <span>This lesson is not available for Preview. Please join the course to access this lesson. <a href="/courses/{{ course.name }}">Checkout Course Details.</a></span>
<a href="/courses/{{ course.name }}">Checkout Course Details.</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% endmacro %} {% endmacro %}
{% macro pagination(prev_chap, prev_url, next_chap, next_url) %} {% macro pagination(prev_url, next_url) %}
<div class="lesson-pagination"> <div class="lesson-pagination">
<div> <div>

View File

@@ -1,13 +1,27 @@
frappe.ready(() => { frappe.ready(() => {
localStorage.removeItem($("#quiz-title").text());
save_current_lesson(); save_current_lesson();
$(".option").click((e) => {
enable_check(e);
})
$("#progress").click((e) => { $("#progress").click((e) => {
mark_progress(e); mark_progress(e);
}); });
$("#submit-quiz").click((e) => { $("#summary").click((e) => {
submit_quiz(e); quiz_summary(e);
});
$("#check").click((e) => {
check_answer(e);
});
$("#next").click((e) => {
mark_active_question(e);
}); });
$("#try-again").click((e) => { $("#try-again").click((e) => {
@@ -25,6 +39,27 @@ var save_current_lesson = () => {
} }
} }
var enable_check = (e) => {
if ($(".option:checked").length && $("#check").attr("disabled")) {
$("#check").removeAttr("disabled");
}
}
var mark_active_question = (e = undefined) => {
var current_index;
var next_index = 1;
if (e) {
e.preventDefault();
current_index = $(".active-question").attr("data-qt-index");
next_index = parseInt(current_index) + 1;
}
$(".question").addClass("hide").removeClass("active-question");
$(`.question[data-qt-index='${next_index}']`).removeClass("hide").addClass("active-question");
$(".current-question").text(`${next_index}`);
$("#check").removeClass("hide").attr("disabled", true);
$("#next").addClass("hide");
}
var mark_progress = (e) => { var mark_progress = (e) => {
var status = $(e.currentTarget).attr("data-progress"); var status = $(e.currentTarget).attr("data-progress");
frappe.call({ frappe.call({
@@ -56,47 +91,77 @@ var change_progress_indicators = (status, e) => {
$(e.currentTarget).text(label).attr("data-progress", data_progress); $(e.currentTarget).text(label).attr("data-progress", data_progress);
} }
var submit_quiz = (e) => { var quiz_summary = (e) => {
e.preventDefault(); e.preventDefault();
var result = []; var quiz_name = $("#quiz-title").text();
$('.question').each((i, element) => { var total_questions = $(".question").length;
var options = $(element).find(".option");
var answers = [];
options.filter((i, op) => $(op).prop("checked")).each((i, elem) => answers.push(decodeURIComponent(elem.value)));
result.push({
"question": element.dataset.question,
"answer": answers
});
});
frappe.call({ frappe.call({
method: "community.lms.doctype.lms_quiz.lms_quiz.submit", method: "community.lms.doctype.lms_quiz.lms_quiz.quiz_summary",
args: { args: {
quiz: $("#title").text(), "quiz": quiz_name,
result: result "results": localStorage.getItem(quiz_name)
}, },
callback: (data) => { callback: (data) => {
$("#submit-quiz").addClass("hide"); var message = data.message == total_questions ? "Excellent Work" : "You were almost there."
$(".question").addClass("hide");
$(".quiz-footer").addClass("hide");
$("#quiz-form").parent().prepend(
`<div class="text-center summary"><h2>${message} 👏 </h2>
<div class="font-weight-bold">${data.message}/${total_questions} correct.</div></div>`);
$("#try-again").removeClass("hide"); $("#try-again").removeClass("hide");
$(":input[type='checkbox']").prop("disabled", true);
$(":input[type='radio']").prop("disabled", true);
if (data.message == result.length) {
$(".success-message").text("Congratulations, you cleared the quiz!");
}
else {
$(".success-message").text("Some of your answers weren't correct. You can give it another shot.");
}
$(".score").text(`Score: ${data.message}/${result.length}`);
} }
}) })
} }
var try_quiz_again = (e) => { var try_quiz_again = (e) => {
e.preventDefault(); window.location.reload();
$(":input[type='checkbox']").prop("disabled", false); }
$(":input[type='radio']").prop("disabled", false);
$("#quiz-form").trigger("reset"); var check_answer = (e) => {
$(".success-message").text(""); e.preventDefault();
$(".score").text("");
$("#submit-quiz").removeClass("hide"); var quiz_name = $("#quiz-title").text();
$("#try-again").addClass("hide"); var total_questions = $(".question").length;
var current_index = $(".active-question").attr("data-qt-index");
$(".explanation").removeClass("hide");
$("#check").addClass("hide");
current_index == total_questions ? $("#summary").removeClass("hide") : $("#next").removeClass("hide");
var [answer, is_correct] = parse_options();
add_to_local_storage(quiz_name, current_index, answer, is_correct)
}
var parse_options = () => {
var answer = [];
var is_correct = [];
$(".active-question input").each((i, element) => {
var correct = parseInt($(element).attr("data-correct"));
if ($(element).prop("checked")) {
answer.push(decodeURIComponent($(element).val()));
correct && is_correct.push(1);
correct ? add_icon(element, "check") : add_icon(element, "wrong");
}
else {
correct && is_correct.push(0);
correct ? add_icon(element, "minus-circle-green") : add_icon(element, "minus-circle");
}
})
return [answer, is_correct];
}
var add_icon = (element, icon) => {
$(element).parent().empty().html(`<img class="mr-3" src="/assets/community/icons/${icon}.svg">`);
}
var add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
var quiz_stored = JSON.parse(localStorage.getItem(quiz_name));
var quiz_obj = {
"question_index": current_index,
"answer": answer.join(),
"is_correct": is_correct
}
quiz_stored ? quiz_stored.push(quiz_obj) : quiz_stored = [quiz_obj]
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored))
} }

View File

@@ -12,7 +12,6 @@ def get_context(context):
lesson_index = frappe.form_dict.get("lesson") lesson_index = frappe.form_dict.get("lesson")
lesson_number = f"{chapter_index}.{lesson_index}" lesson_number = f"{chapter_index}.{lesson_index}"
course_name = context.course.name
if not chapter_index or not lesson_index: if not chapter_index or not lesson_index:
if context.batch: if context.batch:
index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1" index_ = get_lesson_index(context.course, context.batch, frappe.session.user) or "1.1"
@@ -21,20 +20,19 @@ def get_context(context):
frappe.local.flags.redirect_location = context.course.get_learn_url(index_) + context.course.query_parameter frappe.local.flags.redirect_location = context.course.get_learn_url(index_) + context.course.query_parameter
raise frappe.Redirect raise frappe.Redirect
context.lesson = context.course.get_lesson(chapter_index, lesson_index) context.lesson = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))[0]
context.lesson_index = lesson_index neighbours = context.course.get_neighbours(lesson_number, context.lessons)
context.chapter_index = chapter_index context.next_url = get_learn_url(neighbours["next"], context.course)
context.prev_url = get_learn_url(neighbours["prev"], context.course)
outline = context.course.get_outline() meta_info = context.lesson.title + " - " + context.course.title
prev_ = outline.get_prev(lesson_number) context.metatags = {
next_ = outline.get_next(lesson_number) "title": meta_info,
context.prev_chap = get_chapter_title(course_name, prev_) "keywords": meta_info,
context.next_chap = get_chapter_title(course_name, next_) "description": meta_info
context.next_url = context.course.get_learn_url(next_) and context.course.get_learn_url(next_) + context.course.query_parameter }
context.prev_url = context.course.get_learn_url(prev_) and context.course.get_learn_url(prev_) + context.course.query_parameter
context.page_extensions = get_page_extensions() context.page_extensions = get_page_extensions()
context.page_context = { context.page_context = {
"course": context.course.name, "course": context.course.name,
"batch": context.get("batch") and context.batch.name, "batch": context.get("batch") and context.batch.name,
@@ -42,6 +40,9 @@ def get_context(context):
"is_member": context.membership is not None "is_member": context.membership is not None
} }
def get_learn_url(lesson_number, course):
return course.get_learn_url(lesson_number) and course.get_learn_url(lesson_number) + course.query_parameter
def get_chapter_title(course_name, lesson_number): def get_chapter_title(course_name, lesson_number):
if not lesson_number: if not lesson_number:
return return

View File

@@ -1,40 +0,0 @@
{% extends "templates/base.html" %}
{% block title %}Members{% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
<meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %}
{% block content %}
<div class="container">
{{ widgets.BatchTabs(course=course, membership=membership) }}
{{ MembersList(members)}}
</div>
{% endblock %}
{% macro MembersList(members) %}
<div class="mt-5">
{% for member in members %}
<div class="d-flex align-items-center">
{{ widgets.Avatar(member=member, avatar_class="avatar-medium") }}
<div class="d-flex flex-column ml-2">
<div class="d-flex">
<a class="anchor_style ml-2" href="/{{member.username}}">
<div class="review-content">{{ member.full_name }}</div>
</a>
{% if course.is_mentor(member.name) %}
<div class="badge badge-success ml-2 align-self-start">Mentor</div>
{% endif %}
</div>
</div>
</div>
{% if loop.index != member_count %}
<hr>
{% endif %}
{% endfor %}
</div>
{% endmacro %}

View File

@@ -1,7 +0,0 @@
import frappe
from . import utils
def get_context(context):
utils.get_common_context(context)
if not context.membership:
utils.redirect_to_lesson(context.course)

View File

@@ -1,51 +0,0 @@
{% extends "templates/base.html" %}
{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
{% block title %}{{ course.title }} - Batch Dashboard{% endblock %}
{% block head_include %}
<meta name="description" content="{{course.title}} - Batch Dashboard" />
<meta name="keywords" content="{{course.title}} - Batch Dashboard" />
<style>
</style>
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="/assets/css/lms.css">
<script src="{{ livecode_url }}/static/codemirror/lib/codemirror.js"></script>
<script src="{{ livecode_url }}/static/codemirror/mode/python/python.js"></script>
<script src="{{ livecode_url }}/static/codemirror/keymap/sublime.js"></script>
<script src="{{ livecode_url }}/static/codemirror/addon/edit/matchbrackets.js"></script>
<script src="{{ livecode_url }}/static/codemirror/addon/comment/comment.js"></script>
{% endblock %}
{% block content %}
<div class="container">
{{ widgets.BatchTabs(course=course, membership=membership) }}
<div class="mentor-dashboard">
<h3>Batch Progress</h3>
{% for exercise in report.exercises %}
<div class="exercise-submissions">
<h2>Exercise {{exercise.index_label}}: {{exercise.title}}</h2>
{% for s in report.get_submissions_of_exercise(exercise.name) %}
<div class="submission">
<h4><a href="/{{s.owner.username}}">{{s.owner.full_name}}</a></h4>
<div class="livecode-editor-small">
{{ LiveCodeEditor(name=s.name, code=s.solution, reset_code=s.solution) }}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{%- block script %}
{{ super() }}
{{ LiveCodeEditorJS() }}
{% endblock %}

View File

@@ -1,62 +0,0 @@
import frappe
from community.lms.models import Course
from collections import defaultdict
from . import utils
def get_context(context):
utils.get_common_context(context)
exercise_name = frappe.form_dict.get("exercise")
if exercise_name:
exercise = frappe.get_doc("Exercise", exercise_name)
else:
exercise = None
context.exercise = exercise
context.report = BatchReport(context.course, context.batch)
class BatchReport:
def __init__(self, course, batch):
self.submissions = get_submissions(course, batch)
self.exercises = self.get_exercises(course.name)
self.submissions_by_exercise = defaultdict(list)
for s in self.submissions:
self.submissions_by_exercise[s.exercise].append(s)
def get_exercises(self, course_name):
return frappe.get_all("Exercise", {"course": course_name, "lesson": ["!=", ""]}, ["name", "title", "index_label"], order_by="index_label")
def get_submissions_of_exercise(self, exercise_name):
return self.submissions_by_exercise[exercise_name]
def get_submissions(course, batch):
students = course.get_students(batch.name)
if not len(students):
return []
students_map = {s.email: s for s in students}
names, values = nparams("s", students_map.keys())
sql = """
select owner, exercise, name, solution, creation, image
from (
select owner, exercise, name, solution, creation, image,
row_number() over (partition by owner, exercise order by creation desc) as ix
from `tabExercise Submission`) as t
where t.ix=1 and owner IN {}
""".format(names)
data = frappe.db.sql(sql, values=values, as_dict=True)
for row in data:
row['owner'] = students_map[row['owner']]
return data
def nparams(name, values):
"""Creates n paramters from a list of values for a db query.
>>> nparams("name", ["a", "b])
("(%(name_1)s, %(name_2)s)", {"name_1": "a", "name_2": "b"})
"""
keys = [f"{name}_{i}" for i, _ in enumerate(values, start=1)]
param_names = [f"%({k})s" for k in keys]
param_values = dict(zip(keys, values))
joined_names = "(" + ", ".join(param_names) + ")"
return joined_names, param_values

View File

@@ -1,17 +0,0 @@
{% extends "templates/base.html" %}
{% block title %}Schedule{% endblock %}
{% block head_include %}
<meta name="description" content="Courses" />
<meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %}
{% block content %}
<div class="container">
{{ widgets.BatchTabs(course=course, batch=batch) }}
<h3>
Schedule
</h3>
</div>
{% endblock %}

View File

@@ -1,20 +0,0 @@
import frappe
from community.lms.models import Course
def get_context(context):
context.no_cache = 1
course_name = frappe.form_dict["course"]
batch_name = frappe.form_dict["batch"]
course = Course.find(course_name)
if not course:
context.template = "www/404.html"
return
batch = course.get_batch(batch_name)
if not batch:
frappe.local.flags.redirect_location = "/courses/" + course_name
raise frappe.Redirect
context.course = course
context.batch = batch

View File

@@ -10,12 +10,12 @@ def get_common_context(context):
except KeyError: except KeyError:
batch_name = None batch_name = None
course = Course.find(course_name) course = frappe.get_doc("LMS Course", course_name)
if not course: if not course:
context.template = "www/404.html" context.template = "www/404.html"
return return
context.course = course context.course = course
context.lessons = course.get_lessons()
membership = course.get_membership(frappe.session.user, batch_name) membership = course.get_membership(frappe.session.user, batch_name)
context.membership = membership context.membership = membership
if membership: if membership:
@@ -24,9 +24,6 @@ def get_common_context(context):
if batch: if batch:
context.batch = batch context.batch = batch
context.members = course.get_mentors(membership.batch) + course.get_students(membership.batch)
context.member_count = len(context.members)
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.livecode_url = get_livecode_url() context.livecode_url = get_livecode_url()

View File

@@ -3,13 +3,11 @@
{% block title %}{{ course.title }} {% block title %}{{ course.title }}
{% endblock %} {% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="Courses" />
<meta name="keywords" content="Courses {{course.title}}" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css"> <link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="common-page-style"> <div class="common-page-style">
<div class="col course-home-page"> <div class="container course-home-page">
{{ widgets.BreadCrumb(course=course) }} {{ widgets.BreadCrumb(course=course) }}
{{ CourseCardWide(course) }} {{ CourseCardWide(course) }}
{{ CourseOutlineAndCreator(course) }} {{ CourseOutlineAndCreator(course) }}
@@ -24,20 +22,26 @@
{% macro CourseCardWide(course) %} {% macro CourseCardWide(course) %}
<div class="common-card-style course-card-wide"> <div class="common-card-style course-card-wide">
<div class="course-image-wide" style="background-image: url({{ course.image }});"> <div class="course-image-wide {% if not course.image %} default-image {% endif %}"
{% if course.image %}style="background-image: url({{ course.image }});"{% endif %}>
<div class="course-tags"> <div class="course-tags">
{% for tag in course.get_tags() %} {% for tag in course.get_tags() %}
<div class="course-card-pills">{{ tag }}</div> <div class="course-card-pills">{{ tag }}</div>
{% endfor %} {% endfor %}
</div> </div>
{% if not course.image %}
<div class="default-image-text">{{ course.title[0] }}</div>
{% endif %}
</div> </div>
<div class="course-card-wide-content"> <div class="course-card-wide-content">
<div class="course-info">
<div class="course-card-wide-title"> <div class="course-card-wide-title">
{{ course.title }} {{ course.title }}
</div> </div>
<div class="course-card-wide-intro"> <div class="course-card-wide-intro">
{{ course.short_introduction }} {{ course.short_introduction }}
</div> </div>
</div>
<div class="course-buttons"> <div class="course-buttons">
{% if not course.disable_self_learning and not membership %} {% if not course.disable_self_learning and not membership %}
<div class="button wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}"> <div class="button wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}">
@@ -88,34 +92,43 @@
{% macro CourseOutlineAndCreator(course) %} {% macro CourseOutlineAndCreator(course) %}
<div class="course-outline-instructor-parent"> <div class="course-outline-instructor-parent">
<div class="course-home-outline col"> <div class="course-home-outline">
{{ widgets.CourseOutline(course=course, membership=membership) }} {{ widgets.CourseOutline(course=course, membership=membership) }}
</div> </div>
<div class="course-creator-progress-parent col-sm-auto"> <div class="course-creator-progress-parent">
<div class="course-creator-section"> <div class="course-creator-section">
<div class="course-home-headings"> <div class="course-home-headings">
Creator Creator
</div> </div>
{{ widgets.MemberCard(member=course.get_instructor(), show_course_count=True, dimension_class="member-card-large") }} {{ widgets.MemberCard(member=course.get_instructor(), show_course_count=True, dimension_class="member-card-large") }}
</div> </div>
{% if course.get_course_progress() %} {% set progress = course.get_course_progress() %}
{% if progress %}
<div class="course-progress-section"> <div class="course-progress-section">
<div class="course-home-headings"> <div class="course-home-headings">
Your Progress Your Progress
</div> </div>
<div class="common-card-style progress-card"> <div class="common-card-style progress-card">
<p class="small-title"> <p class="small-title">
{% if progress != 100 %}
Great work so far! Great work so far!
{% else %}
Excellent Work on completing this course 👏
{% endif %}
</p> </p>
<p class="progress-text"> <p class="progress-text">
{% if progress != 100 %}
Challenge yourself to complete the lessons and grow professionally. Challenge yourself to complete the lessons and grow professionally.
{% else %}
You have reached a new level in your journey to success!
{% endif %}
</p> </p>
<div class="progress-percentage"> <div class="progress-percentage">
{{ frappe.utils.rounded(course.get_course_progress()) }}% {{ frappe.utils.rounded(progress) }}%
</div> </div>
<div class="progress"> <div class="progress">
<div class="progress-bar" role="progressbar" style="width: {{ course.get_course_progress() }}%" <div class="progress-bar" role="progressbar" style="width: {{ progress }}%"
aria-valuenow="{{ course.get_course_progress() }}" aria-valuemin="0" aria-valuemax="100"></div> aria-valuenow="{{ progress }}" aria-valuemin="0" aria-valuemax="100"></div>
</div> </div>
</div> </div>
</div> </div>
@@ -151,7 +164,7 @@
{% macro CourseDescriptionAndOverview(course) %} {% macro CourseDescriptionAndOverview(course) %}
<div class="course-outline-instructor-parent"> <div class="course-outline-instructor-parent">
<div class="course-description-section col"> <div class="course-description-section">
<div class="course-home-headings"> <div class="course-home-headings">
Course Description Course Description
</div> </div>
@@ -161,7 +174,7 @@
</div> </div>
{% set avg_rating = course.get_average_rating() %} {% set avg_rating = course.get_average_rating() %}
{% if course.get_students() | length or avg_rating %} {% if course.get_students() | length or avg_rating %}
<div class="course-overview-section col-sm-auto"> <div class="course-overview-section">
<div class="course-home-headings"> <div class="course-home-headings">
Overview Overview
</div> </div>
@@ -183,55 +196,3 @@
{% endif %} {% endif %}
</div> </div>
{% endmacro %} {% endmacro %}
{% macro BatchSection(course) %}
<div class="row">
<div class="col-lg-8 col-md-12">
{% if course.is_mentor(frappe.session.user) %}
{{ BatchSectionForMentors(course, course.get_batches(mentor=frappe.session.user)) }}
{% else %}
{{ BatchSectionForStudents(course, course.get_upcoming_batches()) }}
{% endif %}
</div>
</div>
{% endmacro %}
{% macro BatchSectionForMentors(course, mentor_batches) %}
<h2>Your Batches</h2>
{% if mentor_batches %}
<div class="row">
{% for batch in mentor_batches %}
<div class="col-lg-4 col-md-6">
{{ widgets.RenderBatch(course=course, batch=batch, can_manage=True) }}
</div>
{% endfor %}
</div>
<a class="add-batch margin-bottom" href="/add-a-new-batch?new=1&course={{course.name}}">Add a new batch</a>
{% else %}
<div class="mentor_message">
<p>
You are a mentor for this course.
</p>
<a class="" href="/add-a-new-batch?new=1&course={{course.name}}">Create your first batch</a>
</div>
{% endif %}
{% endmacro %}
{% macro BatchSectionForStudents(course, upcoming_batches) %}
{% if upcoming_batches %}
<div class="mt-5">
<h3 class="upcoming">Upcoming Batches</h3>
<div class="row">
{% for batch in upcoming_batches %}
<div class="col-lg-4 col-md-6">
{{ widgets.RenderBatch(course=course, batch=batch, can_join=True) }}
</div>
{% endfor %}
</div>
{% else %}
<div class="mt-5 upcoming">There are no Upcoming Batches for this course currently.</div>
{% endif %}
</div>
{% endmacro %}

View File

@@ -1,5 +1,4 @@
import frappe import frappe
from community.lms.models import Course
def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
@@ -10,7 +9,7 @@ def get_context(context):
frappe.local.flags.redirect_location = "/courses" frappe.local.flags.redirect_location = "/courses"
raise frappe.Redirect raise frappe.Redirect
course = Course.find(course_name) course = frappe.get_doc("LMS Course", course_name)
if course is None: if course is None:
frappe.local.flags.redirect_location = "/courses" frappe.local.flags.redirect_location = "/courses"
raise frappe.Redirect raise frappe.Redirect
@@ -19,3 +18,9 @@ def get_context(context):
membership = course.get_membership(frappe.session.user) membership = course.get_membership(frappe.session.user)
context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else "" context.course.query_parameter = "?batch=" + membership.batch if membership and membership.batch else ""
context.membership = membership context.membership = membership
context.metatags = {
"title": course.title,
"image": course.image,
"description": course.short_introduction,
"keywords": course.title
}

View File

@@ -2,15 +2,13 @@
{% from "www/hackathons/macros/card.html" import null_card %} {% from "www/hackathons/macros/card.html" import null_card %}
{% block title %}{{ 'Courses' }}{% endblock %} {% block title %}{{ 'Courses' }}{% endblock %}
{% block head_include %} {% block head_include %}
<meta name="description" content="{{ 'Courses' }}" />
<meta name="keywords" content="Courses" />
<style> <style>
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="common-page-style"> <div class="common-page-style">
<div class="col course-top-section"> <div class="container">
<div class="courses-header"> <div class="courses-header">
{{ 'All Courses' }} {{ 'All Courses' }}
</div> </div>
@@ -22,16 +20,3 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block script %}
<script>
frappe.ready(() => {
$(".course-card-title").each((i, element) => {
var title = $(element).text();
var length = $(window).width() <= 375 ? 60 : 65;
var suffix = title.length > length ? "..." : "";
$(element).text(title.substring(0, length) + suffix);
})
})
</script>
{% endblock %}

View File

@@ -3,9 +3,15 @@ import frappe
def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
context.courses = get_courses() context.courses = get_courses()
context.metatags = {
"title": "All Courses",
"image": frappe.db.get_single_value("Website Settings", "banner_image"),
"description": "This page lists all the courses published on our website",
"keywords": "All Courses, Courses, Learn"
}
def get_courses(): def get_courses():
course_names = frappe.get_all("LMS Course", pluck="name") course_names = frappe.get_all("LMS Course", filters={"is_published": True}, pluck="name")
courses = [] courses = []
for course in course_names: for course in course_names:
courses.append(frappe.get_doc("LMS Course", course)) courses.append(frappe.get_doc("LMS Course", course))

View File

@@ -1,82 +0,0 @@
{% extends "templates/base.html" %}
{% from "www/hackathons/macros/card.html" import null_card %}
{% block title %}{{ 'My Courses' }}{% endblock %}
{% block head_include %}
<meta name="description" content="My Courses" />
<meta name="keywords" content="" />
<style>
div.card-hero-img {
height: 220px;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-color: rgb(250, 251, 252);
}
.card-image-wrapper {
display: flex;
overflow: hidden;
height: 220px;
background-color: rgb(250, 251, 252);
justify-content: center;
}
.image-body {
align-self: center;
color: #d1d8dd;
font-size: 24px;
font-weight: 600;
line-height: 1;
padding: 20px;
}
.no-courses {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
section {
padding: 5rem 0 5rem 0;
}
</style>
{% endblock %}
{% macro card(course) %}
<div class="col-sm-4 mb-4 text-left">
<a href="//courses/course?course={{course.name}}" class="no-decoration no-underline">
<div class="card h-100">
<div class='card-body'>
<h5 class='card-title'>{{ course.title }}</h5>
</div>
</div>
</a>
</div>
{% endmacro %}
{% block content %}
<section class="section">
<div class='container'>
{% if frappe.session.user != "Guest" %}
{% for course in my_courses %}
{{ card(course) }}
{% endfor %}
{% if my_courses %}
{% for n in range( (3 - (my_courses|length)) %3) %}
{{ null_card() }}
{% endfor %}
{% else %}
<div class="no-courses">You haven't enrolled in any Course yet. <a href="/courses">Check out the availabe
courses.</a></div>
{% endif %}
{% else %}
<div class="no-courses">
<p>Please sign up to access this page.</p>
<a id="signup" class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@@ -1,15 +0,0 @@
import frappe
def get_context(context):
context.no_cache = 1
context.my_courses = get_my_courses()
def get_my_courses():
my_courses = []
courses = frappe.get_all("LMS Course Enrollment", {"owner": frappe.session.user}, ["course"])
for course in courses:
my_courses.append({
"name": course.course,
"title": frappe.db.get_value("LMS Course", course.course, ["title"])
})
return my_courses

View File

@@ -6,23 +6,62 @@
{% block content %} {% block content %}
<div class="common-page-style"> <div class="common-page-style">
<div class="course-home-page"> <div class="container profile-page">
{{ widgets.MemberCard(member=member, show_course_count=True, dimension_class="member-card-xl") }} {{ ProfileBanner(member) }}
{{ AboutOverviewSection(member) }} {{ AboutOverviewSection(member) }}
<div class="profile-courses">
{{ CoursesCreated(member) }} {{ CoursesCreated(member) }}
{{ CoursesMentored(member) }} {{ CoursesMentored(member) }}
{{ CoursesEnrolled(member) }} {{ CoursesEnrolled(member) }}
</div>
{{ ProfileTabs(profile_tabs) }} {{ ProfileTabs(profile_tabs) }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% macro ProfileBanner(member) %}
<div class="">
<div class="profile-banner" style="background-image: url(/assets/community/images/profile-banner.png)">
<div class="profile-avatar">
{{ widgets.Avatar(member=member, avatar_class="avatar-xl") }}
<div class="profile-name"> {{ member.full_name }} </div>
{% if member.get_authored_courses() | length %}
<div class="creator-badge"> Creator </div>
{% endif %}
</div>
</div>
<div class="profile-info">
<div class="profile-profession">
{% if member.profession %}
<span class=""> {{ member.profession }} </span>
{% endif %}
<span class="social-icons">
{% if member.linkedin %}
<a class="linkedin button-links" href="{{ member.linkedin }}">
<img src="/assets/community/images/linkedin.png">
</a>
{% endif %}
{% if member.medium %}
<a class="medium button-links" href="{{ member.medium}}">
<img src="/assets/community/icons/medium.svg">
</a>
{% endif %}
{% if member.github %}
<a class="github button-links" href="{{ member.github }}">
<img src="/assets/community/icons/github.svg">
</a>
{% endif %}
</span>
{% if frappe.session.user == member.email %}
<a class="dark-links pull-right" href="edit-profile?name={{ member.email }}">Edit Profile</a>
{% endif %}
</div>
</div>
</div>
{% endmacro %}
{% macro AboutOverviewSection(member) %} {% macro AboutOverviewSection(member) %}
<div class="course-outline-instructor-parent"> <div class="profile-parent-section">
{% if member.bio %} {% if member.bio %}
<div class="course-overview-section"> <div class="profile-about-section">
<div class="course-home-headings"> <div class="course-home-headings">
About About
</div> </div>
@@ -62,6 +101,7 @@
{% macro CoursesCreated(member) %} {% macro CoursesCreated(member) %}
{% if member.get_authored_courses() | length %} {% if member.get_authored_courses() | length %}
<div class="profile-courses">
<div class="course-home-headings"> <div class="course-home-headings">
Courses Created Courses Created
</div> </div>
@@ -71,25 +111,29 @@
{{ widgets.CourseCard(course=course_details) }} {{ widgets.CourseCard(course=course_details) }}
{% endfor %} {% endfor %}
</div> </div>
</div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro CoursesMentored(member) %} {% macro CoursesMentored(member) %}
{% if member.get_course_membership("Mentor") | length %} {% if member.get_mentored_courses() | length %}
<div class="profile-courses">
<div class="course-home-headings"> <div class="course-home-headings">
Courses Mentored Courses Mentored
</div> </div>
<div class="cards-parent"> <div class="cards-parent">
{% for membership in member.get_course_membership("Mentor") %} {% for mentorship in member.get_mentored_courses() %}
{% set course_details = frappe.get_doc("LMS Course", membership.course) %} {% set course_details = frappe.get_doc("LMS Course", mentorship.course) %}
{{ widgets.CourseCard(course=course_details) }} {{ widgets.CourseCard(course=course_details) }}
{% endfor %} {% endfor %}
</div> </div>
</div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro CoursesEnrolled(member) %} {% macro CoursesEnrolled(member) %}
{% if member.get_course_membership("Student") | length %} {% if member.get_course_membership("Student") | length %}
<div class="profile-courses">
<div class="course-home-headings"> <div class="course-home-headings">
Courses Enrolled Courses Enrolled
</div> </div>
@@ -99,6 +143,7 @@
{{ widgets.CourseCard(course=course_details) }} {{ widgets.CourseCard(course=course_details) }}
{% endfor %} {% endfor %}
</div> </div>
</div>
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}