Improve comment system UX and product page layout
- **Comment Form Styling**: Unified button styling across reply/edit forms with new CSS classes - **Comment Actions**: Improved spacing, consistent button styling, and proper grid layout - **Comment Navigation**: Added anchor redirects for delete/approve/unapprove actions - **Product Layout**: Enhanced desktop two-column grid (2fr 1fr) with better proportions - **Mobile Optimization**: Fixed button width and section ordering for mobile view - **Template Consolidation**: Moved comments and description into main grid layout - **Auto-refresh Removal**: Removed disruptive timers from content pages
This commit is contained in:
parent
e67178abdb
commit
6745824fc3
7 changed files with 186 additions and 126 deletions
|
|
@ -299,6 +299,49 @@ button.mps-button-red {
|
|||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.mps-cancel-button,
|
||||
.mps-comment-form-button {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background: #f9f9f9;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
margin-bottom: 42px;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: repeat(auto-fit, max-content);
|
||||
justify-content: start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.reply-link,
|
||||
.edit-link,
|
||||
.delete-link,
|
||||
.moderate-link {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background: #f9f9f9;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a.product-edit-button {
|
||||
background-color: #98b6fa;
|
||||
}
|
||||
|
|
@ -685,10 +728,28 @@ textarea.markup-editor-textarea {
|
|||
@media (max-width: 800px) {
|
||||
/* the two-column is stacked by default. */
|
||||
section.two-column {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* On mobile, show price section before description */
|
||||
section.product-left {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
section.product-right {
|
||||
order: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Ensure buttons get full width on mobile */
|
||||
section.product-right .mps-button {
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
/* the cart-grid is stacked by default. */
|
||||
section.cart-grid {
|
||||
|
|
@ -725,13 +786,14 @@ textarea.markup-editor-textarea {
|
|||
/* We have the room to really break into 2 columns */
|
||||
section.two-column {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-auto-columns: max-content;
|
||||
grid-auto-flow: dense;
|
||||
gap: 0px 60px;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
justify-items: center;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
gap: 40px;
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 40px;
|
||||
padding-right: 40px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
/* if we have room, break markup-editor into 2 columns */
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
{% extends "base.j2" -%}
|
||||
|
||||
{% block content -%}
|
||||
|
||||
<section class="one-column">
|
||||
<section class="well">
|
||||
|
||||
<h3>Edit Comment</h3>
|
||||
{% block title %}Edit Comment{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h2>Edit Comment</h2>
|
||||
|
||||
<div class="original-comment" style="background-color: #f5f5f5; padding: 15px; border-left: 4px solid #007cba; margin-bottom: 20px;">
|
||||
<div class="comment-header">
|
||||
<strong>{{ comment.user.name if comment.user else "Anonymous" }}</strong>
|
||||
<span class="comment-date">{{ comment.ago_string }}</span>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
{{ comment.data_html | safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/comments/{{ comment.id }}/edit">
|
||||
|
||||
<label for="comment_data">Comment:</label>
|
||||
<textarea name="data" id="comment_data" rows="6" cols="60" required>{{ comment.data }}</textarea>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<input type="submit" name="submit" class="mps-submit" value="Update Comment" />
|
||||
<a href="{{ product.absolute_url(request) }}" class="mps-button">Cancel</a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div class="form-group">
|
||||
<label for="comment_data">Edit Your Comment:</label>
|
||||
<textarea name="data" id="comment_data" rows="6" cols="80" required placeholder="Edit your comment... (You can use Markdown formatting)">{{ comment.data }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="{{ product.absolute_url(request) }}" class="mps-cancel-button">Cancel</a>
|
||||
<input type="submit" value="Update Comment" class="mps-comment-form-button" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{%- endblock -%}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -19,13 +19,12 @@
|
|||
<form method="post" action="{{ request.route_url('comment_reply', comment_id=parent_comment.id) }}">
|
||||
<div class="form-group">
|
||||
<label for="comment_data">Your Reply:</label>
|
||||
<textarea name="data" id="comment_data" rows="6" cols="80" required placeholder="Write your reply..."></textarea>
|
||||
<small>You can use Markdown formatting.</small>
|
||||
<textarea name="data" id="comment_data" rows="6" cols="80" required placeholder="Write your reply... (You can use Markdown formatting)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="Post Reply" class="mps-submit" />
|
||||
<a href="{{ product.absolute_url(request) }}" class="mps-cancel">Cancel</a>
|
||||
<a href="{{ product.absolute_url(request) }}" class="mps-cancel-button">Cancel</a>
|
||||
<input type="submit" value="Post Reply" class="mps-comment-form-button" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,16 +17,7 @@
|
|||
<meta property="og:image" content="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ product.s3_path }}/{{ og_thumbnail }}?ts={{ product.updated_timestamp }}" />
|
||||
{%- endif %}
|
||||
|
||||
{% if product.has_product_file and signed_get_object_url is not none %}
|
||||
<script>
|
||||
// Simple web 1.5 refresh mechanism:
|
||||
// redirects to self every 9 minutes to get fresh signed & secure download URIs.
|
||||
// 540000 is 9 minutes in milliseconds.
|
||||
setTimeout(function() {
|
||||
window.location.href = window.location.href;
|
||||
}, 540000);
|
||||
</script>
|
||||
{% endif %}
|
||||
{# Removed auto-refresh timer - better UX to let download links expire than interrupt reading #}
|
||||
{%- endblock append_to_head_tag_section -%}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,16 +17,7 @@
|
|||
<meta property="og:image" content="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ product.s3_path }}/{{ og_thumbnail }}?ts={{ product.updated_timestamp }}" />
|
||||
{%- endif %}
|
||||
|
||||
{% if product.has_product_file and signed_get_object_url is not none %}
|
||||
<script>
|
||||
// Simple web 1.5 refresh mechanism:
|
||||
// redirects to self every 9 minutes to get fresh signed & secure download URIs.
|
||||
// 540000 is 9 minutes in milliseconds.
|
||||
setTimeout(function() {
|
||||
window.location.href = window.location.href;
|
||||
}, 540000);
|
||||
</script>
|
||||
{% endif %}
|
||||
{# Removed auto-refresh timer - better UX to let download links expire than interrupt reading #}
|
||||
{%- endblock append_to_head_tag_section -%}
|
||||
|
||||
{%- block call_to_action -%}
|
||||
|
|
@ -41,11 +32,7 @@
|
|||
|
||||
<section class="product-left">
|
||||
|
||||
<h1>{{ product.title }}</h1>
|
||||
<h3>
|
||||
sold by
|
||||
<a href="{{ product.shop.absolute_about_url(request) }}" rel="nofollow" class="shop-theme-link-color">{{ product.shop.name }}</a>
|
||||
</h3>
|
||||
<h1>{{ product.title }} <span style="font-size: 0.6em; font-weight: normal;">sold by <a href="{{ product.shop.absolute_about_url(request) }}" rel="nofollow" class="shop-theme-link-color">{{ product.shop.name }}</a></span></h1>
|
||||
|
||||
{% if "thumbnail1" in product.extensions %}
|
||||
<img src="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ product.s3_path }}/thumbnail1?ts={{ product.updated_timestamp }}" class="product-main" />
|
||||
|
|
@ -59,14 +46,47 @@
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<b>Description</b>
|
||||
<section>{{ product.description_html | safe }}</section>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
{% if product.is_bundle %}
|
||||
{% if product.products_in_this_bundle %}
|
||||
<b>Bundle Contents</b>
|
||||
<ul>
|
||||
{% for p in product.products_in_this_bundle %}
|
||||
<li>
|
||||
{{p.title}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% elif product.has_product_file %}
|
||||
<b>File Type</b>
|
||||
<br/>
|
||||
{{ product.get_content_type("product") }} {{ product_size }}
|
||||
<br/>
|
||||
Please make sure you have an application to open this file type.
|
||||
{% endif %}
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<!-- Comments Section -->
|
||||
{% include 'snippets/comments.j2' %}
|
||||
|
||||
<br/>
|
||||
<a href="/" class="product-edit-button mps-button">Back to shop</a>
|
||||
|
||||
</section>
|
||||
|
||||
<section class="product-right">
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<div class="well">
|
||||
<h1>${{ '{:,.2f}'.format(product.price) }}</h1>
|
||||
|
||||
|
|
@ -132,46 +152,5 @@
|
|||
|
||||
</section>
|
||||
|
||||
<section class="one-column" style="max-width: 960px;">
|
||||
|
||||
<br/>
|
||||
|
||||
<b>Description</b>
|
||||
<section>{{ product.description_html | safe }}</section>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
{% if product.is_bundle %}
|
||||
|
||||
{% if product.products_in_this_bundle %}
|
||||
<ul>
|
||||
{% for p in product.products_in_this_bundle %}
|
||||
<li>
|
||||
{{p.title}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% elif product.has_product_file %}
|
||||
<b>File Type</b>
|
||||
<br/>
|
||||
{{ product.get_content_type("product") }} {{ product_size }}
|
||||
<br/>
|
||||
Please make sure you have an application to open this file type.
|
||||
|
||||
{% endif %}
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<!-- Comments Section -->
|
||||
{% include 'snippets/comments.j2' %}
|
||||
|
||||
<br/>
|
||||
<a href="/" class="product-edit-button mps-button">Back to shop</a>
|
||||
|
||||
</section>
|
||||
|
||||
{%- endblock -%}
|
||||
|
|
|
|||
|
|
@ -17,30 +17,29 @@
|
|||
</div>
|
||||
|
||||
<div class="comment-actions">
|
||||
{% if request.user.authenticated and not comment.is_locked %}
|
||||
{% set can_comment, error_msg = comment.can_user_comment(request.user, shop) %}
|
||||
{% if can_comment %}
|
||||
<a href="/comments/{{ comment.id }}/reply" class="reply-link">Reply</a>
|
||||
{% if request.user.authenticated and (request.user.id == comment.user_id or comment.can_user_moderate(request.user, shop)) %}
|
||||
<input type="submit" value="Delete" class="delete-link" onclick="return confirm('Are you sure you want to delete this comment?')" form="delete-{{ comment.id }}" />
|
||||
<form id="delete-{{ comment.id }}" method="post" action="/comments/{{ comment.id }}/delete" style="display: none;"></form>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.authenticated and comment.can_user_moderate(request.user, shop) %}
|
||||
{% if comment.approved %}
|
||||
<input type="submit" value="Unapprove" class="moderate-link" form="unapprove-{{ comment.id }}" />
|
||||
<form id="unapprove-{{ comment.id }}" method="post" action="/comments/{{ comment.id }}/unapprove" style="display: none;"></form>
|
||||
{% else %}
|
||||
<input type="submit" value="Approve" class="moderate-link" form="approve-{{ comment.id }}" />
|
||||
<form id="approve-{{ comment.id }}" method="post" action="/comments/{{ comment.id }}/approve" style="display: none;"></form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.authenticated and (request.user.id == comment.user_id or comment.can_user_moderate(request.user, shop)) %}
|
||||
<a href="/comments/{{ comment.id }}/edit" class="edit-link">Edit</a>
|
||||
|
||||
<form method="post" action="/comments/{{ comment.id }}/delete" style="display: inline;">
|
||||
<input type="submit" value="Delete" class="delete-link" onclick="return confirm('Are you sure you want to delete this comment?')" />
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.authenticated and comment.can_user_moderate(request.user, shop) %}
|
||||
{% if comment.approved %}
|
||||
<form method="post" action="/comments/{{ comment.id }}/unapprove" style="display: inline;">
|
||||
<input type="submit" value="Unapprove" class="moderate-link" />
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" action="/comments/{{ comment.id }}/approve" style="display: inline;">
|
||||
<input type="submit" value="Approve" class="moderate-link" />
|
||||
</form>
|
||||
{% if request.user.authenticated and not comment.is_locked %}
|
||||
{% set can_comment, error_msg = comment.can_user_comment(request.user, shop) %}
|
||||
{% if can_comment %}
|
||||
<a href="/comments/{{ comment.id }}/reply" class="reply-link">Reply</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -84,7 +83,7 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<input type="submit" value="Post Comment" />
|
||||
<input type="submit" value="Post Comment" class="mps-comment-form-button" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -268,11 +268,23 @@ def comment_delete(request):
|
|||
|
||||
product_url = comment.product.absolute_url(request)
|
||||
|
||||
# Determine which comment to anchor to after deletion
|
||||
if comment.parent_id:
|
||||
# For replies, anchor to the parent comment
|
||||
anchor_comment_id = comment.parent_id
|
||||
else:
|
||||
# For root comments, just go to the product page
|
||||
anchor_comment_id = None
|
||||
|
||||
# Soft delete using disable method
|
||||
comment.disable()
|
||||
|
||||
request.session.flash(("Comment deleted successfully", "success"))
|
||||
return HTTPFound(location=product_url)
|
||||
|
||||
if anchor_comment_id:
|
||||
return HTTPFound(location=f"{product_url}#comment-{anchor_comment_id}")
|
||||
else:
|
||||
return HTTPFound(location=product_url)
|
||||
|
||||
|
||||
@view_config(route_name="comment_approve", request_method="POST")
|
||||
|
|
@ -294,7 +306,15 @@ def comment_approve(request):
|
|||
comment.stamp_updated_timestamp()
|
||||
|
||||
request.session.flash(("Comment approved", "success"))
|
||||
return HTTPFound(location=get_referer_or_home(request))
|
||||
|
||||
# Determine which comment to anchor to after approval
|
||||
product_url = comment.product.absolute_url(request)
|
||||
if comment.parent_id:
|
||||
# For replies, anchor to the parent comment
|
||||
return HTTPFound(location=f"{product_url}#comment-{comment.parent_id}")
|
||||
else:
|
||||
# For root comments, anchor to the comment itself
|
||||
return HTTPFound(location=f"{product_url}#comment-{comment.id}")
|
||||
|
||||
|
||||
@view_config(route_name="comment_unapprove", request_method="POST")
|
||||
|
|
@ -316,7 +336,15 @@ def comment_unapprove(request):
|
|||
comment.stamp_updated_timestamp()
|
||||
|
||||
request.session.flash(("Comment unapproved", "success"))
|
||||
return HTTPFound(location=get_referer_or_home(request))
|
||||
|
||||
# Determine which comment to anchor to after unapproval
|
||||
product_url = comment.product.absolute_url(request)
|
||||
if comment.parent_id:
|
||||
# For replies, anchor to the parent comment
|
||||
return HTTPFound(location=f"{product_url}#comment-{comment.parent_id}")
|
||||
else:
|
||||
# For root comments, anchor to the comment itself
|
||||
return HTTPFound(location=f"{product_url}#comment-{comment.id}")
|
||||
|
||||
|
||||
@view_config(route_name="comment_undelete", request_method="POST")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue