fix: conflicts

This commit is contained in:
pateljannat
2021-09-06 19:35:32 +05:30
39 changed files with 591 additions and 425 deletions

View File

@@ -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)

View File

@@ -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) {
// }

View File

@@ -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": [
{

View File

@@ -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)

View File

@@ -4,5 +4,5 @@
# import frappe
import unittest
class TestDiscussionThread(unittest.TestCase):
class TestDiscussionReply(unittest.TestCase):
pass

View File

@@ -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)

View File

@@ -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) {
// }

View File

@@ -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
}
}

View File

@@ -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"])

View File

@@ -4,5 +4,5 @@
# import frappe
import unittest
class TestDiscussionMessage(unittest.TestCase):
class TestDiscussionTopic(unittest.TestCase):
pass

View File

@@ -0,0 +1,2 @@
{% set docname = frappe.db.get_value("Web Page", {"route": ""}, ["name"])%}
{{ widgets.DiscussionMessage(doctype="Web Page", docname=docname) }}

View File

@@ -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"
}

View 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>

View File

@@ -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>

View File

@@ -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 mt-5" id="login-from-discussion">Log In</div>
{% elif not condition %}
<div class="button is-primary mt-5" 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 %}

View File

@@ -141,9 +141,8 @@ website_route_rules = [
{"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"},
{"from_route": "/discussions/<discussion>", "to_route": "discussions/discussion"},
{"from_route": "/users/<string(minlength=4):username>", "to_route": "profiles/profile"},
{"from_route": "/users", "to_route": "profiles/profile"},
{"from_route": "/users", "to_route": "profiles/profile"}
]
website_redirects = [

View File

@@ -64,10 +64,11 @@
})
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;
}
});
}

View File

@@ -9,3 +9,5 @@ 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")

View File

@@ -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;
@@ -109,8 +108,6 @@ input[type=checkbox] {
.muted-text {
font-size: 12px;
line-height: 135%;
color: var(--muted-text);
height: 15px;
}
.course-card-meta {
@@ -146,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;
}
@@ -165,7 +162,6 @@ input[type=checkbox] {
.course-student-count {
font-size: 12px;
line-height: 135%;
color: var(--muted-text);
float: right;
}
@@ -222,8 +218,13 @@ input[type=checkbox] {
}
}
.button-links {
color: #4C5A67;
}
.button-links:hover {
text-decoration: none;
color: #4C5A67;
}
.icon-background {
@@ -434,7 +435,7 @@ input[type=checkbox] {
.button {
box-shadow: var(--btn-shadow);
border-radius: 8px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
@@ -1282,17 +1283,77 @@ pre {
.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 {
@@ -1300,17 +1361,24 @@ 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 {

View File

@@ -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

View 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

View 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

View 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 %}

View 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}`;
}

View 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="/user/{{ 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>

View 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 %}

View 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>

View 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>

View 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">&times;</span>
</button>
</div>
<div class="modal-body">
{{ widgets.CommentBox(doctype=doctype, docname=docname) }}
</div>
</div>
</div>
</div>

View File

@@ -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="/users/{{ 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>

View File

@@ -27,14 +27,9 @@
{% if membership %}
{{ pagination(prev_url, next_url) }}
{% endif %}
{% set title = lesson.title + " - " + course.title %}
{% set condition = membership or is_instructor %}
{{ widgets.DiscussionMessage(doctype="Lesson", docname=lesson.name,
title=title, condition=condition, button_name="Start Learning",
redirect_to="/courses/" + course.name) }}
</div>
</div>
{{ Discussions() }}
</div>
</div>
{% endblock %}
@@ -96,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>
@@ -106,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() }}

View File

@@ -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 %}

View File

@@ -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

View File

@@ -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">&times;</span>
</button>
</div>
<div class="modal-body">
{{ widgets.DiscussionComment() }}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -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");
}

View File

@@ -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