fix: discussions structure

This commit is contained in:
pateljannat
2021-08-30 12:46:08 +05:30
parent e6502784ea
commit 7a9039090d
22 changed files with 201 additions and 217 deletions

View File

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

View File

@@ -5,49 +5,31 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"thread", "topic",
"column_break_2", "reply"
"parent_message",
"section_break_4",
"message"
], ],
"fields": [ "fields": [
{ {
"fieldname": "message", "fieldname": "reply",
"fieldtype": "Long Text", "fieldtype": "Long Text",
"in_list_view": 1, "in_list_view": 1,
"label": "Message" "label": "Reply"
}, },
{ {
"fieldname": "thread", "fieldname": "topic",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Thread", "label": "Topic",
"options": "Discussion Thread" "options": "Discussion Topic"
},
{
"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"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2021-08-12 15:59:04.811286", "modified": "2021-08-27 15:06:51.362714",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Community", "module": "Community",
"name": "Discussion Message", "name": "Discussion Reply",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -3,20 +3,22 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from community.widgets import Widget, Widgets from community.widgets import Widgets
class DiscussionMessage(Document): class DiscussionReply(Document):
def after_insert(self): def after_insert(self):
data = { data = {
"message": self, "reply": self,
"topic": {
"name": self.topic
},
"widgets": Widgets() "widgets": Widgets()
} }
template = frappe.render_template("community/templates/message_card.html", data) template = frappe.render_template("community/templates/reply_card.html", data)
thread_info = frappe.db.get_value("Discussion Thread", self.thread, ["reference_doctype", "reference_docname"], as_dict=True) topic_info = frappe.db.get_value("Discussion Topic", self.topic, ["reference_doctype", "reference_docname", "name", "title"], as_dict=True)
frappe.publish_realtime(event="publish_message", frappe.publish_realtime(event="publish_message",
message = { message = {
"thread": self.thread,
"template": template, "template": template,
"thread_info": thread_info "topic_info": topic_info
}, },
after_commit=True) after_commit=True)

View File

@@ -4,5 +4,5 @@
# import frappe # import frappe
import unittest import unittest
class TestDiscussionThread(unittest.TestCase): class TestDiscussionReply(unittest.TestCase):
pass 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 // Copyright (c) 2021, FOSS United and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Discussion Message', { frappe.ui.form.on('Discussion Topic', {
// refresh: function(frm) { // refresh: function(frm) {
// } // }

View File

@@ -33,7 +33,7 @@
"modified": "2021-08-11 12:29:43.564123", "modified": "2021-08-11 12:29:43.564123",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Community", "module": "Community",
"name": "Discussion Thread", "name": "Discussion Topic",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -0,0 +1,31 @@
# 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)

View File

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

View File

@@ -0,0 +1 @@
{{ widgets.DiscussionMessage }}

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

@@ -1,36 +1,49 @@
<div class="discussions"> <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">
<div class="discussions">
<form class="discussion-form {% if doctype or thread %} discussion-on-page {% endif %}" id="discussion-form"> <form class="discussion-form" id="discussion-form">
<div class="form-group" {% if title or thread %} style="display: none;" {% endif %}> <div class="form-group">
<div class="control-input-wrapper"> <div class="control-input-wrapper">
<div class="control-input"> <div class="control-input">
<input type="text" autocomplete="off" class="input-with-feedback form-control thread-title" <input type="text" autocomplete="off" class="input-with-feedback form-control topic-title"
data-fieldtype="Data" data-fieldname="feedback_comments" placeholder="Title" spellcheck="false" {% if title data-fieldtype="Data" data-fieldname="feedback_comments" placeholder="Title"
%} value="{{ title }}" {% endif %}></input> 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-secondary pull-right mb-5" id="submit-discussion"
data-doctype="{{ doctype | urlencode }}" data-docname="{{ docname | urlencode }}">
Post</div>
</div>
</form>
</div> </div>
</div> </div>
</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> </div>
<script> <script>
frappe.ready(() => { frappe.ready(() => {
$("#submit-discussion").click((e) => { $("#submit-discussion").click((e) => {
@@ -39,9 +52,10 @@
}) })
var submit_discussion = (e) => { var submit_discussion = (e) => {
var message = $(".comment-field").val().trim(); var title = $(".topic-title").val().trim();
var reply = $(".comment-field").val().trim();
if (message) { if (reply) {
var doctype = $(e.currentTarget).attr("data-doctype"); var doctype = $(e.currentTarget).attr("data-doctype");
doctype = doctype ? decodeURIComponent(doctype) : doctype; doctype = doctype ? decodeURIComponent(doctype) : doctype;
@@ -49,21 +63,17 @@
docname = docname ? decodeURIComponent(docname) : docname; docname = docname ? decodeURIComponent(docname) : docname;
frappe.call({ frappe.call({
method: "community.community.doctype.discussion_thread.discussion_thread.submit_discussion", method: "community.community.doctype.discussion_topic.discussion_topic.submit_discussion",
args: { args: {
"doctype": doctype ? doctype : "", "doctype": doctype ? doctype : "",
"docname": docname ? docname : "", "docname": docname ? docname : "",
"message": $(".comment-field").val(), "reply": reply,
"title": $(".thread-title").val(), "title": title,
"thread_name": $(e.currentTarget).attr("data-thread") "topic_name": $(e.currentTarget).attr("data-topic")
}, },
callback: (data) => { callback: (data) => {
if (! $(".discussion-on-page").length) {
$("#discussion-modal").modal("hide");
window.location.href = `/discussions/${data.message}`;
} }
} })
})
} }
} }

View File

@@ -1,80 +1,83 @@
{% if doctype and docname and not thread %} {% set topics = frappe.get_all("Discussion Topic",
{"reference_doctype": doctype, "reference_docname": docname}, ["name", "title"]) %}
{% set thread_info = frappe.get_all("Discussion Thread", {"reference_doctype": doctype, "reference_docname": docname}, <div class="courses-header mt-10">
["name"]) %} <span>{{_('Discussions')}}</span>
<div class="button is-primary pull-right reply">
<img src="/assets/community/icons/small-add.svg">
Start a Discussion
</div>
</div>
{% if thread_info | length %} {{ widgets.DiscussionComment(doctype=doctype, docname=docname) }}
{% set thread = thread_info[0].name %}
{% endif %}
{% endif %} <div class="common-card-style thread-card discussions-section" data-doctype="{{ doctype }}"
data-docname="{{ docname }}">
{% if thread %} {% for topic in topics %}
{% set messages = frappe.get_all("Discussion Message", {"thread": thread}, ["name", "message", "owner", "creation"], <div class="topic-parent" data-topic="{{ topic.name }}">
order_by="creation") %} <div class="font-weight-bold mb-5">{{ topic.title }}</div>
{% endif %} {% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name},
["reply", "owner", "creation"], order_by="creation")%}
{% if doctype %} {% for reply in replies %}
<div class="course-home-headings mt-5"> Discussions </div> {% include "community/templates/reply_card.html" %}
{% endif %} {% endfor %}
</div>
<div class="messages mt-5">
{% for message in messages %}
{% include "community/templates/message_card.html" %}
{% endfor %} {% endfor %}
</div> </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> <script>
frappe.ready(() => { frappe.ready(() => {
setup_socket_io(); setup_socket_io();
$("#login-from-discussion").click((e) => { $(document).on("click", ".reply", (e) => {
login_from_discussion(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");
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 = () => { var setup_socket_io = () => {
const assets = [ const assets = [
"/assets/frappe/js/lib/socket.io.min.js", "/assets/frappe/js/lib/socket.io.min.js",
"/assets/frappe/js/frappe/socketio_client.js", "/assets/frappe/js/frappe/socketio_client.js",
] ]
frappe.require(assets, () => {
frappe.require(assets, () => {
if (window.dev_server) { if (window.dev_server) {
frappe.boot.socketio_port = "9000"; frappe.boot.socketio_port = "9000";
} }
frappe.socketio.init(9000); frappe.socketio.init(9000);
var target = $("#submit-discussion");
frappe.socketio.socket.on("publish_message", (data) => { frappe.socketio.socket.on("publish_message", (data) => {
if (target.attr("data-thread") == data.thread publish_message(data);
|| (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 publish_message = (data) => {
var redirect = $(e.currentTarget).attr("data-redirect") || window.location.href; $(".comment-field").val("");
window.location.href = `/login?redirect-to=${redirect}`; $("#discussion-modal").modal("hide");
}
if ($(`.topic-parent[data-topic=${data.topic_info.name}]`).length) {
$(`.topic-parent[data-topic=${data.topic_info.name}]`).append(data.template);
}
else if ((decodeURIComponent($(".discussions-section").attr("data-doctype")) == data.topic_info.reference_doctype
&& decodeURIComponent($(".discussions-section").attr("data-docname")) == data.topic_info.reference_docname)) {
var html = `<div class="topic-parent" data-topic="${data.topic_info.name}">
<div class="font-weight-bold mb-5">${data.topic_info.title}</div>
${data.template}
</div>`;
$(".discussions-section").prepend(html);
}
}
</script> </script>

View File

@@ -9,3 +9,5 @@ community.patches.replace_member_with_user_in_mentor_request
community.patches.v0_0.chapter_lesson_index_table community.patches.v0_0.chapter_lesson_index_table
execute:frappe.delete_doc("DocType", "LMS Message") execute:frappe.delete_doc("DocType", "LMS Message")
community.patches.v0_0.course_instructor_update community.patches.v0_0.course_instructor_update
execute:frappe.delete_doc("DocType", "Discussion Message")
execute:frappe.delete_doc("DocType", "Discussion Thread")

View File

@@ -1387,9 +1387,13 @@ textarea.form-control {
height: 300px; height: 300px;
} }
.discussion-on-page textarea.form-control { .discussion-on-page .form-control {
background-color: #FFFFFF; background-color: #FFFFFF;
font-size: inherit;
color: inherit; color: inherit;
}
.discussion-on-page textarea {
height: 160px; height: 160px;
} }
@@ -1398,14 +1402,6 @@ textarea.form-control {
justify-content: flex-end; justify-content: flex-end;
} }
.comment-field {
font-size: inherit;
}
.thread-title {
font-size: inherit;
}
.message-author { .message-author {
color: #192734; color: #192734;
margin-left: 0.5rem; margin-left: 0.5rem;

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="/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>

View File

@@ -0,0 +1,14 @@
<div>
{% set member = frappe.get_doc("User", reply.owner) %}
<div class="mb-4">
{% set member = frappe.get_doc("User", reply.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">{{ frappe.utils.format_datetime(reply.creation, "hh:mm a, dd MMM YYYY") }}</span>
<a href="" class="dark-links muted-text pull-right reply" data-topic="{{ topic.name }}">Reply</a>
</div>
<div class="card-divider"></div>
<div>{{ reply.reply }}</div>
</div>

View File

@@ -31,7 +31,7 @@
{% set title = lesson.title + " - " + course.title %} {% set title = lesson.title + " - " + course.title %}
{% set condition = membership or is_instructor %} {% set condition = membership or is_instructor %}
{{ widgets.DiscussionMessage(doctype="Lesson", docname=lesson.name, {{ widgets.DiscussionMessage(doctype="Lesson", docname=lesson.name,
title=title, condition=condition, button_name="Start Learning", condition=condition, button_name="Start Learning",
redirect_to="/courses/" + course.name) }} redirect_to="/courses/" + course.name) }}
</div> </div>
</div> </div>

View File

@@ -46,20 +46,6 @@
</div> </div>
<!-- New Topic Modal --> <!-- 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 %} {% endblock %}