feat: discussions component redesignversion-14
@@ -37,24 +37,24 @@ context('Discussions', () => { | |||
}; | |||
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.') | |||
.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.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'); | |||
}; | |||
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.') | |||
.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 = () => { | |||
@@ -62,13 +62,13 @@ context('Discussions', () => { | |||
cy.get('.discussions-sidebar').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.') | |||
.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.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'); | |||
}; | |||
@@ -88,6 +88,20 @@ | |||
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 { | |||
background-color: $dark; | |||
border-color: darken($primary, 40%); | |||
@@ -1,9 +1,6 @@ | |||
{% if frappe.session.user != "Guest" and | |||
(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) }} | |||
<!-- 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> | |||
{% endif %} |
@@ -28,7 +28,7 @@ | |||
</div> | |||
<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") }} | |||
</div> | |||
</div> | |||
@@ -4,8 +4,6 @@ frappe.ready(() => { | |||
add_color_to_avatars(); | |||
expand_first_discussion(); | |||
$(".search-field").keyup((e) => { | |||
search_topic(e); | |||
}); | |||
@@ -14,11 +12,11 @@ frappe.ready(() => { | |||
show_new_topic_modal(e); | |||
}); | |||
$("#login-from-discussion").click((e) => { | |||
$(".login-from-discussion").click((e) => { | |||
login_from_discussion(e); | |||
}); | |||
$(".sidebar-topic").click((e) => { | |||
$(".sidebar-parent").click((e) => { | |||
if ($(e.currentTarget).attr("aria-expanded") == "true") { | |||
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) => { | |||
submit_discussion(e); | |||
}); | |||
@@ -50,16 +37,26 @@ frappe.ready(() => { | |||
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); | |||
}); | |||
$(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) => { | |||
@@ -79,10 +76,17 @@ const setup_socket_io = () => { | |||
if (window.dev_server) { | |||
frappe.boot.socketio_port = "9000"; | |||
} | |||
frappe.socketio.init(9000); | |||
frappe.socketio.socket.on("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 single_thread = $(".is-single-thread").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) { | |||
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) { | |||
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); | |||
if (topic.owner == frappe.session.user) { | |||
$(".discussion-on-page") && $(".discussion-on-page").collapse(); | |||
$(".sidebar-topic").first().click(); | |||
$(".sidebar-parent").first().click(); | |||
} | |||
} else if (single_thread && document_match_found) { | |||
post_message_cleanup(); | |||
data.template = style_avatar_frame(data.template); | |||
$(data.template).insertBefore(`.discussion-form`); | |||
$(".discussion-on-page").attr("data-topic", topic.name); | |||
} else if (topic.owner == frappe.session.user && document_match_found) { | |||
post_message_cleanup(); | |||
window.location.reload(); | |||
} | |||
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 = () => { | |||
$(".topic-title").val(""); | |||
$(".comment-field").val(""); | |||
$(".discussion-on-page .comment-field").css("height", "48px"); | |||
$(".discussion-form .comment-field").val(""); | |||
$("#discussion-modal").modal("hide"); | |||
$("#no-discussions").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); | |||
}; | |||
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) => { | |||
let input = $(e.currentTarget).val(); | |||
@@ -160,7 +158,7 @@ const search_topic = (e) => { | |||
} | |||
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 */ | |||
let match_in_reply = false; | |||
@@ -201,16 +199,20 @@ const submit_discussion = (e) => { | |||
e.preventDefault(); | |||
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 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) { | |||
let doctype = $(e.currentTarget).closest(".discussions-parent").attr("data-doctype"); | |||
let doctype = target.closest(".discussions-parent").attr("data-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; | |||
frappe.call({ | |||
method: "frappe.website.doctype.discussion_topic.discussion_topic.submit_discussion", | |||
args: { | |||
@@ -218,7 +220,8 @@ const submit_discussion = (e) => { | |||
"docname": docname ? docname : "", | |||
"reply": reply, | |||
"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 = () => { | |||
$(".discussion-on-page .comment-field").val(""); | |||
$(".discussion-form .comment-field").val(""); | |||
$(".cancel-comment").removeClass("show").addClass("hide"); | |||
$(".discussion-on-page .comment-field").css("height", "48px"); | |||
}; | |||
const hide_sidebar = () => { | |||
$(".discussions-sidebar").addClass("hide"); | |||
$("#discussion-group").removeClass("hide"); | |||
$(".search-field").addClass("hide"); | |||
$(".reply").addClass("hide"); | |||
}; | |||
const back_to_sidebar = () => { | |||
$(".discussions-sidebar").removeClass("hide"); | |||
$("#discussion-group").addClass("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"> | |||
<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 %} | |||
{% include "frappe/templates/discussions/button.html" %} | |||
{% endif %} | |||
</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 %} | |||
<div class="discussions-sidebar"> | |||
{% include "frappe/templates/discussions/search.html" %} | |||
<div class="discussions-sidebar card-style"> | |||
{% for topic in topics %} | |||
{% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name})%} | |||
{% include "frappe/templates/discussions/sidebar.html" %} | |||
{% if loop.index != topics | length %} | |||
<div class="card-divider"></div> | |||
{% endif %} | |||
{% endfor %} | |||
</div> | |||
<div class="mr-2" id="discussion-group"> | |||
<div class="hide" id="discussion-group"> | |||
{% for topic in topics %} | |||
{% include "frappe/templates/discussions/reply_section.html" %} | |||
{% endfor %} | |||
@@ -38,19 +44,25 @@ | |||
{% include "frappe/templates/discussions/reply_section.html" %} | |||
{% 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> | |||
{% else %} | |||
{% include "frappe/templates/discussions/button.html" %} | |||
{% endif %} | |||
</div> | |||
{% endif %} | |||
</div> | |||
@@ -1,14 +1,50 @@ | |||
{% 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) %} | |||
<div class="d-flex align-items-center small mb-2"> | |||
{% if loop.index == 1 or single_thread %} | |||
<div class="reply-header"> | |||
{{ 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 }} | |||
</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 class="reply-text">{{ frappe.utils.md_to_html(reply.reply) }}</div> | |||
</div> | |||
@@ -1,43 +1,52 @@ | |||
{% if topic %} | |||
{% set replies = frappe.get_all("Discussion Reply", {"topic": topic.name}, | |||
["reply", "owner", "creation"], order_by="creation")%} | |||
["reply", "owner", "creation", "name"], order_by="creation")%} | |||
{% 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 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 %} | |||
{% set index = loop.index %} | |||
{% include "frappe/templates/discussions/reply_card.html" %} | |||
{% if loop.index != replies | length %} | |||
<div class="card-divider-dark mb-8"></div> | |||
{% endif %} | |||
{% endfor %} | |||
{% 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> | |||
{% endif %} | |||
</div> | |||
{% else %} | |||
{% include "frappe/templates/discussions/comment_box.html" %} | |||
{% endif %} | |||
</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="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"> | |||
<span class="reply-count">{{ replies | length }}</span> | |||
</span> | |||
<span> {{ frappe.utils.format_date(topic.creation, "dd MMM YYYY") }} </span> | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="card-divider"></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 { | |||
height: 300px; | |||
resize: none; | |||
} | |||
.discussion-on-page .comment-field { | |||
height: 48px; | |||
box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.2); | |||
padding: 1rem; | |||
} | |||
.modal .cancel-comment { | |||
@@ -31,61 +16,49 @@ | |||
} | |||
.cancel-comment { | |||
font-size: 0.75rem; | |||
font-size: var(--text-sm); | |||
margin-right: 0.5rem; | |||
cursor: pointer; | |||
} | |||
.no-discussions { | |||
width: 500px; | |||
margin: 0 auto; | |||
text-align: center; | |||
} | |||
.no-discussions .button { | |||
margin: auto; | |||
} | |||
.discussions-header { | |||
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 { | |||
float: right; | |||
} | |||
.discussions-parent .search-field { | |||
background-color: #E2E6E9; | |||
.search-field { | |||
background-image: url(/assets/frappe/icons/timeless/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; | |||
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 { | |||
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 { | |||
@@ -95,23 +68,46 @@ | |||
} | |||
.reply-card { | |||
margin-bottom: 2rem; | |||
margin-bottom: 1.5rem; | |||
} | |||
.discussions-parent .collapsing { | |||
transition: height 0s; | |||
.reply-card .dropdown { | |||
float: right; | |||
} | |||
.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 { | |||
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 { | |||
@@ -130,6 +126,10 @@ | |||
font-size: 1rem; | |||
} | |||
.reply-text p { | |||
margin-bottom: 0; | |||
} | |||
.sidebar-info { | |||
margin-top: 0.5rem; | |||
display: flex; | |||
@@ -139,12 +139,11 @@ | |||
.discussion-heading { | |||
font-weight: 600; | |||
font-size: 22px; | |||
font-size: var(--text-3xl); | |||
line-height: 146%; | |||
letter-spacing: -0.0175em; | |||
color: var(--gray-900); | |||
margin-bottom: 1rem; | |||
padding: 0 1rem; | |||
color: var(--text-color); | |||
flex-grow: 1; | |||
} | |||
.card-style { | |||
@@ -152,7 +151,7 @@ | |||
background: white; | |||
border-radius: 8px; | |||
position: relative; | |||
border: 1px solid var(--gray-200); | |||
box-shadow: var(--shadow-sm); | |||
} | |||
.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; | |||
} | |||
.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): | |||
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", | |||
"title": title, | |||
"route": route, | |||
"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): | |||
topic = frappe.db.exists("Discussion Topic",{ | |||
"reference_doctype": "Web Page", | |||
"reference_docname": web_page.name | |||
"reference_docname": web_page | |||
}) | |||
if not topic: | |||
topic = frappe.get_doc({ | |||
"doctype": "Discussion Topic", | |||
"reference_doctype": "Web Page", | |||
"reference_docname": web_page.name, | |||
"reference_docname": web_page, | |||
"title": "Test Topic" | |||
}) | |||
topic.save() | |||
@@ -274,7 +275,6 @@ def update_child_table(name): | |||
doc.save() | |||
@frappe.whitelist() | |||
def insert_doctype_with_child_table_record(name): | |||
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 | |||
import frappe | |||
from frappe.model.document import 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}) | |||
topic_info = frappe.get_all("Discussion Topic", | |||
{"name": self.topic}, | |||
@@ -37,6 +46,19 @@ class DiscussionReply(Document): | |||
"template": template, | |||
"topic_info": topic_info[0], | |||
"sidebar": sidebar, | |||
"new_topic_template": new_topic_template | |||
"new_topic_template": new_topic_template, | |||
"reply_owner": self.owner | |||
}, | |||
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 | |||
@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: | |||
save_message(reply, topic_name) | |||
return topic_name | |||