aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--apioforum/db.py25
-rw-r--r--apioforum/forum.py23
-rw-r--r--apioforum/roles.py1
-rw-r--r--apioforum/static/style.css4
-rw-r--r--apioforum/templates/common.html29
-rw-r--r--apioforum/templates/config_thread.html19
-rw-r--r--apioforum/templates/edit_permissions.html2
-rw-r--r--apioforum/templates/edit_post.html8
-rw-r--r--apioforum/templates/view_thread.html80
-rw-r--r--apioforum/thread.py33
10 files changed, 165 insertions, 59 deletions
diff --git a/apioforum/db.py b/apioforum/db.py
index ead4e5a..9dcf1ee 100644
--- a/apioforum/db.py
+++ b/apioforum/db.py
@@ -261,6 +261,31 @@ ALTER TABLE users ADD COLUMN banreason TEXT;
"""
ALTER TABLE threads ADD COLUMN locked INT NOT NULL DEFAULT 0;
""",
+"""
+ALTER TABLE threads ADD COLUMN type TEXT NOT NULL DEFAULT "thread";
+ALTER TABLE posts ADD COLUMN wiki INT NOT NULL DEFAULT 0;
+ALTER TABLE posts ADD COLUMN updated_by_others INT NOT NULL DEFAULT 0;
+CREATE TABLE revisions (
+ post NOT NULL REFERENCES posts(id),
+ id INTEGER PRIMARY KEY,
+ author NOT NULL REFERENCES users(username),
+ content TEXT NOT NULL,
+ time TIMESTAMP,
+ comment TEXT
+);
+CREATE TABLE wiki_pages (
+ path TEXT NOT NULL,
+ forum NOT NULL REFERENCES forums(id),
+ thread REFERENCES threads(id),
+ type TEXT NOT NULL DEFAULT "page",
+ PRIMARY KEY (path, forum)
+);
+ALTER TABLE role_config ADD COLUMN p_edit_wiki INT NOT NULL DEFAULT 1
+""",
+"""
+INSERT INTO revisions (post, author, content, time)
+ SELECT id, author, content, updated FROM posts;
+""",
]
diff --git a/apioforum/forum.py b/apioforum/forum.py
index a955ed5..b731a24 100644
--- a/apioforum/forum.py
+++ b/apioforum/forum.py
@@ -280,9 +280,17 @@ def create_thread(forum):
if forum is None:
flash("that forum doesn't exist")
return redirect(url_for('index'))
+
+ if "type" in request.args:
+ thread_type = request.args["type"]
+ else:
+ thread_type = "thread"
+ thread_type = thread_type.replace('_', " ")
+ if thread_type != "thread" and thread_type != "wiki page":
+ abort(404)
if g.user is None:
- flash("you need to be logged in to create a thread")
+ flash("you need to be logged in to create a " + thread_type)
return redirect(url_for('index'))
if request.method == "POST":
@@ -298,15 +306,21 @@ def create_thread(forum):
lock = int(request.form.get('lock','0'))
cur = db.cursor()
cur.execute(
- """INSERT INTO threads (title,creator,created,updated,forum,locked)
- VALUES (?,?,current_timestamp,current_timestamp,?,?);""",
- (title,g.user,forum['id'],lock)
+ """INSERT INTO threads (title,creator,created,updated,forum,locked,type)
+ VALUES (?,?,current_timestamp,current_timestamp,?,?,?);""",
+ (title,g.user,forum['id'],lock,thread_type)
)
thread_id = cur.lastrowid
cur.execute(
"INSERT INTO posts (thread,created,author,content) VALUES (?,current_timestamp,?,?);",
(thread_id,g.user,content)
)
+ db.execute("""
+ INSERT INTO revisions (post,author,content,time,comment)
+ VALUES (?,?,?,current_timestamp,?);
+ """, (cur.lastrowid,g.user,content,"(created)"))
+ if thread_type == "wiki page":
+ db.execute("UPDATE posts SET wiki = 1 WHERE id = ?;", (cur.lastrowid,))
thread = {'id': thread_id, 'forum': forum['id']}
apply_selected_tags(thread, request.form)
db.commit()
@@ -321,6 +335,7 @@ def create_thread(forum):
return render_template("config_thread.html",
forum=forum,
+ thread_type=thread_type,
avail_tags=get_avail_tags(forum['id']))
@forum_route("roles",methods=("GET","POST"))
diff --git a/apioforum/roles.py b/apioforum/roles.py
index 42ce21c..e2428af 100644
--- a/apioforum/roles.py
+++ b/apioforum/roles.py
@@ -10,6 +10,7 @@ permissions = [
"p_view_threads",
"p_vote",
"p_create_polls",
+ "p_edit_wiki",
"p_approve",
"p_create_subforum",
"p_view_forum"
diff --git a/apioforum/static/style.css b/apioforum/static/style.css
index 573411c..8a30355 100644
--- a/apioforum/static/style.css
+++ b/apioforum/static/style.css
@@ -527,3 +527,7 @@ sitelen {
src: local(nasin-nanpa), url("nasin-nanpa.otf") format("opentype");
}
+.wiki-body {
+ margin-bottom: 5px;
+}
+
diff --git a/apioforum/templates/common.html b/apioforum/templates/common.html
index 20bc00b..1177405 100644
--- a/apioforum/templates/common.html
+++ b/apioforum/templates/common.html
@@ -20,12 +20,13 @@
<span class="post-heading-a">
{{disp_user(post.author)}}
- {% set role = get_user_role(forum, post.author) %}
- {% if role != "other" %}
- <span class="user-role">
- ({{ role }})
- </span>
- {% endif %}
+ {%- set role = get_user_role(forum, post.author) -%}
+ {%- if role != "other" %}
+ <span class="user-role"> ({{ role }})</span>
+ {%- endif -%}
+ {%- if post.updated_by_others == 1 -%}
+ , <i>et al.</i>
+ {%- endif %}
{{ts(post.created)}}
@@ -35,7 +36,11 @@
</span>
<span class="post-heading-b">
{% if buttons %}
- {% if post.author == g.user %}
+ {% if post.author == g.user or (
+ post.wiki == 1 and
+ has_permission(forum, g.user, "p_edit_wiki")
+ and not thread_info(post.thread).locked == 1)
+ %}
<a class="actionbutton"
href="{{url_for('thread.edit_post',post_id=post.id)}}">edit</a>
{% endif %}
@@ -173,12 +178,12 @@
</svg>
{% endmacro %}
-{% macro pagination_nav(page,max_pageno,view,bottom=False) %}
+{% macro pagination_nav(page,max_pageno,view,bottom=False,fragment="") %}
<nav aria-label="pagination" class="pages{% if not bottom %} pages-top{% endif %}">
<div class="links">
{% if page > 1 %}
- <a href="{{url_for(view,**kwargs)}}" aria-label="first page">&lt;&lt; first</a>
- <a href="{{url_for(view,page=page-1,**kwargs)}}" aria-label="previous page">&lt; prev</a>
+ <a href="{{url_for(view,**kwargs)}}#{{fragment}}" aria-label="first page">&lt;&lt; first</a>
+ <a href="{{url_for(view,page=page-1,**kwargs)}}#{{fragment}}" aria-label="previous page">&lt; prev</a>
{% else %}
<a aria-label="first page">&lt;&lt; first</a>
<a aria-label="previous page">&lt; prev</a>
@@ -187,8 +192,8 @@
<div class="pageno"><span id="the-word-page">page </span>{{page}} of {{max_pageno}}</div>
<div class="links">
{% if page < max_pageno %}
- <a href="{{url_for(view,page=page+1,**kwargs)}}" aria-label="next page">next &gt;</a>
- <a href="{{url_for(view,page=max_pageno,**kwargs)}}" aria-label="last page">last &gt;&gt;</a>
+ <a href="{{url_for(view,page=page+1,**kwargs)}}#{{fragment}}" aria-label="next page">next &gt;</a>
+ <a href="{{url_for(view,page=max_pageno,**kwargs)}}#{{fragment}}" aria-label="last page">last &gt;&gt;</a>
{% else %}
<a aria-label="next page">next &gt;</a>
<a aria-label="last page">last &gt;&gt;</a>
diff --git a/apioforum/templates/config_thread.html b/apioforum/templates/config_thread.html
index aa4575f..a97e8e6 100644
--- a/apioforum/templates/config_thread.html
+++ b/apioforum/templates/config_thread.html
@@ -1,24 +1,27 @@
{% extends 'base.html' %}
{% from 'common.html' import tag %}
{% block header %}
+
<h1>
{% block title %}
- {% if thread %}configure thread '{{thread.title}}'
- {% else %}create new thread{% endif %}
+ {% set type = thread_type or thread.type %}
+ {% if thread %}configure {{type}} '{{thread.title}}'
+ {% else %}create new {{type}}{% endif %}
{% endblock %}
</h1>
{% endblock %}
{% block content %}
+{% set type = thread_type or thread.type %}
<form method="post">
<fieldset>
-<legend>thread</legend>
-<label for="title">thread title</label>
+<legend>{{type}}</legend>
+<label for="title">{{type}} title</label>
<input type="text" id="title" name="title" {%if thread%}value="{{thread.title}}"{%endif%}>
{% if not thread %}
<br>
- <label for="content">thread content</label>
- <textarea name="content" id="content" class="new-post-box" placeholder="thread content here"></textarea>
+ <label for="content">{{type}} content</label>
+ <textarea name="content" id="content" class="new-post-box" placeholder="welcome to my extraordinary {{type}}."></textarea>
{% endif %}
</fieldset>
@@ -54,7 +57,7 @@
<input type="submit" value="confirm">
<a href="{{url_for('thread.view_thread',thread_id=thread.id)}}">cancel</a>
{% else %}
- <input type="submit" value="create thread">
+ <input type="submit" value="create {{type}}">
<a href="{{url_for('forum.view_forum',forum_id=forum.id)}}">cancel</a>
{% endif %}
</form>
@@ -76,7 +79,7 @@
</form>
{% else %}
<h2>delete poll</h2>
-<p>there is already a poll attached to this thread. you can delete it, which will allow you to create a new one, but this will erase all existing votes and data for the current poll.</p>
+<p>there is already a poll attached to this {{type}}. you can delete it, which will allow you to create a new one, but this will erase all existing votes and data for the current poll.</p>
<form action="{{url_for('thread.delete_poll',thread_id=thread.id)}}" method="post">
<input type="submit" value="confirm: delete poll">
</form>
diff --git a/apioforum/templates/edit_permissions.html b/apioforum/templates/edit_permissions.html
index 59c9093..3903048 100644
--- a/apioforum/templates/edit_permissions.html
+++ b/apioforum/templates/edit_permissions.html
@@ -45,6 +45,8 @@
"allow users with the role to add a poll to a thread")}}
{{perm("p_vote","vote",
"allow users with the role to vote in polls")}}
+ {{perm("p_edit_wiki","edit wiki",
+ "edit the content of wiki pages")}}
{{perm("p_create_subforum","create subforæ",
"allow users with the role to create subforæ in this forum. " +
"they will automatically become a bureaucrat in this subforum.")}}
diff --git a/apioforum/templates/edit_post.html b/apioforum/templates/edit_post.html
index 275f97f..573fd4e 100644
--- a/apioforum/templates/edit_post.html
+++ b/apioforum/templates/edit_post.html
@@ -1,12 +1,18 @@
{% from 'common.html' import disp_post %}
{% extends 'base.html' %}
{% block header %}
-<h1>{%block title%}editing post{%endblock%}</h1>
+<h1>{%block title%}editing {% if post.wiki == 1 %}wiki{% endif %} post{%endblock%}</h1>
{% endblock %}
{% block content %}
<form method="post">
<textarea class="new-post-box" name="newcontent">{{post.content}}</textarea>
+{% if post.wiki == 1 %}
+<p>
+<label for="comment">comment</label>
+<input type="text" id="comment" name="comment" placeholder="fixed spelling issues">
+</p>
+{% endif %}
<p>confirm edit?</p>
<input type="submit" value="confirm">
<a href="{{post_jump(post.id)}}">cancel</a>
diff --git a/apioforum/templates/view_thread.html b/apioforum/templates/view_thread.html
index 1a74f60..e5d69c7 100644
--- a/apioforum/templates/view_thread.html
+++ b/apioforum/templates/view_thread.html
@@ -18,10 +18,10 @@
<div class="thread-top-bar">
<span class="thread-top-bar-a">
{% if g.user == thread.creator or has_permission(thread.forum, g.user, "p_manage_threads") %}
- <a class="actionbutton" href="{{url_for('thread.config_thread',thread_id=thread.id)}}">configure thread</a>
+ <a class="actionbutton" href="{{url_for('thread.config_thread',thread_id=thread.id)}}">configure {{thread.type}}</a>
{% endif %}
{% if has_permission(thread.forum, g.user, "p_delete_posts") %}
- <a class="actionbutton" href="{{url_for('thread.delete_thread',thread_id=thread.id)}}">delete thread</a>
+ <a class="actionbutton" href="{{url_for('thread.delete_thread',thread_id=thread.id)}}">delete {{thread.type}}</a>
{% endif %}
</span>
&nbsp;
@@ -32,53 +32,68 @@
</span>
</div>
-{{ pagination_nav(page,max_pageno,'thread.view_thread',thread_id=thread.id) }}
-<div class="posts">
- {% for post in posts %}
- {% if votes[post.id] %}
+{% macro show_post(post) %}
+ {% if votes[post.id] %}
- {% set vote = votes[post.id] %}
- {% set option_idx = vote.option_idx %}
-
- {# this is bad but it's going to get refactored anyway #}
- {% set footer %}
- {% if vote.is_retraction %}
- {% if not post.deleted %}
- {{post.author}} retracted their vote
- {% else %}
- this post retracted a vote
- {% endif %}
+ {% set vote = votes[post.id] %}
+ {% set option_idx = vote.option_idx %}
+
+ {# this is bad but it's going to get refactored anyway #}
+ {% set footer %}
+ {% if vote.is_retraction %}
+ {% if not post.deleted %}
+ {{post.author}} retracted their vote
{% else %}
- {% set option = poll.options[option_idx-1] %}
- {% if vote.current %}
- {{post.author}} votes for {{option_idx}}: {{option.text}}
+ this post retracted a vote
+ {% endif %}
+ {% else %}
+ {% set option = poll.options[option_idx-1] %}
+ {% if vote.current %}
+ {{post.author}} votes for {{option_idx}}: {{option.text}}
+ {% else %}
+ {% if not post.deleted %}
+ {{post.author}} voted for {{option_idx}}: {{option.text}}, but later changed their vote
{% else %}
- {% if not post.deleted %}
- {{post.author}} voted for {{option_idx}}: {{option.text}}, but later changed their vote
- {% else %}
- this post presented a vote that was later changed
- {% endif %}
+ this post presented a vote that was later changed
{% endif %}
{% endif %}
+ {% endif %}
- {% endset %}
+ {% endset %}
- {{ disp_post(post, forum=thread.forum, buttons=True, footer=footer) }}
-
- {% else %}
- {{ disp_post(post, forum=thread.forum, buttons=True) }}
+ {{ disp_post(post, forum=thread.forum, buttons=True, footer=footer) }}
+
+ {% else %}
+ {{ disp_post(post, forum=thread.forum, buttons=True) }}
+ {% endif %}
+{% endmacro %}
+
+{% if wiki_body %}
+<div class="wiki-body">
+ {{show_post(wiki_body)}}
+</div>
+{% endif %}
+
+<div id="replies">
+{% if not wiki_body or page != 1 or posts|length > 1 %}
+ {{ pagination_nav(page,max_pageno,'thread.view_thread',thread_id=thread.id,fragment="replies") }}
+{% endif %}
+<div class="posts">
+ {% for post in posts %}
+ {% if post != wiki_body %}
+ {{show_post(post)}}
{% endif %}
{% endfor %}
</div>
{% if page != max_pageno %}
- {{ pagination_nav(page,max_pageno,'thread.view_thread',thread_id=thread.id,bottom=True) }}
+ {{ pagination_nav(page,max_pageno,'thread.view_thread',thread_id=thread.id,bottom=True,fragment="replies") }}
{% elif g.user and has_permission(thread.forum, g.user, "p_reply_threads") %}
{% if thread.locked and not has_permission(thread.forum, g.user, "p_manage_threads") %}
<p>this thread is locked, and may not recieve further replies</p>
{% else %}
<form class="new-post" action="{{url_for('thread.create_post',thread_id=thread.id)}}" method="POST">
{% if thread.locked %}<p>this thread is locked, but you can reply to it anyway</p>{% endif %}
- <textarea class="new-post-box" placeholder="your post here..." name="content"></textarea>
+ <textarea class="new-post-box" placeholder="your reply here..." name="content"></textarea>
{% if poll and has_permission(thread.forum, g.user, "p_vote") %}
<fieldset>
<legend>poll: {{poll.title}}</legend>
@@ -109,4 +124,5 @@
{% else %}
<p>please log in to reply to this thread</p>
{% endif %}
+</div>
{% endblock %}
diff --git a/apioforum/thread.py b/apioforum/thread.py
index 3c4a3a3..8911e95 100644
--- a/apioforum/thread.py
+++ b/apioforum/thread.py
@@ -116,6 +116,14 @@ def view_thread(thread_id,page=1):
db.execute("DELETE FROM read WHERE user = ? AND thread = ?;", (g.user, thread_id))
db.execute("INSERT INTO read (user,thread,time) VALUES (?,?,current_timestamp);", (g.user, thread_id))
db.commit()
+
+ wiki_body = None
+ if thread['type'] == "wiki page":
+ wiki_body = db.execute("""
+ SELECT * FROM posts
+ WHERE posts.thread = ?
+ ORDER BY created ASC;
+ """,(thread_id,)).fetchone()
return render_template(
"view_thread.html",
@@ -125,6 +133,7 @@ def view_thread(thread_id,page=1):
poll=poll,
votes=votes,
has_voted=has_voted,
+ wiki_body=wiki_body,
page=page,
max_pageno=max_pageno,
)
@@ -266,6 +275,10 @@ def create_post(thread_id):
VALUES (?,?,?,current_timestamp,?);
""",(thread_id,g.user,content,vote_id))
post_id = cur.lastrowid
+ cur.execute("""
+ INSERT INTO revisions (post,author,content,time,comment)
+ VALUES (?,?,?,current_timestamp,?);
+ """, (post_id,g.user,content,"(created)"))
cur.execute(
"UPDATE threads SET updated = current_timestamp WHERE id = ?;",
(thread_id,)
@@ -330,23 +343,39 @@ def delete_thread(thread_id):
def edit_post(post_id):
db = get_db()
post = db.execute("SELECT * FROM posts WHERE id = ?",(post_id,)).fetchone()
+ thread = thread_info(post['thread'])
if post is None:
flash("that post doesn't exist")
return redirect(url_for('index'))
jump_url = post_jump(post_id)
if post['author'] != g.user:
- flash("you can only edit posts that you created")
- return redirect(jump_url)
+ if post['wiki'] != 1:
+ flash("you can only edit posts that you created")
+ return redirect(jump_url)
+ elif thread['locked'] == 1:
+ flash("you cannot edit this post because the page is locked")
+ return redirect(jump_url)
+ if not has_permission(thread['forum'], g.user, "p_edit_wiki"):
+ flash("you do not have permission to do that")
+ return redirect(jump_url)
if request.method == "POST":
err = None
newcontent = request.form['newcontent']
+ comment = request.form['comment'] if 'comment' in request.form else None
+
if len(newcontent.strip()) == 0:
err="post contents can't be empty"
if err is None:
db.execute(
"UPDATE posts SET content = ?, edited = 1, updated = current_timestamp WHERE id = ?",(newcontent,post_id))
+ db.execute("""
+ INSERT INTO revisions (post, author, content, time, comment)
+ VALUES (?,?,?,current_timestamp,?);
+ """, (post_id,g.user,newcontent,comment))
+ if g.user != post['author']:
+ db.execute("UPDATE posts SET updated_by_others = 1 WHERE id = ?",(post_id,))
db.commit()
flash("post edited editiously")
return redirect(jump_url)