mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
community wiki - revisions #127
This commit is contained in:
parent
2c3f1763b3
commit
82cc9389ba
6 changed files with 215 additions and 7 deletions
|
@ -848,9 +848,9 @@ def community_edit(community_id: int):
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
community.title = form.title.data
|
community.title = form.title.data
|
||||||
community.description = form.description.data
|
community.description = form.description.data
|
||||||
community.description_html = markdown_to_html(form.description.data)
|
community.description_html = markdown_to_html(form.description.data, anchors_new_tab=False)
|
||||||
community.rules = form.rules.data
|
community.rules = form.rules.data
|
||||||
community.rules_html = markdown_to_html(form.rules.data)
|
community.rules_html = markdown_to_html(form.rules.data, anchors_new_tab=False)
|
||||||
community.nsfw = form.nsfw.data
|
community.nsfw = form.nsfw.data
|
||||||
community.local_only = form.local_only.data
|
community.local_only = form.local_only.data
|
||||||
community.restricted_to_mods = form.restricted_to_mods.data
|
community.restricted_to_mods = form.restricted_to_mods.data
|
||||||
|
@ -1443,6 +1443,91 @@ def community_wiki_view(actor, slug):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<actor>/wiki/<slug>/<revision_id>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def community_wiki_view_revision(actor, slug, revision_id):
|
||||||
|
community = actor_to_community(actor)
|
||||||
|
|
||||||
|
if community is not None:
|
||||||
|
page: CommunityWikiPage = CommunityWikiPage.query.filter_by(slug=slug, community_id=community.id).first()
|
||||||
|
revision: CommunityWikiPageRevision = CommunityWikiPageRevision.query.get_or_404(revision_id)
|
||||||
|
if page is None or revision 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_revision_view.html', title=page.title, page=page,
|
||||||
|
community=community, breadcrumbs=breadcrumbs, is_moderator=community.is_moderator(),
|
||||||
|
is_owner=community.is_owner(), revision=revision,
|
||||||
|
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>/wiki/<slug>/<revision_id>/revert', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def community_wiki_revert_revision(actor, slug, revision_id):
|
||||||
|
community = actor_to_community(actor)
|
||||||
|
|
||||||
|
if community is not None:
|
||||||
|
page: CommunityWikiPage = CommunityWikiPage.query.filter_by(slug=slug, community_id=community.id).first()
|
||||||
|
revision: CommunityWikiPageRevision = CommunityWikiPageRevision.query.get_or_404(revision_id)
|
||||||
|
if page is None or revision is None:
|
||||||
|
abort(404)
|
||||||
|
else:
|
||||||
|
if page.can_edit(current_user, community):
|
||||||
|
page.body = revision.body
|
||||||
|
page.body_html = revision.body_html
|
||||||
|
page.edited_at = utcnow()
|
||||||
|
|
||||||
|
new_revision = CommunityWikiPageRevision(wiki_page_id=page.id, user_id=current_user.id,
|
||||||
|
community_id=community.id, title=revision.title,
|
||||||
|
body=revision.body, body_html=revision.body_html)
|
||||||
|
db.session.add(new_revision)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
flash(_('Reverted to old version of the page.'))
|
||||||
|
return redirect(url_for('community.community_wiki_revisions', actor=community.link(), page_id=page.id))
|
||||||
|
else:
|
||||||
|
abort(401)
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/<actor>/moderate/wiki/<int:page_id>/edit', methods=['GET', 'POST'])
|
@bp.route('/<actor>/moderate/wiki/<int:page_id>/edit', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def community_wiki_edit(actor, page_id):
|
def community_wiki_edit(actor, page_id):
|
||||||
|
@ -1490,6 +1575,35 @@ def community_wiki_edit(actor, page_id):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<actor>/moderate/wiki/<int:page_id>/revisions', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def community_wiki_revisions(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'
|
||||||
|
|
||||||
|
revisions = CommunityWikiPageRevision.query.filter_by(wiki_page_id=page.id).\
|
||||||
|
order_by(desc(CommunityWikiPageRevision.edited_at)).all()
|
||||||
|
|
||||||
|
most_recent_revision = revisions[0].id
|
||||||
|
|
||||||
|
return render_template('community/community_wiki_revisions.html', title=_('%(title)s revisions', title=page.title),
|
||||||
|
community=community, page=page, revisions=revisions, most_recent_revision=most_recent_revision,
|
||||||
|
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'])
|
@bp.route('/<actor>/moderate/wiki/<int:page_id>/delete', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def community_wiki_delete(actor, page_id):
|
def community_wiki_delete(actor, page_id):
|
||||||
|
|
|
@ -1296,6 +1296,8 @@ class CommunityWikiPageRevision(db.Model):
|
||||||
body_html = db.Column(db.Text)
|
body_html = db.Column(db.Text)
|
||||||
edited_at = db.Column(db.DateTime, default=utcnow)
|
edited_at = db.Column(db.DateTime, default=utcnow)
|
||||||
|
|
||||||
|
author = db.relationship('User', lazy='joined', foreign_keys=[user_id])
|
||||||
|
|
||||||
|
|
||||||
class UserFollower(db.Model):
|
class UserFollower(db.Model):
|
||||||
local_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
local_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
|
||||||
|
|
|
@ -45,7 +45,8 @@
|
||||||
Actions
|
Actions
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<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_view', actor=community.link(), slug=page.slug) }}">{{ _('View page') }}</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{{ url_for('community.community_wiki_revisions', actor=community.link(), page_id=page.id) }}">{{ _('View revisions') }}</a></li>
|
||||||
<li><a class="dropdown-item"
|
<li><a class="dropdown-item"
|
||||||
href="{{ url_for('community.community_wiki_edit', actor=community.link(), page_id=page.id, return='list') }}">
|
href="{{ url_for('community.community_wiki_edit', actor=community.link(), page_id=page.id, return='list') }}">
|
||||||
{{ _('Edit') }}</a></li>
|
{{ _('Edit') }}</a></li>
|
||||||
|
@ -59,6 +60,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<p>{{ _('Add a link to the wiki in the community description.') }}</p>
|
||||||
{% else -%}
|
{% else -%}
|
||||||
<p>{{ _('There are no wiki pages in this community.') }}</p>
|
<p>{{ _('There are no wiki pages in this community.') }}</p>
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
30
app/templates/community/community_wiki_revision_view.html
Normal file
30
app/templates/community/community_wiki_revision_view.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{% 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">{{ revision.title }}</h1>
|
||||||
|
{{ revision.body_html | safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "_side_pane.html" %}
|
||||||
|
</div>
|
||||||
|
{% endblock -%}
|
60
app/templates/community/community_wiki_revisions.html
Normal file
60
app/templates/community/community_wiki_revisions.html
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{% 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">{{ _('Revisions of %(title)s', title=page.title) }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if revisions -%}
|
||||||
|
<table class="table table-responsive">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ _('Author') }}</th>
|
||||||
|
<th>{{ _('When') }}</th>
|
||||||
|
<th> </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for revision in revisions %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ render_username(revision.author) }}</td>
|
||||||
|
<td>{{ moment(revision.edited_at).fromNow() }}</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_revision', actor=community.link(), slug=page.slug, revision_id=revision.id) }}">{{ _('View') }}</a></li>
|
||||||
|
{% if revision.id != most_recent_revision %}<li><a class="dropdown-item confirm_first"
|
||||||
|
href="{{ url_for('community.community_wiki_revert_revision', actor=community.link(), slug=page.slug, revision_id=revision.id, return='list') }}">
|
||||||
|
{{ _('Revert') }}</a></li>{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif -%}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -216,7 +216,7 @@ def mime_type_using_head(url):
|
||||||
|
|
||||||
|
|
||||||
# sanitise HTML using an allow list
|
# sanitise HTML using an allow list
|
||||||
def allowlist_html(html: str) -> str:
|
def allowlist_html(html: str, a_target='_blank') -> str:
|
||||||
if html is None or html == '':
|
if html is None or html == '':
|
||||||
return ''
|
return ''
|
||||||
allowed_tags = ['p', 'strong', 'a', 'ul', 'ol', 'li', 'em', 'blockquote', 'cite', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre',
|
allowed_tags = ['p', 'strong', 'a', 'ul', 'ol', 'li', 'em', 'blockquote', 'cite', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre',
|
||||||
|
@ -262,7 +262,7 @@ def allowlist_html(html: str) -> str:
|
||||||
# Add nofollow and target=_blank to anchors
|
# Add nofollow and target=_blank to anchors
|
||||||
if tag.name == 'a':
|
if tag.name == 'a':
|
||||||
tag.attrs['rel'] = 'nofollow ugc'
|
tag.attrs['rel'] = 'nofollow ugc'
|
||||||
tag.attrs['target'] = '_blank'
|
tag.attrs['target'] = a_target
|
||||||
# Add loading=lazy to images
|
# Add loading=lazy to images
|
||||||
if tag.name == 'img':
|
if tag.name == 'img':
|
||||||
tag.attrs['loading'] = 'lazy'
|
tag.attrs['loading'] = 'lazy'
|
||||||
|
@ -275,14 +275,14 @@ def allowlist_html(html: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
# this is for pyfedi's version of Markdown (differs from lemmy for: newlines for soft breaks, ...)
|
# this is for pyfedi's version of Markdown (differs from lemmy for: newlines for soft breaks, ...)
|
||||||
def markdown_to_html(markdown_text) -> str:
|
def markdown_to_html(markdown_text, anchors_new_tab=True) -> str:
|
||||||
if markdown_text:
|
if markdown_text:
|
||||||
raw_html = markdown2.markdown(markdown_text, safe_mode=True,
|
raw_html = markdown2.markdown(markdown_text, safe_mode=True,
|
||||||
extras={'middle-word-em': False, 'tables': True, 'fenced-code-blocks': True, 'strike': True, 'breaks': {'on_newline': True, 'on_backslash': True}})
|
extras={'middle-word-em': False, 'tables': True, 'fenced-code-blocks': True, 'strike': True, 'breaks': {'on_newline': True, 'on_backslash': True}})
|
||||||
# support lemmy's spoiler format
|
# support lemmy's spoiler format
|
||||||
re_spoiler = re.compile(r':{3}\s*?spoiler\s+?(\S.+?)(?:\n|</p>)(.+?)(?:\n|<p>):{3}', re.S)
|
re_spoiler = re.compile(r':{3}\s*?spoiler\s+?(\S.+?)(?:\n|</p>)(.+?)(?:\n|<p>):{3}', re.S)
|
||||||
raw_html = re_spoiler.sub(r'<details><summary>\1</summary><p>\2</p></details>', raw_html)
|
raw_html = re_spoiler.sub(r'<details><summary>\1</summary><p>\2</p></details>', raw_html)
|
||||||
return allowlist_html(raw_html)
|
return allowlist_html(raw_html, a_target='_blank' if anchors_new_tab else '')
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue