Compare commits
42 Commits
web-form-u
...
profile-ur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0b77f5d08 | ||
|
|
77c4b53b71 | ||
|
|
035a674cff | ||
|
|
f52e5067b6 | ||
|
|
d657525359 | ||
|
|
916e64d607 | ||
|
|
9e1daf5062 | ||
|
|
941a34784c | ||
|
|
cd4ffa2eff | ||
|
|
17a7af74f2 | ||
|
|
ff22eaa606 | ||
|
|
417436d7b6 | ||
|
|
f228489173 | ||
|
|
a49563e23f | ||
|
|
b3403b78ee | ||
|
|
7a9039090d | ||
|
|
289195e6c9 | ||
|
|
e6502784ea | ||
|
|
54f301e8eb | ||
|
|
ed91801769 | ||
|
|
6965148e4e | ||
|
|
b5481e1dd5 | ||
|
|
4ec9b56366 | ||
|
|
530fcf9a39 | ||
|
|
ff1363b437 | ||
|
|
952e3a9906 | ||
|
|
9d530e35fb | ||
|
|
2c2ad78eb7 | ||
|
|
f61c5a2fa1 | ||
|
|
3e24ff9678 | ||
|
|
b0280c3be4 | ||
|
|
b10eb5c979 | ||
|
|
e2072c72da | ||
|
|
84a43912db | ||
|
|
841819436a | ||
|
|
14a984c75f | ||
|
|
445de61ce4 | ||
|
|
aefee791ca | ||
|
|
00154d80df | ||
|
|
a1e12d29ac | ||
|
|
c6d3994383 | ||
|
|
eaec991f47 |
@@ -63,7 +63,7 @@ To setup the repository locally follow the steps mentioned below:
|
||||
1. Run bench get-app https://github.com/fossunited/community.
|
||||
1. Run bench --site community.test install-app community.
|
||||
1. Map your site to localhost with the command ```bench --site community.test add-to-hosts```
|
||||
1. Now open the URL http://community.test:8000/docs in your browser, you should see the app running.
|
||||
1. Now open the URL http://community.test:8000/ in your browser, you should see the app running.
|
||||
|
||||
### Contribution Guidelines (for The Hard Way)
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright (c) 2021, FOSS United and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from community.widgets import Widget, Widgets
|
||||
|
||||
class DiscussionMessage(Document):
|
||||
def after_insert(self):
|
||||
data = {
|
||||
"message": self,
|
||||
"widgets": Widgets()
|
||||
}
|
||||
template = frappe.render_template("community/templates/message_card.html", data)
|
||||
thread_info = frappe.db.get_value("Discussion Thread", self.thread, ["reference_doctype", "reference_docname"], as_dict=True)
|
||||
frappe.publish_realtime(event="publish_message",
|
||||
message = {
|
||||
"thread": self.thread,
|
||||
"template": template,
|
||||
"thread_info": thread_info
|
||||
},
|
||||
after_commit=True)
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2021, FOSS United and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Discussion Thread', {
|
||||
frappe.ui.form.on('Discussion Reply', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
@@ -5,49 +5,31 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"thread",
|
||||
"column_break_2",
|
||||
"parent_message",
|
||||
"section_break_4",
|
||||
"message"
|
||||
"topic",
|
||||
"reply"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "message",
|
||||
"fieldname": "reply",
|
||||
"fieldtype": "Long Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Message"
|
||||
"label": "Reply"
|
||||
},
|
||||
{
|
||||
"fieldname": "thread",
|
||||
"fieldname": "topic",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Thread",
|
||||
"options": "Discussion Thread"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "parent_message",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Parent Message",
|
||||
"options": "Discussion Message"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
"label": "Topic",
|
||||
"options": "Discussion Topic"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-12 15:59:04.811286",
|
||||
"modified": "2021-08-27 15:06:51.362714",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Community",
|
||||
"name": "Discussion Message",
|
||||
"name": "Discussion Reply",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2021, FOSS United and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from community.widgets import Widgets
|
||||
|
||||
class DiscussionReply(Document):
|
||||
def after_insert(self):
|
||||
data = {
|
||||
"reply": self,
|
||||
"topic": {
|
||||
"name": self.topic
|
||||
},
|
||||
"widgets": Widgets()
|
||||
}
|
||||
template = frappe.render_template("community/templates/discussions/reply_card.html", data)
|
||||
topic_info = frappe.get_all("Discussion Topic", {"name": self.topic}, ["reference_doctype", "reference_docname", "name", "title", "owner", "creation"])
|
||||
sidebar = frappe.render_template("community/templates/discussions/sidebar.html", { "topic": topic_info[0], "widgets": Widgets() })
|
||||
new_topic_template = frappe.render_template("community/templates/discussions/reply_section.html", { "topics": topic_info, "widgets": Widgets() })
|
||||
|
||||
frappe.publish_realtime(event="publish_message",
|
||||
message = {
|
||||
"template": template,
|
||||
"topic_info": topic_info[0],
|
||||
"sidebar": sidebar,
|
||||
"new_topic_template": new_topic_template
|
||||
},
|
||||
after_commit=True)
|
||||
@@ -4,5 +4,5 @@
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestDiscussionThread(unittest.TestCase):
|
||||
class TestDiscussionReply(unittest.TestCase):
|
||||
pass
|
||||
@@ -1,48 +0,0 @@
|
||||
# Copyright (c) 2021, FOSS United and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DiscussionThread(Document):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_discussion(doctype, docname, message, title=None, thread_name=None):
|
||||
thread = []
|
||||
filters = {}
|
||||
if doctype and docname:
|
||||
filters = {
|
||||
"reference_doctype": doctype,
|
||||
"reference_docname": docname
|
||||
}
|
||||
|
||||
elif thread_name:
|
||||
filters = {
|
||||
"name": thread_name
|
||||
}
|
||||
|
||||
if filters:
|
||||
thread = frappe.get_all("Discussion Thread",filters)
|
||||
if len(thread):
|
||||
thread = thread[0]
|
||||
save_message(message, thread)
|
||||
|
||||
else:
|
||||
thread = frappe.get_doc({
|
||||
"doctype": "Discussion Thread",
|
||||
"title": title,
|
||||
"reference_doctype": doctype,
|
||||
"reference_docname": docname
|
||||
})
|
||||
thread.save(ignore_permissions=True)
|
||||
save_message(message, thread)
|
||||
|
||||
return thread.name
|
||||
|
||||
def save_message(message, thread):
|
||||
frappe.get_doc({
|
||||
"doctype": "Discussion Message",
|
||||
"message": message,
|
||||
"thread": thread.name
|
||||
}).save(ignore_permissions=True)
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2021, FOSS United and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Discussion Message', {
|
||||
frappe.ui.form.on('Discussion Topic', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
@@ -33,7 +33,7 @@
|
||||
"modified": "2021-08-11 12:29:43.564123",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Community",
|
||||
"name": "Discussion Thread",
|
||||
"name": "Discussion Topic",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -54,4 +54,4 @@
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Copyright (c) 2021, FOSS United and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DiscussionTopic(Document):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_discussion(doctype, docname, reply, title, topic_name=None):
|
||||
if topic_name:
|
||||
save_message(reply, topic_name)
|
||||
return topic_name
|
||||
|
||||
topic = frappe.get_doc({
|
||||
"doctype": "Discussion Topic",
|
||||
"title": title,
|
||||
"reference_doctype": doctype,
|
||||
"reference_docname": docname
|
||||
})
|
||||
topic.save(ignore_permissions=True)
|
||||
save_message(reply, topic.name)
|
||||
return topic.name
|
||||
|
||||
def save_message(reply, topic):
|
||||
frappe.get_doc({
|
||||
"doctype": "Discussion Reply",
|
||||
"reply": reply,
|
||||
"topic": topic
|
||||
}).save(ignore_permissions=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_docname(route):
|
||||
return frappe.db.get_value("Web Page", {"route": route}, ["name"])
|
||||
@@ -4,5 +4,5 @@
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestDiscussionMessage(unittest.TestCase):
|
||||
class TestDiscussionTopic(unittest.TestCase):
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
{% set docname = frappe.db.get_value("Web Page", {"route": ""}, ["name"])%}
|
||||
{{ widgets.DiscussionMessage(doctype="Web Page", docname=docname) }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"__islocal": true,
|
||||
"__unsaved": 1,
|
||||
"creation": "2021-08-30 12:42:31.550200",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Template",
|
||||
"fields": [],
|
||||
"idx": 0,
|
||||
"modified": "2021-08-30 12:42:31.550200",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Community",
|
||||
"name": "Discussions",
|
||||
"owner": "Administrator",
|
||||
"standard": 1,
|
||||
"template": "",
|
||||
"type": "Section"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{% set color = member.get_palette() %}
|
||||
<a class="button-links" href="/user/{{member.username}}">
|
||||
<a class="button-links" href="{{ get_profile_url(member.username) }}">
|
||||
<span class="avatar {{ avatar_class }}" title="{{ member.full_name }}">
|
||||
{% if member.user_image %}
|
||||
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}" title="{{ member.full_name }}">
|
||||
|
||||
28
community/community/widgets/CommentBox.html
Normal file
28
community/community/widgets/CommentBox.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<form class="discussion-form">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<input type="text" autocomplete="off" class="input-with-feedback form-control topic-title" data-fieldtype="Data"
|
||||
data-fieldname="feedback_comments" placeholder="Title" spellcheck="false"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<textarea type="text" autocomplete="off" class="input-with-feedback form-control comment-field"
|
||||
data-fieldtype="Text" data-fieldname="feedback_comments" placeholder="Enter a comment..."
|
||||
spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comment-footer">
|
||||
<div class="button is-default pull-right mb-5 submit-discussion" data-doctype="{{ doctype | urlencode }}"
|
||||
data-docname="{{ docname | urlencode }}">
|
||||
Post</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -1,70 +0,0 @@
|
||||
<div class="discussions">
|
||||
|
||||
<form class="discussion-form {% if doctype or thread %} discussion-on-page {% endif %}" id="discussion-form">
|
||||
|
||||
|
||||
<div class="form-group" {% if title or thread %} style="display: none;" {% endif %}>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<input type="text" autocomplete="off" class="input-with-feedback form-control thread-title"
|
||||
data-fieldtype="Data" data-fieldname="feedback_comments" placeholder="Title" spellcheck="false" {% if title
|
||||
%} value="{{ title }}" {% endif %}></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<textarea type="text" autocomplete="off" class="input-with-feedback form-control comment-field"
|
||||
data-fieldtype="Text" data-fieldname="feedback_comments" placeholder="Enter a comment..."
|
||||
spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-footer">
|
||||
<div class="button is-secondary pull-right" id="submit-discussion"
|
||||
{% if doctype %} data-doctype="{{ doctype | urlencode}}" {% endif %}
|
||||
{% if docname %} data-docname="{{ docname | urlencode}}" {% endif %}
|
||||
{% if thread %} data-thread="{{ thread }}" {% endif %}>
|
||||
Post</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
$("#submit-discussion").click((e) => {
|
||||
submit_discussion(e);
|
||||
})
|
||||
})
|
||||
|
||||
var submit_discussion = (e) => {
|
||||
var message = $(".comment-field").val().trim();
|
||||
|
||||
if (message) {
|
||||
var doctype = $(e.currentTarget).attr("data-doctype");
|
||||
doctype = doctype ? decodeURIComponent(doctype) : doctype;
|
||||
|
||||
var docname = $(e.currentTarget).attr("data-docname");
|
||||
docname = docname ? decodeURIComponent(docname) : docname;
|
||||
|
||||
frappe.call({
|
||||
method: "community.community.doctype.discussion_thread.discussion_thread.submit_discussion",
|
||||
args: {
|
||||
"doctype": doctype ? doctype : "",
|
||||
"docname": docname ? docname : "",
|
||||
"message": $(".comment-field").val(),
|
||||
"title": $(".thread-title").val(),
|
||||
"thread_name": $(e.currentTarget).attr("data-thread")
|
||||
},
|
||||
callback: (data) => {
|
||||
if (! $(".discussion-on-page").length) {
|
||||
$("#discussion-modal").modal("hide");
|
||||
window.location.href = `/discussions/${data.message}`;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,80 +1,54 @@
|
||||
{% if doctype and docname and not thread %}
|
||||
{% set topics = frappe.get_all("Discussion Topic",
|
||||
{"reference_doctype": doctype, "reference_docname": docname}, ["name", "title", "owner", "creation"]) %}
|
||||
|
||||
{% set thread_info = frappe.get_all("Discussion Thread", {"reference_doctype": doctype, "reference_docname": docname},
|
||||
["name"]) %}
|
||||
{% include "community/templates/discussions/topic_modal.html" %}
|
||||
|
||||
{% if thread_info | length %}
|
||||
{% set thread = thread_info[0].name %}
|
||||
<div class="discussions-parent">
|
||||
<div class="discussions-header">
|
||||
<span class="course-home-headings">{{_('Discussions')}}</span>
|
||||
{% if topics %}
|
||||
{% include "community/templates/discussions/button.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if topics %}
|
||||
<div class="common-card-style thread-card course-content-parent" data-doctype="{{ doctype }}"
|
||||
data-docname="{{ docname }}">
|
||||
|
||||
<div class="discussions-sidebar">
|
||||
{% include "community/templates/discussions/search.html" %}
|
||||
{% for topic in topics %}
|
||||
{% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name})%}
|
||||
{% include "community/templates/discussions/sidebar.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="mr-2" id="discussion-group">
|
||||
{% include "community/templates/discussions/reply_section.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div id="no-discussions" class="common-card-style thread-card">
|
||||
<div class="no-discussions">
|
||||
<div class="font-weight-bold">No Discussions</div>
|
||||
<div class="muted-text mt-3 mb-3">There are no discussions for this {{ doctype | lower }}, why don't you start
|
||||
one! </div>
|
||||
{% if frappe.session.user == "Guest" %}
|
||||
<div class="button is-primary mt-3" id="login-from-discussion">Log In</div>
|
||||
{% elif not condition %}
|
||||
<div class="button is-primary mt-3" id="login-from-discussion" data-redirect="{{ redirect_to }}">
|
||||
{{ button_name }}
|
||||
</div>
|
||||
{% else %}
|
||||
{% include "community/templates/discussions/button.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if thread %}
|
||||
{% set messages = frappe.get_all("Discussion Message", {"thread": thread}, ["name", "message", "owner", "creation"],
|
||||
order_by="creation") %}
|
||||
{% endif %}
|
||||
|
||||
{% if doctype %}
|
||||
<div class="course-home-headings mt-5"> Discussions </div>
|
||||
{% endif %}
|
||||
|
||||
<div class="messages mt-5">
|
||||
{% for message in messages %}
|
||||
{% include "community/templates/message_card.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if frappe.session.user == "Guest" or (condition is defined and not condition) %}
|
||||
<div class="d-flex flex-column align-items-center font-weight-bold">
|
||||
Want to join the discussion?
|
||||
{% if frappe.session.user == "Guest" %}
|
||||
<div class="button is-primary" id="login-from-discussion">Log In</div>
|
||||
{% elif not condition %}
|
||||
<div class="button is-primary" id="login-from-discussion" data-redirect="{{ redirect_to }}">{{ button_name }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ widgets.DiscussionComment(doctype=doctype, docname=docname, title=title, thread=thread ) }}
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
setup_socket_io();
|
||||
|
||||
$("#login-from-discussion").click((e) => {
|
||||
login_from_discussion(e);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var setup_socket_io = () => {
|
||||
const assets = [
|
||||
"/assets/frappe/js/lib/socket.io.min.js",
|
||||
"/assets/frappe/js/frappe/socketio_client.js",
|
||||
]
|
||||
frappe.require(assets, () => {
|
||||
|
||||
if (window.dev_server) {
|
||||
frappe.boot.socketio_port = "9000";
|
||||
}
|
||||
|
||||
frappe.socketio.init(9000);
|
||||
var target = $("#submit-discussion");
|
||||
|
||||
frappe.socketio.socket.on("publish_message", (data) => {
|
||||
if (target.attr("data-thread") == data.thread
|
||||
|| (decodeURIComponent(target.attr("data-doctype")) == data.thread_info.reference_doctype
|
||||
&& decodeURIComponent(target.attr("data-docname")) == data.thread_info.reference_docname)) {
|
||||
$(".comment-field").val("");
|
||||
$(".messages").append(data.template);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var login_from_discussion = (e) => {
|
||||
var redirect = $(e.currentTarget).attr("data-redirect") || window.location.href;
|
||||
window.location.href = `/login?redirect-to=${redirect}`;
|
||||
}
|
||||
|
||||
</script>
|
||||
{% block script %}
|
||||
<script>{% include "community/templates/discussions/discussions.js" %}</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Speaker', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
onload: function (frm) {
|
||||
frm.set_query('user', function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
"ignore_user_type": 1,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -41,8 +41,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "thumbnail",
|
||||
"fieldtype": "Data",
|
||||
"label": "Preview Image (Link)"
|
||||
"fieldtype": "Attach",
|
||||
"label": "Preview Image"
|
||||
},
|
||||
{
|
||||
"fieldname": "event",
|
||||
@@ -113,7 +113,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-20 17:02:51.609288",
|
||||
"modified": "2021-08-24 16:47:34.557010",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Event Management",
|
||||
"name": "Talk",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class Talk(Document):
|
||||
def before_save(self):
|
||||
if not self.speaker:
|
||||
@@ -25,5 +24,6 @@ class Talk(Document):
|
||||
"doctype": "Speaker",
|
||||
"event": self.event,
|
||||
"user": frappe.session.user
|
||||
}).save(ignore_permissions=True)
|
||||
self.speaker = speaker
|
||||
})
|
||||
speaker.save(ignore_permissions=True)
|
||||
self.speaker = speaker.name
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
frappe.ready(function () {
|
||||
frappe.web_form.after_load = () => {
|
||||
frappe.web_form.set_value("user", frappe.session.user);
|
||||
}
|
||||
|
||||
frappe.web_form.after_save = () => {
|
||||
setTimeout(function () {
|
||||
window.location.href = '/event/conference2021/about';
|
||||
window.location.href = '/about';
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
@@ -19,24 +19,25 @@
|
||||
"is_standard": 1,
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2021-08-23 10:13:19.224367",
|
||||
"modified": "2021-08-24 19:57:25.516319",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Event Management",
|
||||
"name": "attendee-registration",
|
||||
"owner": "Administrator",
|
||||
"payment_button_label": "Buy Now",
|
||||
"published": 1,
|
||||
"route": "event/conference2021/attendee-registration",
|
||||
"route": "attendee-registration",
|
||||
"route_to_success_link": 1,
|
||||
"show_attachments": 0,
|
||||
"show_in_grid": 0,
|
||||
"show_sidebar": 0,
|
||||
"sidebar_items": [],
|
||||
"success_url": "/event/conference2021/about",
|
||||
"success_url": "/about",
|
||||
"title": "Attendee Registration",
|
||||
"web_form_fields": [
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"default": "",
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@@ -73,4 +74,4 @@
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
frappe.ready(function () {
|
||||
|
||||
frappe.web_form.after_load = () => {
|
||||
frappe.web_form.set_value("user", frappe.session.user);
|
||||
}
|
||||
|
||||
frappe.web_form.after_save = () => {
|
||||
setTimeout(function () {
|
||||
window.location.href = '/event/conference2021/about';
|
||||
window.location.href = '/about';
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
frappe.ready(function () {
|
||||
frappe.web_form.after_load = () => {
|
||||
frappe.web_form.set_value("user", frappe.session.user);
|
||||
}
|
||||
|
||||
frappe.web_form.after_save = () => {
|
||||
setTimeout(function () {
|
||||
window.location.href = '/event/conference2021/about';
|
||||
window.location.href = '/about';
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
@@ -20,7 +20,7 @@
|
||||
"is_standard": 1,
|
||||
"login_required": 1,
|
||||
"max_attachment_size": 0,
|
||||
"modified": "2021-08-23 10:18:17.486228",
|
||||
"modified": "2021-08-24 19:57:06.806994",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Event Management",
|
||||
"name": "purpose-a-talk",
|
||||
@@ -74,6 +74,18 @@
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "thumbnail",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 0,
|
||||
"label": "Preview Image",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "about",
|
||||
@@ -85,18 +97,6 @@
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"show_in_filter": 0
|
||||
},
|
||||
{
|
||||
"allow_read_on_all_link_options": 0,
|
||||
"fieldname": "attachment",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 0,
|
||||
"label": "Attachment",
|
||||
"max_length": 0,
|
||||
"max_value": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"show_in_filter": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
frappe.ready(function () {
|
||||
frappe.web_form.after_load = () => {
|
||||
frappe.web_form.set_value("user", frappe.session.user);
|
||||
}
|
||||
frappe.web_form.after_save = () => {
|
||||
setTimeout(function () {
|
||||
window.location.href = '/event/conference2021/propose-talk';
|
||||
|
||||
@@ -11,22 +11,7 @@
|
||||
{% set member = frappe.get_doc("User", exhibitor_doc.user) %}
|
||||
|
||||
<div class="common-card-style member-card">
|
||||
{% set color = member.get_palette() %}
|
||||
<a class="button-links" href="/user/{{member.username}}">
|
||||
<span class="avatar avatar-large" title="{{ member.full_name }}">
|
||||
{% if member.user_image %}
|
||||
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
|
||||
title="{{ member.full_name }}">
|
||||
</img>
|
||||
{% else %}
|
||||
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
|
||||
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
|
||||
{{ frappe.utils.get_abbr(member.full_name) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-large")}}
|
||||
<div class="small-title member-card-title">
|
||||
{{ member.full_name }}
|
||||
</div>
|
||||
|
||||
@@ -10,21 +10,7 @@
|
||||
{% set member = frappe.get_doc("User", speaker_doc.user) %}
|
||||
|
||||
<div class="common-card-style member-card">
|
||||
{% set color = member.get_palette() %}
|
||||
<a class="button-links" href="/user/{{member.username}}">
|
||||
<span class="avatar avatar-large" title="{{ member.full_name }}">
|
||||
{% if member.user_image %}
|
||||
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
|
||||
title="{{ member.full_name }}">
|
||||
</img>
|
||||
{% else %}
|
||||
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
|
||||
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
|
||||
{{ frappe.utils.get_abbr(member.full_name) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-large") }}
|
||||
|
||||
<div class="small-title member-card-title">
|
||||
{{ member.full_name }}
|
||||
|
||||
@@ -4,40 +4,61 @@
|
||||
<p class="section-description">{{ subtitle }}</p>
|
||||
{%- endif -%}
|
||||
<div class="cards-parent">
|
||||
{% for talk in talk_details %}
|
||||
{% set talk_doc = frappe.db.get_value('Talk', talk.talk, ["title", "category", "speaker", "url", "thumbnail"], as_dict=True) %}
|
||||
{% set speaker_info = frappe.db.get_value("Speaker", talk_doc.speaker, ["user"], as_dict=True) %}
|
||||
|
||||
{% for talk in talk_details %}
|
||||
|
||||
{% set talk_doc = frappe.db.get_value('Talk', talk.talk,
|
||||
["title", "category", "speaker", "url", "thumbnail", "date", "start_time", "end_time"], as_dict=True) %}
|
||||
{% set speaker_info = frappe.db.get_value("Speaker", talk_doc.speaker, ["user"], as_dict=True) %}
|
||||
{% set member = frappe.get_doc("User", speaker_info.user) %}
|
||||
|
||||
{% if talk_doc.thumbnail %}
|
||||
{% set thumbnail = talk_doc.thumbnail %}
|
||||
{% else %}
|
||||
{% set video_id = talk_doc.url and talk_doc.url.split("/")[-1] %}
|
||||
{% set thumbnail = video_id and "https://img.youtube.com/vi/" + video_id + "/maxresdefault.jpg" %}
|
||||
{% endif %}
|
||||
|
||||
<div class="common-card-style flex-column">
|
||||
<div class="course-image" style="background-image: url({{talk_doc.thumbnail}})">
|
||||
<div class="course-tags"></div>
|
||||
<div class="course-image {% if not thumbnail %}default-image{% endif %}" {% if thumbnail %}
|
||||
style="background-image: url( {{ thumbnail }} );" {% endif %}>
|
||||
<div class="course-tags">
|
||||
{% for tag in talk_doc.category.split(",") %}
|
||||
<div class="course-card-pills">{{ tag }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if not thumbnail %}
|
||||
<div class="default-image-text">{{ talk_doc.title[0] }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="course-card-content">
|
||||
<div class="course-card-meta muted-text">
|
||||
<span> {{talk_doc.category}} </span>
|
||||
</div>
|
||||
<div class="course-card-content"></div>
|
||||
<div class="course-card-title">{{talk_doc.title}}</div>
|
||||
<div class="muted-text mb-3">
|
||||
{% if talk_doc.date %}
|
||||
<span>
|
||||
<img src="/assets/community/icons/calendar.svg">
|
||||
{{ frappe.utils.format_date(talk_doc.date, "medium") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="pull-right">
|
||||
{% if talk_doc.start_time %}
|
||||
<span class="mr-3">
|
||||
<b>From:</b>
|
||||
{{ frappe.utils.format_time(talk_doc.start_time, "HH:mm") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if talk_doc.end_time %}
|
||||
<span>
|
||||
<b>To:</b>
|
||||
{{ frappe.utils.format_time(talk_doc.end_time, "HH:mm") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-divider"></div>
|
||||
<div class="course-card-meta-2">
|
||||
|
||||
{% set color = member.get_palette() %}
|
||||
<a class="button-links" href="/user/{{member.username}}">
|
||||
<span class="avatar avatar-small" title="{{ member.full_name }}">
|
||||
{% if member.user_image %}
|
||||
<img class="avatar-frame standard-image" style="object-fit: cover;" src="{{ member.user_image }}"
|
||||
title="{{ member.full_name }}">
|
||||
</img>
|
||||
{% else %}
|
||||
<span class="avatar-frame standard-image" title="{{ member.full_name }}"
|
||||
style="background-color: var({{color[0]}}); color: var({{color[1]}});">
|
||||
{{ frappe.utils.get_abbr(member.full_name) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-small")}}
|
||||
<span class="course-instructor"> {{ member.full_name }} </span>
|
||||
<span class="small-title company-name"></span>
|
||||
</div>
|
||||
@@ -49,5 +70,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,7 +85,8 @@ web_include_css = "community.bundle.css"
|
||||
# Override standard doctype classes
|
||||
|
||||
override_doctype_class = {
|
||||
"User": "community.overrides.user.CustomUser"
|
||||
"User": "community.overrides.user.CustomUser",
|
||||
"Web Template": "community.overrides.web_template.CustomWebTemplate"
|
||||
}
|
||||
|
||||
# Document Events
|
||||
@@ -130,69 +131,31 @@ fixtures = ["Custom Field"]
|
||||
# auto_cancel_exempted_doctypes = ["Auto Repeat"]
|
||||
|
||||
# Add all simple route rules here
|
||||
primary_rules = [
|
||||
{"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"},
|
||||
{"from_route": "/courses/<course>", "to_route": "courses/course"},
|
||||
{"from_route": "/courses/<course>/<topic>", "to_route": "courses/topic"},
|
||||
{"from_route": "/hackathons/<hackathon>", "to_route": "hackathons/hackathon"},
|
||||
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
|
||||
{"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>/learn", "to_route": "batch/learn"},
|
||||
{"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"},
|
||||
{"from_route": "/courses/<course>/schedule", "to_route": "batch/schedule"},
|
||||
{"from_route": "/courses/<course>/members", "to_route": "batch/members"},
|
||||
{"from_route": "/courses/<course>/discuss", "to_route": "batch/discuss"},
|
||||
{"from_route": "/courses/<course>/about", "to_route": "batch/about"},
|
||||
{"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
|
||||
{"from_route": "/courses/<course>/join", "to_route": "batch/join"},
|
||||
{"from_route": "/discussions/<discussion>", "to_route": "discussions/discussion"},
|
||||
website_route_rules = [
|
||||
{"from_route": "/sketches/<sketch>", "to_route": "sketches/sketch"},
|
||||
{"from_route": "/courses/<course>", "to_route": "courses/course"},
|
||||
{"from_route": "/courses/<course>/<certificate>", "to_route": "courses/certificate"},
|
||||
{"from_route": "/hackathons/<hackathon>", "to_route": "hackathons/hackathon"},
|
||||
{"from_route": "/hackathons/<hackathon>/<project>", "to_route": "hackathons/project"},
|
||||
{"from_route": "/courses/<course>/learn", "to_route": "batch/learn"},
|
||||
{"from_route": "/courses/<course>/learn/<int:chapter>.<int:lesson>", "to_route": "batch/learn"},
|
||||
{"from_route": "/courses/<course>/progress", "to_route": "batch/progress"},
|
||||
{"from_route": "/courses/<course>/join", "to_route": "batch/join"},
|
||||
]
|
||||
|
||||
# Any frappe default URL is blocked by profile-rules, add it here to unblock it
|
||||
whitelist = [
|
||||
"/home",
|
||||
"/login",
|
||||
"/update-password",
|
||||
"/update-profile",
|
||||
"/third-party-apps",
|
||||
"/website_script.js",
|
||||
"/courses",
|
||||
"/sketches",
|
||||
"/admin",
|
||||
"/socket.io",
|
||||
"/hackathons",
|
||||
"/dashboard",
|
||||
"/join-request",
|
||||
"/add-a-new-batch",
|
||||
"/new-sign-up",
|
||||
"/message",
|
||||
"/about",
|
||||
"/edit-profile",
|
||||
"/attendee-registration",
|
||||
"/speaker-registration",
|
||||
"/event",
|
||||
"/hello",
|
||||
"/exhibitor-registration",
|
||||
"/discussions",
|
||||
"/propose-talk",
|
||||
|
||||
]
|
||||
whitelist_rules = [{"from_route": p, "to_route": p[1:]} for p in whitelist]
|
||||
|
||||
# regex rule to match all profiles
|
||||
profile_rules = [
|
||||
{"from_route": "/<string(minlength=4):username>", "to_route": "profiles/profile"},
|
||||
]
|
||||
|
||||
website_route_rules = primary_rules + whitelist_rules + profile_rules
|
||||
|
||||
website_redirects = [
|
||||
{"source": "/update-profile", "target": "/edit-profile"},
|
||||
{"source": "/update-profile", "target": "/edit-profile"},
|
||||
]
|
||||
|
||||
update_website_context = 'community.widgets.update_website_context'
|
||||
|
||||
update_website_context = [
|
||||
'community.widgets.update_website_context',
|
||||
]
|
||||
jinja = {
|
||||
"methods": [
|
||||
"community.page_renderers.get_profile_url"
|
||||
],
|
||||
"filters": []
|
||||
}
|
||||
## Specify the additional tabs to be included in the user profile page.
|
||||
## Each entry must be a subclass of community.community.plugins.ProfileTab
|
||||
# profile_tabs = []
|
||||
@@ -211,4 +174,14 @@ community_markdown_macro_renderers = {
|
||||
"Exercise": "community.plugins.exercise_renderer",
|
||||
"Quiz": "community.plugins.quiz_renderer",
|
||||
"YouTubeVideo": "community.plugins.youtube_video_renderer",
|
||||
"Video": "community.plugins.video_renderer"
|
||||
}
|
||||
|
||||
# page_renderer to manage profile pages
|
||||
page_renderer = [
|
||||
"community.page_renderers.ProfileRedirectPage",
|
||||
"community.page_renderers.ProfilePage"
|
||||
]
|
||||
|
||||
# set this to "/" to have profiles on the top-level
|
||||
profile_url_prefix = "/users/"
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
"field_order": [
|
||||
"course",
|
||||
"title",
|
||||
"column_break_3",
|
||||
"description",
|
||||
"section_break_5",
|
||||
"lessons"
|
||||
],
|
||||
"fields": [
|
||||
@@ -16,11 +18,12 @@
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title"
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
@@ -28,13 +31,22 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "LMS Course"
|
||||
"options": "LMS Course",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "lessons",
|
||||
"fieldtype": "Table",
|
||||
"label": "Lessons",
|
||||
"options": "Lessons"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
@@ -45,7 +57,7 @@
|
||||
"link_fieldname": "chapter"
|
||||
}
|
||||
],
|
||||
"modified": "2021-07-27 16:28:08.667964",
|
||||
"modified": "2021-08-31 10:43:45.866864",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Chapter",
|
||||
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from ..lesson.lesson import update_progress
|
||||
|
||||
class ExerciseSubmission(Document):
|
||||
|
||||
def after_insert(self):
|
||||
course_details = frappe.get_doc("LMS Course", self.course)
|
||||
if not (course_details.is_mentor(frappe.session.user) or frappe.flags.in_test):
|
||||
update_progress(self.lesson)
|
||||
pass
|
||||
|
||||
@@ -17,6 +17,15 @@ frappe.ui.form.on('Lesson', {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4">
|
||||
Video
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{{ Video("url_of_source") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4">
|
||||
YouTube Video
|
||||
|
||||
@@ -22,18 +22,21 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Chapter",
|
||||
"options": "Chapter"
|
||||
"options": "Chapter",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title"
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "body",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Body"
|
||||
"label": "Body",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "index_label",
|
||||
@@ -67,7 +70,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-27 16:28:29.203624",
|
||||
"modified": "2021-08-31 10:44:14.168257",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Lesson",
|
||||
|
||||
@@ -8,7 +8,7 @@ from frappe.model.document import Document
|
||||
from ...md import markdown_to_html, find_macros
|
||||
|
||||
class Lesson(Document):
|
||||
def before_save(self):
|
||||
def on_update(self):
|
||||
dynamic_documents = ["Exercise", "Quiz"]
|
||||
for section in dynamic_documents:
|
||||
self.update_lesson_name_in_document(section)
|
||||
@@ -93,41 +93,3 @@ def save_progress(lesson, course, status):
|
||||
}).save(ignore_permissions=True)
|
||||
course_details = frappe.get_doc("LMS Course", course)
|
||||
return course_details.get_course_progress()
|
||||
|
||||
def update_progress(lesson):
|
||||
user = frappe.session.user
|
||||
if not all_dynamic_content_submitted(lesson, user):
|
||||
return
|
||||
if frappe.db.exists("LMS Course Progress", {"lesson": lesson, "owner": user}):
|
||||
course_progress = frappe.get_doc("LMS Course Progress", {"lesson": lesson, "owner": user})
|
||||
course_progress.status = "Complete"
|
||||
course_progress.save(ignore_permissions=True)
|
||||
|
||||
def all_dynamic_content_submitted(lesson, user):
|
||||
all_exercises_submitted = check_all_exercise_submission(lesson, user)
|
||||
all_quiz_submitted = check_all_quiz_submitted(lesson, user)
|
||||
return all_exercises_submitted and all_quiz_submitted
|
||||
|
||||
def check_all_exercise_submission(lesson, user):
|
||||
exercise_names = frappe.get_list("Exercise", {"lesson": lesson}, pluck="name", ignore_permissions=True)
|
||||
if not len(exercise_names):
|
||||
return True
|
||||
query = {
|
||||
"exercise": ["in", exercise_names],
|
||||
"owner": user
|
||||
}
|
||||
if frappe.db.count("Exercise Submission", query) == len(exercise_names):
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_all_quiz_submitted(lesson, user):
|
||||
quizzes = frappe.get_list("LMS Quiz", {"lesson": lesson}, pluck="name", ignore_permissions=True)
|
||||
if not len(quizzes):
|
||||
return True
|
||||
query = {
|
||||
"quiz": ["in", quizzes],
|
||||
"owner": user
|
||||
}
|
||||
if frappe.db.count("LMS Quiz Submission", query) == len(quizzes):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Lesson",
|
||||
"options": "Lesson"
|
||||
"options": "Lesson",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-27 16:53:52.732191",
|
||||
"modified": "2021-08-31 10:44:42.048232",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Lessons",
|
||||
|
||||
@@ -35,14 +35,6 @@ class LMSBatch(Document):
|
||||
filters['member_type'] = member_type
|
||||
return frappe.db.exists("LMS Batch Membership", filters)
|
||||
|
||||
def get_messages(self):
|
||||
messages = frappe.get_all("LMS Message", {"batch": self.name}, ["*"], order_by="creation")
|
||||
for message in messages:
|
||||
message.message = frappe.utils.md_to_html(message.message)
|
||||
if message.author == frappe.session.user:
|
||||
message.author_name = "You"
|
||||
message.is_author = True
|
||||
return messages
|
||||
|
||||
def get_membership(self, email):
|
||||
"""Returns the membership document of given user.
|
||||
|
||||
@@ -29,13 +29,17 @@ def create_certificate(course):
|
||||
return certificate
|
||||
|
||||
else:
|
||||
expires_after_yrs = course_details.expiry
|
||||
expires_after_yrs = int(course_details.expiry)
|
||||
expiry_date = None
|
||||
if expires_after_yrs:
|
||||
expiry_date = add_years(nowdate(), expires_after_yrs)
|
||||
|
||||
certificate = frappe.get_doc({
|
||||
"doctype": "LMS Certification",
|
||||
"student": frappe.session.user,
|
||||
"course": course,
|
||||
"issue_date": nowdate(),
|
||||
"expiry_date": add_years(nowdate(), int(expires_after_yrs))
|
||||
"expiry_date": expiry_date
|
||||
})
|
||||
certificate.save(ignore_permissions=True)
|
||||
return certificate.name
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
frappe.ui.form.on('LMS Course', {
|
||||
|
||||
onload: function (frm) {
|
||||
|
||||
frm.set_query("chapter", "chapters", function () {
|
||||
return {
|
||||
filters: {
|
||||
@@ -11,6 +12,14 @@ frappe.ui.form.on('LMS Course', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("instructor", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
"ignore_user_type": 1,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -18,9 +18,13 @@
|
||||
"video_link",
|
||||
"image",
|
||||
"column_break_3",
|
||||
"instructor",
|
||||
"tags",
|
||||
"section_break_7",
|
||||
"is_published",
|
||||
"column_break_9",
|
||||
"upcoming",
|
||||
"column_break_11",
|
||||
"disable_self_learning",
|
||||
"section_break_5",
|
||||
"short_introduction",
|
||||
@@ -98,6 +102,27 @@
|
||||
"label": "Chapters",
|
||||
"options": "Chapters"
|
||||
},
|
||||
{
|
||||
"fieldname": "instructor",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Instructor",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Course Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "certification_section",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -137,7 +162,7 @@
|
||||
"link_fieldname": "course"
|
||||
}
|
||||
],
|
||||
"modified": "2021-08-18 18:02:12.623807",
|
||||
"modified": "2021-08-25 11:04:57.211898",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course",
|
||||
|
||||
@@ -144,6 +144,8 @@ class LMSCourse(Document):
|
||||
return batch_name and frappe.get_doc("LMS Batch", batch_name)
|
||||
|
||||
def get_instructor(self):
|
||||
if self.instructor:
|
||||
return frappe.get_doc("User", self.instructor)
|
||||
return frappe.get_doc("User", self.owner)
|
||||
|
||||
def get_chapters(self):
|
||||
|
||||
@@ -26,13 +26,14 @@
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Course",
|
||||
"options": "LMS Course"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-05 14:57:03.841430",
|
||||
"modified": "2021-08-31 12:37:23.832131",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course Review",
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
# Copyright (c) 2021, FOSS United and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from community.lms.doctype.lesson.lesson import update_progress
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
import json
|
||||
from frappe import _
|
||||
from ..lesson.lesson import update_progress
|
||||
|
||||
class LMSQuiz(Document):
|
||||
def validate(self):
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
{
|
||||
"fieldname": "quiz",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Quiz",
|
||||
"options": "LMS Quiz"
|
||||
},
|
||||
@@ -25,12 +27,13 @@
|
||||
{
|
||||
"fieldname": "score",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Score"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-07 14:19:54.958989",
|
||||
"modified": "2021-08-26 12:24:15.973829",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Quiz Submission",
|
||||
|
||||
@@ -20,6 +20,7 @@ import markdown
|
||||
from markdown import Extension
|
||||
from markdown.inlinepatterns import InlineProcessor
|
||||
import xml.etree.ElementTree as etree
|
||||
import html as HTML
|
||||
|
||||
def markdown_to_html(text):
|
||||
"""Renders markdown text into html.
|
||||
@@ -109,4 +110,5 @@ def sanitize_html(html, macro):
|
||||
classname = ""
|
||||
if macro == "YouTubeVideo":
|
||||
classname = "lesson-video"
|
||||
|
||||
return "<div class='" + classname + "'>" + "\n".join(str(node) for node in nodes) + "</div>"
|
||||
|
||||
@@ -8,7 +8,7 @@ frappe.ready(function () {
|
||||
|
||||
frappe.web_form.after_save = () => {
|
||||
setTimeout(() => {
|
||||
window.location.href = `/user/${frappe.web_form.get_value(["username"])}`;
|
||||
window.location.href = `/profile_/${frappe.web_form.get_value(["username"])}`;
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
0
community/lms/web_template/__init__.py
Normal file
0
community/lms/web_template/__init__.py
Normal file
0
community/lms/web_template/course_cards/__init__.py
Normal file
0
community/lms/web_template/course_cards/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
<div>
|
||||
<h2 class="section-title">{{ title }}</h2>
|
||||
<div class="cards-parent mt-10">
|
||||
{% for course_row in courses %}
|
||||
{% set course = frappe.get_doc("LMS Course", course_row.course) %}
|
||||
{{ widgets.CourseCard(course=course) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
39
community/lms/web_template/course_cards/course_cards.json
Normal file
39
community/lms/web_template/course_cards/course_cards.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"__unsaved": 1,
|
||||
"creation": "2021-08-26 10:28:55.796139",
|
||||
"docstatus": 0,
|
||||
"doctype": "Web Template",
|
||||
"fields": [
|
||||
{
|
||||
"__unsaved": 1,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"__unsaved": 1,
|
||||
"fieldname": "courses",
|
||||
"fieldtype": "Table Break",
|
||||
"label": "Courses",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"__unsaved": 1,
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"label": "Course",
|
||||
"options": "LMS Course",
|
||||
"reqd": 0
|
||||
}
|
||||
],
|
||||
"idx": 0,
|
||||
"modified": "2021-08-26 10:35:35.903834",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Course Cards",
|
||||
"owner": "Administrator",
|
||||
"standard": 1,
|
||||
"template": "",
|
||||
"type": "Section"
|
||||
}
|
||||
@@ -14,15 +14,15 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
<div class="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 %}
|
||||
<a class="lesson-links"
|
||||
href="{{ course.get_learn_url(lesson.number) }}{{course.query_parameter}}"
|
||||
{% if membership or lesson.include_in_preview or is_instructor %}
|
||||
<a class="lesson-links" href="{{ course.get_learn_url(lesson.number) }}{{course.query_parameter}}"
|
||||
data-course="{{ course.name }}">
|
||||
{{ lesson.title }}
|
||||
|
||||
@@ -56,13 +56,19 @@
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
expand_the_active_chapter();
|
||||
|
||||
$(".chapter-title").unbind().click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
var expand_the_first_chapter = () => {
|
||||
var elements = $(".collapse");
|
||||
var elements = $(".course-outline .collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i <= 1) {
|
||||
if (i < 1) {
|
||||
show_section(element);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -97,4 +103,12 @@
|
||||
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
|
||||
}
|
||||
|
||||
var 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");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<a class="stretched-link" href="{{ course.get_learn_url(lesson_index) }}{{ query_parameter }}"></a>
|
||||
|
||||
{% else %}
|
||||
<div class="view-course-link">
|
||||
<div class="view-course-link is-default">
|
||||
View Course <img class="ml-3" src="/assets/community/icons/black-arrow.svg" />
|
||||
</div>
|
||||
<a class="stretched-link" href="/courses/{{ course.name }}"></a>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="course-home-headings">
|
||||
Course Outline
|
||||
</div>
|
||||
<div class="coure-outline">
|
||||
<div class="common-card-style course-outline">
|
||||
{% for chapter in course.get_chapters() %}
|
||||
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, membership=membership) }}
|
||||
{% endfor %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{% endif %}
|
||||
<div class="course-author">
|
||||
{% with author = course.get_instructor() %}
|
||||
{{ widgets.Avatar(member=author, avatar_class="avatar-medium") }} <a href="/user/{{author.username}}">{{ author.full_name }}</a>
|
||||
{{ widgets.Avatar(member=author, avatar_class="avatar-medium") }} <a href="{{get_profile_url(author.username)}}">{{ author.full_name }}</a>
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="instructor">
|
||||
{{ widgets.Avatar(member=instructor, avatar_class="avatar-medium") }}
|
||||
<a class="ml-1 instructor-title" href="/user/{{instructor.username}}">{{ instructor.full_name }}</a>
|
||||
<a class="ml-1 instructor-title" href="{{get_profile_url(instructor.username)}}">{{ instructor.full_name }}</a>
|
||||
<div class="instructor-subtitle">Course Creator</div>
|
||||
<!-- <div class="instructor-subtitle">Created {{instructor.get_course_count()}} courses</div> -->
|
||||
</div>
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
Created {{ course_count }} {{ suffix }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<a class="stretched-link" href="/user/{{ member.username }}"></a>
|
||||
<a class="stretched-link" href="{{ get_profile_url(member.username) }}"></a>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{% if not course.upcoming %}
|
||||
<div class="reviews-parent">
|
||||
{% set reviews = course.get_reviews() %}
|
||||
{% if reviews | length or course.is_eligible_to_review(membership) %}
|
||||
@@ -18,7 +19,7 @@
|
||||
<div class="review-card-footer">
|
||||
<div>
|
||||
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
|
||||
<a class="button-links" href="/user/{{review.owner_details.username}}">
|
||||
<a class="button-links" href="{{get_profile_url(review.owner_details.username) }}">
|
||||
<span class="course-instructor">
|
||||
{{ review.owner_details.full_name }}
|
||||
</span>
|
||||
@@ -35,6 +36,10 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="common-card-style thread-card">
|
||||
<span class="text-center"> No Reviews <img src="/assets/community/icons/slash.svg"></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -89,3 +94,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -49,6 +49,15 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "LMS Course Interest",
|
||||
"link_to": "LMS Course Interest",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@@ -121,7 +130,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-06-29 15:11:07.324651",
|
||||
"modified": "2021-08-31 10:33:39.838535",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS",
|
||||
|
||||
@@ -58,7 +58,7 @@ class CustomUser(User):
|
||||
"""
|
||||
return frappe.get_all(
|
||||
'LMS Course', {
|
||||
'owner': self.name,
|
||||
'instructor': self.name,
|
||||
'is_published': True
|
||||
})
|
||||
|
||||
|
||||
15
community/overrides/web_template.py
Normal file
15
community/overrides/web_template.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import frappe
|
||||
from frappe.website.doctype.web_template.web_template import WebTemplate
|
||||
from community.widgets import Widgets
|
||||
import json
|
||||
|
||||
class CustomWebTemplate(WebTemplate):
|
||||
|
||||
def render(self, values=None):
|
||||
if not values:
|
||||
values = {}
|
||||
values = frappe.parse_json(values)
|
||||
values.update({"values": values})
|
||||
values.update({"widgets": Widgets()})
|
||||
template = self.get_template(self.standard)
|
||||
return frappe.render_template(template, values)
|
||||
105
community/page_renderers.py
Normal file
105
community/page_renderers.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""Custom page renderers for Community app.
|
||||
|
||||
Handles rendering of profile pages.
|
||||
"""
|
||||
import re
|
||||
import frappe
|
||||
from frappe.website.page_renderers.base_renderer import BaseRenderer
|
||||
from frappe.website.page_renderers.template_page import TemplatePage
|
||||
|
||||
from frappe.website.page_renderers.document_page import DocumentPage
|
||||
from frappe.website.page_renderers.list_page import ListPage
|
||||
from frappe.website.page_renderers.not_found_page import NotFoundPage
|
||||
from frappe.website.page_renderers.print_page import PrintPage
|
||||
from frappe.website.page_renderers.redirect_page import RedirectPage
|
||||
from frappe.website.page_renderers.static_page import StaticPage
|
||||
from frappe.website.page_renderers.template_page import TemplatePage
|
||||
from frappe.website.page_renderers.web_form import WebFormPage
|
||||
|
||||
|
||||
def get_profile_url(username):
|
||||
"""Returns the profile URL given username.
|
||||
|
||||
The default URL prefix for profiles is /users, but tha can be customized.
|
||||
|
||||
This functions looks at the current value from the config and generates
|
||||
the URL for the profile.
|
||||
"""
|
||||
return get_profile_url_prefix() + username
|
||||
|
||||
def get_profile_url_prefix():
|
||||
hooks = frappe.get_hooks("profile_url_prefix") or ["/users/"]
|
||||
return hooks[-1]
|
||||
|
||||
RE_USERNAME = re.compile("[a-zA-Z0-9_]{4,}")
|
||||
|
||||
class ProfileRedirectPage(BaseRenderer):
|
||||
"""Renderer to redirect /profile_/foo to <profile_prefix>/foo.
|
||||
|
||||
This is useful to redirect to profile pages from javascript as there is no
|
||||
easy to find the profile prefix.
|
||||
"""
|
||||
def can_render(self):
|
||||
return self.path.startswith("profile_/")
|
||||
|
||||
def render(self):
|
||||
username = self.path[len("profile_/"):]
|
||||
frappe.flags.redirect_location = get_profile_url_prefix() + username
|
||||
return RedirectPage(self.path).render()
|
||||
|
||||
class ProfilePage(BaseRenderer):
|
||||
def __init__(self, path, http_status_code):
|
||||
super().__init__(path, http_status_code)
|
||||
self.renderer = None
|
||||
|
||||
def can_render(self):
|
||||
if "." in self.path:
|
||||
return False
|
||||
|
||||
# has prefix and path starts with prefix?
|
||||
prefix = get_profile_url_prefix().lstrip("/")
|
||||
if prefix and not self.path.startswith(prefix):
|
||||
return False
|
||||
|
||||
# not a userpage?
|
||||
username = self.get_username()
|
||||
if not RE_USERNAME.match(username):
|
||||
return False
|
||||
|
||||
# if there is prefix then we can allow all usernames
|
||||
if prefix:
|
||||
return True
|
||||
|
||||
# if we are having top-level usernames, then give preference to
|
||||
# the existing website_route_rules, web pages, web forms etc.
|
||||
|
||||
# Don't handle any of the exsiting website_route_rules
|
||||
routes = [rule['to_route'] for rule in frappe.get_hooks("website_route_rules")]
|
||||
if self.path in routes:
|
||||
return False
|
||||
|
||||
# if any of the existing renders can render, let them do
|
||||
renderers = [StaticPage, WebFormPage, DocumentPage, TemplatePage, ListPage, PrintPage]
|
||||
for renderer in renderers:
|
||||
renderer_instance = renderer(self.path, 200)
|
||||
if renderer_instance.can_render():
|
||||
self.renderer = renderer_instance
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
def get_username(self):
|
||||
prefix = get_profile_url_prefix().lstrip("/")
|
||||
return self.path[len(prefix):]
|
||||
|
||||
def render(self):
|
||||
if self.renderer:
|
||||
return self.renderer.render()
|
||||
else:
|
||||
username = self.get_username()
|
||||
return render_portal_page("profiles/profile", username=username)
|
||||
|
||||
def render_portal_page(path, **kwargs):
|
||||
frappe.form_dict.update(kwargs)
|
||||
page = TemplatePage(path)
|
||||
return page.render()
|
||||
@@ -8,3 +8,6 @@ community.patches.replace_member_with_user_in_lms_message
|
||||
community.patches.replace_member_with_user_in_mentor_request
|
||||
community.patches.v0_0.chapter_lesson_index_table
|
||||
execute:frappe.delete_doc("DocType", "LMS Message")
|
||||
community.patches.v0_0.course_instructor_update
|
||||
execute:frappe.delete_doc("DocType", "Discussion Message")
|
||||
execute:frappe.delete_doc("DocType", "Discussion Thread")
|
||||
|
||||
7
community/patches/v0_0/course_instructor_update.py
Normal file
7
community/patches/v0_0/course_instructor_update.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("lms", "doctype", "lms_course")
|
||||
courses = frappe.get_all("LMS Course", fields=["name", "owner"])
|
||||
for course in courses:
|
||||
frappe.db.set_value("LMS Course", course.name, "instructor", course.owner)
|
||||
@@ -106,3 +106,6 @@ def youtube_video_renderer(video_id):
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
"""
|
||||
|
||||
def video_renderer(src):
|
||||
return "<video controls width='100%'><source src={0} type='video/mp4'></video>".format(src)
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
--received-message: var(--c8);
|
||||
--checkbox-size: 14px;
|
||||
--control-bg: var(--gray-100);
|
||||
--muted-text: #4C5A67;
|
||||
--button-background: #EEF0F2;
|
||||
--text-xs: 11px;
|
||||
--text-sm: 12px;
|
||||
@@ -32,107 +31,10 @@
|
||||
--text-3xl: 22px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.chapter-plan {
|
||||
border-radius: 10px;
|
||||
margin: 20px 0px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddc;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.chapter-plan h3 {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.batch {
|
||||
border-radius: 10px;
|
||||
margin: 10px 0px;
|
||||
background: white;
|
||||
box-shadow: 0px 5px 10px rgb(0 0 0 / 10%);
|
||||
border: 1px solid #ddc;
|
||||
}
|
||||
|
||||
.batch-details {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.batch .cta {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
min-height: 28px;
|
||||
text-align: right;
|
||||
border-top: 1px solid #ddc;
|
||||
}
|
||||
|
||||
.batch .right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
img.profile-photo {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.lesson-type {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 5rem 0 5rem 0;
|
||||
}
|
||||
|
||||
.batch-header {
|
||||
background: #eee;
|
||||
border: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.page-card {
|
||||
max-width: 360px;
|
||||
padding: 15px;
|
||||
margin: 70px auto;
|
||||
border: 1px solid #d1d8dd;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-card .page-card-head {
|
||||
padding: 10px 15px;
|
||||
margin: -15px;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
}
|
||||
|
||||
.page-card .page-card-head .indicator {
|
||||
color: #36414C;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.page-card .page-card-head .indicator::before {
|
||||
margin: 0 6px 0.5px 0px;
|
||||
}
|
||||
|
||||
.page-card .btn {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
appearance: auto;
|
||||
}
|
||||
|
||||
.progress-image {
|
||||
margin-right: 3px;
|
||||
border-radius: 50px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.course-image {
|
||||
height: 168px;
|
||||
width: 100%;
|
||||
@@ -162,8 +64,8 @@ input[type=checkbox] {
|
||||
.course-tags {
|
||||
display: flex;
|
||||
position: relative;
|
||||
top: 0.75rem;
|
||||
left: 0.75rem;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
}
|
||||
|
||||
.course-card-pills {
|
||||
@@ -184,8 +86,9 @@ input[type=checkbox] {
|
||||
|
||||
.common-page-style {
|
||||
background: #F4F5F6;
|
||||
padding-bottom: 2rem;
|
||||
padding-bottom: 5rem;
|
||||
min-height: 60vh;
|
||||
padding-top: 3rem;
|
||||
}
|
||||
|
||||
.common-card-style {
|
||||
@@ -194,7 +97,7 @@ input[type=checkbox] {
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
border: 1px solid #EEF0F2;
|
||||
box-shadow: 0 1px 4px 4px rgb(25, 39, 52, 0.02);
|
||||
box-shadow: 0 0px 4px 2px #19273405;
|
||||
}
|
||||
|
||||
.course-card {
|
||||
@@ -205,8 +108,6 @@ input[type=checkbox] {
|
||||
.muted-text {
|
||||
font-size: 12px;
|
||||
line-height: 135%;
|
||||
color: var(--muted-text);
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.course-card-meta {
|
||||
@@ -242,12 +143,12 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
border: 1px solid #F4F5F6;
|
||||
border: 1px solid #EEF0F2;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card-divider-dark {
|
||||
border: 1px solid #E2E6E9;
|
||||
border: 1px solid #C8CFD5;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -265,37 +166,24 @@ input[type=checkbox] {
|
||||
.course-student-count {
|
||||
font-size: 12px;
|
||||
line-height: 135%;
|
||||
color: var(--muted-text);
|
||||
float: right;
|
||||
}
|
||||
|
||||
.view-course-link {
|
||||
height: 32px;
|
||||
background: var(--button-background);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 8px 0px 8px;
|
||||
text-align: center;
|
||||
line-height: 135%;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.view-talk-link {
|
||||
background: var(--button-background);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
padding: 8px 22px 8px;
|
||||
text-align: center;
|
||||
line-height: 135%;
|
||||
color: var(--text-color);
|
||||
|
||||
}
|
||||
|
||||
.cards-parent {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
-moz-column-gap: 32px;
|
||||
column-gap: 32px;
|
||||
row-gap: 32px;
|
||||
-moz-column-gap: 40px;
|
||||
column-gap: 40px;
|
||||
row-gap: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -318,7 +206,8 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.courses-header {
|
||||
padding: 50px 20px 20px;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 0 1rem;
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
@@ -333,8 +222,13 @@ input[type=checkbox] {
|
||||
}
|
||||
}
|
||||
|
||||
.button-links {
|
||||
color: #4C5A67;
|
||||
}
|
||||
|
||||
.button-links:hover {
|
||||
text-decoration: none;
|
||||
color: #4C5A67;
|
||||
}
|
||||
|
||||
.icon-background {
|
||||
@@ -420,7 +314,7 @@ input[type=checkbox] {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.custom-checkbox>label>input:checked+.empty-checkbox {
|
||||
@@ -438,7 +332,7 @@ input[type=checkbox] {
|
||||
flex-direction: row;
|
||||
padding: 24px;
|
||||
background: #E2E6E9;
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@@ -463,7 +357,7 @@ input[type=checkbox] {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 32px;
|
||||
border-radius: 5px;
|
||||
border-radius: 8px;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
}
|
||||
@@ -545,7 +439,7 @@ input[type=checkbox] {
|
||||
|
||||
.button {
|
||||
box-shadow: var(--btn-shadow);
|
||||
border-radius: 6px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -586,8 +480,8 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.is-default {
|
||||
background: #98A1A9;
|
||||
color: #ffffff;
|
||||
background: #F4F5F6;
|
||||
color: #1F272E;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@@ -630,6 +524,10 @@ input[type=checkbox] {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.course-content-parent .chapter-description {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.chapter-icon {
|
||||
margin-right: .25rem;
|
||||
}
|
||||
@@ -732,9 +630,8 @@ input[type=checkbox] {
|
||||
margin-left: .875rem;
|
||||
}
|
||||
|
||||
.coure-outline {
|
||||
background: #FFFFFF;
|
||||
border-radius: 12px;
|
||||
.course-outline {
|
||||
flex-direction: column;
|
||||
padding: 16px 12px 16px;
|
||||
}
|
||||
|
||||
@@ -759,7 +656,7 @@ input[type=checkbox] {
|
||||
padding: 20px 0px 16px;
|
||||
}
|
||||
|
||||
.member-card .talk-title{
|
||||
.member-card .talk-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -788,7 +685,6 @@ input[type=checkbox] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.member-card-xl .member-card-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -833,6 +729,7 @@ input[type=checkbox] {
|
||||
letter-spacing: -0.0175em;
|
||||
color: #192734;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.course-detail-headings {
|
||||
@@ -843,7 +740,7 @@ input[type=checkbox] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.avatar-medium-schedule{
|
||||
.avatar-medium-schedule {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
}
|
||||
@@ -877,6 +774,14 @@ input[type=checkbox] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.description-card p {
|
||||
line-height: 1.72;
|
||||
}
|
||||
|
||||
.description-card p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.overview-card {
|
||||
padding: 1.5rem;
|
||||
width: 256px;
|
||||
@@ -992,7 +897,7 @@ input[type=checkbox] {
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
padding: 1rem 0 0;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
@@ -1048,7 +953,7 @@ input[type=checkbox] {
|
||||
|
||||
.active-lesson {
|
||||
background-color: #EBF5FF;
|
||||
border-radius: 4px;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.lesson-progress {
|
||||
@@ -1057,7 +962,7 @@ input[type=checkbox] {
|
||||
font-size: 10px;
|
||||
line-height: 120%;
|
||||
margin: 0px 10px 20px;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -1067,7 +972,7 @@ input[type=checkbox] {
|
||||
|
||||
.profile-banner {
|
||||
height: 248px;
|
||||
border-radius: 12px 12px 0px 0px;
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
@@ -1086,7 +991,7 @@ input[type=checkbox] {
|
||||
.profile-info {
|
||||
height: 68px;
|
||||
background: #ffffff;
|
||||
border-radius: 0px 0px 12px 12px;
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
@@ -1295,10 +1200,6 @@ pre {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a.talk-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.speaker-cards-parent {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
@@ -1333,13 +1234,12 @@ a.talk-link {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.schedule-info{
|
||||
.schedule-info {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
||||
.drop-down-icon {
|
||||
padding : 5px 0 0 5px;
|
||||
padding: 5px 0 0 5px;
|
||||
}
|
||||
|
||||
.event-btn {
|
||||
@@ -1354,12 +1254,12 @@ a.talk-link {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.exhibitor-card .company-name{
|
||||
.exhibitor-card .company-name {
|
||||
font-size: 25px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.exhibitor-card .company-logo{
|
||||
.exhibitor-card .company-logo {
|
||||
height: 158px;
|
||||
width: 252px;
|
||||
object-fit: contain;
|
||||
@@ -1376,23 +1276,83 @@ a.talk-link {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.info-avatar img{
|
||||
.info-avatar img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.thread-card {
|
||||
flex-direction: column;
|
||||
padding: 1.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
textarea.form-control {
|
||||
.discussions-parent .form-control {
|
||||
background-color: #FFFFFF;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.discussion-on-page .comment-field {
|
||||
height: 48px;
|
||||
box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.modal .comment-field {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.discussion-on-page textarea.form-control {
|
||||
background-color: #FFFFFF;
|
||||
color: inherit;
|
||||
height: 160px;
|
||||
.no-discussions {
|
||||
width: 250px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-discussions .button {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.discussions-header {
|
||||
margin: 2.5rem 0 1.25rem;
|
||||
}
|
||||
|
||||
.discussions-header .button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.discussions-parent .search-field {
|
||||
background-color: #E2E6E9;
|
||||
background-image: url(/assets/community/icons/search.svg);
|
||||
background-repeat: no-repeat;
|
||||
text-indent: 1.5rem;
|
||||
background-position: 1rem 0.7rem;
|
||||
height: 36px;
|
||||
font-size: 12px;
|
||||
padding: 0.65rem 0.9rem;
|
||||
}
|
||||
|
||||
.discussions-sidebar {
|
||||
background-color: #F4F5F6;
|
||||
padding: 0.75rem;
|
||||
border-radius: 4px;
|
||||
max-height: 700px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#discussion-group {
|
||||
max-height: 700px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar-topic {
|
||||
padding: 0.75rem;
|
||||
margin: 0.75rem 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar-topic[aria-expanded="true"] {
|
||||
background: #FFFFFF;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.comment-footer {
|
||||
@@ -1400,52 +1360,87 @@ textarea.form-control {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.comment-field {
|
||||
font-size: inherit;
|
||||
.reply-card {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.thread-title {
|
||||
font-size: inherit;
|
||||
.discussions-parent .collapsing {
|
||||
transition: height 0s;
|
||||
}
|
||||
|
||||
.message-author {
|
||||
color: #192734;
|
||||
margin-left: 0.5rem;
|
||||
.discussion-topic-title {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.discussion-on-page .topic-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.discussions-sidebar .sidebar-parent:last-child .card-divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.certificate-page .common-card-style {
|
||||
flex-direction: column;
|
||||
font-family: Inter;
|
||||
color: black;
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
padding: 5rem;
|
||||
background-image: url(/assets/community/images/certificate-background.png);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.certificate-content {
|
||||
padding: 5rem 9rem;
|
||||
}
|
||||
|
||||
.certificate-footer {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.certificate-ribbon {
|
||||
background-color: var(--primary-color);
|
||||
margin-right: 3rem;
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.certificate-heading {
|
||||
font-size: 4rem;
|
||||
font-size: 3rem;
|
||||
margin-bottom: 3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.certificate-para {
|
||||
margin-bottom: 3rem;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.certificate-logo {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.certificate-content {
|
||||
padding: 3rem 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.certificate-page .common-card-style {
|
||||
padding: 2rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.certificate-heading {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.certificate-heading {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.certificate-page .common-card-style {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.certificate-content {
|
||||
padding: 3rem;
|
||||
}
|
||||
.certificate-ribbon {
|
||||
background-color: var(--primary-color);
|
||||
margin-right: 2rem;
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 375px) {
|
||||
.certificate-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.00001 3.00001H12L15.5 3C16.6046 3 17.5 3.89543 17.5 5V9.78889V12.2556C17.5 13.3601 16.6046 14.2556 15.5 14.2556H14.5C14.2239 14.2556 14 14.4794 14 14.7556V16.4507C14 16.8715 13.5119 17.1041 13.185 16.839L10.2754 14.4789C10.0972 14.3344 9.87483 14.2556 9.64544 14.2556H4.49997C3.39539 14.2556 2.49995 13.3601 2.49997 12.2555L2.50001 9.78889V5.00001C2.50001 3.89544 3.39544 3.00001 4.50001 3.00001L6.00001 3.00001Z" stroke="#1F272E" stroke-miterlimit="10" stroke-linecap="square"/>
|
||||
<path d="M6 6.5H13" stroke="#1F272E" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M6 9H10" stroke="#1F272E" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.66659 9.77761C2.66659 10.0919 2.79145 10.3934 3.01372 10.6157C3.23598 10.8379 3.53744 10.9628 3.85177 10.9628H10.9629L13.3333 13.3332V3.85169C13.3333 3.53736 13.2084 3.2359 12.9861 3.01364C12.7639 2.79137 12.4624 2.6665 12.1481 2.6665H3.85177C3.53744 2.6665 3.23598 2.79137 3.01372 3.01364C2.79145 3.2359 2.66659 3.53736 2.66659 3.85169V9.77761Z" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 763 B After Width: | Height: | Size: 528 B |
3
community/public/icons/search.svg
Normal file
3
community/public/icons/search.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 7.44462C3.5 5.26607 5.26607 3.5 7.44462 3.5C9.62318 3.5 11.3892 5.26607 11.3892 7.44462C11.3892 8.50829 10.9683 9.47362 10.2838 10.1831C10.265 10.1972 10.247 10.2128 10.2299 10.2299C10.2128 10.247 10.1972 10.265 10.1831 10.2838C9.47362 10.9683 8.50829 11.3892 7.44462 11.3892C5.26607 11.3892 3.5 9.62318 3.5 7.44462ZM10.5696 11.2767C9.71788 11.9722 8.62996 12.3892 7.44462 12.3892C4.71378 12.3892 2.5 10.1755 2.5 7.44462C2.5 4.71378 4.71378 2.5 7.44462 2.5C10.1755 2.5 12.3892 4.71378 12.3892 7.44462C12.3892 8.62996 11.9722 9.71788 11.2767 10.5696L13.3538 12.6467C13.549 12.8419 13.549 13.1585 13.3538 13.3538C13.1585 13.549 12.8419 13.549 12.6467 13.3538L10.5696 11.2767Z" fill="#4C5A67"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 849 B |
4
community/public/icons/slash.svg
Normal file
4
community/public/icons/slash.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22C17.5228 22 22 17.5229 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5229 6.47715 22 12 22Z" stroke="#4C5A67" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 4.93005L19.14 19.0701" stroke="#4C5A67" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 428 B |
3
community/public/icons/small-add-black.svg
Normal file
3
community/public/icons/small-add-black.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.5005 4.3335C8.5005 4.05735 8.27665 3.8335 8.0005 3.8335C7.72436 3.8335 7.5005 4.05735 7.5005 4.3335V7.50062H4.33337C4.05723 7.50062 3.83337 7.72447 3.83337 8.00062C3.83337 8.27676 4.05723 8.50062 4.33337 8.50062H7.5005V11.6677C7.5005 11.9439 7.72436 12.1677 8.0005 12.1677C8.27665 12.1677 8.5005 11.9439 8.5005 11.6677V8.50062H11.6676C11.9438 8.50062 12.1676 8.27676 12.1676 8.00062C12.1676 7.72447 11.9438 7.50062 11.6676 7.50062H8.5005V4.3335Z" fill="#4C5A67"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 619 B |
@@ -1,32 +1,37 @@
|
||||
<div class="common-card-style">
|
||||
<div class="certificate-heading">
|
||||
Certificate of Completion
|
||||
</div>
|
||||
<div class="certificate-para">
|
||||
This is to certify that <span class="font-weight-bold">{{ student.full_name }}</span> has successfully completed
|
||||
<span class="font-weight-bold">{{ course.title }}</span> online course on
|
||||
<span class="font-weight-bold">{{ frappe.utils.format_date(certificate.issue_date, "medium") }}</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;" class="certificate-footer">
|
||||
<div>
|
||||
<div class="font-weight-bold">
|
||||
Instructor:
|
||||
</div>
|
||||
<div>
|
||||
{{ instructor.full_name }}
|
||||
</div>
|
||||
<div class="certificate-content">
|
||||
<div class="certificate-heading">
|
||||
Certificate of Completion
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-weight-bold">
|
||||
Expiry Date:
|
||||
</div>
|
||||
<div>
|
||||
{{ frappe.utils.format_date(certificate.expiry_date, "medium") }}
|
||||
</div>
|
||||
<div class="certificate-para">
|
||||
This is to certify that <span class="font-weight-bold">{{ student.full_name }}</span> has successfully completed
|
||||
<span class="font-weight-bold">{{ course.title }}</span> online course on
|
||||
<span class="font-weight-bold">{{ frappe.utils.format_date(certificate.issue_date, "medium") }}</span>
|
||||
</div>
|
||||
<div class="certificate-footer">
|
||||
{% if instructor %}
|
||||
<div>
|
||||
<span>
|
||||
Instructor:
|
||||
</span>
|
||||
<span class="font-weight-bold">
|
||||
{{ instructor.full_name }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if certificate.expiry_date %}
|
||||
<div>
|
||||
<span>
|
||||
Expiry Date:
|
||||
</span>
|
||||
<span class="font-weight-bold">
|
||||
{{ frappe.utils.format_date(certificate.expiry_date, "medium") }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<img src="{{ logo }}" class="certificate-logo">
|
||||
</div>
|
||||
<div>
|
||||
<img src="{{ logo }}" style="height: 50px;">
|
||||
</div>
|
||||
<div class="certificate-ribbon"></div>
|
||||
</div>
|
||||
<script src="/assets/community/js/html2canvas.js"></script>
|
||||
|
||||
7
community/templates/discussions/button.html
Normal file
7
community/templates/discussions/button.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% if frappe.session.user != "Guest" and
|
||||
(condition is not defined or (condition is defined and condition )) %}
|
||||
<span class="button is-secondary reply">
|
||||
New Discussion
|
||||
<img src="/assets/community/icons/small-add-black.svg">
|
||||
</span>
|
||||
{% endif %}
|
||||
198
community/templates/discussions/discussions.js
Normal file
198
community/templates/discussions/discussions.js
Normal file
@@ -0,0 +1,198 @@
|
||||
frappe.ready(() => {
|
||||
setup_socket_io();
|
||||
|
||||
set_docname_if_missing();
|
||||
|
||||
expand_first_discussion();
|
||||
|
||||
$(".search-field").keyup((e) => {
|
||||
search_topic(e);
|
||||
});
|
||||
|
||||
$(".reply").click((e) => {
|
||||
show_new_topic_modal(e);
|
||||
});
|
||||
|
||||
$("#login-from-discussion").click((e) => {
|
||||
login_from_discussion(e);
|
||||
});
|
||||
|
||||
$(".sidebar-topic").click((e) => {
|
||||
if ($(e.currentTarget).attr("aria-expanded") == "true") {
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", ".submit-discussion", (e) => {
|
||||
submit_discussion(e);
|
||||
});
|
||||
})
|
||||
|
||||
var show_new_topic_modal = (e) => {
|
||||
e.preventDefault();
|
||||
$("#discussion-modal").modal("show");
|
||||
var topic = $(e.currentTarget).attr("data-topic");
|
||||
$(".modal-headings").text(topic ? "Reply" : "Start a Discussion");
|
||||
topic ? $(".topic-title").addClass("hide") : $(".topic-title").removeClass("hide");
|
||||
$("#submit-discussion").attr("data-topic", topic ? topic : "");
|
||||
}
|
||||
|
||||
var setup_socket_io = () => {
|
||||
const assets = [
|
||||
"/assets/frappe/js/lib/socket.io.min.js",
|
||||
"/assets/frappe/js/frappe/socketio_client.js",
|
||||
]
|
||||
|
||||
frappe.require(assets, () => {
|
||||
if (window.dev_server) {
|
||||
frappe.boot.socketio_port = "9000";
|
||||
}
|
||||
frappe.socketio.init(9000);
|
||||
frappe.socketio.socket.on("publish_message", (data) => {
|
||||
publish_message(data);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var publish_message = (data) => {
|
||||
post_message_cleanup();
|
||||
|
||||
if ($(`.discussion-on-page[data-topic=${data.topic_info.name}]`).length) {
|
||||
if ($(`.discussion-on-page[data-topic=${data.topic_info.name}] .card-divider-dark`).length) {
|
||||
$(data.template).insertAfter(`.discussion-on-page[data-topic=${data.topic_info.name}] .card-divider-dark`);
|
||||
}
|
||||
else {
|
||||
|
||||
$('<div class="card-divider-dark mb-10"></div>' + data.template).insertAfter(`.discussion-on-page[data-topic=${data.topic_info.name}] .discussion-form`);
|
||||
}
|
||||
}
|
||||
else if ((decodeURIComponent($(".discussions-parent .course-content-parent").attr("data-doctype")) == data.topic_info.reference_doctype
|
||||
&& decodeURIComponent($(".discussions-parent .course-content-parent").attr("data-docname")) == data.topic_info.reference_docname)) {
|
||||
$(data.sidebar).insertAfter(`.discussions-sidebar .form-group`);
|
||||
$(`#discussion-group`).prepend(data.new_topic_template);
|
||||
|
||||
if (data.topic_info.owner == frappe.session.user) {
|
||||
$(".discussion-on-page").collapse();
|
||||
$(".sidebar-topic").first().click();
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
update_reply_count(data.topic_info.name);
|
||||
}
|
||||
|
||||
var post_message_cleanup = () => {
|
||||
$(".comment-field").val("");
|
||||
$("#discussion-modal").modal("hide");
|
||||
$("#no-discussions").addClass("hide");
|
||||
}
|
||||
|
||||
var update_reply_count = (topic) => {
|
||||
var reply_count = $(`[data-target='#t${topic}']`).find(".reply-count").text();
|
||||
reply_count = parseInt(reply_count) + 1;
|
||||
$(`[data-target='#t${topic}']`).find(".reply-count").text(reply_count);
|
||||
}
|
||||
|
||||
var set_docname_if_missing = () => {
|
||||
if ($("[data-docname='None']").length) {
|
||||
frappe.call({
|
||||
method: "community.community.doctype.discussion_topic.discussion_topic.get_docname",
|
||||
args: {
|
||||
"route": window.location.href.split("/").slice(-1)[0]
|
||||
},
|
||||
callback: (data) => {
|
||||
$("[data-docname='None']").attr("data-docname", data.message);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var expand_first_discussion = () => {
|
||||
$($(".discussions-parent .collapse")[0]).addClass("show");
|
||||
$($(".discussions-sidebar [data-toggle='collapse']")[0]).attr("aria-expanded", true);
|
||||
}
|
||||
|
||||
var search_topic = (e) => {
|
||||
var input = $(e.currentTarget).val();
|
||||
|
||||
var topics = $(".discussions-parent .discussion-topic-title");
|
||||
if (input.length < 3 || input.trim() == "") {
|
||||
topics.closest(".sidebar-parent").removeClass("hide");
|
||||
return
|
||||
}
|
||||
|
||||
topics.each((i, elem) => {
|
||||
var topic_id = $(elem).parent().attr("data-target");
|
||||
|
||||
/* Check match in replies */
|
||||
var match_in_reply = false;
|
||||
var replies = $(`${topic_id}`);
|
||||
for (var reply of replies.find(".reply-text")) {
|
||||
if (has_common_substring($(reply).text(), input)) {
|
||||
match_in_reply = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Match found in title or replies, then show */
|
||||
if (has_common_substring($(elem).text(), input) || match_in_reply) {
|
||||
$(elem).closest(".sidebar-parent").removeClass("hide")
|
||||
}
|
||||
else {
|
||||
$(elem).closest(".sidebar-parent").addClass("hide");
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
var has_common_substring = (str1, str2) => {
|
||||
var str1_arr = str1.toLowerCase().split(" ");
|
||||
var str2_arr = str2.toLowerCase().split(" ");
|
||||
|
||||
var substring_found = false;
|
||||
for (var first_word of str1_arr) {
|
||||
for (var second_word of str2_arr) {
|
||||
if (first_word.indexOf(second_word) > -1) {
|
||||
substring_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return substring_found;
|
||||
}
|
||||
|
||||
var submit_discussion = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
var title = $(".topic-title:visible").length ? $(".topic-title:visible").val().trim() : "";
|
||||
var reply = $(".comment-field:visible").val().trim();
|
||||
|
||||
if (reply) {
|
||||
var doctype = $(e.currentTarget).attr("data-doctype");
|
||||
doctype = doctype ? decodeURIComponent(doctype) : doctype;
|
||||
|
||||
var docname = $(e.currentTarget).attr("data-docname");
|
||||
docname = docname ? decodeURIComponent(docname) : docname;
|
||||
|
||||
frappe.call({
|
||||
method: "community.community.doctype.discussion_topic.discussion_topic.submit_discussion",
|
||||
args: {
|
||||
"doctype": doctype ? doctype : "",
|
||||
"docname": docname ? docname : "",
|
||||
"reply": reply,
|
||||
"title": title,
|
||||
"topic_name": $(e.currentTarget).closest(".discussion-on-page").attr("data-topic")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var login_from_discussion = (e) => {
|
||||
var redirect = $(e.currentTarget).attr("data-redirect") || window.location.href;
|
||||
window.location.href = `/login?redirect-to=${redirect}`;
|
||||
}
|
||||
13
community/templates/discussions/reply_card.html
Normal file
13
community/templates/discussions/reply_card.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="reply-card">
|
||||
{% set member = frappe.get_doc("User", reply.owner) %}
|
||||
<div class="d-flex align-items-center muted-text">
|
||||
{% set member = frappe.get_doc("User", reply.owner) %}
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-small")}}
|
||||
<a class="button-links ml-2" href="{{ get_profile_url(member.username) }}">
|
||||
{{ member.full_name }}
|
||||
</a>
|
||||
<div class="ml-2 frappe-timestamp" data-timestamp="{{ reply.creation }}"> just now </div>
|
||||
</div>
|
||||
<div class="card-divider mt-3"></div>
|
||||
<div class="reply-text">{{ reply.reply }}</div>
|
||||
</div>
|
||||
41
community/templates/discussions/reply_section.html
Normal file
41
community/templates/discussions/reply_section.html
Normal file
@@ -0,0 +1,41 @@
|
||||
{% for topic in topics %}
|
||||
{% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name},
|
||||
["reply", "owner", "creation"], order_by="creation desc")%}
|
||||
|
||||
<!-- Hack to reorganize the replies array. So even though we want reverse cronology, the first reply should appear first. -->
|
||||
{% if replies.insert(0, replies.pop()) %}{% endif %}
|
||||
|
||||
{% if replies %}
|
||||
<div class="collapse discussion-on-page" id="t{{ topic.name }}" data-topic="{{ topic.name }}"
|
||||
data-parent="#discussion-group">
|
||||
<div class="course-home-headings p-0">{{ topic.title }}</div>
|
||||
{% for reply in replies %}
|
||||
|
||||
{% if loop.index == 2 %}
|
||||
<div class="card-divider-dark mb-10"></div>
|
||||
{% endif %}
|
||||
|
||||
{% include "community/templates/discussions/reply_card.html" %}
|
||||
|
||||
{% if loop.index == 1 %}
|
||||
|
||||
{% if frappe.session.user == "Guest" or (condition is defined and not condition) %}
|
||||
<div class="d-flex flex-column align-items-center muted-text">
|
||||
Want to join the discussion?
|
||||
{% if frappe.session.user == "Guest" %}
|
||||
<div class="button is-primary mt-3 mb-3" id="login-from-discussion">Log In</div>
|
||||
{% elif not condition %}
|
||||
<div class="button is-primary mt-3 mb-3" id="login-from-discussion" data-redirect="{{ redirect_to }}">{{ button_name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ widgets.CommentBox(doctype=doctype, docname=docname) }}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
9
community/templates/discussions/search.html
Normal file
9
community/templates/discussions/search.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="form-group">
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<textarea type="text" autocomplete="off" class="input-with-feedback form-control search-field"
|
||||
data-fieldtype="Text" data-fieldname="feedback_comments" placeholder="Search Topics"
|
||||
spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
20
community/templates/discussions/sidebar.html
Normal file
20
community/templates/discussions/sidebar.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<div class="sidebar-parent">
|
||||
<div class="sidebar-topic" data-target="#t{{ topic.name }}" data-toggle="collapse" aria-expanded="false">
|
||||
<div class="discussion-topic-title">{{ topic.title }}</div>
|
||||
<div class="mt-2 mb-3">
|
||||
{% set creator = frappe.get_doc("User", topic.owner) %}
|
||||
{{ widgets.Avatar(member=creator, avatar_class="avatar-small") }}
|
||||
<span class="course-instructor">
|
||||
{{ creator.full_name }}
|
||||
</span>
|
||||
<span class="muted-text pull-right">
|
||||
<span class="mr-2">
|
||||
<img src="/assets/community/icons/message.svg">
|
||||
<span class="reply-count">{{ replies | length }}</span>
|
||||
</span>
|
||||
<span> {{ frappe.utils.format_date(topic.creation, "dd MMM YYYY") }} </span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-divider"></div>
|
||||
</div>
|
||||
15
community/templates/discussions/topic_modal.html
Normal file
15
community/templates/discussions/topic_modal.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="modal fade discussion-modal" id="discussion-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="course-home-headings modal-headings">Start a Discussion</div>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ widgets.CommentBox(doctype=doctype, docname=docname) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
<div class="common-card-style thread-card mb-5">
|
||||
<div class="mb-4">
|
||||
{% set member = frappe.get_doc("User", message.owner) %}
|
||||
{{ widgets.Avatar(member=member, avatar_class="avatar-small")}}
|
||||
<a class="button-links" href="/user/{{ member.username }}">
|
||||
<span class="message-author">{{ member.full_name }}</span>
|
||||
</a>
|
||||
<span class="muted-text pull-right">{{ frappe.utils.format_datetime(message.creation, "dd MMM hh:mm") }}</span>
|
||||
</div>
|
||||
<div class="card-divider"></div>
|
||||
<div>{{ message.message }}</div>
|
||||
</div>
|
||||
@@ -26,10 +26,9 @@
|
||||
<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"/>
|
||||
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>
|
||||
<span class="label-area">{{ frappe.utils.md_to_html(option) }}</span>
|
||||
</div>
|
||||
@@ -51,6 +50,7 @@
|
||||
<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>
|
||||
<small id="submission-message" class="font-weight-bold hide"> Please join the course to submit the Quiz.</small>
|
||||
</div>
|
||||
<div class="button is-secondary pull-right hide" id="try-again">Try Again</div>
|
||||
<h4 class="success-message"></h4>
|
||||
|
||||
@@ -27,26 +27,26 @@
|
||||
{% if membership %}
|
||||
{{ pagination(prev_url, next_url) }}
|
||||
{% endif %}
|
||||
|
||||
{% set title = lesson.title + " - " + course.title %}
|
||||
{{ widgets.DiscussionMessage(doctype="Lesson", docname=lesson.name,
|
||||
title=title, condition=membership, button_name="Start Learning",
|
||||
redirect_to="/courses/" + course.name) }}
|
||||
</div>
|
||||
</div>
|
||||
{{ Discussions() }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro LessonContent(lesson) %}
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
<div class="lesson-content">
|
||||
<div class="course-home-headings title {% if membership %} is-member {% endif %}" data-lesson="{{ lesson.name }}"
|
||||
<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 }}
|
||||
<span class="lesson-progress {{hide if course.get_progress(lesson.name) != 'Complete' else ''}}">COMPLETED</span>
|
||||
</div>
|
||||
|
||||
{% if membership or lesson.include_in_preview %}
|
||||
|
||||
{% if membership or lesson.include_in_preview or is_instructor %}
|
||||
<div class="common-card-style lesson-content-card markdown-source">{{ lesson.render_html() }}</div>
|
||||
{% else %}
|
||||
<div class="common-card-style lesson-content-card">
|
||||
@@ -91,7 +91,7 @@
|
||||
Next
|
||||
<img class="ml-2" src="/assets/community/icons/side-arrow-white.svg">
|
||||
</a>
|
||||
{% elif course.enable_certification %}
|
||||
{% elif course.enable_certification %}
|
||||
<div class="button is-primary {% if course.get_course_progress() != 100 %} hide {% endif %}" id="certification">
|
||||
Get Certificate
|
||||
</div>
|
||||
@@ -101,6 +101,15 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro Discussions() %}
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
{% set title = lesson.title + " - " + course.title %}
|
||||
{% set condition = is_instructor if is_instructor else membership %}
|
||||
{{ widgets.DiscussionMessage(doctype="Lesson", docname=lesson.name,
|
||||
condition=condition, button_name="Start Learning",
|
||||
redirect_to="/courses/" + course.name) }}
|
||||
{% endmacro %}
|
||||
|
||||
{%- block script %}
|
||||
{{ super() }}
|
||||
|
||||
|
||||
@@ -133,7 +133,18 @@ var check_answer = (e) => {
|
||||
|
||||
$(".explanation").removeClass("hide");
|
||||
$("#check").addClass("hide");
|
||||
current_index == total_questions ? $("#summary").removeClass("hide") : $("#next").removeClass("hide");
|
||||
|
||||
if (current_index == total_questions) {
|
||||
if ($(".eligible-for-submission").length) {
|
||||
$("#summary").removeClass("hide")
|
||||
}
|
||||
else {
|
||||
$("#submission-message").removeClass("hide");
|
||||
}
|
||||
}
|
||||
else {
|
||||
$("#next").removeClass("hide")
|
||||
}
|
||||
|
||||
var [answer, is_correct] = parse_options();
|
||||
add_to_local_storage(quiz_name, current_index, answer, is_correct)
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
frappe.ready(() => {
|
||||
|
||||
if ($(document).width() <= 550) {
|
||||
$(".certificate-footer").css("flex-direction", "column");
|
||||
$(".certificate-footer").children().addClass("mb-5");
|
||||
}
|
||||
|
||||
$("#export-as-pdf").click((e) => {
|
||||
export_as_pdf(e);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var export_as_pdf = (e) => {
|
||||
|
||||
@@ -16,9 +16,9 @@ def get_context(context):
|
||||
redirect_to_course_list()
|
||||
|
||||
context.course = frappe.db.get_value("LMS Course", course_name,
|
||||
["owner", "title", "name"], as_dict=True)
|
||||
["instructor", "title", "name"], as_dict=True)
|
||||
|
||||
context.instructor = frappe.db.get_value("User", context.course.owner,
|
||||
context.instructor = frappe.db.get_value("User", context.course.instructor,
|
||||
["full_name", "username"], as_dict=True)
|
||||
|
||||
context.student = frappe.db.get_value("User", context.certificate.student,
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
|
||||
{% macro CourseCardWide(course) %}
|
||||
<div class="common-card-style course-card-wide">
|
||||
<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-image-wide {% if not course.image %} default-image {% endif %}" {% if course.image
|
||||
%}style="background-image: url({{ course.image }});" {% endif %}>
|
||||
<div class="course-tags">
|
||||
{% for tag in course.get_tags() %}
|
||||
<div class="course-card-pills">{{ tag }}</div>
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-buttons">
|
||||
{% if not course.disable_self_learning and not membership %}
|
||||
{% if not course.disable_self_learning and not membership and not course.upcoming %}
|
||||
<div class="button wide-button start-learning is-primary join-batch" data-course="{{ course.name | urlencode }}">
|
||||
Start Learning
|
||||
<img class="ml-2" src="/assets/community/icons/white-arrow.svg" />
|
||||
@@ -59,8 +59,8 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if course.upcoming %}
|
||||
<button class="button wide-button is-default" id="notify-me" data-course="{{course.name | urlencode}}"
|
||||
{% if is_user_interested %} disabled="disabled" title="Your interest has already been noted." {% endif %} >
|
||||
<button class="button wide-button is-default" id="notify-me" data-course="{{course.name | urlencode}}" {% if
|
||||
is_user_interested %} disabled="disabled" title="Your interest has already been noted." {% endif %}>
|
||||
Notify me when available
|
||||
</button>
|
||||
{% endif %}
|
||||
@@ -70,10 +70,6 @@
|
||||
<img class="ml-2" src="/assets/community/images/play.png" />
|
||||
</div>
|
||||
{% endif %}
|
||||
{% set certificate = course.is_certified() %}
|
||||
{% if certificate %}
|
||||
<a class="button wide-button is-secondary dark-links" href="/courses/{{ course.name }}/{{ certificate }}">Get Certificate</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -101,6 +97,7 @@
|
||||
<!-- Course Outline and Creator -->
|
||||
|
||||
{% macro CourseOutlineAndCreator(course) %}
|
||||
{% set certificate = course.is_certified() %}
|
||||
<div class="course-outline-instructor-parent">
|
||||
<div class="course-home-outline">
|
||||
{{ widgets.CourseOutline(course=course, membership=membership) }}
|
||||
@@ -137,9 +134,13 @@
|
||||
{{ frappe.utils.rounded(progress) }}%
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: {{ progress }}%"
|
||||
aria-valuenow="{{ progress }}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar" role="progressbar" style="width: {{ progress }}%" aria-valuenow="{{ progress }}"
|
||||
aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
{% if certificate %}
|
||||
<a class="muted-text dark-links mt-5 text-center" href="/courses/{{ course.name }}/{{ certificate }}">Get
|
||||
Certificate</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -178,7 +179,7 @@
|
||||
<div class="course-home-headings">
|
||||
Course Description
|
||||
</div>
|
||||
<div class="common-card-style description-card small-title">
|
||||
<div class="common-card-style description-card">
|
||||
{{ frappe.utils.md_to_html(course.description) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,10 +29,6 @@ frappe.ready(() => {
|
||||
show_review_dialog(e);
|
||||
});
|
||||
|
||||
$(".chapter-title").click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
$(".icon-rating").click((e) => {
|
||||
highlight_rating(e);
|
||||
});
|
||||
@@ -168,16 +164,6 @@ var show_review_dialog = (e) => {
|
||||
$("#review-modal").modal("show");
|
||||
}
|
||||
|
||||
var rotate_chapter_icon = (e) => {
|
||||
e.preventDefault();
|
||||
var icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
}
|
||||
|
||||
var highlight_rating = (e) => {
|
||||
var rating = $(e.currentTarget).attr("data-rating");
|
||||
$(".icon-rating").removeClass("star-click");
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{% extends "templates/base.html" %}
|
||||
{% block title %}{{ thread.title }}{% endblock %}
|
||||
{% block head_include %}
|
||||
<style>
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="common-page-style">
|
||||
<div class="container">
|
||||
{{ widgets.BreadCrumb(thread=thread) }}
|
||||
<div class="course-home-headings">{{ thread.title }}</div>
|
||||
{{ widgets.DiscussionMessage(thread=thread.name) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,18 +0,0 @@
|
||||
import frappe
|
||||
|
||||
def get_context(context):
|
||||
context.no_cache = 1
|
||||
|
||||
try:
|
||||
thread_name = frappe.form_dict["discussion"]
|
||||
except KeyError:
|
||||
redirect_to_discussions()
|
||||
|
||||
context.thread = frappe.db.get_value("Discussion Thread", thread_name, ["name", "title"], as_dict=True)
|
||||
|
||||
if not len(context.thread):
|
||||
redirect_to_discussions
|
||||
|
||||
def redirect_to_discussions():
|
||||
frappe.local.flags.redirect_location = "/discussions"
|
||||
raise frappe.Redirect
|
||||
@@ -1,65 +0,0 @@
|
||||
{% extends "templates/base.html" %}
|
||||
{% block title %}{{ 'Discussions' }}{% endblock %}
|
||||
{% block head_include %}
|
||||
<style>
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="common-page-style">
|
||||
<div class="container">
|
||||
<div class="courses-header">
|
||||
<span>{{_('Discussions')}}</span>
|
||||
<div id="new-topic" class="button is-primary pull-right">
|
||||
<img src="/assets/community/icons/small-add.svg">
|
||||
Start a Discussion</div>
|
||||
</div>
|
||||
<div class="card-divider-dark"></div>
|
||||
{% if threads | length %}
|
||||
<div class="cards-parent">
|
||||
{% for thread in threads %}
|
||||
<div class="common-card-style thread-card">
|
||||
<div class="course-card-title">{{ thread.title }}</div>
|
||||
<div class="card-divider"></div>
|
||||
<div>
|
||||
<span class="course-student-count">
|
||||
<span class="mr-4">
|
||||
<img class="icon-background" src="/assets/community/icons/message.svg" />
|
||||
{{ thread.message_count }}
|
||||
</span>
|
||||
<span class="mr-4">
|
||||
<img class="icon-background" src="/assets/community/icons/user.svg" />
|
||||
{{ thread.member_count }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<a class="stretched-link" href="/discussions/{{ thread.name }}"></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center font-weight-bold mt-5">
|
||||
No discussions yet.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New Topic Modal -->
|
||||
<div class="modal fade discussion-modal" id="discussion-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="course-home-headings modal-headings">Start a Discussion</div>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ widgets.DiscussionComment() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,14 +0,0 @@
|
||||
frappe.ready(() => {
|
||||
$("#new-topic").click((e) => {
|
||||
show_new_topic_modal(e);
|
||||
})
|
||||
})
|
||||
|
||||
var show_new_topic_modal = (e) => {
|
||||
e.preventDefault();
|
||||
if (frappe.session.user == "Guest") {
|
||||
window.location.href = `/login?redirect-to=/discussions/`;
|
||||
return;
|
||||
}
|
||||
$("#discussion-modal").modal("show");
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import frappe
|
||||
|
||||
def get_context(context):
|
||||
context.threads = get_threads()
|
||||
|
||||
def get_threads():
|
||||
threads = frappe.get_all("Discussion Thread", fields=["name", "title"])
|
||||
for thread in threads:
|
||||
messages = frappe.get_all("Discussion Message",
|
||||
{
|
||||
"thread": thread.name
|
||||
},
|
||||
["owner"],
|
||||
as_list=True)
|
||||
thread.message_count = len(messages)
|
||||
thread.member_count = len(set(messages))
|
||||
return threads
|
||||
@@ -51,7 +51,7 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if frappe.session.user == member.email %}
|
||||
<a class="dark-links pull-right" href="edit-profile?name={{ member.email }}">Edit Profile</a>
|
||||
<a class="dark-links pull-right" href="/edit-profile?name={{ member.email }}">Edit Profile</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,6 +59,9 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro AboutOverviewSection(member) %}
|
||||
{% set enrollment = member.get_course_membership("Student") | length %}
|
||||
{% set mentorship = member.get_course_membership("Mentor") | length %}
|
||||
{% set reviews = member.get_user_reviews() | length %}
|
||||
<div class="profile-parent-section">
|
||||
{% if member.bio %}
|
||||
<div class="profile-about-section">
|
||||
@@ -70,31 +73,35 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if enrollment or reviews or mentorship %}
|
||||
<div class="course-overview-section">
|
||||
<div class="course-home-headings">
|
||||
Overview
|
||||
</div>
|
||||
<div class="common-card-style overview-card small-title">
|
||||
{% if member.get_course_membership("Student") | length %}
|
||||
{% if enrollment %}
|
||||
<div class="overtime-item">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/user.svg" />
|
||||
{{ member.get_course_membership("Student") | length }} Enrolled
|
||||
{{ enrollment }} Enrolled
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if member.get_user_reviews() | length %}
|
||||
{% if reviews %}
|
||||
<div class="overtime-item">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/rating.svg" />
|
||||
{{ member.get_user_reviews() | length }} Created
|
||||
{{ reviews }} Created
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if member.get_course_membership("Mentor") | length%}
|
||||
{% if mentorship %}
|
||||
<div class="overtime-item">
|
||||
<img class="icon-background mr-1" src="/assets/community/icons/calendar.svg" />
|
||||
{{ member.get_course_membership("Mentor") | length }} Mentored
|
||||
{{ mentorship }} Mentored
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# Mockups
|
||||
|
||||
HTML Mockups using [Mockdown][].
|
||||
|
||||
[Mockdown]: https://github.com/anandology/mockdown
|
||||
|
||||
## How to use
|
||||
|
||||
**Step 1:** Get into `mockups` directory
|
||||
|
||||
```
|
||||
$ cd mockups
|
||||
```
|
||||
|
||||
**Step 2:** Instal `mockdown`
|
||||
|
||||
```
|
||||
$ pip install mockdown
|
||||
```
|
||||
|
||||
**Step 3:** Start mockdown server
|
||||
|
||||
```
|
||||
$ mockdown
|
||||
...
|
||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
...
|
||||
```
|
||||
|
||||
**Step 4:** See the mockups at <http://localhost:5000/>.
|
||||
|
||||
## How does it work?
|
||||
|
||||
Mockdown uses [Jinja][] templates for writing HTML.
|
||||
|
||||
[Jinja]: https://jinja.palletsprojects.com/
|
||||
|
||||
To make is easy to provide test data, Mockdown looks for YAML file with the same name as the template. For example, `home.html` template uses the data from `home.yml`.
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
|
||||
<link href="/static/style.css" rel="stylesheet">
|
||||
|
||||
<title>{% block title %}FOSS United{% endblock %}</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-light navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/"><span>Home</span></a>
|
||||
<button aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" data-target="#navbarSupportedContent" data-toggle="collapse" type="button">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="mr-auto navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/sketches.html">
|
||||
Sketches
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="ml-auto navbar-nav">
|
||||
<!-- post login tools -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link btn-login-area" href="/login.html">Login</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% block content %}
|
||||
<h1>Lorem ipsum...</h1>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,114 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="course-header">
|
||||
<div class="course-type">course</div>
|
||||
<h1>{{title}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-9 col-md-12">
|
||||
<div class="course-details">
|
||||
|
||||
<h2>Course Description</h2>
|
||||
|
||||
<div class="course-description">
|
||||
{{ description }}
|
||||
</div>
|
||||
|
||||
<div class="preview-video">
|
||||
<iframe
|
||||
width="560"
|
||||
height="315"
|
||||
src="{{youtube_embed_url}}"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
<h2>Upcoming Batches</h2>
|
||||
|
||||
<div class="row">
|
||||
{% for batch in batches %}
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="batch">
|
||||
<div class="batch-details">
|
||||
<div>Session every {{batch.weekdays}}</div>
|
||||
<div>{{batch.timeslot}}</div>
|
||||
<div>Starting from {{batch.start_date}}</div>
|
||||
|
||||
<div class="course-type" style="color: #888; padding: 10px 0px;">mentors</div>
|
||||
|
||||
{% for m in batch.mentors %}
|
||||
<div>
|
||||
<img class="profile-photo" src="{{m.photo_url}}">
|
||||
<span class="instructor-title">{{m.name}}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="cta">
|
||||
<div class="">
|
||||
<button type="button">Join this Batch</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Course Outline</h2>
|
||||
|
||||
{% for chapter in chapters %}
|
||||
<div class="chapter-plan">
|
||||
<h3><span class="chapter-number">{{loop.index}}</span> {{chapter.title}}</h3>
|
||||
<div class="chapter-description">
|
||||
{{chapter.description}}
|
||||
</div>
|
||||
|
||||
<div class="lessons">
|
||||
{% for lesson in chapter.lessons %}
|
||||
<div class="lesson">
|
||||
<span class="lesson-type"><i class="{{lesson.icon}}"></i></span>
|
||||
<span class="lesson-title">{{lesson.title}}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-12">
|
||||
<div class="sidebar">
|
||||
<h3>Instructor</h3>
|
||||
<div class="instructor">
|
||||
<div class="instructor-title">{{instructor.name}}</div>
|
||||
<div class="instructor-subtitle">Created {{instructor.num_courses}} courses</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<h3>Mentors</h3>
|
||||
{% for m in mentors %}
|
||||
<div class="instructor">
|
||||
<div class="instructor-title">{{m.name}}</div>
|
||||
<div class="instructor-subtitle">Mentored {{m.num_courses}} batches</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="notice">
|
||||
Interested to become a mentor?
|
||||
|
||||
<div><a href="#">Apply Now!</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,89 +0,0 @@
|
||||
title: The Joy of Programming
|
||||
description: |
|
||||
Learn the joy of programming by turning the computer into a canvas.
|
||||
youtube_embed_url: "https://www.youtube.com/embed/IFWAYnUeHR8?start=149"
|
||||
stats:
|
||||
chapters: 4
|
||||
lessons: 25
|
||||
videos: 6
|
||||
completed: 287
|
||||
|
||||
instructor:
|
||||
name: Anand Chitipothu
|
||||
num_courses: 4
|
||||
|
||||
mentors:
|
||||
- name: Anand Chitipothu
|
||||
num_courses: 4
|
||||
- name: Rushabh Mehta
|
||||
num_courses: 3
|
||||
- name: Jannat Patel
|
||||
num_courses: 3
|
||||
|
||||
batches:
|
||||
- id: jp01
|
||||
status: scheduled
|
||||
mentors:
|
||||
- name: Anand Chitipothu
|
||||
photo_url: https://pbs.twimg.com/profile_images/2599066714/igu5hx4wlg3mxucodinl.jpeg
|
||||
num_batches: 4
|
||||
start_date: May 3, 2021
|
||||
weekdays: Mon, Thu
|
||||
timeslot: 5:00-6:00 PM
|
||||
|
||||
- id: jp02
|
||||
status: scheduled
|
||||
mentors:
|
||||
- name: Anand Chitipothu
|
||||
photo_url: https://pbs.twimg.com/profile_images/2599066714/igu5hx4wlg3mxucodinl.jpeg
|
||||
num_batches: 4
|
||||
start_date: May 4, 2021
|
||||
weekdays: Tue, Fri
|
||||
timeslot: 5:00-6:00 PM
|
||||
|
||||
- id: jp03
|
||||
status: scheduled
|
||||
mentors:
|
||||
- name: Rusbhabh Mehta
|
||||
photo_url: https://pbs.twimg.com/profile_images/2599066714/igu5hx4wlg3mxucodinl.jpeg
|
||||
num_batches: 4
|
||||
start_date: May 15, 2021
|
||||
weekdays: Sat
|
||||
timeslot: 5:00-6:00 PM
|
||||
|
||||
|
||||
chapters:
|
||||
- title: Getting Started
|
||||
description: |
|
||||
Getting started with programming by turning the computer into a canvas.
|
||||
lessons:
|
||||
- index: 1
|
||||
type: video
|
||||
icon: bi bi-play-circle
|
||||
title: Introduction to Programming
|
||||
- index: 2
|
||||
type: practice
|
||||
icon: bi bi-code-square
|
||||
title: Drawing Shapes
|
||||
|
||||
- title: Repeating Things
|
||||
description: |
|
||||
Isn't it very boring to do the same thing again and again?
|
||||
Well, that is for humans. Computers love to do the same thing again and again.
|
||||
Learn how to tell the computer to repeat multiple times the same task, or
|
||||
with slight change every time.
|
||||
|
||||
lessons:
|
||||
- index: 1
|
||||
type: video
|
||||
icon: bi bi-play-circle
|
||||
title: Rinse and Repeat
|
||||
- index: 2
|
||||
type: practice
|
||||
title: many circles
|
||||
icon: bi bi-check2-circle
|
||||
- index: 3
|
||||
type: practice
|
||||
icon: bi bi-code-square
|
||||
title: print, print, print!
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="course-header">
|
||||
<h1>Sketches</h1>
|
||||
</div>
|
||||
|
||||
<div class="row sketches-gallery">
|
||||
{% for s in sketches %}
|
||||
<div class="col-md-3">
|
||||
<div class="sketch-card">
|
||||
<div class="sketch-image">
|
||||
<a href="#">
|
||||
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" stroke="black" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="150" cy="150" r="150.0"></circle>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="sketch-footer">
|
||||
<div class="sketch-title">
|
||||
<a href="#">{{s.title}}</a>
|
||||
</div>
|
||||
<div class="sketch-author">
|
||||
by {{s.author}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,38 +0,0 @@
|
||||
sketches:
|
||||
- id: 20
|
||||
title: Big Circle
|
||||
author: Anand Chitipothu
|
||||
- id: 19
|
||||
title: Small Circle
|
||||
author: Anand Chitipothu
|
||||
- id: 18
|
||||
title: Circles in Queue
|
||||
author: Chaitanya
|
||||
- id: 17
|
||||
title: Random Bottom Circles
|
||||
author: Anand Chitipothu
|
||||
- id: 16
|
||||
title: Pipes
|
||||
author: Vishal
|
||||
- id: 15
|
||||
title: New Sketch
|
||||
author: Malleshwari
|
||||
|
||||
- id: 20
|
||||
title: Big Circle
|
||||
author: Anand Chitipothu
|
||||
- id: 19
|
||||
title: Small Circle
|
||||
author: Anand Chitipothu
|
||||
- id: 18
|
||||
title: Circles in Queue
|
||||
author: Chaitanya
|
||||
- id: 17
|
||||
title: Random Bottom Circles
|
||||
author: Anand Chitipothu
|
||||
- id: 16
|
||||
title: Pipes
|
||||
author: Vishal
|
||||
- id: 15
|
||||
title: New Sketch
|
||||
author: Malleshwari
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user