Merge v2.46.0 into develop — resolve all conflicts
- yarn.lock, components.d.ts, ru.po: accepted upstream v2.46.0 - AppSidebar.vue: kept custom sidebar links (MyPoints, LeaderBoard, ChatGPT, MyChild, Profile) + adopted async watch from v2.46.0 - MobileLayout.vue: merged onMounted with sidebarSettings.reload + custom addSideBar() for role-based mobile links - EditProfile.vue: adopted new Dialog options structure (size only) - Courses/Courses.vue: unified tab values to lowercase (enrolled, upcoming, new, created, unpublished) - CourseDetail.vue (old): removed — logic migrated to Courses/CourseDetail.vue; transferred custom tag flex-wrap styling to CourseOverview.vue - LessonForm.vue: kept Rutube video support - App.vue: clean (no conflict markers) - user.py: merged imports + kept custom sign_up params (phone, user_role) - utils.py: kept render_html (Rutube), is_mentor, is_eligible_to_review; added type hints for get_course_progress from v2.46.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
<br>
|
||||
<p> {{ _(" Please evaluate and grade it.") }} </p>
|
||||
<br>`
|
||||
<a href="/lms/assignment-submission/{{ assignment_name }}/{{ submission_name }}">
|
||||
<a href="{{ get_lms_route('assignment-submission/' ~ assignment_name ~ '/' ~ submission_name) }}">
|
||||
{{ _("Open Assignment") }}
|
||||
</a>
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<br>
|
||||
<p>
|
||||
{{ _("Visit the following link to view your ") }}
|
||||
<a href="/lms/batches/{{ name }}">{{ _("Batch Details") }}</a>
|
||||
<a href="{{ get_lms_route('batches/' ~ name) }}">{{ _("Batch Details") }}</a>
|
||||
</p>
|
||||
<p>
|
||||
{{ _("If you have any questions or require assistance, feel free to contact us.") }}
|
||||
@@ -32,4 +32,3 @@
|
||||
<p>
|
||||
{{ _("Best Regards") }}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
<a href="/lms/batches/{{ name }}">👉 {{ _("Visit your batch") }}</a>
|
||||
<a href="{{ get_lms_route('batches/' ~ name) }}">👉 {{ _("Visit your batch") }}</a>
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<p> {{ _("Hey {0}").format(member_name) }} </p>
|
||||
<br>
|
||||
<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(course, date, start_time, timezone) }}</p>
|
||||
<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} ({3} time).').format(course, date, start_time, timezone) }}</p>
|
||||
<br>
|
||||
<p> {{ _("Your evaluator is {0}").format(evaluator) }} </p>
|
||||
<br>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
<a href="/lms/batches/{{ batch_name }}">👉 {{ _("Visit your batch") }}</a>
|
||||
<a href="{{ get_lms_route('batches/' ~ batch_name) }}">👉 {{ _("Visit your batch") }}</a>
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
@@ -26,4 +26,4 @@
|
||||
<br>
|
||||
<p>
|
||||
{{ _("Best Regards") }}
|
||||
</p>
|
||||
</p>
|
||||
|
||||
54
lms/templates/emails/published_batch_notification.html
Normal file
54
lms/templates/emails/published_batch_notification.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<div style="width: 70%; margin: 0 auto;">
|
||||
<img src="{{ brand_logo }}" style="width: 30px; height: 30px;" />
|
||||
<p style="font-size: 16px; font-weight: 600;">
|
||||
{{ _("Hello Learner") }},
|
||||
</p>
|
||||
<p>
|
||||
{{ _("A new batch has been published on ")}} {{ brand_name }} {{ _("that might interest you!") }} {{ _("Here are the details:") }}
|
||||
</p>
|
||||
<div style="background-color: #F8F8F8; border-radius: 12px; padding: 12px; margin-bottom: 6px;">
|
||||
<div style="font-weight: 600; margin-bottom: 6px; font-size: 15px;">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div>
|
||||
{{ short_introduction }}
|
||||
</div>
|
||||
<div style="margin-top: 20px; font-size: 13px;">
|
||||
{% if end_date %}
|
||||
<span>
|
||||
{{ _("From ") }} {{ frappe.utils.format_date(start_date, "dd MMM YYYY") }} {{ _(" to ") }} {{ frappe.utils.format_date(end_date, "dd MMM YYYY") }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span>
|
||||
{{ frappe.utils.format_date(start_date, "dd MMM YYYY") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div style="color: #525252; margin-top: 4px; font-size: 13px;">
|
||||
<span>
|
||||
{{ _("Time: ") }} {{ frappe.utils.format_time(start_time, "HH:mm a") }} {{ timezone }}
|
||||
</span>
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
{% for instructor in instructors %}
|
||||
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
||||
{% if instructor.user_image %}
|
||||
<img src="{{ instructor.user_image }}" style="width: 20px; height: 20px; border-radius: 50%; margin-right: 5px;" />
|
||||
{% else %}
|
||||
<div style="width: 20px; height: 20px; border-radius: 50%; background-color: #ccc; display: flex; align-items: center; justify-content: center; margin-right: 5px;">
|
||||
<span style="font-size: 12px; color: #fff;">
|
||||
{{ instructor.full_name.split("")[0] | upper }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
{{ instructor.full_name }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ batch_url }}" style="display: inline-block; padding: 4px 8px; background-color: #171717; color: #fff; text-decoration: none; cursor: pointer; border-radius: 8px; margin-top: 10px;">
|
||||
{{ _("Checkout the batch") }}
|
||||
</a>
|
||||
</div>
|
||||
38
lms/templates/emails/published_course_notification.html
Normal file
38
lms/templates/emails/published_course_notification.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<div style="width: 70%; margin: 0 auto;">
|
||||
<img src="{{ brand_logo }}" style="width: 30px; height: 30px;" />
|
||||
<p style="font-size: 16px; font-weight: 600;">
|
||||
{{ _("Hello Learner") }},
|
||||
</p>
|
||||
<p>
|
||||
{{ _("A new course has been published on ")}} {{ brand_name }} {{ _("that might interest you!") }} {{ _("Here are the details:") }}
|
||||
</p>
|
||||
<div style="background-color: #F8F8F8; border-radius: 12px; padding: 12px; margin-bottom: 6px;">
|
||||
<div style="font-weight: 600; margin-bottom: 6px;">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div>
|
||||
{{ short_introduction }}
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
{% for instructor in instructors %}
|
||||
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
||||
{% if instructor.user_image %}
|
||||
<img src="{{ instructor.user_image }}" style="width: 20px; height: 20px; border-radius: 50%; margin-right: 5px;" />
|
||||
{% else %}
|
||||
<div style="width: 20px; height: 20px; border-radius: 50%; background-color: #ccc; display: flex; align-items: center; justify-content: center; margin-right: 5px;">
|
||||
<span style="font-size: 12px; color: #fff;">
|
||||
{{ instructor.full_name.split("")[0] | upper }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
{{ instructor.full_name }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ course_url }}" style="display: inline-block; padding: 4px 8px; background-color: #171717; color: #fff; text-decoration: none; cursor: pointer; border-radius: 8px; margin-top: 10px;">
|
||||
{{ _("Checkout the course") }}
|
||||
</a>
|
||||
</div>
|
||||
@@ -1,168 +0,0 @@
|
||||
<script type="text/javascript" src="/assets/frappe/node_modules/moment/min/moment-with-locales.min.js"></script>
|
||||
<script type="text/javascript" src="/assets/frappe/node_modules/moment-timezone/builds/moment-timezone-with-data.min.js"></script>
|
||||
<script type="text/javascript" src="/assets/frappe/js/frappe/utils/datetime.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
// comment_when is failing because of this
|
||||
if (!frappe.sys_defaults) {
|
||||
frappe.sys_defaults = {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="{{ livecode_url }}/static/livecode.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/assets/mon_school/js/livecode-files.js"></script>
|
||||
|
||||
<template id="livecode-template">
|
||||
<div class="livecode-editor livecode-editor-inline">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 col-md-6">
|
||||
<div class="controls">
|
||||
<button class="run">{{ _("Run") }}</button>
|
||||
|
||||
<div class="exercise-controls pull-right">
|
||||
<span style="padding-right: 10px;"><span class="last-submitted human-time" data-timestamp=""></span></span>
|
||||
<button class="submit btn-primary">{{ _("Submit") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="code-editor">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 col-md-6">
|
||||
<div class="code-wrapper">
|
||||
<textarea class="code"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6 canvas-wrapper">
|
||||
<div class="svg-image" width="300" height="300"></div>
|
||||
<pre class="output"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
function getLiveCodeOptions() {
|
||||
return {
|
||||
base_url: "{{ livecode_url }}",
|
||||
runtime: "python",
|
||||
files: LIVECODE_FILES, // loaded from livecode-files.js
|
||||
command: ["python", "start.py"],
|
||||
codemirror: true,
|
||||
onMessage: {
|
||||
image: function(editor, msg) {
|
||||
const element = editor.parent.querySelector(".svg-image");
|
||||
element.innerHTML = msg.image;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
var editorLookup = {};
|
||||
|
||||
$("pre.example, pre.exercise").each((i, e) => {
|
||||
var code = $(e).text();
|
||||
var template = document.querySelector('#livecode-template');
|
||||
var clone = template.content.cloneNode(true);
|
||||
|
||||
$(e)
|
||||
.wrap('<div></div>')
|
||||
.hide()
|
||||
.parent()
|
||||
.append(clone)
|
||||
.find("textarea.code")
|
||||
.val(code);
|
||||
|
||||
if ($(e).hasClass("exercise")) {
|
||||
var last_submitted = $(e).data("last-submitted");
|
||||
if (last_submitted) {
|
||||
$(e).parent().find(".last-submitted")
|
||||
.data("timestamp", last_submitted)
|
||||
.html(__("Submitted {0}", [comment_when(last_submitted)]));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$(e).parent().find(".exercise-controls").remove();
|
||||
}
|
||||
|
||||
var editor = new LiveCodeEditor(e.parentElement, {
|
||||
...getLiveCodeOptions(),
|
||||
codemirror: true,
|
||||
onMessage: {
|
||||
image: function(editor, msg) {
|
||||
const canvasElement = editor.parent.querySelector("div.svg-image");
|
||||
canvasElement.innerHTML = msg.image;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(e).parent().find(".submit").on('click', function() {
|
||||
var name = $(e).data("name");
|
||||
let code = editor.codemirror.doc.getValue();
|
||||
|
||||
frappe.call("lms.lms.api.submit_solution", {
|
||||
"exercise": name,
|
||||
"code": code
|
||||
}).then(r => {
|
||||
if (r.message.name) {
|
||||
frappe.msgprint("Submitted successfully!");
|
||||
|
||||
let d = r.message.creation;
|
||||
$(e).parent().find(".human-time").html(__("Submitted {0}", [comment_when(d)]));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(".exercise-image").each((i, e) => {
|
||||
var svg = JSON.parse($(e).data("image"));
|
||||
$(e).html(svg);
|
||||
});
|
||||
|
||||
$("pre.exercise").each((i, e) => {
|
||||
var svg = JSON.parse($(e).data("image"));
|
||||
$(e).parent().find(".svg-image").html(svg);
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.svg-image {
|
||||
border: 5px solid #ddd;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
width: 310px;
|
||||
height: 310px;
|
||||
}
|
||||
.livecode-editor {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.livecode-editor-small .svg-image {
|
||||
border: 5px solid #ddd;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
}
|
||||
|
||||
/* work-in-progress styles for showing admonition */
|
||||
.admonition {
|
||||
border: 1px solid #aaa;
|
||||
border-left: .5rem solid #888;
|
||||
border-radius: .3em;
|
||||
font-size: 0.9em;
|
||||
margin: 1.5em 0;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
.admonition-title {
|
||||
padding: 0.5em 0px;
|
||||
font-weight: bold;
|
||||
padding-top:
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +0,0 @@
|
||||
<link rel="stylesheet" href="{{ livecode_url }}/static/codemirror/lib/codemirror.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>
|
||||
@@ -1,6 +1,6 @@
|
||||
{% set onboarding_status = is_onboarding_complete() %}
|
||||
|
||||
{% if has_course_moderator_role() and not onboarding_status.is_onboarded %}
|
||||
{% if has_moderator_role() and not onboarding_status.is_onboarded %}
|
||||
<div class="onboarding-parent">
|
||||
<div class="container">
|
||||
<div class="onboarding-skip">{{ _("Skip") }}</div>
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
{% if not course.upcoming %}
|
||||
<div class="reviews-parent">
|
||||
{% set reviews = get_reviews(course.name) %}
|
||||
<div class="page-title mb-5"> {{ _("Reviews") }} </div>
|
||||
|
||||
|
||||
{% if avg_rating %}
|
||||
<div class="reviews-header">
|
||||
<div class="text-center">
|
||||
<div class="avg-rating">
|
||||
{{ frappe.utils.flt(avg_rating, frappe.get_system_settings("float_precision") or 3) }}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="avg-rating-stars">
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-lg {% if i <= frappe.utils.ceil(avg_rating) %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="course-meta"> {{ reviews | length }} {{ _("ratings") }} </div>
|
||||
<!--
|
||||
|
||||
-->
|
||||
|
||||
<div class="mt-5">
|
||||
{% include "lms/templates/reviews_cta.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="vertical-divider"></div>
|
||||
{% set sorted_reviews = get_sorted_reviews(course.name) %}
|
||||
<div>
|
||||
{% for review in sorted_reviews %}
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="course-meta mr-2">
|
||||
{{ frappe.utils.cint(review) }} {{ _("stars") }}
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="{{ sorted_reviews[review] }}"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width:{{ sorted_reviews[review] }}%">
|
||||
<span class="sr-only"> {{ sorted_reviews[review] }} {{ _("Complete") }} </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-meta ml-3"> {{ frappe.utils.cint(sorted_reviews[review]) }}% </div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if reviews | length %}
|
||||
<div class="mt-12">
|
||||
{% for review in reviews %}
|
||||
<div class="mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="mr-4">
|
||||
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<a class="button-links mr-4" href="/lms/users/{{ review.owner_details.username }}">
|
||||
<span class="bold-heading">
|
||||
{{ review.owner_details.full_name }}
|
||||
</span>
|
||||
</a>
|
||||
<div class="frappe-timestamp course-meta" data-timestamp="{{ review.creation }}">
|
||||
{{ review.creation }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md {% if i <= review.rating %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="review-content"> {{ review.review }} </div>
|
||||
</div>
|
||||
{% if loop.index != reviews | length %}
|
||||
<div class="card-divider"></div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||
<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 class="mt-2">
|
||||
{% include "lms/templates/reviews_cta.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade review-modal" id="review-modal" tabindex="-1" role="dialog"
|
||||
aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">{{ _("Write a Review") }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-body">
|
||||
<form class="review-form" id="review-form">
|
||||
<div class="form-group">
|
||||
<div class="clearfix">
|
||||
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Rating") }}</label>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<div class="rating rating-field" id="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md icon-rating" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="clearfix">
|
||||
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Review") }}</label>
|
||||
</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<textarea type="text" autocomplete="off" class="input-with-feedback form-control review-field"
|
||||
data-fieldtype="Text" data-fieldname="feedback_comments" spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="error-field muted-text"></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary btn-sm mr-2" data-dismiss="modal" aria-label="Close">
|
||||
{{ _("Discard") }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-primary btn-sm" data-course="{{ course.name | urlencode}}" id="submit-review">
|
||||
{{ _("Submit") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,9 +0,0 @@
|
||||
{% if membership and is_eligible_to_review(course.name) %}
|
||||
<span class="btn btn-secondary btn-sm review-link">
|
||||
{{ _("Write a review") }}
|
||||
</span>
|
||||
{% elif not is_instructor and frappe.session.user == "Guest" %}
|
||||
<a class="btn btn-secondary btn-sm" href="/login?redirect-to=/courses/{{ course.name }}">
|
||||
{{ _("Write a review") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
Reference in New Issue
Block a user