mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
poll ui, wip #181
This commit is contained in:
parent
9e003a5d8b
commit
526ac5260f
11 changed files with 259 additions and 40 deletions
|
@ -187,6 +187,41 @@ class CreateImageForm(FlaskForm):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePollForm(FlaskForm):
|
||||||
|
communities = SelectField(_l('Community'), validators=[DataRequired()], coerce=int, render_kw={'class': 'form-select'})
|
||||||
|
poll_title = StringField(_l('Title'), validators=[DataRequired(), Length(min=3, max=255)])
|
||||||
|
poll_body = TextAreaField(_l('Body'), validators=[Optional(), Length(min=3, max=5000)], render_kw={'rows': 5})
|
||||||
|
mode = SelectField(_('Mode'), validators=[DataRequired()], choices=[('single', _l('Single choice')), ('multiple', _l('Multiple choices'))])
|
||||||
|
finish_choices=[
|
||||||
|
('30m', _l('30 minutes')),
|
||||||
|
('1h', _l('1 hour')),
|
||||||
|
('6h', _l('6 hours')),
|
||||||
|
('12h', _l('12 hours')),
|
||||||
|
('1d', _l('1 day')),
|
||||||
|
('3d', _l('3 days')),
|
||||||
|
('7d', _l('7 days')),
|
||||||
|
]
|
||||||
|
finish_in = SelectField(_('End voting in'), validators=[DataRequired()], choices=finish_choices)
|
||||||
|
local_only = BooleanField(_l('Accept votes from this instance only'))
|
||||||
|
choice_1 = StringField('Choice') # intentionally left out of internationalization (no _l()) as this label is not used
|
||||||
|
choice_2 = StringField('Choice')
|
||||||
|
choice_3 = StringField('Choice')
|
||||||
|
choice_4 = StringField('Choice')
|
||||||
|
choice_5 = StringField('Choice')
|
||||||
|
choice_6 = StringField('Choice')
|
||||||
|
choice_7 = StringField('Choice')
|
||||||
|
choice_8 = StringField('Choice')
|
||||||
|
choice_9 = StringField('Choice')
|
||||||
|
choice_10 = StringField('Choice')
|
||||||
|
tags = StringField(_l('Tags'), validators=[Optional(), Length(min=3, max=5000)])
|
||||||
|
sticky = BooleanField(_l('Sticky'))
|
||||||
|
nsfw = BooleanField(_l('NSFW'))
|
||||||
|
nsfl = BooleanField(_l('Gore/gross'))
|
||||||
|
notify_author = BooleanField(_l('Notify about replies'))
|
||||||
|
language_id = SelectField(_l('Language'), validators=[DataRequired()], coerce=int, render_kw={'class': 'form-select'})
|
||||||
|
submit = SubmitField(_l('Save'))
|
||||||
|
|
||||||
|
|
||||||
class ReportCommunityForm(FlaskForm):
|
class ReportCommunityForm(FlaskForm):
|
||||||
reason_choices = [('1', _l('Breaks instance rules')),
|
reason_choices = [('1', _l('Breaks instance rules')),
|
||||||
('2', _l('Abandoned by moderators')),
|
('2', _l('Abandoned by moderators')),
|
||||||
|
|
|
@ -15,7 +15,7 @@ from app.chat.util import send_message
|
||||||
from app.community.forms import SearchRemoteCommunity, CreateDiscussionForm, CreateImageForm, CreateLinkForm, \
|
from app.community.forms import SearchRemoteCommunity, CreateDiscussionForm, CreateImageForm, CreateLinkForm, \
|
||||||
ReportCommunityForm, \
|
ReportCommunityForm, \
|
||||||
DeleteCommunityForm, AddCommunityForm, EditCommunityForm, AddModeratorForm, BanUserCommunityForm, \
|
DeleteCommunityForm, AddCommunityForm, EditCommunityForm, AddModeratorForm, BanUserCommunityForm, \
|
||||||
EscalateReportForm, ResolveReportForm, CreateVideoForm
|
EscalateReportForm, ResolveReportForm, CreateVideoForm, CreatePollForm
|
||||||
from app.community.util import search_for_community, actor_to_community, \
|
from app.community.util import search_for_community, actor_to_community, \
|
||||||
opengraph_parse, url_to_thumbnail_file, save_post, save_icon_file, save_banner_file, send_to_remote_instance, \
|
opengraph_parse, url_to_thumbnail_file, save_post, save_icon_file, save_banner_file, send_to_remote_instance, \
|
||||||
delete_post_from_community, delete_post_reply_from_community, community_in_list
|
delete_post_from_community, delete_post_reply_from_community, community_in_list
|
||||||
|
@ -769,6 +769,73 @@ def add_video_post(actor):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<actor>/submit_poll', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@validation_required
|
||||||
|
def add_poll_post(actor):
|
||||||
|
if current_user.banned:
|
||||||
|
return show_ban_message()
|
||||||
|
community = actor_to_community(actor)
|
||||||
|
|
||||||
|
form = CreatePollForm()
|
||||||
|
|
||||||
|
if g.site.enable_nsfl is False:
|
||||||
|
form.nsfl.render_kw = {'disabled': True}
|
||||||
|
if community.nsfw:
|
||||||
|
form.nsfw.data = True
|
||||||
|
form.nsfw.render_kw = {'disabled': True}
|
||||||
|
if community.nsfl:
|
||||||
|
form.nsfl.data = True
|
||||||
|
form.nsfw.render_kw = {'disabled': True}
|
||||||
|
if not(community.is_moderator() or community.is_owner() or current_user.is_admin()):
|
||||||
|
form.sticky.render_kw = {'disabled': True}
|
||||||
|
|
||||||
|
form.communities.choices = [(c.id, c.display_name()) for c in current_user.communities()]
|
||||||
|
if not community_in_list(community.id, form.communities.choices):
|
||||||
|
form.communities.choices.append((community.id, community.display_name()))
|
||||||
|
|
||||||
|
form.language_id.choices = languages_for_form()
|
||||||
|
|
||||||
|
if not can_create_post(current_user, community):
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
community = Community.query.get_or_404(form.communities.data)
|
||||||
|
if not can_create_post(current_user, community):
|
||||||
|
abort(401)
|
||||||
|
post = Post(user_id=current_user.id, community_id=form.communities.data, instance_id=1)
|
||||||
|
save_post(form, post, 'poll')
|
||||||
|
community.post_count += 1
|
||||||
|
community.last_active = g.site.last_active = utcnow()
|
||||||
|
db.session.commit()
|
||||||
|
post.ap_id = f"https://{current_app.config['SERVER_NAME']}/post/{post.id}"
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
upvote_own_post(post)
|
||||||
|
|
||||||
|
notify_about_post(post)
|
||||||
|
|
||||||
|
if not community.local_only:
|
||||||
|
federate_post(community, post)
|
||||||
|
federate_post_to_user_followers(post)
|
||||||
|
|
||||||
|
return redirect(f"/post/{post.id}")
|
||||||
|
else:
|
||||||
|
form.communities.data = community.id
|
||||||
|
form.notify_author.data = True
|
||||||
|
form.language_id.data = current_user.language_id if current_user.is_authenticated and current_user.language_id else english_language_id()
|
||||||
|
form.finish_in.data = '3d'
|
||||||
|
if community.posting_warning:
|
||||||
|
flash(community.posting_warning)
|
||||||
|
|
||||||
|
return render_template('community/add_poll_post.html', title=_('Add poll to community'), form=form, community=community,
|
||||||
|
markdown_editor=current_user.markdown_editor, low_bandwidth=False, actor=actor,
|
||||||
|
moderating_communities=moderating_communities(current_user.get_id()),
|
||||||
|
joined_communities=joined_communities(current_user.id),
|
||||||
|
inoculation=inoculation[randint(0, len(inoculation) - 1)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def federate_post(community, post):
|
def federate_post(community, post):
|
||||||
page = {
|
page = {
|
||||||
'type': 'Page',
|
'type': 'Page',
|
||||||
|
|
|
@ -12,9 +12,10 @@ from app import db, cache, celery
|
||||||
from app.activitypub.signature import post_request, default_context
|
from app.activitypub.signature import post_request, default_context
|
||||||
from app.activitypub.util import find_actor_or_create, actor_json_to_model, post_json_to_model, ensure_domains_match, \
|
from app.activitypub.util import find_actor_or_create, actor_json_to_model, post_json_to_model, ensure_domains_match, \
|
||||||
find_hashtag_or_create
|
find_hashtag_or_create
|
||||||
from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_VIDEO, NOTIF_POST
|
from app.constants import POST_TYPE_ARTICLE, POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_VIDEO, NOTIF_POST, \
|
||||||
|
POST_TYPE_POLL
|
||||||
from app.models import Community, File, BannedInstances, PostReply, PostVote, Post, utcnow, CommunityMember, Site, \
|
from app.models import Community, File, BannedInstances, PostReply, PostVote, Post, utcnow, CommunityMember, Site, \
|
||||||
Instance, Notification, User, ActivityPubLog, NotificationSubscription, Language, Tag
|
Instance, Notification, User, ActivityPubLog, NotificationSubscription, Language, Tag, PollChoice, Poll
|
||||||
from app.utils import get_request, gibberish, markdown_to_html, domain_from_url, allowlist_html, \
|
from app.utils import get_request, gibberish, markdown_to_html, domain_from_url, allowlist_html, \
|
||||||
is_image_url, ensure_directory_exists, inbox_domain, post_ranking, shorten_string, parse_page, \
|
is_image_url, ensure_directory_exists, inbox_domain, post_ranking, shorten_string, parse_page, \
|
||||||
remove_tracking_from_link, ap_datetime, instance_banned, blocked_phrases
|
remove_tracking_from_link, ap_datetime, instance_banned, blocked_phrases
|
||||||
|
@ -360,7 +361,10 @@ def save_post(form, post: Post, type: str):
|
||||||
db.session.add(file)
|
db.session.add(file)
|
||||||
|
|
||||||
elif type == 'poll':
|
elif type == 'poll':
|
||||||
...
|
post.title = form.poll_title.data
|
||||||
|
post.body = form.poll_body.data
|
||||||
|
post.body_html = markdown_to_html(post.body)
|
||||||
|
post.type = POST_TYPE_POLL
|
||||||
else:
|
else:
|
||||||
raise Exception('invalid post type')
|
raise Exception('invalid post type')
|
||||||
|
|
||||||
|
@ -386,10 +390,29 @@ def save_post(form, post: Post, type: str):
|
||||||
|
|
||||||
db.session.add(post)
|
db.session.add(post)
|
||||||
else:
|
else:
|
||||||
db.session.execute(text('DELETE FROM "post_tag" WHERE post_id = :post_id'), { 'post_id': post.id})
|
db.session.execute(text('DELETE FROM "post_tag" WHERE post_id = :post_id'), {'post_id': post.id})
|
||||||
post.tags = tags_from_string(form.tags.data)
|
post.tags = tags_from_string(form.tags.data)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
# Save poll choices. NB this will delete all votes whenever a poll is edited. Partially because it's easier to code but also to stop malicious alterations to polls after people have already voted
|
||||||
|
if type == 'poll':
|
||||||
|
db.session.execute(text('DELETE FROM "poll_choice_vote" WHERE post_id = :poll_id'), {'post_id': post.id})
|
||||||
|
db.session.execute(text('DELETE FROM "poll_choice" WHERE post_id = :poll_id'), {'post_id': post.id})
|
||||||
|
for i in range(1, 10):
|
||||||
|
choice_data = getattr(form, f"choice_{i}").data.strip()
|
||||||
|
if choice_data != '':
|
||||||
|
db.session.add(PollChoice(post_id=post.id, choice_text=choice_data, sort_order=i))
|
||||||
|
|
||||||
|
poll = Poll.query.filter_by(post_id=post.id).first()
|
||||||
|
if poll is None:
|
||||||
|
poll = Poll(post_id=post.id)
|
||||||
|
db.session.add(poll)
|
||||||
|
poll.mode = form.mode.data
|
||||||
|
poll.end_poll = end_poll_date(form.finish_in.data)
|
||||||
|
poll.local_only = form.local_only.data
|
||||||
|
poll.latest_vote = None
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
# Notify author about replies
|
# Notify author about replies
|
||||||
# Remove any subscription that currently exists
|
# Remove any subscription that currently exists
|
||||||
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == post.id,
|
existing_notification = NotificationSubscription.query.filter(NotificationSubscription.entity_id == post.id,
|
||||||
|
@ -408,6 +431,23 @@ def save_post(form, post: Post, type: str):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def end_poll_date(end_choice):
|
||||||
|
delta_mapping = {
|
||||||
|
'30m': timedelta(minutes=30),
|
||||||
|
'1h': timedelta(hours=1),
|
||||||
|
'6h': timedelta(hours=6),
|
||||||
|
'12h': timedelta(hours=12),
|
||||||
|
'1d': timedelta(days=1),
|
||||||
|
'3d': timedelta(days=3),
|
||||||
|
'7d': timedelta(days=7)
|
||||||
|
}
|
||||||
|
|
||||||
|
if end_choice in delta_mapping:
|
||||||
|
return datetime.utcnow() + delta_mapping[end_choice]
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid choice")
|
||||||
|
|
||||||
|
|
||||||
def tags_from_string(tags: str) -> List[Tag]:
|
def tags_from_string(tags: str) -> List[Tag]:
|
||||||
return_value = []
|
return_value = []
|
||||||
tags = tags.strip()
|
tags = tags.strip()
|
||||||
|
|
|
@ -1372,6 +1372,7 @@ class NotificationSubscription(db.Model):
|
||||||
class Poll(db.Model):
|
class Poll(db.Model):
|
||||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), primary_key=True)
|
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), primary_key=True)
|
||||||
end_poll = db.Column(db.DateTime)
|
end_poll = db.Column(db.DateTime)
|
||||||
|
mode = db.Column(db.String(10)) # 'single' or 'multiple' determines whether people can vote for one or multiple options
|
||||||
local_only = db.Column(db.Boolean)
|
local_only = db.Column(db.Boolean)
|
||||||
latest_vote = db.Column(db.DateTime)
|
latest_vote = db.Column(db.DateTime)
|
||||||
|
|
||||||
|
|
9
app/templates/community/_add_post_types.html
Normal file
9
app/templates/community/_add_post_types.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<div id="type_of_post" class="btn-group flex-wrap" role="navigation">
|
||||||
|
<a href="{{ url_for('community.add_discussion_post', actor=actor) }}" class="btn {{ 'btn-primary' if request.path.endswith('submit') else 'btn-outline-secondary' }}" aria-label="{{ _('Start a discussion') }}">{{ _('Discussion') }}</a>
|
||||||
|
<a href="{{ url_for('community.add_link_post', actor=actor) }}" class="btn {{ 'btn-primary' if request.path.endswith('submit_link') else 'btn-outline-secondary' }}" aria-label="{{ _('Share a link') }}">{{ _('Link') }}</a>
|
||||||
|
<a href="{{ url_for('community.add_image_post', actor=actor) }}" class="btn {{ 'btn-primary' if request.path.endswith('submit_image') else 'btn-outline-secondary' }}" aria-label="{{ _('Share an image') }}">{{ _('Image') }}</a>
|
||||||
|
<a href="{{ url_for('community.add_video_post', actor=actor) }}" class="btn {{ 'btn-primary' if request.path.endswith('submit_video') else 'btn-outline-secondary' }}" aria-label="{{ _('Share a video') }}">{{ _('Video') }}</a>
|
||||||
|
<a href="{{ url_for('community.add_poll_post', actor=actor) }}" class="btn {{ 'btn-primary' if request.path.endswith('submit_poll') else 'btn-outline-secondary' }}" aria-label="{{ _('Create a poll') }}">{{ _('Poll') }}</a>
|
||||||
|
<!--
|
||||||
|
<a href="#" class="btn" aria-label="{{ _('Create an event') }}">{{ _('Event') }}</a> -->
|
||||||
|
</div>
|
|
@ -15,14 +15,7 @@
|
||||||
<label class="form-control-label" for="type_of_post">
|
<label class="form-control-label" for="type_of_post">
|
||||||
{{ _('Type of post') }}
|
{{ _('Type of post') }}
|
||||||
</label>
|
</label>
|
||||||
<div id="type_of_post" class="btn-group flex-wrap" role="navigation">
|
{% include 'community/_add_post_types.html' %}
|
||||||
<a href="{{ url_for('community.add_discussion_post', actor=actor) }}" class="btn btn-primary" aria-label="{{ _('Start a discussion') }}">{{ _('Discussion') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_link_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share a link') }}">{{ _('Link') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_image_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share an image') }}">{{ _('Image') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_video_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share a video') }}">{{ _('Video') }}</a>
|
|
||||||
<!-- <a href="#" class="btn" aria-label="{{ _('Create a poll') }}">{{ _('Poll') }}</a>
|
|
||||||
<a href="#" class="btn" aria-label="{{ _('Create an event') }}">{{ _('Event') }}</a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ render_field(form.communities) }}
|
{{ render_field(form.communities) }}
|
||||||
|
|
|
@ -15,14 +15,7 @@
|
||||||
<label class="form-control-label" for="type_of_post">
|
<label class="form-control-label" for="type_of_post">
|
||||||
{{ _('Type of post') }}
|
{{ _('Type of post') }}
|
||||||
</label>
|
</label>
|
||||||
<div id="type_of_post" class="btn-group flex-wrap" role="navigation">
|
{% include 'community/_add_post_types.html' %}
|
||||||
<a href="{{ url_for('community.add_discussion_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Start a discussion') }}">{{ _('Discussion') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_link_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share a link') }}">{{ _('Link') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_image_post', actor=actor) }}" class="btn btn-primary" aria-label="{{ _('Share an image') }}">{{ _('Image') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_video_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share a video') }}">{{ _('Video') }}</a>
|
|
||||||
<!-- <a href="#" class="btn" aria-label="{{ _('Create a poll') }}">{{ _('Poll') }}</a>
|
|
||||||
<a href="#" class="btn" aria-label="{{ _('Create an event') }}">{{ _('Event') }}</a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{ render_field(form.communities) }}
|
{{ render_field(form.communities) }}
|
||||||
{{ render_field(form.image_title) }}
|
{{ render_field(form.image_title) }}
|
||||||
|
|
|
@ -15,14 +15,7 @@
|
||||||
<label class="form-control-label" for="type_of_post">
|
<label class="form-control-label" for="type_of_post">
|
||||||
{{ _('Type of post') }}
|
{{ _('Type of post') }}
|
||||||
</label>
|
</label>
|
||||||
<div id="type_of_post" class="btn-group flex-wrap" role="navigation">
|
{% include 'community/_add_post_types.html' %}
|
||||||
<a href="{{ url_for('community.add_discussion_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Start a discussion') }}">{{ _('Discussion') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_link_post', actor=actor) }}" class="btn btn-primary" aria-label="{{ _('Share a link') }}">{{ _('Link') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_image_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share an image') }}">{{ _('Image') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_video_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share a video') }}">{{ _('Video') }}</a>
|
|
||||||
<!-- <a href="#" class="btn" aria-label="{{ _('Create a poll') }}">{{ _('Poll') }}</a>
|
|
||||||
<a href="#" class="btn" aria-label="{{ _('Create an event') }}">{{ _('Event') }}</a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{ render_field(form.communities) }}
|
{{ render_field(form.communities) }}
|
||||||
|
|
||||||
|
|
94
app/templates/community/add_poll_post.html
Normal file
94
app/templates/community/add_poll_post.html
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
{% if theme() and file_exists('app/templates/themes/' + theme() + '/base.html') %}
|
||||||
|
{% extends 'themes/' + theme() + '/base.html' %}
|
||||||
|
{% else %}
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% endif %} %}
|
||||||
|
{% from 'bootstrap/form.html' import render_field %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-8 position-relative main_pane">
|
||||||
|
<h1>{{ _('Create post') }}</h1>
|
||||||
|
<form method="post" enctype="multipart/form-data" role="form">
|
||||||
|
{{ form.csrf_token() }}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-control-label" for="type_of_post">
|
||||||
|
{{ _('Type of post') }}
|
||||||
|
</label>
|
||||||
|
{% include 'community/_add_post_types.html' %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ render_field(form.communities) }}
|
||||||
|
{{ render_field(form.poll_title) }}
|
||||||
|
{{ render_field(form.poll_body) }}
|
||||||
|
{% if not low_bandwidth %}
|
||||||
|
{% if markdown_editor %}
|
||||||
|
<script nonce="{{ session['nonce'] }}">
|
||||||
|
window.addEventListener("load", function () {
|
||||||
|
var downarea = new DownArea({
|
||||||
|
elem: document.querySelector('#poll_body'),
|
||||||
|
resize: DownArea.RESIZE_VERTICAL,
|
||||||
|
hide: ['heading', 'bold-italic'],
|
||||||
|
});
|
||||||
|
setupAutoResize('discussion_body');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% else %}
|
||||||
|
<a href="#" aria-hidden="true" class="markdown_editor_enabler create_post_markdown_editor_enabler" data-id="discussion_body">{{ _('Enable markdown editor') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{{ render_field(form.mode) }}
|
||||||
|
{{ render_field(form.finish_in) }}
|
||||||
|
{{ render_field(form.local_only) }}
|
||||||
|
{{ render_field(form.tags) }}
|
||||||
|
<small class="field_hint">{{ _('Separate each tag with a comma.') }}</small>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-3">
|
||||||
|
{{ render_field(form.notify_author) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
{{ render_field(form.sticky) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
{{ render_field(form.nsfw) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
{{ render_field(form.nsfl) }}
|
||||||
|
</div>
|
||||||
|
<div class="col post_language_chooser">
|
||||||
|
{{ render_field(form.language_id) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ render_field(form.submit) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside id="side_pane" class="col-12 col-md-4 side_pane" role="complementary">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>{{ community.title }}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p>{{ community.description_html|safe if community.description_html else '' }}</p>
|
||||||
|
<p>{{ community.rules_html|safe if community.rules_html else '' }}</p>
|
||||||
|
{% if len(mods) > 0 and not community.private_mods %}
|
||||||
|
<h3>Moderators</h3>
|
||||||
|
<ul class="moderator_list">
|
||||||
|
{% for mod in mods %}
|
||||||
|
<li>{{ render_username(mod) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% if rss_feed %}
|
||||||
|
<p class="mt-4">
|
||||||
|
<a class="no-underline" href="{{ rss_feed }}" rel="nofollow"><span class="fe fe-rss"></span> RSS feed</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "_inoculation_links.html" %}
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -15,14 +15,7 @@
|
||||||
<label class="form-control-label" for="type_of_post">
|
<label class="form-control-label" for="type_of_post">
|
||||||
{{ _('Type of post') }}
|
{{ _('Type of post') }}
|
||||||
</label>
|
</label>
|
||||||
<div id="type_of_post" class="btn-group flex-wrap" role="navigation">
|
{% include 'community/_add_post_types.html' %}
|
||||||
<a href="{{ url_for('community.add_discussion_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Start a discussion') }}">{{ _('Discussion') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_link_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share a link') }}">{{ _('Link') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_image_post', actor=actor) }}" class="btn btn-outline-secondary" aria-label="{{ _('Share an image') }}">{{ _('Image') }}</a>
|
|
||||||
<a href="{{ url_for('community.add_video_post', actor=actor) }}" class="btn btn-primary" aria-label="{{ _('Share a video') }}">{{ _('Video') }}</a>
|
|
||||||
<!-- <a href="#" class="btn" aria-label="{{ _('Create a poll') }}">{{ _('Poll') }}</a>
|
|
||||||
<a href="#" class="btn" aria-label="{{ _('Create an event') }}">{{ _('Event') }}</a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{ render_field(form.communities) }}
|
{{ render_field(form.communities) }}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""polls
|
"""polls
|
||||||
|
|
||||||
Revision ID: 92bdb39f6c72
|
Revision ID: 05e3a7023a1e
|
||||||
Revises: 9752fb47d7a6
|
Revises: 9752fb47d7a6
|
||||||
Create Date: 2024-05-16 20:42:05.491951
|
Create Date: 2024-05-16 20:47:35.566151
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '92bdb39f6c72'
|
revision = '05e3a7023a1e'
|
||||||
down_revision = '9752fb47d7a6'
|
down_revision = '9752fb47d7a6'
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
@ -21,6 +21,7 @@ def upgrade():
|
||||||
op.create_table('poll',
|
op.create_table('poll',
|
||||||
sa.Column('post_id', sa.Integer(), nullable=False),
|
sa.Column('post_id', sa.Integer(), nullable=False),
|
||||||
sa.Column('end_poll', sa.DateTime(), nullable=True),
|
sa.Column('end_poll', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('mode', sa.String(length=10), nullable=True),
|
||||||
sa.Column('local_only', sa.Boolean(), nullable=True),
|
sa.Column('local_only', sa.Boolean(), nullable=True),
|
||||||
sa.Column('latest_vote', sa.DateTime(), nullable=True),
|
sa.Column('latest_vote', sa.DateTime(), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['post_id'], ['post.id'], ),
|
sa.ForeignKeyConstraint(['post_id'], ['post.id'], ),
|
Loading…
Reference in a new issue