Compare commits

...

15 Commits

Author SHA1 Message Date
pateljannat
573019bbcc fix: added build dist folder to gitignore 2021-05-21 10:23:44 +05:30
Anand Chitipothu
f1b3ee19b6 Merge pull request #93 from fossunited/exercises
Added Exercise and Exercise Submission doctypes
2021-05-20 16:43:29 +05:30
Anand Chitipothu
9cb9fad05c fix: fixed failing tests 2021-05-20 16:24:41 +05:30
Anand Chitipothu
0859afdf34 style: fixed the styles of the sidebar 2021-05-20 15:08:12 +05:30
Anand Chitipothu
6407b24324 feat: added course, batch and lesson to exercise submission
Useful to find all the submissions for a batch/lesson.
2021-05-20 15:07:14 +05:30
Anand Chitipothu
34e993cf86 refactor: added lesson to exercise
usualy to know which lesson an exercise is part of by looking at the
exercise.
2021-05-20 13:27:30 +05:30
Anand Chitipothu
8c889ffb92 chore: switched to new build system for css.
- added community.bundle.less file to create css bundle
- removed the obsolete build.json in favor of new build system
- updated the paths in the books
- removed the imported urls from css files (the new build system doesn't support that)
- removed obsolete lms.css
2021-05-20 13:15:08 +05:30
Anand Chitipothu
a67ad67be1 feat: show image for the exercise
generate the image from the answer and display it along with
description. The image is geneated when the exercise is saved.
2021-05-20 12:09:12 +05:30
Anand Chitipothu
646a7b723f fix: fix broken pagination links 2021-05-19 20:12:35 +05:30
Anand Chitipothu
6f7011ca58 feat: integrated exercises into lessons
- an exercise can now be added to a lesson
- it is rendered using livecode editor with submit button
- remembers the submitted code and shows the submission time

Issue #90
2021-05-19 20:06:20 +05:30
Anand Chitipothu
d61acb552a feat: added Exercise and Exercise Submission doctypes
Also:
- added methods to submit an exercise and get the submission for a user
- added test cases

Issue #90
2021-05-19 20:01:17 +05:30
Jannat Patel
265c78e76e Merge pull request #88 from fossunited/minor-fixes
fix: removed slug field ref from courses page
2021-05-19 13:59:43 +05:30
pateljannat
7d180e141c fix: removed print statememt 2021-05-19 13:19:34 +05:30
pateljannat
29f9141ad8 fix: removed slug field ref from courses page 2021-05-19 13:16:46 +05:30
Anand Chitipothu
e6f58f56e0 Merge pull request #87 from fossunited/remove-branding
refactor: removed the branding and customization for mon school
2021-05-19 10:41:45 +05:30
31 changed files with 497 additions and 80 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
*.swp *.swp
tags tags
community/docs/current community/docs/current
community/public/dist

View File

@@ -15,11 +15,11 @@ app_license = "AGPL"
# ------------------ # ------------------
# include js, css files in header of desk.html # include js, css files in header of desk.html
app_include_css = "/assets/community/css/community.css" # app_include_css = "/assets/community/css/community.css"
app_include_js = "/assets/community/js/community.js" # app_include_js = "/assets/community/js/community.js"
# include js, css files in header of web template # include js, css files in header of web template
web_include_css = "/assets/css/community.css" web_include_css = "community.bundle.css"
# web_include_css = "/assets/community/css/community.css" # web_include_css = "/assets/community/css/community.css"
# web_include_js = "/assets/community/js/community.js" # web_include_js = "/assets/community/js/community.js"

View File

@@ -21,3 +21,16 @@ def get_section(name):
""" """
doc = frappe.get_doc("LMS Section", name) doc = frappe.get_doc("LMS Section", name)
return doc and doc.as_dict() return doc and doc.as_dict()
@frappe.whitelist()
def submit_solution(exercise, code):
"""Submits a solution.
@exerecise: name of the exercise to submit
@code: solution to the exercise
"""
ex = frappe.get_doc("Exercise", exercise)
if not ex:
return
doc = ex.submit(code)
return {"name": doc.name, "creation": doc.creation}

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Exercise', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,106 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-05-19 17:43:39.923430",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"description",
"code",
"answer",
"column_break_4",
"course",
"hints",
"tests",
"image",
"lesson"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title"
},
{
"fieldname": "course",
"fieldtype": "Link",
"label": "Course",
"options": "LMS Course"
},
{
"columns": 4,
"fieldname": "description",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Description"
},
{
"columns": 4,
"fieldname": "answer",
"fieldtype": "Code",
"label": "Answer"
},
{
"fieldname": "tests",
"fieldtype": "Code",
"label": "Tests"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"columns": 4,
"fieldname": "hints",
"fieldtype": "Small Text",
"label": "Hints"
},
{
"columns": 4,
"fieldname": "code",
"fieldtype": "Code",
"label": "Code"
},
{
"fieldname": "image",
"fieldtype": "Code",
"label": "Image",
"read_only": 1
},
{
"fieldname": "lesson",
"fieldtype": "Link",
"label": "Lesson",
"options": "Lesson"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-05-20 13:23:12.340928",
"modified_by": "Administrator",
"module": "LMS",
"name": "Exercise",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "title",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "title",
"track_changes": 1
}

