feat: discussions component redesignversion-14
@@ -37,24 +37,24 @@ context('Discussions', () => { | |||||
}; | }; | ||||
const reply_through_comment_box = () => { | const reply_through_comment_box = () => { | ||||
cy.get('.discussion-on-page:visible .comment-field') | |||||
cy.get('.discussion-form:visible .comment-field') | |||||
.type('This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.') | .type('This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.') | ||||
.should('have.value', 'This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.'); | .should('have.value', 'This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.'); | ||||
cy.get('.discussion-on-page:visible .submit-discussion').click(); | |||||
cy.get('.discussion-form:visible .submit-discussion').click(); | |||||
cy.wait(3000); | cy.wait(3000); | ||||
cy.get('.discussion-on-page:visible').should('have.class', 'show'); | cy.get('.discussion-on-page:visible').should('have.class', 'show'); | ||||
cy.get('.discussion-on-page:visible').children(".reply-card").eq(1).children(".reply-text") | |||||
cy.get('.discussion-on-page:visible').children(".reply-card").eq(1).find(".reply-text") | |||||
.should('have.text', 'This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.\n'); | .should('have.text', 'This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.\n'); | ||||
}; | }; | ||||
const cancel_and_clear_comment_box = () => { | const cancel_and_clear_comment_box = () => { | ||||
cy.get('.discussion-on-page:visible .comment-field') | |||||
cy.get('.discussion-form:visible .comment-field') | |||||
.type('This is a discussion from the cypress ui tests.') | .type('This is a discussion from the cypress ui tests.') | ||||
.should('have.value', 'This is a discussion from the cypress ui tests.'); | .should('have.value', 'This is a discussion from the cypress ui tests.'); | ||||
cy.get('.discussion-on-page:visible .cancel-comment').click(); | |||||
cy.get('.discussion-on-page:visible .comment-field').should('have.value', ''); | |||||
cy.get('.discussion-form:visible .cancel-comment').click(); | |||||
cy.get('.discussion-form:visible .comment-field').should('have.value', ''); | |||||
}; | }; | ||||
const single_thread_discussion = () => { | const single_thread_discussion = () => { | ||||
@@ -62,13 +62,13 @@ context('Discussions', () => { | |||||
cy.get('.discussions-sidebar').should('have.length', 0); | cy.get('.discussions-sidebar').should('have.length', 0); | ||||
cy.get('.reply').should('have.length', 0); | cy.get('.reply').should('have.length', 0); | ||||
cy.get('.discussion-on-page .comment-field') | |||||
cy.get('.discussion-form:visible .comment-field') | |||||
.type('This comment is being made on a single thread discussion.') | .type('This comment is being made on a single thread discussion.') | ||||
.should('have.value', 'This comment is being made on a single thread discussion.'); | .should('have.value', 'This comment is being made on a single thread discussion.'); | ||||
cy.get('.discussion-on-page .submit-discussion').click(); | |||||
cy.get('.discussion-form:visible .submit-discussion').click(); | |||||
cy.wait(3000); | cy.wait(3000); | ||||
cy.get('.discussion-on-page').children(".reply-card").eq(-1).children(".reply-text") | |||||
cy.get('.discussion-on-page').children(".reply-card").eq(-1).find(".reply-text") | |||||
.should('have.text', 'This comment is being made on a single thread discussion.\n'); | .should('have.text', 'This comment is being made on a single thread discussion.\n'); | ||||
}; | }; | ||||
@@ -88,6 +88,20 @@ | |||||
border-radius: $dropdown-border-radius; | border-radius: $dropdown-border-radius; | ||||
} | } | ||||
.dropdown-item:active { | |||||
color: var(--fg-color); | |||||
text-decoration: none; | |||||
background-color: var(--gray-600); | |||||
} | |||||
.dropdown-item:active:hover { | |||||
color: var(--fg-color); | |||||
} | |||||
.dropdown-menu a:hover { | |||||
cursor: pointer; | |||||
} | |||||
.input-dark { | .input-dark { | ||||
background-color: $dark; | background-color: $dark; | ||||
border-color: darken($primary, 40%); | border-color: darken($primary, 40%); | ||||
@@ -1,9 +1,6 @@ | |||||
{% if frappe.session.user != "Guest" and | {% if frappe.session.user != "Guest" and | ||||
(condition is not defined or (condition is defined and condition )) %} | (condition is not defined or (condition is defined and condition )) %} | ||||
<span class="btn btn-md btn-default reply"> | |||||
<span class="btn btn-md btn-secondary-dark reply"> | |||||
{{ _(cta_title) }} | {{ _(cta_title) }} | ||||
<!-- Below svg is not a part of the current design. Hence it is commented. | |||||
The comment will be removed after all design changes are implemented. --> | |||||
<!-- <svg class="icon icon-sm ml-1"><use href="#icon-add" style="stroke: var(--gray-700)"></use></svg> --> | |||||
</span> | </span> | ||||
{% endif %} | {% endif %} |
@@ -28,7 +28,7 @@ | |||||
</div> | </div> | ||||
<a class="dark-links cancel-comment hide"> {{ _("Cancel") }} </a> | <a class="dark-links cancel-comment hide"> {{ _("Cancel") }} </a> | ||||
<div class="btn btn-md btn-default submit-discussion pull-right mb-1"> | |||||
<div class="btn btn-sm btn-default submit-discussion pull-right mb-1"> | |||||
{{ _("Post") }} | {{ _("Post") }} | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -4,8 +4,6 @@ frappe.ready(() => { | |||||
add_color_to_avatars(); | add_color_to_avatars(); | ||||
expand_first_discussion(); | |||||
$(".search-field").keyup((e) => { | $(".search-field").keyup((e) => { | ||||
search_topic(e); | search_topic(e); | ||||
}); | }); | ||||
@@ -14,11 +12,11 @@ frappe.ready(() => { | |||||
show_new_topic_modal(e); | show_new_topic_modal(e); | ||||
}); | }); | ||||
$("#login-from-discussion").click((e) => { | |||||
$(".login-from-discussion").click((e) => { | |||||
login_from_discussion(e); | login_from_discussion(e); | ||||
}); | }); | ||||
$(".sidebar-topic").click((e) => { | |||||
$(".sidebar-parent").click((e) => { | |||||
if ($(e.currentTarget).attr("aria-expanded") == "true") { | if ($(e.currentTarget).attr("aria-expanded") == "true") { | ||||
e.stopPropagation(); | e.stopPropagation(); | ||||
} | } | ||||
@@ -31,17 +29,6 @@ frappe.ready(() => { | |||||
} | } | ||||
}); | }); | ||||
$(document).on("input", ".discussion-on-page .comment-field", (e) => { | |||||
if ($(e.currentTarget).val()) { | |||||
$(e.currentTarget).css("height", "48px"); | |||||
$(".cancel-comment").removeClass("hide").addClass("show"); | |||||
$(e.currentTarget).css("height", $(e.currentTarget).prop("scrollHeight")); | |||||
} else { | |||||
$(".cancel-comment").removeClass("show").addClass("hide"); | |||||
$(e.currentTarget).css("height", "48px"); | |||||
} | |||||
}); | |||||
$(document).on("click", ".submit-discussion", (e) => { | $(document).on("click", ".submit-discussion", (e) => { | ||||
submit_discussion(e); | submit_discussion(e); | ||||
}); | }); | ||||
@@ -50,16 +37,26 @@ frappe.ready(() => { | |||||
clear_comment_box(); | clear_comment_box(); | ||||
}); | }); | ||||
if ($(document).width() <= 550) { | |||||
$(document).on("click", ".sidebar-parent", () => { | |||||
hide_sidebar(); | |||||
}); | |||||
} | |||||
$(document).on("click", ".sidebar-parent", () => { | |||||
hide_sidebar(); | |||||
}); | |||||
$(document).on("click", ".back", (e) => { | |||||
$(document).on("click", ".back-button", (e) => { | |||||
back_to_sidebar(e); | back_to_sidebar(e); | ||||
}); | }); | ||||
$(document).on("click", ".dismiss-reply", (e) => { | |||||
dismiss_reply(e); | |||||
}); | |||||
$(document).on("click", ".reply-card .dropdown-menu", (e) => { | |||||
perform_action(e); | |||||
}); | |||||
$(document).on("input", ".discussion-on-page .comment-field", (e) => { | |||||
adjust_comment_box(e); | |||||
}); | |||||
}); | }); | ||||
const show_new_topic_modal = (e) => { | const show_new_topic_modal = (e) => { | ||||
@@ -79,10 +76,17 @@ const setup_socket_io = () => { | |||||
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); | ||||
frappe.socketio.socket.on("publish_message", (data) => { | frappe.socketio.socket.on("publish_message", (data) => { | ||||
publish_message(data); | publish_message(data); | ||||
}); | }); | ||||
frappe.socketio.socket.on("update_message", (data) => { | |||||
update_message(data); | |||||
}); | |||||
frappe.socketio.socket.on("delete_message", (data) => { | |||||
delete_message(data); | |||||
}); | |||||
}); | }); | ||||
}; | }; | ||||
@@ -92,44 +96,47 @@ const publish_message = (data) => { | |||||
const topic = data.topic_info; | const topic = data.topic_info; | ||||
const single_thread = $(".is-single-thread").length; | const single_thread = $(".is-single-thread").length; | ||||
const first_topic = !$(".reply-card").length; | const first_topic = !$(".reply-card").length; | ||||
const document_match_found = doctype == topic.reference_doctype && docname == topic.reference_docname; | |||||
const document_match_found = (doctype == topic.reference_doctype) && (docname == topic.reference_docname); | |||||
post_message_cleanup(); | |||||
data.template = hide_actions_on_conditions(data.template, data.reply_owner); | |||||
data.template = style_avatar_frame(data.template); | |||||
data.sidebar = style_avatar_frame(data.sidebar); | |||||
data.new_topic_template = style_avatar_frame(data.new_topic_template); | |||||
if ($(`.discussion-on-page[data-topic=${topic.name}]`).length) { | if ($(`.discussion-on-page[data-topic=${topic.name}]`).length) { | ||||
post_message_cleanup(); | |||||
data.template = style_avatar_frame(data.template); | |||||
$('<div class="card-divider-dark mb-8"></div>' + data.template) | |||||
.insertBefore(`.discussion-on-page[data-topic=${topic.name}] .discussion-form`); | |||||
$(data.template).insertBefore(`.discussion-on-page[data-topic=${topic.name}] .discussion-form`); | |||||
} else if (!first_topic && !single_thread && document_match_found) { | } else if (!first_topic && !single_thread && document_match_found) { | ||||
post_message_cleanup(); | |||||
data.new_topic_template = style_avatar_frame(data.new_topic_template); | |||||
$(data.sidebar).insertAfter(`.discussions-sidebar .form-group`); | |||||
$(data.sidebar).insertBefore($(`.discussions-sidebar .sidebar-parent`).first()); | |||||
$(`#discussion-group`).prepend(data.new_topic_template); | $(`#discussion-group`).prepend(data.new_topic_template); | ||||
if (topic.owner == frappe.session.user) { | if (topic.owner == frappe.session.user) { | ||||
$(".discussion-on-page") && $(".discussion-on-page").collapse(); | $(".discussion-on-page") && $(".discussion-on-page").collapse(); | ||||
$(".sidebar-topic").first().click(); | |||||
$(".sidebar-parent").first().click(); | |||||
} | } | ||||
} else if (single_thread && document_match_found) { | } else if (single_thread && document_match_found) { | ||||
post_message_cleanup(); | |||||
data.template = style_avatar_frame(data.template); | |||||
$(data.template).insertBefore(`.discussion-form`); | $(data.template).insertBefore(`.discussion-form`); | ||||
$(".discussion-on-page").attr("data-topic", topic.name); | $(".discussion-on-page").attr("data-topic", topic.name); | ||||
} else if (topic.owner == frappe.session.user && document_match_found) { | } else if (topic.owner == frappe.session.user && document_match_found) { | ||||
post_message_cleanup(); | |||||
window.location.reload(); | window.location.reload(); | ||||
} | } | ||||
update_reply_count(topic.name); | update_reply_count(topic.name); | ||||
}; | }; | ||||
const update_message = (data) => { | |||||
const reply_card = $(`[data-reply=${data.reply_name}]`); | |||||
reply_card.find(".reply-body").removeClass("hide"); | |||||
reply_card.find(".reply-edit-card").addClass("hide"); | |||||
reply_card.find(".reply-text").html(data.reply); | |||||
reply_card.find(".reply-actions").addClass("hide"); | |||||
}; | |||||
const post_message_cleanup = () => { | const post_message_cleanup = () => { | ||||
$(".topic-title").val(""); | $(".topic-title").val(""); | ||||
$(".comment-field").val(""); | |||||
$(".discussion-on-page .comment-field").css("height", "48px"); | |||||
$(".discussion-form .comment-field").val(""); | |||||
$("#discussion-modal").modal("hide"); | $("#discussion-modal").modal("hide"); | ||||
$("#no-discussions").addClass("hide"); | $("#no-discussions").addClass("hide"); | ||||
$(".cancel-comment").addClass("hide"); | $(".cancel-comment").addClass("hide"); | ||||
@@ -141,15 +148,6 @@ const update_reply_count = (topic) => { | |||||
$(`[data-target='#t${topic}']`).find(".reply-count").text(reply_count); | $(`[data-target='#t${topic}']`).find(".reply-count").text(reply_count); | ||||
}; | }; | ||||
const expand_first_discussion = () => { | |||||
if ($(document).width() > 550) { | |||||
$($(".discussions-parent .collapse")[0]).addClass("show"); | |||||
$($(".discussions-sidebar [data-toggle='collapse']")[0]).attr("aria-expanded", true); | |||||
} else { | |||||
$("#discussion-group").addClass("hide"); | |||||
} | |||||
}; | |||||
const search_topic = (e) => { | const search_topic = (e) => { | ||||
let input = $(e.currentTarget).val(); | let input = $(e.currentTarget).val(); | ||||
@@ -160,7 +158,7 @@ const search_topic = (e) => { | |||||
} | } | ||||
topics.each((i, elem) => { | topics.each((i, elem) => { | ||||
let topic_id = $(elem).parent().attr("data-target"); | |||||
let topic_id = $(elem).closest(".sidebar-parent").attr("data-target"); | |||||
/* Check match in replies */ | /* Check match in replies */ | ||||
let match_in_reply = false; | let match_in_reply = false; | ||||
@@ -201,16 +199,20 @@ const submit_discussion = (e) => { | |||||
e.preventDefault(); | e.preventDefault(); | ||||
e.stopImmediatePropagation(); | e.stopImmediatePropagation(); | ||||
const target = $(e.currentTarget); | |||||
const reply_name = target.closest(".reply-card").data("reply"); | |||||
const title = $(".topic-title:visible").length ? $(".topic-title:visible").val().trim() : ""; | const title = $(".topic-title:visible").length ? $(".topic-title:visible").val().trim() : ""; | ||||
const reply = $(".comment-field:visible").val().trim(); | |||||
let reply = reply_name ? target.closest(".reply-card") : target.closest(".discussion-form"); | |||||
reply = reply.find(".comment-field").val().trim(); | |||||
if (reply) { | if (reply) { | ||||
let doctype = $(e.currentTarget).closest(".discussions-parent").attr("data-doctype"); | |||||
let doctype = target.closest(".discussions-parent").attr("data-doctype"); | |||||
doctype = doctype ? decodeURIComponent(doctype) : doctype; | doctype = doctype ? decodeURIComponent(doctype) : doctype; | ||||
let docname = $(e.currentTarget).closest(".discussions-parent").attr("data-docname"); | |||||
let docname = target.closest(".discussions-parent").attr("data-docname"); | |||||
docname = docname ? decodeURIComponent(docname) : docname; | docname = docname ? decodeURIComponent(docname) : docname; | ||||
frappe.call({ | frappe.call({ | ||||
method: "frappe.website.doctype.discussion_topic.discussion_topic.submit_discussion", | method: "frappe.website.doctype.discussion_topic.discussion_topic.submit_discussion", | ||||
args: { | args: { | ||||
@@ -218,7 +220,8 @@ const submit_discussion = (e) => { | |||||
"docname": docname ? docname : "", | "docname": docname ? docname : "", | ||||
"reply": reply, | "reply": reply, | ||||
"title": title, | "title": title, | ||||
"topic_name": $(e.currentTarget).closest(".discussion-on-page").attr("data-topic") | |||||
"topic_name": target.closest(".discussion-on-page").attr("data-topic"), | |||||
"reply_name": reply_name | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
@@ -252,18 +255,64 @@ const style_avatar_frame = (template) => { | |||||
}; | }; | ||||
const clear_comment_box = () => { | const clear_comment_box = () => { | ||||
$(".discussion-on-page .comment-field").val(""); | |||||
$(".discussion-form .comment-field").val(""); | |||||
$(".cancel-comment").removeClass("show").addClass("hide"); | $(".cancel-comment").removeClass("show").addClass("hide"); | ||||
$(".discussion-on-page .comment-field").css("height", "48px"); | |||||
}; | }; | ||||
const hide_sidebar = () => { | const hide_sidebar = () => { | ||||
$(".discussions-sidebar").addClass("hide"); | $(".discussions-sidebar").addClass("hide"); | ||||
$("#discussion-group").removeClass("hide"); | $("#discussion-group").removeClass("hide"); | ||||
$(".search-field").addClass("hide"); | |||||
$(".reply").addClass("hide"); | |||||
}; | }; | ||||
const back_to_sidebar = () => { | const back_to_sidebar = () => { | ||||
$(".discussions-sidebar").removeClass("hide"); | $(".discussions-sidebar").removeClass("hide"); | ||||
$("#discussion-group").addClass("hide"); | $("#discussion-group").addClass("hide"); | ||||
$(".discussion-on-page").collapse("hide"); | $(".discussion-on-page").collapse("hide"); | ||||
$(".search-field").removeClass("hide"); | |||||
$(".reply").removeClass("hide"); | |||||
}; | |||||
const perform_action = (e) => { | |||||
const action = $(e.target).data().action; | |||||
const reply_card = $(e.target).closest(".reply-card"); | |||||
if (action === "edit") { | |||||
reply_card.find(".reply-edit-card").removeClass("hide"); | |||||
reply_card.find(".reply-body").addClass("hide"); | |||||
reply_card.find(".reply-actions").removeClass("hide"); | |||||
} else if (action === "delete") { | |||||
frappe.call({ | |||||
method: "frappe.website.doctype.discussion_reply.discussion_reply.delete_message", | |||||
args: { | |||||
"reply_name": $(e.target).closest(".reply-card").data("reply") | |||||
} | |||||
}); | |||||
} | |||||
}; | |||||
const dismiss_reply = (e) => { | |||||
const reply_card = $(e.currentTarget).closest(".reply-card"); | |||||
reply_card.find(".reply-edit-card").addClass("hide"); | |||||
reply_card.find(".reply-body").removeClass("hide"); | |||||
reply_card.find(".reply-actions").addClass("hide"); | |||||
}; | |||||
const adjust_comment_box = (e) => { | |||||
if ($(e.currentTarget).val()) { | |||||
$(".cancel-comment").removeClass("hide").addClass("show"); | |||||
} else { | |||||
$(".cancel-comment").removeClass("show").addClass("hide"); | |||||
} | |||||
}; | |||||
const hide_actions_on_conditions = (template, owner) => { | |||||
let $template = $(template); | |||||
frappe.session.user != owner && $template.find(".dropdown").addClass("hide"); | |||||
return $template.prop("outerHTML"); | |||||
}; | |||||
const delete_message = (data) => { | |||||
$(`[data-reply=${data.reply_name}]`).addClass("hide"); | |||||
}; | }; |
@@ -9,25 +9,31 @@ | |||||
<div class="discussions-header"> | <div class="discussions-header"> | ||||
<span class="discussion-heading">{{ _(title) }}</span> | <span class="discussion-heading">{{ _(title) }}</span> | ||||
{% if topics | length and not single_thread %} | |||||
{% include "frappe/templates/discussions/search.html" %} | |||||
{% endif %} | |||||
{% if topics and not single_thread %} | {% if topics and not single_thread %} | ||||
{% include "frappe/templates/discussions/button.html" %} | {% include "frappe/templates/discussions/button.html" %} | ||||
{% endif %} | {% endif %} | ||||
</div> | </div> | ||||
<div class="card-style thread-card {% if topics | length and not single_thread %} discussions-card {% endif %} | |||||
{% if not topics | length %} empty-state {% endif %}"> | |||||
<div class=""> | |||||
{% if topics and not single_thread %} | {% if topics and not single_thread %} | ||||
<div class="discussions-sidebar"> | |||||
{% include "frappe/templates/discussions/search.html" %} | |||||
<div class="discussions-sidebar card-style"> | |||||
{% for topic in topics %} | {% for topic in topics %} | ||||
{% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name})%} | {% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name})%} | ||||
{% include "frappe/templates/discussions/sidebar.html" %} | {% include "frappe/templates/discussions/sidebar.html" %} | ||||
{% if loop.index != topics | length %} | |||||
<div class="card-divider"></div> | |||||
{% endif %} | |||||
{% endfor %} | {% endfor %} | ||||
</div> | </div> | ||||
<div class="mr-2" id="discussion-group"> | |||||
<div class="hide" id="discussion-group"> | |||||
{% for topic in topics %} | {% for topic in topics %} | ||||
{% include "frappe/templates/discussions/reply_section.html" %} | {% include "frappe/templates/discussions/reply_section.html" %} | ||||
{% endfor %} | {% endfor %} | ||||
@@ -38,19 +44,25 @@ | |||||
{% include "frappe/templates/discussions/reply_section.html" %} | {% include "frappe/templates/discussions/reply_section.html" %} | ||||
{% else %} | {% else %} | ||||
<div class="no-discussions"> | |||||
<img class="icon icon-xl" src="/assets/frappe/icons/timeless/message.svg"> | |||||
<div class="discussion-heading mt-4 mb-0" style="color: inherit;"> {{ empty_state_title }} </div> | |||||
<div class="small mb-6"> {{ empty_state_subtitle }} </div> | |||||
{% if frappe.session.user == "Guest" %} | |||||
<div class="btn btn-default btn-md mt-3" id="login-from-discussion"> {{ _("Login") }} </div> | |||||
{% elif condition is defined and not condition %} | |||||
<div class="btn btn-default btn-md mt-3" id="login-from-discussion" data-redirect="{{ redirect_to }}"> | |||||
{{ button_name }} | |||||
<div class="empty-state"> | |||||
<div> | |||||
<img class="icon icon-xl" src="/assets/frappe/icons/timeless/message.svg"> | |||||
</div> | |||||
<div class="empty-state-text"> | |||||
<div class="empty-state-heading">{{ empty_state_title }}</div> | |||||
<div class="course-meta">{{ empty_state_subtitle }}</div> | |||||
</div> | |||||
<div> | |||||
{% if frappe.session.user == "Guest" %} | |||||
<div class="btn btn-default btn-md login-from-discussion"> {{ _("Login") }} </div> | |||||
{% elif condition is defined and not condition %} | |||||
<div class="btn btn-default btn-md login-from-discussion" data-redirect="{{ redirect_to }}"> | |||||
{{ button_name }} | |||||
</div> | |||||
{% else %} | |||||
{% include "frappe/templates/discussions/button.html" %} | |||||
{% endif %} | |||||
</div> | </div> | ||||
{% else %} | |||||
{% include "frappe/templates/discussions/button.html" %} | |||||
{% endif %} | |||||
</div> | </div> | ||||
{% endif %} | {% endif %} | ||||
</div> | </div> | ||||
@@ -1,14 +1,50 @@ | |||||
{% from "frappe/templates/includes/avatar_macro.html" import avatar %} | {% from "frappe/templates/includes/avatar_macro.html" import avatar %} | ||||
<div class="reply-card"> | |||||
<div class="reply-card" data-reply="{{ reply.name }}"> | |||||
{% set member = frappe.db.get_value("User", reply.owner, ["name", "full_name", "username"], as_dict=True) %} | {% set member = frappe.db.get_value("User", reply.owner, ["name", "full_name", "username"], as_dict=True) %} | ||||
<div class="d-flex align-items-center small mb-2"> | |||||
{% if loop.index == 1 or single_thread %} | |||||
<div class="reply-header"> | |||||
{{ avatar(reply.owner) }} | {{ avatar(reply.owner) }} | ||||
{% endif %} | |||||
<a class="button-links {% if loop.index == 1 or single_thread %} ml-2 {% endif %}" {% if get_profile_url %} href="{{ get_profile_url(member.username) }}" {% endif %}> | |||||
<a class="button-links topic-author ml-4" | |||||
{% if get_profile_url %} href="{{ get_profile_url(member.username) }}" {% endif %}> | |||||
{{ member.full_name }} | {{ member.full_name }} | ||||
</a> | </a> | ||||
<div class="ml-3 frappe-timestamp" data-timestamp="{{ reply.creation }}"> {{ frappe.utils.pretty_date(reply.creation) }} </div> | |||||
<div class="ml-2 frappe-timestamp small" data-timestamp="{{ reply.creation }}"> {{ frappe.utils.pretty_date(reply.creation) }} </div> | |||||
<div class="reply-actions hide"> | |||||
<div class="submit-discussion mr-2"> {{ _("Post") }} </div> | |||||
<div class="dismiss-reply"> {{ _("Dismiss") }} </div> | |||||
</div> | |||||
</div> | |||||
<div class="reply-body"> | |||||
{% if frappe.session.user == reply.owner %} | |||||
<div class="dropdown"> | |||||
<svg class="icon icon-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | |||||
<use xlink:href="#icon-dot-horizontal"></use> | |||||
</svg> | |||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton"> | |||||
<li> | |||||
<a class="dropdown-item small" data-action="edit"> {{ _("Edit") }} </a> | |||||
</li> | |||||
{% if index != 1 %} | |||||
<li> | |||||
<a class="dropdown-item small" data-action="delete"> {{ _("Delete") }} </a> | |||||
</li> | |||||
{% endif %} | |||||
</ul> | |||||
</div> | |||||
{% endif %} | |||||
<div class="reply-text">{{ frappe.utils.md_to_html(reply.reply) }}</div> | |||||
</div> | |||||
<div class="reply-edit-card hide"> | |||||
<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" spellcheck="false">{{ reply.reply }}</textarea> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
<div class="reply-text">{{ frappe.utils.md_to_html(reply.reply) }}</div> | |||||
</div> | </div> | ||||
@@ -1,43 +1,52 @@ | |||||
{% if topic %} | {% if topic %} | ||||
{% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name}, | {% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name}, | ||||
["reply", "owner", "creation"], order_by="creation")%} | |||||
["reply", "owner", "creation", "name"], order_by="creation")%} | |||||
{% endif %} | {% endif %} | ||||
<div class="collapse discussion-on-page" data-parent="#discussion-group" | |||||
<div class=" {% if not single_thread %} collapse {% endif %} discussion-on-page card-style" data-parent="#discussion-group" | |||||
{% if topic %} id="t{{ topic.name }}" data-topic="{{ topic.name }}" {% endif %}> | {% if topic %} id="t{{ topic.name }}" data-topic="{{ topic.name }}" {% endif %}> | ||||
{% if not single_thread %} | |||||
<div class="btn btn-md btn-default ellipsis back"> | |||||
{{ _("Back") }} | |||||
</div> | |||||
{% endif %} | |||||
<div class="reply-section-header"> | |||||
{% if not single_thread %} | |||||
<div class="back-button"> | |||||
<svg class="icon icon-md mr-0"> | |||||
<use class="" href="#icon-left"></use> | |||||
</svg> | |||||
</div> | |||||
{% endif %} | |||||
{% if topic and topic.title %} | |||||
<div class="discussion-heading p-0">{{ topic.title }}</div> | |||||
{% endif %} | |||||
{% if topic and topic.title %} | |||||
<div class="discussion-heading p-0">{{ topic.title }}</div> | |||||
{% endif %} | |||||
</div> | |||||
{% for reply in replies %} | {% for reply in replies %} | ||||
{% set index = loop.index %} | |||||
{% include "frappe/templates/discussions/reply_card.html" %} | {% include "frappe/templates/discussions/reply_card.html" %} | ||||
{% if loop.index != replies | length %} | |||||
<div class="card-divider-dark mb-8"></div> | |||||
{% endif %} | |||||
{% endfor %} | {% endfor %} | ||||
{% if frappe.session.user == "Guest" or (condition is defined and not condition) %} | {% if frappe.session.user == "Guest" or (condition is defined and not condition) %} | ||||
<div class="d-flex flex-column align-items-center small"> | |||||
{{ _("Want to join the discussion?") }} | |||||
{% if frappe.session.user == "Guest" %} | |||||
<div class="btn btn-default btn-md mt-3 mb-3" id="login-from-discussion">{{ _("Login") }}</div> | |||||
{% elif not condition %} | |||||
<div class="btn btn-default btn-md mt-3 mb-3" id="login-from-discussion" data-redirect="{{ redirect_to }}">{{ button_name }} | |||||
<div class="empty-state"> | |||||
<div> | |||||
<img class="icon icon-xl" src="/assets/frappe/icons/timeless/message.svg"> | |||||
</div> | |||||
<div class="empty-state-text"> | |||||
<div class="empty-state-heading">{{ _("Want to discuss?") }}</div> | |||||
<div class="course-meta">{{ _("Post it here, our mentors will help you out.") }}</div> | |||||
</div> | |||||
<div> | |||||
{% if frappe.session.user == "Guest" %} | |||||
<div class="btn btn-default btn-md login-from-discussion"> {{ _("Login") }} </div> | |||||
{% elif condition is defined and not condition %} | |||||
<div class="btn btn-default btn-md login-from-discussion" data-redirect="{{ redirect_to }}"> | |||||
{{ button_name }} | |||||
</div> | |||||
{% endif %} | |||||
</div> | </div> | ||||
{% endif %} | |||||
</div> | </div> | ||||
{% else %} | {% else %} | ||||
{% include "frappe/templates/discussions/comment_box.html" %} | {% include "frappe/templates/discussions/comment_box.html" %} | ||||
{% endif %} | {% endif %} | ||||
</div> | </div> |
@@ -1,9 +1,2 @@ | |||||
<div class="form-group"> | |||||
<div class="control-input-wrapper"> | |||||
<div class="control-input"> | |||||
<input type="text" autocomplete="off" class="input-with-feedback form-control search-field" | |||||
data-fieldtype="Text" data-fieldname="feedback_comments" placeholder="Search {{ title }}" | |||||
spellcheck="false"></input> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<input type="text" autocomplete="off" class="search-field" data-fieldtype="Text" | |||||
data-fieldname="feedback_comments" placeholder="Search {{ title }}" spellcheck="false"></input> |
@@ -1,19 +1,24 @@ | |||||
<div class="sidebar-parent"> | |||||
<div class="sidebar-topic" data-target="#t{{ topic.name }}" data-toggle="collapse" aria-expanded="false"> | |||||
{% from "frappe/templates/includes/avatar_macro.html" import avatar %} | |||||
{% set creator = frappe.db.get_value("User", topic.owner, ["name", "username", "full_name", "user_image"], as_dict=True) %} | |||||
<div class="sidebar-parent" data-target="#t{{ topic.name }}" data-toggle="collapse" aria-expanded="false"> | |||||
<div class="mr-4"> | |||||
{{ avatar(creator.name, size="avatar-medium") }} | |||||
</div> | |||||
<div class="flex-grow-1"> | |||||
<div class="discussion-topic-title">{{ topic.title }}</div> | <div class="discussion-topic-title">{{ topic.title }}</div> | ||||
<div class="sidebar-info"> | |||||
{% set creator = frappe.get_doc("User", topic.owner) %} | |||||
<span class="reply-author ml-0"> | |||||
{{ creator.full_name }} | |||||
</span> | |||||
<span class="small d-flex"> | |||||
<span class="mr-2 d-flex align-items-center"> | |||||
<div class="sidebar-topic"> | |||||
<svg class="icon icon-md m-0 mr-2"> | |||||
<use class="" href="#icon-reply"></use> | |||||
</svg> | |||||
<div class="topic-author">{{ creator.full_name }}</div> | |||||
<div class="ml-2 frappe-timestamp small" data-timestamp="{{ topic.creation }}"> {{ frappe.utils.pretty_date(topic.creation) }} </div> | |||||
<div class="ml-auto"> | |||||
<span class="d-flex align-items-center"> | |||||
<img class="mr-1" src="/assets/frappe/icons/timeless/message.svg"> | <img class="mr-1" src="/assets/frappe/icons/timeless/message.svg"> | ||||
<span class="reply-count">{{ replies | length }}</span> | <span class="reply-count">{{ replies | length }}</span> | ||||
</span> | </span> | ||||
<span> {{ frappe.utils.format_date(topic.creation, "dd MMM YYYY") }} </span> | |||||
</span> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="card-divider"></div> | |||||
</div> | </div> |
@@ -1,25 +1,10 @@ | |||||
.thread-card { | |||||
flex-direction: column; | |||||
padding: 1rem; | |||||
} | |||||
.thread-card .form-control { | |||||
background-color: #FFFFFF; | |||||
font-size: inherit; | |||||
color: inherit; | |||||
padding: 0.75rem 1rem; | |||||
border-radius: 4px; | |||||
resize: none; | |||||
} | |||||
.modal .comment-field { | .modal .comment-field { | ||||
height: 300px; | height: 300px; | ||||
resize: none; | resize: none; | ||||
} | } | ||||
.discussion-on-page .comment-field { | .discussion-on-page .comment-field { | ||||
height: 48px; | |||||
box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.2); | |||||
padding: 1rem; | |||||
} | } | ||||
.modal .cancel-comment { | .modal .cancel-comment { | ||||
@@ -31,61 +16,49 @@ | |||||
} | } | ||||
.cancel-comment { | .cancel-comment { | ||||
font-size: 0.75rem; | |||||
font-size: var(--text-sm); | |||||
margin-right: 0.5rem; | margin-right: 0.5rem; | ||||
cursor: pointer; | cursor: pointer; | ||||
} | } | ||||
.no-discussions { | |||||
width: 500px; | |||||
margin: 0 auto; | |||||
text-align: center; | |||||
} | |||||
.no-discussions .button { | |||||
margin: auto; | |||||
} | |||||
.discussions-header { | .discussions-header { | ||||
margin: 2.5rem 0 1.25rem; | margin: 2.5rem 0 1.25rem; | ||||
display: flex; | |||||
align-items: center; | |||||
} | |||||
@media (max-width: 500px) { | |||||
.discussions-header { | |||||
flex-direction: column; | |||||
align-items: inherit; | |||||
} | |||||
} | } | ||||
.discussions-header .button { | .discussions-header .button { | ||||
float: right; | float: right; | ||||
} | } | ||||
.discussions-parent .search-field { | |||||
background-color: #E2E6E9; | |||||
.search-field { | |||||
background-image: url(/assets/frappe/icons/timeless/search.svg); | background-image: url(/assets/frappe/icons/timeless/search.svg); | ||||
background-repeat: no-repeat; | background-repeat: no-repeat; | ||||
text-indent: 1.5rem; | 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; | |||||
background-position: 1rem 0.65rem; | |||||
font-size: var(--text-md); | |||||
padding: 0.5rem 1rem; | |||||
border: 1px solid var(--dark-border-color); | |||||
border-radius: var(--border-radius-md); | |||||
margin-right: 0.5rem; | |||||
} | } | ||||
@media (max-width: 550px) { | |||||
.discussions-sidebar { | |||||
padding: 1rem; | |||||
@media (max-width: 500px) { | |||||
.search-field { | |||||
margin: 0.75rem 0; | |||||
} | } | ||||
} | } | ||||
.sidebar-topic { | .sidebar-topic { | ||||
padding: 0.75rem; | |||||
margin: 0.75rem 0; | |||||
cursor: pointer; | |||||
} | |||||
.sidebar-topic[aria-expanded="true"] { | |||||
background: #FFFFFF; | |||||
border-radius: 4px; | |||||
display: flex; | |||||
align-items: center; | |||||
} | } | ||||
.comment-footer { | .comment-footer { | ||||
@@ -95,23 +68,46 @@ | |||||
} | } | ||||
.reply-card { | .reply-card { | ||||
margin-bottom: 2rem; | |||||
margin-bottom: 1.5rem; | |||||
} | } | ||||
.discussions-parent .collapsing { | |||||
transition: height 0s; | |||||
.reply-card .dropdown { | |||||
float: right; | |||||
} | } | ||||
.discussion-topic-title { | .discussion-topic-title { | ||||
color: var(--gray-900); | |||||
color: var(--text-color); | |||||
font-size: var(--text-lg); | |||||
font-weight: 600; | |||||
margin-bottom: 0.5rem; | |||||
} | } | ||||
.discussion-on-page .topic-title { | .discussion-on-page .topic-title { | ||||
display: none; | display: none; | ||||
} | } | ||||
.discussions-sidebar .sidebar-parent:last-child .card-divider { | |||||
display: none; | |||||
.discussion-on-page { | |||||
flex-direction: column; | |||||
padding: 1.5rem; | |||||
} | |||||
.submit-discussion { | |||||
cursor: pointer; | |||||
} | |||||
.reply-body { | |||||
background: var(--bg-color); | |||||
padding: 1rem; | |||||
border-radius: var(--border-radius); | |||||
font-size: var(--text-md); | |||||
color: var(--text-color); | |||||
} | |||||
.reply-actions { | |||||
display: flex; | |||||
align-items: center; | |||||
font-size: var(--text-sm); | |||||
margin-left: auto; | |||||
} | } | ||||
.reply-text h1 { | .reply-text h1 { | ||||
@@ -130,6 +126,10 @@ | |||||
font-size: 1rem; | font-size: 1rem; | ||||
} | } | ||||
.reply-text p { | |||||
margin-bottom: 0; | |||||
} | |||||
.sidebar-info { | .sidebar-info { | ||||
margin-top: 0.5rem; | margin-top: 0.5rem; | ||||
display: flex; | display: flex; | ||||
@@ -139,12 +139,11 @@ | |||||
.discussion-heading { | .discussion-heading { | ||||
font-weight: 600; | font-weight: 600; | ||||
font-size: 22px; | |||||
font-size: var(--text-3xl); | |||||
line-height: 146%; | line-height: 146%; | ||||
letter-spacing: -0.0175em; | letter-spacing: -0.0175em; | ||||
color: var(--gray-900); | |||||
margin-bottom: 1rem; | |||||
padding: 0 1rem; | |||||
color: var(--text-color); | |||||
flex-grow: 1; | |||||
} | } | ||||
.card-style { | .card-style { | ||||
@@ -152,7 +151,7 @@ | |||||
background: white; | background: white; | ||||
border-radius: 8px; | border-radius: 8px; | ||||
position: relative; | position: relative; | ||||
border: 1px solid var(--gray-200); | |||||
box-shadow: var(--shadow-sm); | |||||
} | } | ||||
.discussions-card { | .discussions-card { | ||||
@@ -179,48 +178,93 @@ | |||||
} | } | ||||
} | } | ||||
@media (max-width: 550px) { | |||||
.back { | |||||
margin-top: 0.5rem; | |||||
margin-bottom: 1rem; | |||||
.back-button { | |||||
margin-right: 1rem; | |||||
cursor: pointer; | |||||
} | |||||
.reply-author { | |||||
display: flex; | |||||
align-items: center; | |||||
margin: 0px 8px; | |||||
font-size: var(--text-sm); | |||||
line-height: 135%; | |||||
color: var(--text-color); | |||||
} | |||||
.discussions-header .btn { | |||||
float: right; | |||||
} | |||||
.empty-state { | |||||
background: var(--control-bg); | |||||
border-radius: var(--border-radius-lg); | |||||
padding: 2rem; | |||||
display: flex; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
} | |||||
.empty-state-text { | |||||
flex: 1; | |||||
margin-left: 1.25rem; | |||||
} | |||||
.empty-state-heading { | |||||
font-size: var(--text-xl); | |||||
color: var(--text-color); | |||||
font-weight: 600; | |||||
} | |||||
.sidebar-parent { | |||||
display: flex; | |||||
align-items: center; | |||||
padding: 1.25rem; | |||||
cursor: pointer; | |||||
} | |||||
@media (max-width: 500px) { | |||||
.sidebar-parent { | |||||
padding: 0.5rem; | |||||
} | } | ||||
} | } | ||||
@media (min-width: 550px) { | |||||
.back { | |||||
display: none; | |||||
@media (max-width: 400px) { | |||||
.sidebar-parent { | |||||
font-size: var(--text-sm); | |||||
} | } | ||||
} | } | ||||
.reply-author { | |||||
margin: 0px 8px; | |||||
font-size: 12px; | |||||
line-height: 135%; | |||||
color: var(--gray-900); | |||||
.topic-author { | |||||
color: var(--text-light); | |||||
font-weight: 500; | |||||
} | } | ||||
.card-divider { | |||||
border-top: 1px solid var(--gray-200); | |||||
margin-bottom: 1rem; | |||||
.reply-section-header { | |||||
display: flex; | |||||
align-items: center; | |||||
margin-bottom: 2.5rem; | |||||
} | } | ||||
.card-divider-dark { | |||||
border-top: 1px solid var(--gray-300); | |||||
.reply-header { | |||||
display: flex; | |||||
align-items: center; | |||||
margin-bottom: 1rem; | margin-bottom: 1rem; | ||||
} | } | ||||
.empty-state { | |||||
background: var(--gray-200); | |||||
border: 1px dashed var(--gray-400); | |||||
box-sizing: border-box; | |||||
border-radius: 8px; | |||||
padding: 2.5rem; | |||||
.dismiss-reply { | |||||
cursor: pointer; | |||||
} | } | ||||
.discussions-parent .btn-default { | |||||
color: var(--gray-700); | |||||
.discussions-sidebar { | |||||
flex-direction: column; | |||||
} | } | ||||
.discussions-header .btn { | |||||
float: right; | |||||
.card-divider { | |||||
border-top: 1px solid var(--dark-border-color); | |||||
margin-bottom: 0; | |||||
} | |||||
.reply-body .dropdown-menu { | |||||
min-width: 7rem; | |||||
} | } |
@@ -203,39 +203,40 @@ def create_data_for_discussions(): | |||||
def create_web_page(title, route, single_thread): | def create_web_page(title, route, single_thread): | ||||
web_page = frappe.db.exists("Web Page", {"route": route}) | web_page = frappe.db.exists("Web Page", {"route": route}) | ||||
if not web_page: | |||||
web_page = frappe.get_doc({ | |||||
if web_page: | |||||
return web_page | |||||
web_page = frappe.get_doc({ | |||||
"doctype": "Web Page", | "doctype": "Web Page", | ||||
"title": title, | "title": title, | ||||
"route": route, | "route": route, | ||||
"published": True | "published": True | ||||
}) | }) | ||||
web_page.save() | |||||
web_page.append("page_blocks", { | |||||
"web_template": "Discussions", | |||||
"web_template_values": frappe.as_json({ | |||||
"title": "Discussions", | |||||
"cta_title": "New Discussion", | |||||
"docname": web_page.name, | |||||
"single_thread": single_thread | |||||
}) | |||||
web_page.save() | |||||
web_page.append("page_blocks", { | |||||
"web_template": "Discussions", | |||||
"web_template_values": frappe.as_json({ | |||||
"title": "Discussions", | |||||
"cta_title": "New Discussion", | |||||
"docname": web_page.name, | |||||
"single_thread": single_thread | |||||
}) | }) | ||||
web_page.save() | |||||
}) | |||||
web_page.save() | |||||
return web_page | |||||
return web_page.name | |||||
def create_topic_and_reply(web_page): | def create_topic_and_reply(web_page): | ||||
topic = frappe.db.exists("Discussion Topic",{ | topic = frappe.db.exists("Discussion Topic",{ | ||||
"reference_doctype": "Web Page", | "reference_doctype": "Web Page", | ||||
"reference_docname": web_page.name | |||||
"reference_docname": web_page | |||||
}) | }) | ||||
if not topic: | if not topic: | ||||
topic = frappe.get_doc({ | topic = frappe.get_doc({ | ||||
"doctype": "Discussion Topic", | "doctype": "Discussion Topic", | ||||
"reference_doctype": "Web Page", | "reference_doctype": "Web Page", | ||||
"reference_docname": web_page.name, | |||||
"reference_docname": web_page, | |||||
"title": "Test Topic" | "title": "Test Topic" | ||||
}) | }) | ||||
topic.save() | topic.save() | ||||
@@ -274,7 +275,6 @@ def update_child_table(name): | |||||
doc.save() | doc.save() | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def insert_doctype_with_child_table_record(name): | def insert_doctype_with_child_table_record(name): | ||||
if frappe.db.get_all(name, {'title': 'Test Grid Search'}): | if frappe.db.get_all(name, {'title': 'Test Grid Search'}): | ||||
@@ -1,12 +1,21 @@ | |||||
# Copyright (c) 2021, FOSS United and contributors | |||||
# Copyright (c) 2021, Frappe Technologies and contributors | |||||
# For license information, please see license.txt | # For license information, please see license.txt | ||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class DiscussionReply(Document): | class DiscussionReply(Document): | ||||
def after_insert(self): | |||||
def on_update(self): | |||||
frappe.publish_realtime( | |||||
event="update_message", | |||||
message = { | |||||
"reply": frappe.utils.md_to_html(self.reply), | |||||
"reply_name": self.name | |||||
}, | |||||
after_commit=True) | |||||
def after_insert(self): | |||||
replies = frappe.db.count("Discussion Reply", {"topic": self.topic}) | replies = frappe.db.count("Discussion Reply", {"topic": self.topic}) | ||||
topic_info = frappe.get_all("Discussion Topic", | topic_info = frappe.get_all("Discussion Topic", | ||||
{"name": self.topic}, | {"name": self.topic}, | ||||
@@ -37,6 +46,19 @@ class DiscussionReply(Document): | |||||
"template": template, | "template": template, | ||||
"topic_info": topic_info[0], | "topic_info": topic_info[0], | ||||
"sidebar": sidebar, | "sidebar": sidebar, | ||||
"new_topic_template": new_topic_template | |||||
"new_topic_template": new_topic_template, | |||||
"reply_owner": self.owner | |||||
}, | }, | ||||
after_commit=True) | after_commit=True) | ||||
def after_delete(self): | |||||
frappe.publish_realtime( | |||||
event="delete_message", | |||||
message = { | |||||
"reply_name": self.name | |||||
}, | |||||
after_commit=True) | |||||
@frappe.whitelist() | |||||
def delete_message(reply_name): | |||||
frappe.delete_doc("Discussion Reply", reply_name, ignore_permissions=True) |
@@ -8,7 +8,14 @@ class DiscussionTopic(Document): | |||||
pass | pass | ||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def submit_discussion(doctype, docname, reply, title, topic_name=None): | |||||
def submit_discussion(doctype, docname, reply, title, topic_name=None, reply_name=None): | |||||
if reply_name: | |||||
doc = frappe.get_doc("Discussion Reply", reply_name) | |||||
doc.reply = reply | |||||
doc.save(ignore_permissions=True) | |||||
return | |||||
if topic_name: | if topic_name: | ||||
save_message(reply, topic_name) | save_message(reply, topic_name) | ||||
return topic_name | return topic_name | ||||