masonry tile for image communities

This commit is contained in:
rimu 2024-01-21 15:44:13 +13:00
parent f140d0369e
commit af98706610
13 changed files with 351 additions and 28 deletions

View file

@ -47,14 +47,14 @@ class EditCommunityForm(FlaskForm):
icon_file = FileField(_('Icon image')) icon_file = FileField(_('Icon image'))
banner_file = FileField(_('Banner image')) banner_file = FileField(_('Banner image'))
rules = TextAreaField(_l('Rules')) rules = TextAreaField(_l('Rules'))
nsfw = BooleanField('Porn community') nsfw = BooleanField(_l('Porn community'))
local_only = BooleanField('Only accept posts from current instance') local_only = BooleanField(_l('Only accept posts from current instance'))
restricted_to_mods = BooleanField('Only moderators can post') restricted_to_mods = BooleanField(_l('Only moderators can post'))
new_mods_wanted = BooleanField('New moderators wanted') new_mods_wanted = BooleanField(_l('New moderators wanted'))
show_home = BooleanField('Posts show on home page') show_home = BooleanField(_l('Posts show on home page'))
show_popular = BooleanField('Posts can be popular') show_popular = BooleanField(_l('Posts can be popular'))
show_all = BooleanField('Posts show in All list') show_all = BooleanField(_l('Posts show in All list'))
low_quality = BooleanField("Low quality / toxic - upvotes in here don't add to reputation") low_quality = BooleanField(_l("Low quality / toxic - upvotes in here don't add to reputation"))
options = [(-1, _l('Forever')), options = [(-1, _l('Forever')),
(7, _l('1 week')), (7, _l('1 week')),
(14, _l('2 weeks')), (14, _l('2 weeks')),
@ -69,6 +69,10 @@ class EditCommunityForm(FlaskForm):
] ]
content_retention = SelectField(_l('Retain content'), choices=options, default=1, coerce=int) content_retention = SelectField(_l('Retain content'), choices=options, default=1, coerce=int)
topic = SelectField(_l('Topic'), coerce=int, validators=[Optional()]) topic = SelectField(_l('Topic'), coerce=int, validators=[Optional()])
layouts = [('', _l('List')),
('masonry', _l('Masonry')),
('masonry_wide', _l('Wide masonry'))]
default_layout = SelectField(_l('Layout'), coerce=str, choices=layouts, validators=[Optional()])
submit = SubmitField(_l('Save')) submit = SubmitField(_l('Save'))
def validate(self, extra_validators=None): def validate(self, extra_validators=None):
@ -93,8 +97,8 @@ class EditTopicForm(FlaskForm):
class EditUserForm(FlaskForm): class EditUserForm(FlaskForm):
about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)]) about = TextAreaField(_l('Bio'), validators=[Optional(), Length(min=3, max=5000)])
matrix_user_id = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)]) matrix_user_id = StringField(_l('Matrix User ID'), validators=[Optional(), Length(max=255)])
profile_file = FileField(_('Avatar image')) profile_file = FileField(_l('Avatar image'))
banner_file = FileField(_('Top banner image')) banner_file = FileField(_l('Top banner image'))
bot = BooleanField(_l('This profile is a bot')) bot = BooleanField(_l('This profile is a bot'))
newsletter = BooleanField(_l('Subscribe to email newsletter')) newsletter = BooleanField(_l('Subscribe to email newsletter'))
ignore_bots = BooleanField(_l('Hide posts by bots')) ignore_bots = BooleanField(_l('Hide posts by bots'))

View file

