mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 11:26:56 -08:00
community wiki #127
This commit is contained in:
parent
55e4289a6e
commit
2c3f1763b3
10 changed files with 448 additions and 13 deletions
|
@ -63,6 +63,19 @@ class EditCommunityForm(FlaskForm):
|
|||
submit = SubmitField(_l('Save'))
|
||||
|
||||
|
||||
class EditCommunityWikiPageForm(FlaskForm):
|
||||
title = StringField(_l('Title'), validators=[DataRequired()])
|
||||
slug = StringField(_l('Slug'), validators=[DataRequired()])
|
||||
body = TextAreaField(_l('Body'), render_kw={'rows': '10'})
|
||||
edit_options = [(0, _l('Mods and admins')),
|
||||
(1, _l('Trusted accounts')),
|
||||
(2, _l('Community members')),
|
||||
(3, _l('Any account'))
|
||||
]
|
||||
who_can_edit = SelectField(_l('Who can edit'), coerce=int, choices=edit_options, validators=[Optional()], render_kw={'class': 'form-select'})
|
||||
submit = SubmitField(_l('Save'))
|
||||
|
||||
|
||||
class AddModeratorForm(FlaskForm):
|
||||
user_name = StringField(_l('User name'), validators=[DataRequired()])
|
||||
submit = SubmitField(_l('Find'))
|
||||
|
|
|
@ -15,7 +15,8 @@ from app.chat.util import send_message
|
|||
from app.community.forms import SearchRemoteCommunity, CreateDiscussionForm, CreateImageForm, CreateLinkForm, \
|
||||
ReportCommunityForm, \
|
||||
DeleteCommunityForm, AddCommunityForm, EditCommunityForm, AddModeratorForm, BanUserCommunityForm, \
|
||||
EscalateReportForm, ResolveReportForm, CreateVideoForm, CreatePollForm, RetrieveRemotePost
|
||||
EscalateReportForm, ResolveReportForm, CreateVideoForm, CreatePollForm, RetrieveRemotePost, \
|
||||
EditCommunityWikiPageForm
|
||||
from app.community.util import search_for_community, actor_to_community, \
|
||||
save_post, save_icon_file, save_banner_file, send_to_remote_instance, \
|
||||
delete_post_from_community, delete_post_reply_from_community, community_in_list, find_local_users
|
||||
|
@ -25,7 +26,8 @@ from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LIN
|
|||
from app.inoculation import inoculation
|
||||
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \
|
||||
File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation, PostReply, \
|
||||
NotificationSubscription, UserFollower, Instance, Language, Poll, PollChoice, ModLog
|
||||
NotificationSubscription, UserFollower, Instance, Language, Poll, PollChoice, ModLog, CommunityWikiPage, \
|
||||
CommunityWikiPageRevision
|
||||
from app.community import bp
|
||||
from app.user.utils import search_for_user
|
||||
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
|
||||
|
@ -1326,6 +1328,184 @@ def community_moderate_subscribers(actor):
|
|||
abort(404)
|
||||
|
||||
|
||||
@bp.route('/<actor>/moderate/wiki', methods=['GET'])
|
||||
@login_required
|
||||
def community_wiki_list(actor):
|
||||
community = actor_to_community(actor)
|
||||
|
||||
if community is not None:
|
||||
if community.is_moderator() or current_user.is_admin():
|
||||
low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1'
|
||||
pages = CommunityWikiPage.query.filter(CommunityWikiPage.community_id == community.id).order_by(CommunityWikiPage.title).all()
|
||||
return render_template('community/community_wiki_list.html', title=_('Community Wiki'), community=community,
|
||||
pages=pages, low_bandwidth=low_bandwidth, current='wiki',
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
menu_topics=menu_topics(), site=g.site,
|
||||
inoculation=inoculation[randint(0, len(inoculation) - 1)] if g.site.show_inoculation_block else None
|
||||
)
|
||||
else:
|
||||
abort(401)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@bp.route('/<actor>/moderate/wiki/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def community_wiki_add(actor):
|
||||
community = actor_to_community(actor)
|
||||
|
||||
if community is not None:
|
||||
if community.is_moderator() or current_user.is_admin():
|
||||
low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1'
|
||||
form = EditCommunityWikiPageForm()
|
||||
if form.validate_on_submit():
|
||||
new_page = CommunityWikiPage(community_id=community.id, slug=form.slug.data, title=form.title.data,
|
||||
body=form.body.data, who_can_edit=form.who_can_edit.data)
|
||||
new_page.body_html = markdown_to_html(new_page.body)
|
||||
db.session.add(new_page)
|
||||
db.session.commit()
|
||||
|
||||
initial_revision = CommunityWikiPageRevision(wiki_page_id=new_page.id, user_id=current_user.id,
|
||||
community_id=community.id, title=form.title.data,
|
||||
body=form.body.data, body_html=new_page.body_html)
|
||||
db.session.add(initial_revision)
|
||||
db.session.commit()
|
||||
|
||||
flash(_('Saved'))
|
||||
return redirect(url_for('community.community_wiki_list', actor=community.link()))
|
||||
|
||||
return render_template('community/community_wiki_edit.html', title=_('Add wiki page'), community=community,
|
||||
form=form, low_bandwidth=low_bandwidth, current='wiki',
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
menu_topics=menu_topics(), site=g.site,
|
||||
inoculation=inoculation[randint(0, len(inoculation) - 1)] if g.site.show_inoculation_block else None
|
||||
)
|
||||
else:
|
||||
abort(401)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@bp.route('/<actor>/wiki/<slug>', methods=['GET', 'POST'])
|
||||
def community_wiki_view(actor, slug):
|
||||
community = actor_to_community(actor)
|
||||
|
||||
if community is not None:
|
||||
page: CommunityWikiPage = CommunityWikiPage.query.filter_by(slug=slug, community_id=community.id).first()
|
||||
if page is None:
|
||||
abort(404)
|
||||
else:
|
||||
# Breadcrumbs
|
||||
breadcrumbs = []
|
||||
breadcrumb = namedtuple("Breadcrumb", ['text', 'url'])
|
||||
breadcrumb.text = _('Home')
|
||||
breadcrumb.url = '/'
|
||||
breadcrumbs.append(breadcrumb)
|
||||
|
||||
if community.topic_id:
|
||||
topics = []
|
||||
previous_topic = Topic.query.get(community.topic_id)
|
||||
topics.append(previous_topic)
|
||||
while previous_topic.parent_id:
|
||||
topic = Topic.query.get(previous_topic.parent_id)
|
||||
topics.append(topic)
|
||||
previous_topic = topic
|
||||
topics = list(reversed(topics))
|
||||
|
||||
breadcrumb = namedtuple("Breadcrumb", ['text', 'url'])
|
||||
breadcrumb.text = _('Topics')
|
||||
breadcrumb.url = '/topics'
|
||||
breadcrumbs.append(breadcrumb)
|
||||
|
||||
existing_url = '/topic'
|
||||
for topic in topics:
|
||||
breadcrumb = namedtuple("Breadcrumb", ['text', 'url'])
|
||||
breadcrumb.text = topic.name
|
||||
breadcrumb.url = f"{existing_url}/{topic.machine_name}"
|
||||
breadcrumbs.append(breadcrumb)
|
||||
existing_url = breadcrumb.url
|
||||
else:
|
||||
breadcrumb = namedtuple("Breadcrumb", ['text', 'url'])
|
||||
breadcrumb.text = _('Communities')
|
||||
breadcrumb.url = '/communities'
|
||||
breadcrumbs.append(breadcrumb)
|
||||
|
||||
return render_template('community/community_wiki_page_view.html', title=page.title, page=page,
|
||||
community=community, breadcrumbs=breadcrumbs, is_moderator=community.is_moderator(),
|
||||
is_owner=community.is_owner(),
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
menu_topics=menu_topics(), site=g.site,
|
||||
inoculation=inoculation[
|
||||
randint(0, len(inoculation) - 1)] if g.site.show_inoculation_block else None
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/<actor>/moderate/wiki/<int:page_id>/edit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def community_wiki_edit(actor, page_id):
|
||||
community = actor_to_community(actor)
|
||||
|
||||
if community is not None:
|
||||
page: CommunityWikiPage = CommunityWikiPage.query.get_or_404(page_id)
|
||||
if page.can_edit(current_user, community):
|
||||
low_bandwidth = request.cookies.get('low_bandwidth', '0') == '1'
|
||||
|
||||
form = EditCommunityWikiPageForm()
|
||||
if form.validate_on_submit():
|
||||
page.title = form.title.data
|
||||
page.slug = form.slug.data
|
||||
page.body = form.body.data
|
||||
page.body_html = markdown_to_html(page.body)
|
||||
page.who_can_edit = form.who_can_edit.data
|
||||
page.edited_at = utcnow()
|
||||
new_revision = CommunityWikiPageRevision(wiki_page_id=page.id, user_id=current_user.id,
|
||||
community_id=community.id, title=form.title.data,
|
||||
body=form.body.data, body_html=page.body_html)
|
||||
db.session.add(new_revision)
|
||||
db.session.commit()
|
||||
flash(_('Saved'))
|
||||
if request.args.get('return') == 'list':
|
||||
return redirect(url_for('community.community_wiki_list', actor=community.link()))
|
||||
elif request.args.get('return') == 'page':
|
||||
return redirect(url_for('community.community_wiki_view', actor=community.link(), slug=page.slug))
|
||||
else:
|
||||
form.title.data = page.title
|
||||
form.slug.data = page.slug
|
||||
form.body.data = page.body
|
||||
form.who_can_edit.data = page.who_can_edit
|
||||
|
||||
return render_template('community/community_wiki_edit.html', title=_('Edit wiki page'), community=community,
|
||||
form=form, low_bandwidth=low_bandwidth,
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
menu_topics=menu_topics(), site=g.site,
|
||||
inoculation=inoculation[randint(0, len(inoculation) - 1)] if g.site.show_inoculation_block else None
|
||||
)
|
||||
else:
|
||||
abort(401)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@bp.route('/<actor>/moderate/wiki/<int:page_id>/delete', methods=['GET'])
|
||||
@login_required
|
||||
def community_wiki_delete(actor, page_id):
|
||||
community = actor_to_community(actor)
|
||||
|
||||
if community is not None:
|
||||
page: CommunityWikiPage = CommunityWikiPage.query.get_or_404(page_id)
|
||||
if page.can_edit(current_user, community):
|
||||
db.session.delete(page)
|
||||
db.session.commit()
|
||||
flash(_('Page deleted'))
|
||||
return redirect(url_for('community.community_wiki_list', actor=community.link()))
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
@bp.route('/<actor>/moderate/modlog', methods=['GET'])
|
||||
@login_required
|
||||
def community_modlog(actor):
|
||||
|
|
|
@ -406,6 +406,7 @@ class Community(db.Model):
|
|||
|
||||
posts = db.relationship('Post', lazy='dynamic', cascade="all, delete-orphan")
|
||||
replies = db.relationship('PostReply', lazy='dynamic', cascade="all, delete-orphan")
|
||||
wiki_pages = db.relationship('CommunityWikiPage', lazy='dynamic', backref='community', cascade="all, delete-orphan")
|
||||
icon = db.relationship('File', foreign_keys=[icon_id], single_parent=True, backref='community', cascade="all, delete-orphan")
|
||||
image = db.relationship('File', foreign_keys=[image_id], single_parent=True, cascade="all, delete-orphan")
|
||||
languages = db.relationship('Language', lazy='dynamic', secondary=community_language, backref=db.backref('communities', lazy='dynamic'))
|
||||
|
@ -476,15 +477,25 @@ class Community(db.Model):
|
|||
))
|
||||
).filter(CommunityMember.is_banned == False).all()
|
||||
|
||||
def is_member(self, user):
|
||||
if user is None:
|
||||
return CommunityMember.query.filter(CommunityMember.user_id == current_user.get_id(),
|
||||
CommunityMember.community_id == self.id,
|
||||
CommunityMember.is_banned == False).all()
|
||||
else:
|
||||
return CommunityMember.query.filter(CommunityMember.user_id == user.id,
|
||||
CommunityMember.community_id == self.id,
|
||||
CommunityMember.is_banned == False).all()
|
||||
|
||||
def is_moderator(self, user=None):
|
||||
if user is None:
|
||||
return any(moderator.user_id == current_user.id for moderator in self.moderators())
|
||||
return any(moderator.user_id == current_user.get_id() for moderator in self.moderators())
|
||||
else:
|
||||
return any(moderator.user_id == user.id for moderator in self.moderators())
|
||||
|
||||
def is_owner(self, user=None):
|
||||
if user is None:
|
||||
return any(moderator.user_id == current_user.id and moderator.is_owner for moderator in self.moderators())
|
||||
return any(moderator.user_id == current_user.get_id() and moderator.is_owner for moderator in self.moderators())
|
||||
else:
|
||||
return any(moderator.user_id == user.id and moderator.is_owner for moderator in self.moderators())
|
||||
|
||||
|
@ -1246,6 +1257,46 @@ class CommunityMember(db.Model):
|
|||
created_at = db.Column(db.DateTime, default=utcnow)
|
||||
|
||||
|
||||
class CommunityWikiPage(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
community_id = db.Column(db.Integer, db.ForeignKey('community.id'), index=True)
|
||||
slug = db.Column(db.String(100), index=True)
|
||||
title = db.Column(db.String(255))
|
||||
body = db.Column(db.Text)
|
||||
body_html = db.Column(db.Text)
|
||||
created_at = db.Column(db.DateTime, default=utcnow)
|
||||
edited_at = db.Column(db.DateTime, default=utcnow)
|
||||
who_can_edit = db.Column(db.Integer, default=0) # 0 = mods & admins, 1 = trusted, 2 = community members, 3 = anyone
|
||||
revisions = db.relationship('CommunityWikiPageRevision', backref=db.backref('page'), cascade='all,delete',
|
||||
lazy='dynamic')
|
||||
def can_edit(self, user: User, community: Community):
|
||||
if user.is_anonymous:
|
||||
return False
|
||||
if self.who_can_edit == 0:
|
||||
if user.is_admin() or user.is_staff() or community.is_moderator(user):
|
||||
return True
|
||||
elif self.who_can_edit == 1:
|
||||
if user.is_admin() or user.is_staff() or community.is_moderator(user) or user.trustworthy():
|
||||
return True
|
||||
elif self.who_can_edit == 2:
|
||||
if user.is_admin() or user.is_staff() or community.is_moderator(user) or user.trustworthy() or community.is_member(user):
|
||||
return True
|
||||
elif self.who_can_edit == 3:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class CommunityWikiPageRevision(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
wiki_page_id = db.Column(db.Integer, db.ForeignKey('community_wiki_page.id'), index=True)
|
||||
community_id = db.Column(db.Integer, db.ForeignKey('community.id'), index=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||
title = db.Column(db.String(255))
|
||||
body = db.Column(db.Text)
|
||||
body_html = db.Column(db.Text)
|
||||
edited_at = db.Column(db.DateTime, default=utcnow)
|
||||
|
||||
|
||||
class UserFollower(db.Model):
|
||||
local_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
||||
remote_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
||||
|
|
|
@ -55,7 +55,7 @@ def show_post(post_id: int):
|
|||
flash(_('%(name)s has indicated they made a mistake in this post.', name=post.author.user_name), 'warning')
|
||||
|
||||
mods = community_moderators(community.id)
|
||||
is_moderator = current_user.is_authenticated and any(mod.user_id == current_user.id for mod in mods)
|
||||
is_moderator = community.is_moderator()
|
||||
|
||||
if community.private_mods:
|
||||
mod_list = []
|
||||
|
@ -310,21 +310,19 @@ def show_post(post_id: int):
|
|||
if post.type == POST_TYPE_LINK and body_has_no_archive_link(post.body_html) and url_needs_archive(post.url):
|
||||
archive_link = generate_archive_link(post.url)
|
||||
|
||||
response = render_template('post/post.html', title=post.title, post=post, is_moderator=is_moderator, community=post.community,
|
||||
response = render_template('post/post.html', title=post.title, post=post, is_moderator=is_moderator, is_owner=community.is_owner(),
|
||||
community=post.community,
|
||||
breadcrumbs=breadcrumbs, related_communities=related_communities, mods=mod_list,
|
||||
poll_form=poll_form, poll_results=poll_results, poll_data=poll_data, poll_choices=poll_choices, poll_total_votes=poll_total_votes,
|
||||
canonical=post.ap_id, form=form, replies=replies, THREAD_CUTOFF_DEPTH=constants.THREAD_CUTOFF_DEPTH,
|
||||
description=description, og_image=og_image, POST_TYPE_IMAGE=constants.POST_TYPE_IMAGE,
|
||||
POST_TYPE_LINK=constants.POST_TYPE_LINK, POST_TYPE_ARTICLE=constants.POST_TYPE_ARTICLE,
|
||||
POST_TYPE_VIDEO=constants.POST_TYPE_VIDEO, POST_TYPE_POLL=constants.POST_TYPE_POLL,
|
||||
description=description, og_image=og_image,
|
||||
autoplay=request.args.get('autoplay', False), archive_link=archive_link,
|
||||
noindex=not post.author.indexable, preconnect=post.url if post.url else None,
|
||||
recently_upvoted=recently_upvoted, recently_downvoted=recently_downvoted,
|
||||
recently_upvoted_replies=recently_upvoted_replies, recently_downvoted_replies=recently_downvoted_replies,
|
||||
reply_collapse_threshold=reply_collapse_threshold,
|
||||
etag=f"{post.id}{sort}_{hash(post.last_active)}", markdown_editor=current_user.is_authenticated and current_user.markdown_editor,
|
||||
low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1', SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
||||
SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR,
|
||||
low_bandwidth=request.cookies.get('low_bandwidth', '0') == '1',
|
||||
moderating_communities=moderating_communities(current_user.get_id()),
|
||||
joined_communities=joined_communities(current_user.get_id()),
|
||||
menu_topics=menu_topics(), site=g.site,
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
<a href="{{ url_for('community.community_moderate_subscribers', actor=community.link()) }}" class="btn {{ 'btn-primary' if current == 'subscribers' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
||||
{{ _('Subscribers') }}
|
||||
</a>
|
||||
<a href="{{ url_for('community.community_wiki_list', actor=community.link()) }}" class="btn {{ 'btn-primary' if current == 'wiki' else 'btn-outline-secondary' }}" rel="nofollow noindex">
|
||||
{{ _('Wiki') }}
|
||||
</a>
|
||||
<a href="/community/{{ community.link() }}/moderate/appeals" class="btn {{ 'btn-primary' if current == 'appeals' else 'btn-outline-secondary' }} disabled" rel="nofollow noindex" >
|
||||
{{ _('Appeals') }}
|
||||
</a>
|
||||
|
|
18
app/templates/community/community_wiki_edit.html
Normal file
18
app/templates/community/community_wiki_edit.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{% 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_form, render_field %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 position-relative main_pane">
|
||||
{% block title %}<h1>{{ title }}</h1>{% endblock %}
|
||||
{{ render_form(form) }}
|
||||
</div>
|
||||
|
||||
{% include "_side_pane.html" %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
67
app/templates/community/community_wiki_list.html
Normal file
67
app/templates/community/community_wiki_list.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
{% 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">
|
||||
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">{{ _('Home') }}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ url_for('activitypub.community_profile', actor=community.ap_id if community.ap_id is not none else community.name) }}">{{ (community.title + '@' + community.ap_domain)|shorten }}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ url_for('community.community_edit', community_id=community.id) }}">{{ _('Settings') }}</a></li>
|
||||
<li class="breadcrumb-item active">{{ _('Wiki') }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
{% include "community/_community_moderation_nav.html" %}
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-10">
|
||||
<h1 class="mt-2">{{ _('Wiki pages for %(community)s', community=community.display_name()) }}</h1>
|
||||
</div>
|
||||
<div class="col-12 text-right">
|
||||
<a class="btn btn-primary" href="{{ url_for('community.community_wiki_add', actor=community.link()) }}">{{ _('Add wiki page') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% if pages -%}
|
||||
<table class="table table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _('Name') }}</th>
|
||||
<th>{{ _('Url') }}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page in pages %}
|
||||
<tr>
|
||||
<td>{{ page.title }}</td>
|
||||
<td><a href="{{ url_for('community.community_wiki_view', actor=community.link(), slug=page.slug) }}">{{ page.slug }}</a></td>
|
||||
<td class="text-right">{% if page.can_edit(current_user, community) %}
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-primary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{{ url_for('community.community_wiki_view', actor=community.link(), slug=page.slug) }}">{{ _('View') }}</a></li>
|
||||
<li><a class="dropdown-item"
|
||||
href="{{ url_for('community.community_wiki_edit', actor=community.link(), page_id=page.id, return='list') }}">
|
||||
{{ _('Edit') }}</a></li>
|
||||
<li><a class="confirm_first dropdown-item"
|
||||
href="{{ url_for('community.community_wiki_delete', actor=community.link(), page_id=page.id) }}">
|
||||
{{ _('Delete') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else -%}
|
||||
<p>{{ _('There are no wiki pages in this community.') }}</p>
|
||||
{% endif -%}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
33
app/templates/community/community_wiki_page_view.html
Normal file
33
app/templates/community/community_wiki_page_view.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
{% 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_form -%}
|
||||
|
||||
{% block app_content -%}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-8 position-relative main_pane">
|
||||
<div class="row position-relative">
|
||||
<div class="col post_col post_type_normal">
|
||||
<nav aria-label="breadcrumb" id="breadcrumb_nav" title="Navigation">
|
||||
<ol class="breadcrumb">
|
||||
{% for breadcrumb in breadcrumbs -%}
|
||||
<li class="breadcrumb-item">{% if breadcrumb.url -%}<a href="{{ breadcrumb.url }}">{% endif -%}{{ breadcrumb.text }}{% if breadcrumb.url -%}</a>{% endif -%}</li>
|
||||
{% endfor -%}
|
||||
<li class="breadcrumb-item"><a href="/c/{{ page.community.link() }}">{{ page.community.title }}@{{ page.community.ap_domain }}</a></li>
|
||||
<li class="breadcrumb-item active">{{ page.title|shorten(15) }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1 class="mt-2 post_title">{{ page.title }}</h1>
|
||||
{{ page.body_html | safe }}
|
||||
{% if page.can_edit(current_user, community) -%}
|
||||
<p><a class="btn btn-primary" href="{{ url_for('community.community_wiki_edit', actor=community.link(), page_id=page.id, return='page') }}">{{ _('Edit') }}</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "_side_pane.html" %}
|
||||
</div>
|
||||
{% endblock -%}
|
71
migrations/versions/b97584a7a10b_wikis.py
Normal file
71
migrations/versions/b97584a7a10b_wikis.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
"""wikis
|
||||
|
||||
Revision ID: b97584a7a10b
|
||||
Revises: ea5a07acf23c
|
||||
Create Date: 2024-07-17 20:54:20.626562
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b97584a7a10b'
|
||||
down_revision = 'ea5a07acf23c'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('community_wiki_page',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('community_id', sa.Integer(), nullable=True),
|
||||
sa.Column('slug', sa.String(length=100), nullable=True),
|
||||
sa.Column('title', sa.String(length=255), nullable=True),
|
||||
sa.Column('body', sa.Text(), nullable=True),
|
||||
sa.Column('body_html', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('edited_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('who_can_edit', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['community_id'], ['community.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('community_wiki_page', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_community_wiki_page_community_id'), ['community_id'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_community_wiki_page_slug'), ['slug'], unique=False)
|
||||
|
||||
op.create_table('community_wiki_page_revision',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('wiki_page_id', sa.Integer(), nullable=True),
|
||||
sa.Column('community_id', sa.Integer(), nullable=True),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('title', sa.String(length=255), nullable=True),
|
||||
sa.Column('body', sa.Text(), nullable=True),
|
||||
sa.Column('body_html', sa.Text(), nullable=True),
|
||||
sa.Column('edited_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['community_id'], ['community.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||
sa.ForeignKeyConstraint(['wiki_page_id'], ['community_wiki_page.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
with op.batch_alter_table('community_wiki_page_revision', schema=None) as batch_op:
|
||||
batch_op.create_index(batch_op.f('ix_community_wiki_page_revision_community_id'), ['community_id'], unique=False)
|
||||
batch_op.create_index(batch_op.f('ix_community_wiki_page_revision_wiki_page_id'), ['wiki_page_id'], unique=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('community_wiki_page_revision', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_community_wiki_page_revision_wiki_page_id'))
|
||||
batch_op.drop_index(batch_op.f('ix_community_wiki_page_revision_community_id'))
|
||||
|
||||
op.drop_table('community_wiki_page_revision')
|
||||
with op.batch_alter_table('community_wiki_page', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_community_wiki_page_slug'))
|
||||
batch_op.drop_index(batch_op.f('ix_community_wiki_page_community_id'))
|
||||
|
||||
op.drop_table('community_wiki_page')
|
||||
# ### end Alembic commands ###
|
|
@ -9,7 +9,7 @@ from app import create_app, db, cli
|
|||
import os, click
|
||||
from flask import session, g, json, request, current_app
|
||||
from app.constants import POST_TYPE_LINK, POST_TYPE_IMAGE, POST_TYPE_ARTICLE, POST_TYPE_VIDEO, POST_TYPE_POLL, \
|
||||
SUBSCRIPTION_MODERATOR
|
||||
SUBSCRIPTION_MODERATOR, SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, SUBSCRIPTION_PENDING
|
||||
from app.models import Site
|
||||
from app.utils import getmtime, gibberish, shorten_string, shorten_url, digits, user_access, community_membership, \
|
||||
can_create_post, can_upvote, can_downvote, shorten_number, ap_datetime, current_theme, community_link_to_href, \
|
||||
|
@ -26,7 +26,8 @@ def app_context_processor():
|
|||
return dict(getmtime=getmtime, instance_domain=current_app.config['SERVER_NAME'],
|
||||
POST_TYPE_LINK=POST_TYPE_LINK, POST_TYPE_IMAGE=POST_TYPE_IMAGE,
|
||||
POST_TYPE_ARTICLE=POST_TYPE_ARTICLE, POST_TYPE_VIDEO=POST_TYPE_VIDEO, POST_TYPE_POLL=POST_TYPE_POLL,
|
||||
SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR)
|
||||
SUBSCRIPTION_MODERATOR=SUBSCRIPTION_MODERATOR, SUBSCRIPTION_MEMBER=SUBSCRIPTION_MEMBER,
|
||||
SUBSCRIPTION_OWNER=SUBSCRIPTION_OWNER, SUBSCRIPTION_PENDING=SUBSCRIPTION_PENDING)
|
||||
|
||||
|
||||
@app.shell_context_processor
|
||||
|
|
Loading…
Reference in a new issue