diff options
| -rw-r--r-- | apioforum/db.py | 25 | ||||
| -rw-r--r-- | apioforum/forum.py | 23 | ||||
| -rw-r--r-- | apioforum/roles.py | 1 | ||||
| -rw-r--r-- | apioforum/static/style.css | 4 | ||||
| -rw-r--r-- | apioforum/templates/common.html | 29 | ||||
| -rw-r--r-- | apioforum/templates/config_thread.html | 19 | ||||
| -rw-r--r-- | apioforum/templates/edit_permissions.html | 2 | ||||
| -rw-r--r-- | apioforum/templates/edit_post.html | 8 | ||||
| -rw-r--r-- | apioforum/templates/view_thread.html | 80 | ||||
| -rw-r--r-- | apioforum/thread.py | 33 |
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"><< first</a> - <a href="{{url_for(view,page=page-1,**kwargs)}}" aria-label="previous page">< prev</a> + <a href="{{url_for(view,**kwargs)}}#{{fragment}}" aria-label="first page"><< first</a> + <a href="{{url_for(view,page=page-1,**kwargs)}}#{{fragment}}" aria-label="previous page">< prev</a> {% else %} <a aria-label="first page"><< first</a> <a aria-label="previous page">< 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 ></a> - <a href="{{url_for(view,page=max_pageno,**kwargs)}}" aria-label="last page">last >></a> + <a href="{{url_for(view,page=page+1,**kwargs)}}#{{fragment}}" aria-label="next page">next ></a> + <a href="{{url_for(view,page=max_pageno,**kwargs)}}#{{fragment}}" aria-label="last page">last >></a> {% else %} <a aria-label="next page">next ></a> <a aria-label="last page">last >></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> @@ -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) |