@ -241,6 +241,7 @@ def admin_community_edit(community_id):
community.low_quality = form.low_quality.data community.low_quality = form.low_quality.data
community.content_retention = form.content_retention.data community.content_retention = form.content_retention.data
community.topic_id = form.topic.data if form.topic.data != 0 else None community.topic_id = form.topic.data if form.topic.data != 0 else None
community.default_layout = form.default_layout.data
icon_file = request.files['icon_file'] icon_file = request.files['icon_file']
if icon_file and icon_file.filename != '': if icon_file and icon_file.filename != '':
if community.icon_id: if community.icon_id:
@ -276,6 +277,7 @@ def admin_community_edit(community_id):
form.low_quality.data = community.low_quality form.low_quality.data = community.low_quality
form.content_retention.data = community.content_retention form.content_retention.data = community.content_retention
form.topic.data = community.topic_id if community.topic_id else None form.topic.data = community.topic_id if community.topic_id else None
form.default_layout.data = community.default_layout
return render_template('admin/edit_community.html', title=_('Edit community'), form=form, community=community, return render_template('admin/edit_community.html', title=_('Edit community'), form=form, community=community,
moderating_communities=moderating_communities(current_user.get_id()), moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()) joined_communities=joined_communities(current_user.get_id())

View file

@ -105,6 +105,8 @@ def show_community(community: Community):
page = request.args.get('page', 1, type=int) page = request.args.get('page', 1, type=int)
sort = request.args.get('sort', '' if current_user.is_anonymous else current_user.default_sort) sort = request.args.get('sort', '' if current_user.is_anonymous else current_user.default_sort)
low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1'
post_layout = request.args.get('layout', community.default_layout if not low_bandwidth else '')
# If nothing has changed since their last visit, return HTTP 304 # If nothing has changed since their last visit, return HTTP 304
current_etag = f"{community.id}{sort}_{hash(community.last_active)}" current_etag = f"{community.id}{sort}_{hash(community.last_active)}"
@ -138,7 +140,12 @@ def show_community(community: Community):
posts = posts.order_by(desc(Post.posted_at)) posts = posts.order_by(desc(Post.posted_at))
elif sort == 'active': elif sort == 'active':
posts = posts.order_by(desc(Post.last_active)) posts = posts.order_by(desc(Post.last_active))
posts = posts.paginate(page=page, per_page=100, error_out=False) per_page = 100
if post_layout == 'masonry':
per_page = 200
elif post_layout == 'masonry_wide':
per_page = 300
posts = posts.paginate(page=page, per_page=per_page, error_out=False)
if community.topic_id: if community.topic_id:
related_communities = Community.query.filter_by(topic_id=community.topic_id).\ related_communities = Community.query.filter_by(topic_id=community.topic_id).\
@ -159,11 +166,11 @@ def show_community(community: Community):
og_image=og_image, POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK, SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING, og_image=og_image, POST_TYPE_IMAGE=POST_TYPE_IMAGE, POST_TYPE_LINK=POST_TYPE_LINK, SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING,
SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR,
etag=f"{community.id}{sort}_{hash(community.last_active)}", related_communities=related_communities, etag=f"{community.id}{sort}_{hash(community.last_active)}", related_communities=related_communities,
next_url=next_url, prev_url=prev_url, low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', next_url=next_url, prev_url=prev_url, low_bandwidth=low_bandwidth,
rss_feed=f"https://{current_app.config['SERVER_NAME']}/community/{community.link()}/feed", rss_feed_name=f"{community.title} posts on PieFed", rss_feed=f"https://{current_app.config['SERVER_NAME']}/community/{community.link()}/feed", rss_feed_name=f"{community.title} posts on PieFed",
content_filters=content_filters, moderating_communities=moderating_communities(current_user.get_id()), content_filters=content_filters, moderating_communities=moderating_communities(current_user.get_id()),
joined_communities=joined_communities(current_user.get_id()), sort=sort, joined_communities=joined_communities(current_user.get_id()), sort=sort,
inoculation=inoculation[randint(0, len(inoculation) - 1)]) inoculation=inoculation[randint(0, len(inoculation) - 1)], post_layout=post_layout)
# RSS feed of the community # RSS feed of the community

View file

