Merge branch 'main' of https://github.com/frappe/school into profile-redesign
This commit is contained in:
@@ -183,7 +183,9 @@ jinja = {
|
||||
"school.lms.utils.get_reviews",
|
||||
"school.lms.utils.is_eligible_to_review",
|
||||
"school.lms.utils.get_initial_members",
|
||||
"school.lms.utils.get_sorted_reviews"
|
||||
"school.lms.utils.get_sorted_reviews",
|
||||
"school.lms.utils.is_instructor",
|
||||
"school.lms.utils.convert_number_to_character"
|
||||
],
|
||||
"filters": []
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import re
|
||||
import frappe
|
||||
from frappe.utils import flt, cint, cstr
|
||||
from school.lms.md import markdown_to_html
|
||||
import string
|
||||
|
||||
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
|
||||
|
||||
@@ -81,7 +82,7 @@ def get_lesson_details(chapter):
|
||||
|
||||
for row in lesson_list:
|
||||
lesson_details = frappe.db.get_value("Course Lesson", row.lesson,
|
||||
["name", "title", "include_in_preview", "body"], as_dict=True)
|
||||
["name", "title", "include_in_preview", "body", "creation"], as_dict=True)
|
||||
lesson_details.number = flt("{}.{}".format(chapter.idx, row.idx))
|
||||
lessons.append(lesson_details)
|
||||
return lessons
|
||||
@@ -289,3 +290,9 @@ def get_initial_members(course):
|
||||
member.member, ["name", "username", "full_name", "user_image"], as_dict=True))
|
||||
|
||||
return member_details
|
||||
|
||||
def is_instructor(course):
|
||||
return len(list(filter(lambda x: x.name == frappe.session.user, get_instructors(course)))) > 0
|
||||
|
||||
def convert_number_to_character(number):
|
||||
return string.ascii_uppercase[number]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{{ _("Course Content") }}
|
||||
</div>
|
||||
{% for chapter in get_chapters(course.name) %}
|
||||
<div class="mb-2">
|
||||
<div class="">
|
||||
<div class="chapter-title" data-target="#{{ get_slugified_chapter_title(chapter.title) }}"
|
||||
data-toggle="collapse" aria-expanded="false">
|
||||
<img class="chapter-icon" src="/assets/school/icons/chevron-right.svg">
|
||||
@@ -41,14 +41,14 @@
|
||||
{% elif is_instructor and not lesson.include_in_preview %}
|
||||
<a class="lesson-links"
|
||||
title="This lesson is not available for preview. As you are the Instructor of the course only you can see it."
|
||||
href="{{ course.get_lesson_url(lesson.number) }}{{course.query_parameter}}"
|
||||
href="{{ get_lesson_url(lesson.number) }}{{course.query_parameter}}"
|
||||
data-course="{{ course.name }}">
|
||||
<img class="mr-3" src="/assets/school/icons/lock.svg">
|
||||
<div>{{ lesson.title }}</div>
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<div class="no-preview" title="This lesson is not available for preview">
|
||||
<div class="no-preview" title="This lesson is not available for preview" data-course="{{ course.name }}">
|
||||
<div class="lesson-links">
|
||||
<img class="mr-3" src="/assets/school/icons/lock.svg">
|
||||
<div>{{ lesson.title }}</div>
|
||||
@@ -76,9 +76,13 @@ frappe.ready(() => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
})
|
||||
$(".no-preview").click((e) => {
|
||||
show_no_preview_dialog(e);
|
||||
});
|
||||
|
||||
var expand_the_first_chapter = () => {
|
||||
});
|
||||
|
||||
const expand_the_first_chapter = () => {
|
||||
var elements = $(".course-outline .collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i < 1) {
|
||||
@@ -86,9 +90,9 @@ var expand_the_first_chapter = () => {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var expand_the_active_chapter = () => {
|
||||
const expand_the_active_chapter = () => {
|
||||
|
||||
/* Find anchor matching the URL for course details page */
|
||||
var selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
@@ -111,21 +115,30 @@ var expand_the_active_chapter = () => {
|
||||
else {
|
||||
expand_the_first_chapter();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var show_section = (element) => {
|
||||
const show_section = (element) => {
|
||||
$(element).addClass("show");
|
||||
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
|
||||
$(element).siblings(".chapter-title").attr("aria-expanded", true);
|
||||
}
|
||||
};
|
||||
|
||||
var rotate_chapter_icon = (e) => {
|
||||
const rotate_chapter_icon = (e) => {
|
||||
var icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const show_no_preview_dialog = (e) => {
|
||||
frappe.warn(__("Not available for preview"),
|
||||
__("This lesson is not available for preview. Please join the course to access it."),
|
||||
() => {
|
||||
window.location.href = `/courses/${ $(e.currentTarget).data("course") }`
|
||||
},
|
||||
__("Start Learning"), false);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</div>
|
||||
|
||||
{% set avg_rating = get_average_rating(course.name) %}
|
||||
{% if avg_rating %}
|
||||
<div class="reviews-header">
|
||||
<div class="text-center">
|
||||
<div class="avg-rating"> {{ avg_rating }} </div>
|
||||
@@ -43,9 +44,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if reviews | length %}
|
||||
<div class="mt-12">
|
||||
@@ -58,7 +57,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<a class="button-links" href="{{get_profile_url(review.owner_details.username) }}">
|
||||
<span class="review-author">
|
||||
<span class="bold-heading">
|
||||
{{ review.owner_details.full_name }}
|
||||
</span>
|
||||
</a>
|
||||
@@ -84,19 +83,25 @@
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="empty-state text-center">
|
||||
<img class="icon icon-xl" src="/assets/frappe/icons/timeless/message.svg">
|
||||
<div class="course-home-headings mt-4 mb-0" style="color: inherit;"> {{ _("Review the course") }} </div>
|
||||
<div class="small mb-6"> {{ _("Help us improve our course material.") }} </div>
|
||||
{% if is_eligible_to_review(course.name, membership) %}
|
||||
<span class="review-link button is-secondary ml-auto mr-auto mt-3">
|
||||
<div class="empty-state-new">
|
||||
<div>
|
||||
<img class="icon icon-xl" src="/assets/school/icons/comment.svg">
|
||||
</div>
|
||||
<div class="empty-state-text">
|
||||
<div class="empty-state-heading">{{ _("Review the course") }}</div>
|
||||
<div class="course-meta">{{ _("Help us improve our course material.") }}</div>
|
||||
</div>
|
||||
<div>
|
||||
{% if is_eligible_to_review(course.name, membership) %}
|
||||
<span class="review-link button is-secondary">
|
||||
{{ _("Write a review") }}
|
||||
</span>
|
||||
{% elif frappe.session.user == "Guest" %}
|
||||
<a class="button is-secondary dark-links ml-auto mr-auto mt-3" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a>
|
||||
{% elif not membership %}
|
||||
<div class="button is-secondary join-batch ml-auto mr-auto mt-3" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
||||
{% endif %}
|
||||
{% elif frappe.session.user == "Guest" %}
|
||||
<a class="button is-primary" href="/login?redirect-to=/courses/{{ course.name }}"> {{ _("Login") }} </a>
|
||||
{% elif not membership %}
|
||||
<div class="button is-primary join-batch" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -114,10 +114,11 @@ def exercise_renderer(argument):
|
||||
|
||||
def youtube_video_renderer(video_id):
|
||||
return f"""
|
||||
<iframe width="100%" height="315"
|
||||
<iframe width="100%" height="400"
|
||||
src="https://www.youtube.com/embed/{video_id}"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
style="border-radius: var(--border-radius-lg)"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
|
||||
@@ -308,11 +308,6 @@ input[type=checkbox] {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.custom-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.custom-checkbox>label>input {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -324,6 +319,10 @@ input[type=checkbox] {
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.empty-checkbox {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.custom-checkbox>label>input:checked+.empty-checkbox {
|
||||
background: url(/assets/school/icons/tick.svg);
|
||||
background-repeat: no-repeat;
|
||||
@@ -331,6 +330,8 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.quiz-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -403,6 +404,8 @@ input[type=checkbox] {
|
||||
font-size: var(--text-md);
|
||||
line-height: 20px;
|
||||
box-shadow: var(--btn-shadow);
|
||||
border: none;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
@@ -412,6 +415,7 @@ input[type=checkbox] {
|
||||
.wide-button {
|
||||
padding: 0.5rem 6rem;
|
||||
font-weight: 500;
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -435,10 +439,8 @@ input[type=checkbox] {
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.video-preview {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.is-default:disabled {
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
.is-primary {
|
||||
@@ -465,10 +467,6 @@ input[type=checkbox] {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chapter-title[aria-expanded="true"] {
|
||||
background: var(--gray-100);
|
||||
}
|
||||
|
||||
.chapter-description {
|
||||
margin: 0.75rem 3rem 1rem;
|
||||
}
|
||||
@@ -478,7 +476,7 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.chapter-icon {
|
||||
margin: 0 1.25rem;
|
||||
margin-right: 1.25rem;
|
||||
}
|
||||
|
||||
.course-outline-instructor-parent {
|
||||
@@ -542,14 +540,12 @@ input[type=checkbox] {
|
||||
|
||||
.lesson-info {
|
||||
font-size: 16px;
|
||||
line-height: 250%;
|
||||
letter-spacing: -0.011em;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.lesson-links {
|
||||
display: flex;
|
||||
padding: 0 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -560,16 +556,14 @@ input[type=checkbox] {
|
||||
border-radius: var(--border-radius-md);
|
||||
}
|
||||
|
||||
.course-content-parent .lesson-links {
|
||||
padding: 0 0 0 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
line-height: 200%;
|
||||
.course-content-parent .chapter-title {
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.chapter-content {
|
||||
margin: 0;
|
||||
margin-left: .875rem;
|
||||
.course-content-parent .lesson-links {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: var(--text-md);
|
||||
line-height: 200%;
|
||||
}
|
||||
|
||||
.course-outline {
|
||||
@@ -578,7 +572,7 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.lessons {
|
||||
margin-left: 2rem;
|
||||
margin-left: 2.5rem;
|
||||
}
|
||||
|
||||
.course-buttons {
|
||||
@@ -669,7 +663,7 @@ input[type=checkbox] {
|
||||
font-weight: 600;
|
||||
font-size: var(--text-3-5xl);
|
||||
letter-spacing: -0.0175em;
|
||||
color: var(--gray-800);
|
||||
color: var(--gray-900);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -831,12 +825,16 @@ input[type=checkbox] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.question {
|
||||
flex-direction: column;
|
||||
width: 85%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
flex-direction: column;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.question p {
|
||||
@@ -868,8 +866,7 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.lesson-content-card {
|
||||
padding: 24px;
|
||||
flex-direction: column;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.lesson-content-card .alert-dismissible .close {
|
||||
@@ -896,6 +893,7 @@ input[type=checkbox] {
|
||||
.lesson-pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 24px 0px 0px;
|
||||
}
|
||||
|
||||
@@ -914,8 +912,15 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.active-lesson {
|
||||
background-color: var(--gray-100);
|
||||
background-color: var(--blue-100);
|
||||
border-radius: var(--border-radius-md);
|
||||
color: var(--blue-500);
|
||||
}
|
||||
|
||||
.lesson-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.lesson-progress {
|
||||
@@ -923,7 +928,7 @@ input[type=checkbox] {
|
||||
padding: 4px 8px 4px;
|
||||
font-size: 10px;
|
||||
line-height: 120%;
|
||||
margin: 0px 10px 20px;
|
||||
margin-left: 1rem;
|
||||
border-radius: var(--border-radius-md);
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -1271,6 +1276,26 @@ pre {
|
||||
padding: 2.5rem;
|
||||
}
|
||||
|
||||
.empty-state-new {
|
||||
background: var(--gray-50);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-state-text {
|
||||
flex: 1;
|
||||
margin-left: 1.25rem;
|
||||
}
|
||||
|
||||
.empty-state-heading {
|
||||
font-size: var(--text-xl);
|
||||
color: var(--gray-900);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vertically-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1361,6 +1386,8 @@ pre {
|
||||
.related-courses {
|
||||
background: var(--gray-50);
|
||||
padding: 5rem 0;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.carousel-indicators {
|
||||
@@ -1555,10 +1582,11 @@ pre {
|
||||
box-shadow: var(--shadow-sm);
|
||||
overflow: auto;
|
||||
width: fit-content;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 40%;
|
||||
right: 7%;
|
||||
max-width: 400px;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -1612,8 +1640,8 @@ pre {
|
||||
.overlay-student-count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.course-creators-card {
|
||||
@@ -1664,7 +1692,7 @@ pre {
|
||||
}
|
||||
}
|
||||
|
||||
.review-author {
|
||||
.bold-heading {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--gray-900);
|
||||
font-weight: 600;
|
||||
@@ -1696,3 +1724,40 @@ pre {
|
||||
.reviews-parent .progress-bar {
|
||||
background-color: var(--gray-600);
|
||||
}
|
||||
|
||||
.course-home-top-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.question-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.question-number {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 50%;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.option-text {
|
||||
padding: 0.75rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
border-radius: var(--border-radius-md);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.active-option .option-text {
|
||||
background-color: var(--blue-50);
|
||||
border: 1px solid var(--blue-500);
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: var(--text-lg);
|
||||
color: var(--gray-900);
|
||||
font-weight: 600;
|
||||
flex-grow: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
6
school/public/icons/comment.svg
Normal file
6
school/public/icons/comment.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.875 3.85022H6.875C5.78098 3.85022 4.73177 4.28482 3.95818 5.0584C3.1846 5.83199 2.75 6.8812 2.75 7.97522V21.7252C2.75 22.8192 3.1846 23.8684 3.95818 24.642C4.73177 25.4156 5.78098 25.8502 6.875 25.8502H11V34.1002L19.25 25.8502H28.875C29.969 25.8502 31.0182 25.4156 31.7918 24.642C32.5654 23.8684 33 22.8192 33 21.7252V7.97522C33 6.8812 32.5654 5.83199 31.7918 5.0584C31.0182 4.28482 29.969 3.85022 28.875 3.85022V3.85022Z" stroke="#687178" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5249 10.7252H27.7749" stroke="#687178" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5249 16.2252H19.5249" stroke="#687178" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.5 31.9003H24.75L33 40.1503V31.9003H37.125C38.219 31.9003 39.2682 31.4657 40.0418 30.6921C40.8154 29.9186 41.25 28.8693 41.25 27.7753V14.0253C41.25 12.9313 40.8154 11.8821 40.0418 11.1085C39.2682 10.3349 38.219 9.90033 37.125 9.90033H35.75" stroke="#687178" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -2,43 +2,40 @@
|
||||
|
||||
<div class="card-divider"></div>
|
||||
|
||||
<div class="mt-5">
|
||||
<div class="common-card-style question-card">
|
||||
<form id="quiz-form">
|
||||
<div class="questions">
|
||||
{% for question in quiz.questions %}
|
||||
{% set instruction = _("Choose all answers that apply") if question.multiple else _("Choose 1 answer") %}
|
||||
<div class="question {% if loop.index == 1 %} active-question {% else %} hide {% endif %}"
|
||||
data-question="{{ question.question }}" data-multi="{{ question.multiple}}" data-qt-index="{{ loop.index }}">
|
||||
<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] %}
|
||||
|
||||
{% for option in options %}
|
||||
{% if option %}
|
||||
|
||||
<div class="custom-checkbox">
|
||||
<label class="quiz-label">
|
||||
<input class="option" value="{{ option | urlencode }}"
|
||||
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" />
|
||||
<span class="label-area">{{ frappe.utils.md_to_html(option) }}</span>
|
||||
</label>
|
||||
<div class="question-header">
|
||||
<div class="question-number">{{ loop.index }}</div>
|
||||
<div class="question-text">
|
||||
<div class="course-meta pull-right ml-2"> {{ instruction }} </div>
|
||||
{{ frappe.utils.md_to_html(question.question) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% set explanation = question['explanation_' + loop.index | string] %}
|
||||
{% if explanation %}
|
||||
<small class="explanation muted-text hide">{{ explanation }}</small>
|
||||
{% endif %}
|
||||
{% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %}
|
||||
{% for option in options %}
|
||||
{% if option %}
|
||||
<div class="mb-2">
|
||||
<div class="custom-checkbox">
|
||||
<label class="quiz-label">
|
||||
<div class="course-meta font-weight-bold ml-3"> {{ convert_number_to_character(loop.index - 1) }}</div>
|
||||
<input class="option" value="{{ option | urlencode }}"
|
||||
data-correct="{{ question['is_correct_' + loop.index | string] }}" {% if question.multiple %}
|
||||
type="checkbox" {% else %} type="radio" name="{{ question.question | urlencode }}" {% endif %}>
|
||||
<div class="option-text">{{ frappe.utils.md_to_html(option) }}</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="card-divider"></div>
|
||||
{% set explanation = question['explanation_' + loop.index | string] %}
|
||||
{% if explanation %}
|
||||
<small class="explanation ml-3 hide">{{ explanation }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
@@ -47,12 +44,12 @@
|
||||
</div>
|
||||
<div class="quiz-footer">
|
||||
<span class="font-weight-bold"> <span class="current-question">1</span> of {{ quiz.questions | length }}</span>
|
||||
<button class="button pull-right" id="check" disabled>Check</button>
|
||||
<button class="button hide" id="next">Next Question</button>
|
||||
<button class="button is-default hide" id="summary">Summary</button>
|
||||
<small id="submission-message" class="font-weight-bold hide"> Please join the course to submit the Quiz.</small>
|
||||
<button class="button pull-right is-default" id="check" disabled>{{ _("Check") }}</button>
|
||||
<div class="button is-secondary hide" id="next">{{ _("Next Question") }}</div>
|
||||
<div class="button is-secondary is-default hide" id="summary">{{ _("Summary") }}</div>
|
||||
<small id="submission-message" class="font-weight-bold hide"> {{ _("Please join the course to submit the Quiz.") }} </small>
|
||||
<div class="button is-secondary hide" id="try-again">{{ _("Try Again") }}</div>
|
||||
</div>
|
||||
<div class="button is-secondary pull-right hide" id="try-again">Try Again</div>
|
||||
<h4 class="success-message"></h4>
|
||||
<h5 class="score text-muted"></h5>
|
||||
</form>
|
||||
|
||||
@@ -33,34 +33,60 @@
|
||||
{% endblock %}
|
||||
|
||||
{% macro LessonContent(lesson) %}
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
{% set instructors = get_instructors(course.name) %}
|
||||
{% set is_instructor = is_instructor(course.name) %}
|
||||
<div class="lesson-content">
|
||||
<div class="course-home-headings title
|
||||
{% if membership %} is-member {% endif %}
|
||||
{% if membership or is_instructor %} eligible-for-submission {% endif %}" data-lesson="{{ lesson.name }}"
|
||||
data-course="{{ course.name }}">
|
||||
{{ lesson.title }}
|
||||
<div class="lesson-title">
|
||||
<div class="course-home-headings title mb-0
|
||||
{% if membership %} is-member {% endif %}
|
||||
{% if membership or is_instructor %} eligible-for-submission {% endif %}" data-lesson="{{ lesson.name }}"
|
||||
data-course="{{ course.name }}">{{ lesson.title }}</div>
|
||||
<span class="lesson-progress {{hide if get_progress(course.name, lesson.name) != 'Complete' else ''}}">COMPLETED</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
{% set instructors = instructors %}
|
||||
{% set ins_len = instructors | length %}
|
||||
{% for instructor in instructors %}
|
||||
{% if ins_len > 1 and loop.index == 1 %}
|
||||
<div class="avatar-group overlap">
|
||||
{% endif %}
|
||||
{{ widgets.Avatar(member=instructor, avatar_class="avatar-small") }}
|
||||
|
||||
{% if ins_len > 1 and loop.index == ins_len %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<a class="button-links ml-2" href="{{ get_profile_url(instructors[0].username) }}">
|
||||
<span class="course-creator-name">
|
||||
{% if ins_len == 1 %}
|
||||
{{ instructors[0].full_name }}
|
||||
{% else %}
|
||||
{% set suffix = "other" if ins_len - 1 == 1 else "others" %}
|
||||
{{ instructors[0].full_name.split(" ")[0] }} and {{ ins_len - 1 }} {{ suffix }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
<div class="ml-3 course-meta"> {{ frappe.utils.format_date(lesson.creation, "medium") }} </div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="markdown-source lesson-content-card">
|
||||
{% if membership or lesson.include_in_preview or is_instructor %}
|
||||
<div class="common-card-style lesson-content-card markdown-source">
|
||||
{% if is_instructor and not lesson.include_in_preview %}
|
||||
<small class="alert alert-secondary alert-dismissible">
|
||||
{% if is_instructor and not lesson.include_in_preview %}
|
||||
<div class="small alert alert-secondary alert-dismissible mt-4 mb-4">
|
||||
This lesson is not available for preview. As you are the Instructor of the course only you can see it.
|
||||
<a href="#" class="close" data-dismiss="alert" aria-label="close">×</a>
|
||||
</small>
|
||||
{% endif %}
|
||||
{{ render_html(lesson.body) }}</div>
|
||||
{% else %}
|
||||
<div class="common-card-style lesson-content-card">
|
||||
<div class="w-25 text-center" style="margin: 0 auto;">
|
||||
<small>This lesson is not available for preview. Please join the course to access it.</small>
|
||||
<a class="button is-primary ml-auto mr-auto mt-3" href="/courses/{{ course.name }}"> Start Learning </a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ render_html(lesson.body) }}
|
||||
{% else %}
|
||||
<div class="">
|
||||
<a class="button is-primary pull-right" href="/courses/{{ course.name }}"> Start Learning </a>
|
||||
<div class="">This lesson is not available for preview. Please join the course to access it.</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -82,7 +108,7 @@
|
||||
{% set progress = get_progress(course.name, lesson.name) %}
|
||||
<div class="custom-checkbox {% if progress == 'Complete' %} hide {% endif %}">
|
||||
<label class="quiz-label">
|
||||
<input class="option mark-progress" type="checkbox" checked>
|
||||
<input class="mark-progress" type="checkbox" checked>
|
||||
<img class="empty-checkbox" />
|
||||
<span class="small">{{ _("Mark as complete on moving to the next lesson") }}</span>
|
||||
</label>
|
||||
|
||||
@@ -43,7 +43,7 @@ frappe.ready(() => {
|
||||
|
||||
$(".clear-work").click((e) => {
|
||||
clear_work(e);
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -57,8 +57,10 @@ const save_current_lesson = () => {
|
||||
};
|
||||
|
||||
const enable_check = (e) => {
|
||||
if ($(".option:checked").length && $("#check").attr("disabled")) {
|
||||
if ($(".option:checked").length) {
|
||||
$("#check").removeAttr("disabled");
|
||||
$(".custom-checkbox").removeClass("active-option");
|
||||
$(".option:checked").closest(".custom-checkbox").addClass("active-option");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -164,7 +166,7 @@ const quiz_summary = (e) => {
|
||||
callback: (data) => {
|
||||
var message = data.message == total_questions ? "Excellent Work" : "You were almost there."
|
||||
$(".question").addClass("hide");
|
||||
$(".quiz-footer").addClass("hide");
|
||||
$("#summary").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>`);
|
||||
@@ -222,8 +224,9 @@ const parse_options = () => {
|
||||
};
|
||||
|
||||
const add_icon = (element, icon) => {
|
||||
var label = $(element).parent().find(".label-area p").text();
|
||||
$(element).parent().empty().html(`<img class="mr-3" src="/assets/school/icons/${icon}.svg"> ${label}`);
|
||||
$(element).closest(".custom-checkbox").removeClass("active-option");
|
||||
var label = $(element).siblings(".option-text").text();
|
||||
$(element).parent().empty().html(`<div class="option-text"><img class="mr-3" src="/assets/school/icons/${icon}.svg"> ${label}</div>`);
|
||||
};
|
||||
|
||||
const add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
|
||||
@@ -354,27 +357,27 @@ const clear_work = (e) => {
|
||||
parent.addClass("hide");
|
||||
parent.siblings(".attach-file").removeClass("hide").val(null);
|
||||
parent.siblings(".submit-work").removeClass("hide");
|
||||
}
|
||||
};
|
||||
|
||||
const fetch_assignments = () => {
|
||||
if ($(".attach-file").length > 0) {
|
||||
frappe.call({
|
||||
method: "school.lms.doctype.lesson_assignment.lesson_assignment.get_assignment",
|
||||
args: {
|
||||
"lesson": $(".title").attr("data-lesson")
|
||||
},
|
||||
callback: (data) => {
|
||||
if (data.message && data.message.length) {
|
||||
const assignments = data.message;
|
||||
for (let i in assignments) {
|
||||
let target = $(`#${assignments[i]["id"]}`);
|
||||
target.addClass("hide");
|
||||
target.siblings(".submit-work").addClass("hide");
|
||||
target.siblings(".preview-work").removeClass("hide");
|
||||
target.siblings(".preview-work").find("a").attr("href", assignments[i]["assignment"]).text(assignments[i]["file_name"]);
|
||||
}
|
||||
if ($(".attach-file").length <= 0)
|
||||
return;
|
||||
frappe.call({
|
||||
method: "school.lms.doctype.lesson_assignment.lesson_assignment.get_assignment",
|
||||
args: {
|
||||
"lesson": $(".title").attr("data-lesson")
|
||||
},
|
||||
callback: (data) => {
|
||||
if (data.message && data.message.length) {
|
||||
const assignments = data.message;
|
||||
for (let i in assignments) {
|
||||
let target = $(`#${assignments[i]["id"]}`);
|
||||
target.addClass("hide");
|
||||
target.siblings(".submit-work").addClass("hide");
|
||||
target.siblings(".preview-work").removeClass("hide");
|
||||
target.siblings(".preview-work").find("a").attr("href", assignments[i]["assignment"]).text(assignments[i]["file_name"]);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -37,7 +37,6 @@ def get_context(context):
|
||||
"lesson": context.lesson.name,
|
||||
"is_member": context.membership is not None
|
||||
}
|
||||
print(context)
|
||||
|
||||
def get_current_lesson_details(lesson_number, context):
|
||||
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="common-page-style pt-0">
|
||||
{{ CourseHomeHeader(course) }}
|
||||
<div class="container course-home-page">
|
||||
<div class="course-body-container">
|
||||
{{ Description(course) }}
|
||||
{{ widgets.CourseOutline(course=course, membership=membership) }}
|
||||
{{ CourseCreator(course) }}
|
||||
{{ widgets.Reviews(course=course, membership=membership) }}
|
||||
<div class="course-home-top-container">
|
||||
{{ CourseHomeHeader(course) }}
|
||||
{{ CourseHeaderOverlay(course) }}
|
||||
<div class="container course-home-page">
|
||||
<div class="course-body-container">
|
||||
{{ Description(course) }}
|
||||
{{ widgets.CourseOutline(course=course, membership=membership) }}
|
||||
{{ widgets.Reviews(course=course, membership=membership) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ RelatedCourses(course) }}
|
||||
@@ -26,7 +28,6 @@
|
||||
<div class="container pt-10 pb-10">
|
||||
{{ BreadCrumb(course) }}
|
||||
{{ CourseCardWide(course) }}
|
||||
{{ CourseHeaderOverlay(course) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -125,6 +126,14 @@
|
||||
You have opted to be notified for this course. You will receive an email when the course becomes available.
|
||||
</div>
|
||||
|
||||
<div class="course-home-headings"> {{ course.title }} </div>
|
||||
{% if get_lessons(course.name) | length %}
|
||||
<div class="mt-3">
|
||||
<img class="mr-3" src="/assets/school/icons/book.svg">
|
||||
{{ get_lessons(course.name) | length }} {{ _("Lessons") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if get_students(course.name) | length %}
|
||||
{% set initial_members = get_initial_members(course.name) %}
|
||||
<div class="overlay-student-count">
|
||||
@@ -188,13 +197,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="overlay-heading"> {{ _("Course Include:") }} </div>
|
||||
{% if get_lessons(course.name) | length %}
|
||||
<div class="mt-3">
|
||||
<img class="mr-3" src="/assets/school/icons/book.svg">
|
||||
{{ get_lessons(course.name) | length }} {{ _("Lessons") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -21,10 +21,6 @@ frappe.ready(() => {
|
||||
view_all_mentors(e);
|
||||
});
|
||||
|
||||
$(".video-preview").click((e) => {
|
||||
show_video_dialog(e);
|
||||
});
|
||||
|
||||
$(".review-link").click((e) => {
|
||||
show_review_dialog(e);
|
||||
});
|
||||
@@ -45,6 +41,12 @@ frappe.ready(() => {
|
||||
create_certificate(e);
|
||||
});
|
||||
|
||||
$(document).scroll(function() {
|
||||
let timer;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => { handle_overlay_display.apply(this, arguments); }, 500);
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
var check_mentor_request = () => {
|
||||
@@ -158,11 +160,6 @@ var view_all_mentors = (e) => {
|
||||
}
|
||||
}
|
||||
|
||||
var show_video_dialog = (e) => {
|
||||
e.preventDefault();
|
||||
$("#video-modal").modal("show");
|
||||
}
|
||||
|
||||
var show_review_dialog = (e) => {
|
||||
e.preventDefault();
|
||||
$("#review-modal").modal("show");
|
||||
@@ -235,3 +232,27 @@ const create_certificate = (e) => {
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
const element_not_in_viewport = (el) => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
|
||||
}
|
||||
|
||||
const handle_overlay_display = () => {
|
||||
const element = $(".related-courses").length && $(".related-courses")[0];
|
||||
if (element && element_not_in_viewport(element)) {
|
||||
$(".course-overlay-card").css({
|
||||
"position": "fixed",
|
||||
"top": "30%",
|
||||
"bottom": "inherit"
|
||||
});
|
||||
}
|
||||
else if (element && !element_not_in_viewport(element)) {
|
||||
$(".course-overlay-card").css({
|
||||
"position": "absolute",
|
||||
"top": "inherit",
|
||||
"bottom": "5%"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import frappe
|
||||
from school.lms.utils import slugify, get_membership, get_lessons, get_batch
|
||||
|
||||
from school.lms.utils import slugify, get_membership, get_lessons, get_batch, get_lesson_url
|
||||
def get_common_context(context):
|
||||
context.no_cache = 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user