View File

@@ -0,0 +1,54 @@
# Copyright (c) 2021, FOSS United and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from ..lms_sketch.livecode import livecode_to_svg
class Exercise(Document):
def before_save(self):
self.image = livecode_to_svg(None, self.answer)
def get_user_submission(self):
"""Returns the latest submission for this user.
"""
user = frappe.session.user
if not user or user == "Guest":
return
result = frappe.get_all('Exercise Submission',
fields="*",
filters={
"owner": user,
"exercise": self.name
},
order_by="creation desc",
page_length=1)
if result:
return result[0]
def submit(self, code):
"""Submits the given code as solution to exercise.
"""
user = frappe.session.user
if not user or user == "Guest":
return
old_submission = self.get_user_submission()
if old_submission and old_submission.solution == code:
return old_submission
course = frappe.get_doc("LMS Course", self.course)
batch = course.get_student_batch(user)
doc = frappe.get_doc(
doctype="Exercise Submission",
exercise=self.name,
exercise_title=self.title,
course=self.course,
lesson=self.lesson,
batch=batch and batch.name,
solution=code)
doc.insert()
return doc

View File

@@ -0,0 +1,47 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
import frappe
import unittest
class TestExercise(unittest.TestCase):
def setUp(self):
frappe.db.sql('delete from `tabExercise Submission`')
frappe.db.sql('delete from `tabExercise`')
frappe.db.sql('delete from `tabLMS Course`')
def new_exercise(self):
course = frappe.get_doc({
"doctype": "LMS Course",
"name": "test-course",
"title": "Test Course"
})
course.insert()
e = frappe.get_doc({
"doctype": "Exercise",
"name": "test-problem",
"course": course.name,
"title": "Test Problem",
"description": "draw a circle",
"code": "# draw a single cicle",
"answer": (
"# draw a single circle\n" +
"circle(100, 100, 50)")
})
e.insert()
return e
def test_exercise(self):
e = self.new_exercise()
assert e.get_user_submission() is None
def test_exercise_submission(self):
e = self.new_exercise()
submission = e.submit("circle(100, 100, 50)")
assert submission is not None
assert submission.exercise == e.name
assert submission.course == e.course
user_submission = e.get_user_submission()
assert user_submission is not None
assert user_submission.name == submission.name

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt
frappe.ui.form.on('Exercise Submission', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,83 @@
{
"actions": [],
"creation": "2021-05-19 11:41:18.108316",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"exercise",
"solution",
"exercise_title",
"course",
"batch",
"lesson"
],
"fields": [
{
"fieldname": "exercise",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Exercise",
"options": "Exercise"
},
{
"fieldname": "solution",
"fieldtype": "Code",
"in_list_view": 1,
"label": "Solution"
},
{
"fetch_from": "exercise.title",
"fieldname": "exercise_title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Exercise Title",
"read_only": 1
},
{
"fetch_from": "exercise.course",
"fieldname": "course",
"fieldtype": "Link",
"label": "Course",
"options": "LMS Course",
"read_only": 1
},
{
"fieldname": "batch",
"fieldtype": "Link",
"label": "Batch",
"options": "LMS Batch"
},
{
"fetch_from": "exercise.lesson",
"fieldname": "lesson",
"fieldtype": "Link",
"label": "Lesson",
"options": "Lesson"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-05-20 13:30:16.349278",
"modified_by": "Administrator",
"module": "LMS",
"name": "Exercise Submission",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"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 ExerciseSubmission(Document):
pass

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2021, FOSS United and Contributors
# See license.txt
# import frappe
import unittest
class TestExerciseSubmission(unittest.TestCase):
pass

View File

@@ -11,6 +11,11 @@ class Lesson(Document):
def before_save(self): def before_save(self):
sections = SectionParser().parse(self.body or "") sections = SectionParser().parse(self.body or "")
self.sections = [self.make_lms_section(i, s) for i, s in enumerate(sections)] self.sections = [self.make_lms_section(i, s) for i, s in enumerate(sections)]
for s in self.sections:
if s.type == "exercise":
e = s.get_exercise()
e.lesson = self.name
e.save()
def get_sections(self): def get_sections(self):
return sorted(self.get('sections'), key=lambda s: s.index) return sorted(self.get('sections'), key=lambda s: s.index)
@@ -18,6 +23,7 @@ class Lesson(Document):
def make_lms_section(self, index, section): def make_lms_section(self, index, section):
s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections') s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections')
s.type = section.type s.type = section.type
s.id = section.id
s.label = section.label s.label = section.label
s.contents = section.contents s.contents = section.contents
s.index = index s.index = index

View File

@@ -9,7 +9,8 @@
"contents", "contents",
"code", "code",
"attrs", "attrs",
"index" "index",
"id"
], ],
"fields": [ "fields": [
{ {
@@ -43,12 +44,17 @@
"fieldname": "index", "fieldname": "index",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Index" "label": "Index"
},
{
"fieldname": "id",
"fieldtype": "Data",
"label": "id"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-03-12 17:56:23.118854", "modified": "2021-05-19 18:55:26.019625",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Section", "name": "LMS Section",

View File

@@ -10,6 +10,10 @@ class LMSSection(Document):
def __repr__(self): def __repr__(self):
return f"<LMSSection {self.label!r}>" return f"<LMSSection {self.label!r}>"
def get_exercise(self):
if self.type == "exercise":
return frappe.get_doc("Exercise", self.id)
def get_latest_code_for_user(self): def get_latest_code_for_user(self):
"""Returns the latest code for the logged in user. """Returns the latest code for the logged in user.
""" """

View File

@@ -4,6 +4,7 @@ import websocket
import json import json
from .svg import SVG from .svg import SVG
import frappe import frappe
from urllib.parse import urlparse
# Files to pass to livecode server # Files to pass to livecode server
# The same code is part of livecode-canvas.js # The same code is part of livecode-canvas.js
@@ -60,9 +61,21 @@ def clear():
clear() clear()
''' '''
def get_livecode_url():
doc = frappe.get_cached_doc("LMS Settings")
return doc.livecode_url
def get_livecode_ws_url():
url = urlparse(get_livecode_url())
protocol = "wss" if url.scheme == "https" else "ws"
return protocol + "://" + url.netloc + "/livecode"
def livecode_to_svg(livecode_ws_url, code, *, timeout=3): def livecode_to_svg(livecode_ws_url, code, *, timeout=3):
"""Renders the code as svg. """Renders the code as svg.
""" """
if livecode_ws_url is None:
livecode_ws_url = get_livecode_ws_url()
try: try:
ws = websocket.WebSocket() ws = websocket.WebSocket()
ws.settimeout(timeout) ws.settimeout(timeout)

View File

@@ -0,0 +1,18 @@
{% from "www/macros/livecode.html" import LiveCodeEditorJS, LiveCodeEditor with context %}
<div class="exercise">
<h2>{{ exercise.title }}</h2>
<div class="exercise-description">{{frappe.utils.md_to_html(exercise.description)}}</div>
{% if exercise.image %}
<div class="exercise-image">{{exercise.image}}</div>
{% endif %}
{% set submission = exercise.get_user_submission() %}
{{ LiveCodeEditor(exercise.name,
code=exercise.code,
reset_code=exercise.code,
is_exercise=True,
last_submitted=submission and submission.creation) }}
</div>

View File

@@ -1,14 +0,0 @@
{
"css/lms.css": [
"public/css/lms.css"
],
"css/community.css": [
"public/css/style.css",
"public/css/vars.css",
"public/css/style.less"
],
"js/livecode-canvas.js": [
"public/js/livecode-canvas.js"
]
}

View File

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

View File

@@ -1,26 +0,0 @@
.heading {
background: #eee;
padding: 10px;
clear: both;
color: #212529;
border: 1px solid #ddd;
}
.sketch-header h1 {
font-size: 1.5em;
margin-bottom: 0px;
}
.sketch-header {
margin-bottom: 1em;
}
.sketch-card .sketch-title a {
font-weight: bold;
color: inherit;
}
.hidden {
display: none;
}

View File

@@ -1,5 +1,3 @@
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css");
@import url("https://use.fontawesome.com/releases/v5.13.0/css/all.css");
:root { :root {
--c1: #fefae0; --c1: #fefae0;
@@ -26,7 +24,7 @@
--send-message: var(--c7); --send-message: var(--c7);
--received-message: var(--c8); --received-message: var(--c8);
--primary-color: #08B74F; --primary-color: #08B74F !important;
} }
body { body {

View File

@@ -1,9 +1,4 @@
@primary-color: #08B74F; @primary-color: #08B74F;
@import url('https://rsms.me/inter/inter.css');
body {
font-family: "Inter", sans-serif;
}
h2 { h2 {
margin: 20px 0px; margin: 20px 0px;
@@ -306,3 +301,14 @@ section.lightgray {
.lesson-page { .lesson-page {
margin: 20px 0px; margin: 20px 0px;
} }
.lesson-pagination {
clear: both;
}
.exercise-image svg {
width: 200px;
height: 200px;
border: 1px solid #ddd;
margin-bottom: 20px;
}

View File

@@ -6,6 +6,8 @@
{% block head_include %} {% block head_include %}
<meta name="description" content="Courses" /> <meta name="description" content="Courses" />
<meta name="keywords" content="" /> <meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -6,6 +6,8 @@
{% block head_include %} {% block head_include %}
<meta name="description" content="Courses" /> <meta name="description" content="Courses" />
<meta name="keywords" content="" /> <meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -7,6 +7,6 @@ def get_context(context):
def get_courses(): def get_courses():
courses = frappe.get_all( courses = frappe.get_all(
"LMS Course", "LMS Course",
fields=['name', 'slug', 'title', 'description'] fields=['name', 'title', 'description']
) )
return courses return courses

View File

@@ -9,6 +9,7 @@
<style> <style>
</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="{{ livecode_url }}/static/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="/assets/css/lms.css"> <link rel="stylesheet" href="/assets/css/lms.css">
@@ -26,14 +27,7 @@
<div class="container"> <div class="container">
<div class="lesson-page"> <div class="lesson-page">
<div class="lesson-pagination"> {{ pagination(prev_url, next_url) }}
{% if prev_url %}
<a href="{{prev_url}}" class="btn">&larr; Prev</a>
{% endif %}
{% if next_url %}
<a href="{{next_url}}" class="btn pull-right">Next &rarr;</a>
{% endif %}
</div>
<h2>{{ lesson.title }}</h2> <h2>{{ lesson.title }}</h2>
@@ -43,10 +37,8 @@
</div> </div>
{% endfor %} {% endfor %}
<div class="lesson-pagination"> {{ pagination(prev_url, next_url) }}
<a href="#" class="btn">&larr; Prev</a>
<a href="#" class="btn pull-right">Next &rarr;</a>
</div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
@@ -55,8 +47,14 @@
{% macro render_section(s) %} {% macro render_section(s) %}
{% if s.type == "text" %} {% if s.type == "text" %}
{{ render_section_text(s) }} {{ render_section_text(s) }}
{% elif s.type == "example" or s.type == "code" or s.type == "exercise" %} {% elif s.type == "example" or s.type == "code" %}
{{ LiveCodeEditor(s.name, s.get_latest_code_for_user(), s.type=="exercise", "2 hours ago") }} {{ LiveCodeEditor(s.name,
code=s.get_latest_code_for_user(),
reset_code=s.contents,
is_exercise=False)
}}
{% elif s.type == "exercise" %}
{{ widgets.Exercise(exercise=s.get_exercise())}}
{% else %} {% else %}
<div>Unknown section type: {{s.type}}</div> <div>Unknown section type: {{s.type}}</div>
{% endif %} {% endif %}
@@ -70,6 +68,18 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% macro pagination(prev_url, next_url) %}
<div class="lesson-pagination">
{% if prev_url %}
<a href="{{prev_url}}" class="btn">&larr; Prev</a>
{% endif %}
{% if next_url %}
<a href="{{next_url}}" class="btn pull-right">Next &rarr;</a>
{% endif %}
<div style="clear: both;"></div>
</div>
{% endmacro %}
{%- block script %} {%- block script %}
{{ super() }} {{ super() }}
{{ LiveCodeEditorJS() }} {{ LiveCodeEditorJS() }}

View File

@@ -7,6 +7,7 @@
{% block head_include %} {% block head_include %}
<meta name="description" content="Courses" /> <meta name="description" content="Courses" />
<meta name="keywords" content="" /> <meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -4,6 +4,7 @@
{% block head_include %} {% block head_include %}
<meta name="description" content="Courses" /> <meta name="description" content="Courses" />
<meta name="keywords" content="" /> <meta name="keywords" content="" />
<link rel="stylesheet" href="/assets/frappe/css/font-awesome.css">
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -24,7 +24,7 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% macro LiveCodeEditor(name, code, is_exercise, last_submitted) %} {% macro LiveCodeEditor(name, code, reset_code, is_exercise=False, last_submitted=None) %}
<div class="livecode-editor livecode-editor-inline" id="editor-{{name}}"> <div class="livecode-editor livecode-editor-inline" id="editor-{{name}}">
<div class="row"> <div class="row">
<div class="col-lg-8 col-md-6"> <div class="col-lg-8 col-md-6">
@@ -34,10 +34,13 @@
{% if is_exercise %} {% if is_exercise %}
<button class="submit pull-right btn-primary">Submit</button> <button class="submit pull-right btn-primary">Submit</button>
{% if last_submitted %} {% if last_submitted %}
<span class="pull-right" style="padding-right: 10px;">Submitted <span class="human-time" data-timestamp="{{last_submitted}}">on {{last_submitted}}</span></span> <span class="pull-right" style="padding-right: 10px;"><span class="human-time" data-timestamp="{{last_submitted}}"></span></span>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
<div style="display: none">
<pre class="reset-code">{{reset_code}}</pre>
</div>
</div> </div>
</div> </div>
<div class="code-editor"> <div class="code-editor">
@@ -59,20 +62,67 @@
{% macro LiveCodeEditorJS(name, code) %} {% macro LiveCodeEditorJS(name, code) %}
<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="{{ livecode_url }}/static/livecode.js"></script>
<script type="text/javascript" src="/assets/community/js/livecode-canvas.js"></script> <script type="text/javascript" src="/assets/community/js/livecode-canvas.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var livecodeEditors = []; var livecodeEditors = [];
var livecodeEditorsMap = {};
$(function() { $(function() {
$(".livecode-editor").each((i, e) => { $(".livecode-editor").each((i, e) => {
var name = e.id.replace("editor-", "");
var editor = new LiveCodeEditor(e, { var editor = new LiveCodeEditor(e, {
base_url: "{{ livecode_url }}", base_url: "{{ livecode_url }}",
...getLiveCodeOptions() ...getLiveCodeOptions()
}) })
livecodeEditors.push(editor); livecodeEditors.push(editor);
}) livecodeEditorsMap[e.id] = editor;
})
$(e).find(".reset").on('click', function() {
let code = $(e).find(".reset-code").html();
editor.codemirror.doc.setValue(code);
});
$(e).find(".submit").on('click', function() {
let code = editor.codemirror.doc.getValue();
console.log("submit", name, code);
frappe.call("community.lms.api.submit_solution", {
"exercise": name,
"code": code
}).then(r => {
if (r.message.name) {
frappe.msgprint("Submitted successfully!");
let d = r.message.creation;
$(e).find(".human-time").html(__("Submitted {0}", [comment_when(d)]));
}
});
});
});
});
function updateSubmitTimes() {
$(".human-time").each(function(i, e) {
var d = $(e).data().timestamp;
$(e).html(__("Submitted {0}", [comment_when(d)]));
});
}
updateSubmitTimes();
</script> </script>
{% endmacro %} {% endmacro %}

View File

@@ -1,12 +1,12 @@
{% macro Sidebar(course, batch_code) %} {% macro Sidebar(course, batch_code) %}
<div class="sidebar-batch"> <div class="sidebar-batch">
<a href=""><i class="fas fa-bars fa-lg"></i></a> <a href=""><i class="fa fa-bars fa-lg"></i></a>
<br> <br>
<a href="/courses/{{course}}/{{batch_code}}/learn"><i class="fas fa-book fa-lg"></i></a> <a href="/courses/{{course}}/{{batch_code}}/learn"><i class="fa fa-book fa-lg"></i></a>
<a href="/courses/{{course}}/{{batch_code}}/schedule"><i class="fas fa-calendar-alt fa-lg"></i></a> <a href="/courses/{{course}}/{{batch_code}}/schedule"><i class="fa fa-calendar fa-lg"></i></a>
<a href="/courses/{{course}}/{{batch_code}}/members"><i class="fas fa-users fa-lg"></i></a> <a href="/courses/{{course}}/{{batch_code}}/members"><i class="fa fa-users fa-lg"></i></a>
<a href="/courses/{{course}}/{{batch_code}}/discuss"><i class="fas fa-comments fa-lg"></i></a> <a href="/courses/{{course}}/{{batch_code}}/discuss"><i class="fa fa-comments fa-lg"></i></a>
<a href="/courses/{{course}}/{{batch_code}}/about"><i class="fas fa-info-circle fa-lg"></i></a> <a href="/courses/{{course}}/{{batch_code}}/about"><i class="fa fa-info-circle fa-lg"></i></a>
<!-- <a href="#contact"><i class="fas fa-home fa-lg"></i></a> --> <!-- <a href="#contact"><i class="fas fa-home fa-lg"></i></a> -->
</div> </div>
{% endmacro %} {% endmacro %}