@ -146,6 +146,7 @@ class Community(db.Model):
private_key = db.Column(db.Text) private_key = db.Column(db.Text)
content_retention = db.Column(db.Integer, default=-1) content_retention = db.Column(db.Integer, default=-1)
topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'), index=True) topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'), index=True)
default_layout = db.Column(db.String(15))
ap_id = db.Column(db.String(255), index=True) ap_id = db.Column(db.String(255), index=True)
ap_profile_id = db.Column(db.String(255), index=True) ap_profile_id = db.Column(db.String(255), index=True)

View file

@ -597,6 +597,83 @@ fieldset legend {
font-size: 80%; font-size: 80%;
} }
.post_list_masonry, .post_list_masonry_wide {
-webkit-column-count: 2;
-moz-column-count: 2;
column-count: 2;
-webkit-column-gap: 5px;
-moz-column-gap: 5px;
column-gap: 5px;
clear: both;
}
@media (min-width: 992px) {
.post_list_masonry, .post_list_masonry_wide {
-webkit-column-count: 4;
-moz-column-count: 4;
column-count: 4;
-webkit-column-gap: 5px;
-moz-column-gap: 5px;
column-gap: 5px;
}
}
.post_list_masonry .post_teaser, .post_list_masonry_wide .post_teaser {
margin-bottom: 5px;
position: relative;
}
.post_list_masonry .post_teaser img, .post_list_masonry_wide .post_teaser img {
width: 100%;
height: auto;
}
.post_list_masonry .post_teaser .masonry_info, .post_list_masonry_wide .post_teaser .masonry_info {
position: absolute;
bottom: 0;
background-color: rgba(0, 0, 0, 0.2);
width: 100%;
text-align: center;
}
.post_list_masonry .post_teaser .masonry_info p, .post_list_masonry_wide .post_teaser .masonry_info p {
margin-bottom: 0;
}
.post_list_masonry .post_teaser .masonry_info p a, .post_list_masonry_wide .post_teaser .masonry_info p a {
color: white;
text-decoration: none;
line-height: 40px;
}
@media (min-width: 1280px) {
.post_list_masonry .post_teaser .masonry_info p a, .post_list_masonry_wide .post_teaser .masonry_info p a {
line-height: 30px;
}
}
.post_list_masonry .post_teaser .masonry_info_no_image, .post_list_masonry_wide .post_teaser .masonry_info_no_image {
background-color: rgba(0, 0, 0, 0.2);
width: 100%;
text-align: center;
}
.post_list_masonry .post_teaser .masonry_info_no_image p, .post_list_masonry_wide .post_teaser .masonry_info_no_image p {
margin-bottom: 0;
}
.post_list_masonry .post_teaser .masonry_info_no_image p a, .post_list_masonry_wide .post_teaser .masonry_info_no_image p a {
color: var(--bs-body-color);
text-decoration: none;
}
@media (min-width: 992px) {
.post_list_masonry_wide {
-webkit-column-count: 5;
-moz-column-count: 5;
column-count: 5;
-webkit-column-gap: 5px;
-moz-column-gap: 5px;
column-gap: 5px;
}
}
@media (min-width: 992px) {
.layout_switcher {
float: right;
}
}
.url_thumbnail { .url_thumbnail {
float: right; float: right;
margin-top: -6px; margin-top: -6px;

View file

@ -218,6 +218,86 @@ nav, etc which are used site-wide */
} }
} }
.post_list_masonry, .post_list_masonry_wide {
-webkit-column-count: 2;
-moz-column-count: 2;
column-count: 2;
-webkit-column-gap: 5px;
-moz-column-gap: 5px;
column-gap: 5px;
clear: both;
@include breakpoint(tablet) {
-webkit-column-count: 4;
-moz-column-count: 4;
column-count: 4;
-webkit-column-gap: 5px;
-moz-column-gap: 5px;
column-gap: 5px;
}
.post_teaser {
margin-bottom: 5px;
position: relative;
img {
width: 100%;
height: auto;
}
.masonry_info {
position: absolute;
bottom: 0;
background-color: rgba(0, 0, 0, 0.2);
width: 100%;
text-align: center;
p {
margin-bottom: 0;
a {
color: white;
text-decoration: none;
line-height: 40px;
@include breakpoint(laptop) {
line-height: 30px;
}
}
}
}
.masonry_info_no_image {
background-color: rgba(0, 0, 0, 0.2);
width: 100%;
text-align: center;
p {
margin-bottom: 0;
a {
color: var(--bs-body-color);
text-decoration: none;
}
}
}
}
}
.post_list_masonry_wide {
@include breakpoint(tablet) {
-webkit-column-count: 5;
-moz-column-count: 5;
column-count: 5;
-webkit-column-gap: 5px;
-moz-column-gap: 5px;
column-gap: 5px;
}
}
.layout_switcher {
@include breakpoint(tablet) {
float: right;
}
}
.url_thumbnail { .url_thumbnail {
float: right; float: right;
margin-top: -6px; margin-top: -6px;

View file

@ -46,6 +46,7 @@
{{ render_field(form.low_quality) }} {{ render_field(form.low_quality) }}
{{ render_field(form.content_retention) }} {{ render_field(form.content_retention) }}
{{ render_field(form.topic) }} {{ render_field(form.topic) }}
{{ render_field(form.default_layout) }}
{% if not community.is_local() %} {% if not community.is_local() %}
</fieldset> </fieldset>
{% endif %} {% endif %}

View file

@ -94,7 +94,7 @@
<!-- Page content --> <!-- Page content -->
{% block navbar %} {% block navbar %}
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="container-lg"> <div class="{{ 'container-lg' if post_layout != 'masonry_wide' else 'container-fluid' }}">
<a class="navbar-brand" href="/">{% if not low_bandwidth %}<img src="/static/images/logo2.png" alt="Logo" width="50" height="50" />{% endif %}{{ g.site.name }}</a> <a class="navbar-brand" href="/">{% if not low_bandwidth %}<img src="/static/images/logo2.png" alt="Logo" width="50" height="50" />{% endif %}{{ g.site.name }}</a>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<a class="nav-link d-lg-none" href="/notifications" aria-label="{{ _('Notifications') }}"> <a class="nav-link d-lg-none" href="/notifications" aria-label="{{ _('Notifications') }}">
@ -191,7 +191,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="outer_container" class="container-lg flex-shrink-0"> <div id="outer_container" class="{{ 'container-lg' if post_layout != 'masonry_wide' else 'container-fluid' }} flex-shrink-0">
{% with messages = get_flashed_messages(with_categories=True) %} {% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %} {% if messages %}
{% for category, message in messages %} {% for category, message in messages %}

View file

@ -14,4 +14,17 @@
<a href="?sort=active" class="btn {{ 'btn-primary' if sort == 'active' else 'btn-outline-secondary' }}" rel="nofollow"> <a href="?sort=active" class="btn {{ 'btn-primary' if sort == 'active' else 'btn-outline-secondary' }}" rel="nofollow">
{{ _('Active') }} {{ _('Active') }}
</a> </a>
</div> </div>
{% if post_layout != '' %}
<div class="btn-group mt-1 mb-2 layout_switcher">
<a href="?layout=list" class="btn {{ 'btn-primary' if post_layout == '' or post_layout == 'list' else 'btn-outline-secondary' }}" rel="nofollow">
{{ _('List') }}
</a>
<a href="?layout=masonry" class="btn {{ 'btn-primary' if post_layout == 'masonry' else 'btn-outline-secondary' }}" rel="nofollow">
{{ _('Tile') }}
</a>
<a href="?layout=masonry_wide" class="btn {{ 'btn-primary' if post_layout == 'masonry_wide' else 'btn-outline-secondary' }}" rel="nofollow">
{{ _('Wide tile') }}
</a>
</div>
{% endif %}

View file

@ -64,13 +64,23 @@
</h1> </h1>
{% endif %} {% endif %}
{% include "community/_community_nav.html" %} {% include "community/_community_nav.html" %}
<div class="post_list"> {% if post_layout == 'masonry' or post_layout == 'masonry_wide' %}
{% for post in posts %} <div class="post_list_{{ post_layout }}">
{% include 'post/_post_teaser.html' %} {% for post in posts %}
{% else %} {% include 'post/_post_teaser_masonry.html' %}
<p>{{ _('No posts in this community yet.') }}</p> {% else %}
{% endfor %} <p>{{ _('No posts in this community yet.') }}</p>
</div> {% endfor %}
</div>
{% else %}
<div class="post_list">
{% for post in posts %}
{% include 'post/_post_teaser.html' %}
{% else %}
<p>{{ _('No posts in this community yet.') }}</p>
{% endfor %}
</div>
{% endif %}
<nav aria-label="Pagination" class="mt-4"> <nav aria-label="Pagination" class="mt-4">
{% if prev_url %} {% if prev_url %}

View file

@ -16,15 +16,15 @@
<div class="thumbnail"> <div class="thumbnail">
{% if post.type == POST_TYPE_LINK %} {% if post.type == POST_TYPE_LINK %}
<a href="{{ post.url }}" rel="nofollow ugc" target="_blank" aria-label="{{ _('Read article') }}"><img src="{{ post.image.thumbnail_url() }}" <a href="{{ post.url }}" rel="nofollow ugc" target="_blank" aria-label="{{ _('Read article') }}"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" /></a> alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" loading="lazy" /></a>
{% elif post.type == POST_TYPE_IMAGE %} {% elif post.type == POST_TYPE_IMAGE %}
{% if post.image_id %} {% if post.image_id %}
<a href="{{ post.image.view_url() }}" rel="nofollow ugc" target="_blank"><img src="{{ post.image.thumbnail_url() }}" <a href="{{ post.image.view_url() }}" rel="nofollow ugc" target="_blank"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" /></a> alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" loading="lazy" /></a>
{% endif %} {% endif %}
{% else %} {% else %}
<a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}"><img src="{{ post.image.thumbnail_url() }}" <a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" /></a> alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" loading="lazy" /></a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@ -65,7 +65,5 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}

View file

@ -0,0 +1,98 @@
{% set content_blocked = post.blocked_by_content_filter(content_filters) %}
{% if content_blocked and content_blocked == '-1' %}
{# do nothing - blocked by keyword filter #}
{% else %}
<div class="post_teaser{{ ' reported' if post.reports and current_user.is_authenticated and post.community.is_moderator() }}{{ ' blocked' if content_blocked }}"
{% if content_blocked %} title="{{ _('Filtered: ') }}{{ content_blocked }}"{% endif %}>
{% if post.image_id %}
{% if post_layout == 'masonry' or low_bandwidth %}
{% set thumbnail = post.image.thumbnail_url() %}
{% elif post_layout == 'masonry_wide' %}
{% set thumbnail = post.image.view_url() %}
{% endif %}
<div class="masonry_thumb">
{% if post.type == POST_TYPE_LINK %}
<a href="{{ post.url }}" rel="nofollow ugc" target="_blank" aria-label="{{ _('Read article') }}"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" loading="lazy" /></a>
{% elif post.type == POST_TYPE_IMAGE %}
<a href="{{ post.image.view_url() }}" rel="nofollow ugc" target="_blank"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" loading="lazy" /></a>
{% else %}
<a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" loading="lazy" /></a>
{% endif %}
</div>
<div class="masonry_info">
<p><a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}">{{ post.title|shorten(25) }}</a></p>
</div>
{% else %}
<div class="masonry_info_no_image">
<p><a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}">{{ post.title }}</a></p>
</div>
{% endif %}
</div>
<!--
<div class="row">
<div class="col-12">
<div class="row main_row">
<div class="col">
<h3 class="post_teaser_title">
<div class="voting_buttons">
{% include "post/_post_voting_buttons.html" %}
</div>
{% if post.image_id and not low_bandwidth %}
<div class="thumbnail">
{% if post.type == POST_TYPE_LINK %}
<a href="{{ post.url }}" rel="nofollow ugc" target="_blank" aria-label="{{ _('Read article') }}"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" /></a>
{% elif post.type == POST_TYPE_IMAGE %}
{% if post.image_id %}
<a href="{{ post.image.view_url() }}" rel="nofollow ugc" target="_blank"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" /></a>
{% endif %}
{% else %}
<a href="{{ url_for('activitypub.post_ap', post_id=post.id) }}"><img src="{{ post.image.thumbnail_url() }}"
alt="{{ post.image.alt_text if post.image.alt_text else '' }}" height="50" /></a>
{% endif %}
</div>
{% endif %}
<a href="{{ url_for('activitypub.post_ap', post_id=post.id, sort='new' if sort == 'active' else None) }}" class="post_teaser_title_a">{{ post.title }}</a>
{% if post.type == POST_TYPE_IMAGE %}<span class="fe fe-image"> </span>{% endif %}
{% if post.type == POST_TYPE_LINK and post.domain_id %}
{% if post.url and 'youtube.com' in post.url %}
<span class="fe fe-video"></span>
{% endif %}
<a href="{{ post.url }}" rel="nofollow ugc" target="_blank" class="post_link">
<img src="/static/images/external_link_black.svg" class="external_link_icon" alt="External link" />
</a>
<span class="domain_link">(<a href="/d/{{ post.domain_id }}">{{ post.domain.name }}</a>)</span>
{% endif %}
{% if post.reports and current_user.is_authenticated and post.community.is_moderator(current_user) %}
<span class="red fe fe-report" title="{{ _('Reported. Check post for issues.') }}"></span>
{% endif %}
</h3>
<span class="small">{% if show_post_community %}<strong><a href="/c/{{ post.community.link() }}">c/{{ post.community.name }}</a></strong>{% endif %}
by {{ render_username(post.author) }} {{ moment(post.last_active if sort == 'active' else post.posted_at).fromNow() }}</span>
</div>
</div>
<div class="row utilities_row">
<div class="col-6">
<a href="{{ url_for('activitypub.post_ap', post_id=post.id, sort='new' if sort == 'active' else None, _anchor='post_replies') }}" aria-label="{{ _('View comments') }}"><span class="fe fe-reply"></span></a>
<a href="{{ url_for('activitypub.post_ap', post_id=post.id, sort='new' if sort == 'active' else None, _anchor='post_replies') }}">{{ post.reply_count }}</a>
{% if post.type == POST_TYPE_IMAGE %}
{% if post.image_id %}
<a href="{{ post.image.view_url() }}" rel="nofollow ugc" class="preview_image" aria-label="{{ _('View image') }}"><span class="fe fe-magnify"></span></a>
{% else %}
<a href="{{ post.url }}" rel="nofollow ugc" class="preview_image" target="_blank" aria-label="{{ _('View image') }}"><span class="fe fe-magnify"></span></a>
{% endif %}
{% endif %}
</div>
<div class="col-2"><a href="{{ url_for('post.post_options', post_id=post.id) }}" rel="nofollow" aria-label="{{ _('Options') }}"><span class="fe fe-options" title="Options"> </span></a></div>
</div>
</div>
</div>
</div>
-->
{% endif %}

View file

@ -0,0 +1,32 @@
"""community default layout
Revision ID: a4be1b198b0f
Revises: 6b4774eb6349
Create Date: 2024-01-21 14:22:02.450650
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a4be1b198b0f'
down_revision = '6b4774eb6349'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('community', schema=None) as batch_op:
batch_op.add_column(sa.Column('default_layout', sa.String(length=15), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('community', schema=None) as batch_op:
batch_op.drop_column('default_layout')
# ### end Alembic commands ###