diff options
-rw-r--r-- | apioforum/__init__.py | 2 | ||||
-rw-r--r-- | apioforum/db.py | 37 | ||||
-rw-r--r-- | apioforum/forum.py | 76 | ||||
-rw-r--r-- | apioforum/static/style.css | 57 | ||||
-rw-r--r-- | apioforum/templates/base.html | 2 | ||||
-rw-r--r-- | apioforum/templates/common.html | 27 | ||||
-rw-r--r-- | apioforum/templates/create_thread.html | 2 | ||||
-rw-r--r-- | apioforum/templates/view_forum.html | 64 | ||||
-rw-r--r-- | apioforum/templates/view_thread.html | 3 |
9 files changed, 204 insertions, 66 deletions
diff --git a/apioforum/__init__.py b/apioforum/__init__.py index 9d49f36..30dd813 100644 --- a/apioforum/__init__.py +++ b/apioforum/__init__.py @@ -47,6 +47,8 @@ def create_app(): p += "?" + request.query_string.decode("utf-8") return dict(path_for_next=p) + app.jinja_env.globals.update(forum_path=forum.forum_path) + from .mdrender import render @app.template_filter('md') def md_render(s): diff --git a/apioforum/db.py b/apioforum/db.py index 25bda94..97bd0e2 100644 --- a/apioforum/db.py +++ b/apioforum/db.py @@ -115,7 +115,42 @@ ALTER TABLE posts ADD COLUMN vote INTEGER REFERENCES votes(id); CREATE VIEW vote_counts AS SELECT poll, option_idx, count(*) AS num FROM votes WHERE current GROUP BY option_idx,poll; """, - +""" +CREATE TABLE forums ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + parent INTEGER REFERENCES forums(id), + description TEXT +); +INSERT INTO forums (name,parent,description) values ('apioforum',null, + 'welcome to the apioforum\n\n' || + 'forum rules: do not be a bad person. do not do bad things.'); + +PRAGMA foreign_keys = off; +BEGIN TRANSACTION; +CREATE TABLE threads_new ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL, + creator TEXT NOT NULL, + created TIMESTAMP NOT NULL, + updated TIMESTAMP NOT NULL, + forum NOT NULL REFERENCES forums(id), + poll INTEGER REFERENCES polls(id) +); +INSERT INTO threads_new (id,title,creator,created,updated,forum) + SELECT id,title,creator,created,updated,1 FROM threads; +DROP TABLE threads; +ALTER TABLE threads_new RENAME TO threads; +COMMIT; +PRAGMA foreign_keys = on; +""", +""" +CREATE VIEW most_recent_posts AS + SELECT max(id), * FROM posts GROUP BY thread; + +CREATE VIEW number_of_posts AS + SELECT thread, count(*) AS num_replies FROM posts GROUP BY thread; +""", ] def init_db(): diff --git a/apioforum/forum.py b/apioforum/forum.py index fa6fcc8..1d26f3a 100644 --- a/apioforum/forum.py +++ b/apioforum/forum.py @@ -10,23 +10,45 @@ from .db import get_db from .mdrender import render from sqlite3 import OperationalError +import datetime bp = Blueprint("forum", __name__, url_prefix="/") - @bp.route("/") -def view_forum(): +def not_actual_index(): + return redirect("/1") + +def forum_path(forum_id): + db = get_db() + ancestors = db.execute(""" + WITH RECURSIVE fs AS + (SELECT * FROM forums WHERE id = ? + UNION ALL + SELECT forums.* FROM forums, fs WHERE fs.parent=forums.id) + SELECT * FROM fs; + """,(forum_id,)).fetchall() + ancestors.reverse() + return ancestors + +@bp.route("/<int:forum_id>") +def view_forum(forum_id): db = get_db() + forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone() threads = db.execute( - """SELECT threads.id, threads.title, threads.creator, threads.created, - threads.updated, count(posts.id) as num_replies, max(posts.id), posts.author as last_user + """SELECT + threads.id, threads.title, threads.creator, threads.created, + threads.updated, number_of_posts.num_replies, + most_recent_posts.created as mrp_created, + most_recent_posts.author as mrp_author, + most_recent_posts.id as mrp_id, + most_recent_posts.content as mrp_content FROM threads - INNER JOIN posts ON posts.thread = threads.id - GROUP BY threads.id + INNER JOIN most_recent_posts ON most_recent_posts.thread = threads.id + INNER JOIN number_of_posts ON number_of_posts.thread = threads.id + WHERE threads.forum = ? ORDER BY threads.updated DESC; - """).fetchall() + """,(forum_id,)).fetchall() thread_tags = {} - preview_post = {} #todo: somehow optimise this for thread in threads: thread_tags[thread['id']] = db.execute( @@ -35,19 +57,37 @@ def view_forum(): WHERE thread_tags.thread = ? ORDER BY tags.id; """,(thread['id'],)).fetchall() - preview_post[thread['id']] = db.execute( - """SELECT * FROM posts WHERE thread = ? - ORDER BY created DESC; - """,(thread['id'],)).fetchone() + + subforums_rows = db.execute(""" + SELECT max(threads.updated) as updated, forums.* FROM forums + LEFT OUTER JOIN threads ON threads.forum=forums.id + WHERE parent = ? + GROUP BY forums.id + ORDER BY name ASC + """,(forum_id,)).fetchall() + subforums = [] + for s in subforums_rows: + a={} + a.update(s) + if a['updated'] is not None: + a['updated'] = datetime.datetime.fromisoformat(a['updated']) + subforums.append(a) + + return render_template("view_forum.html", + forum=forum, + subforums=subforums, threads=threads, thread_tags=thread_tags, - preview_post=preview_post ) -@bp.route("/create_thread",methods=("GET","POST")) -def create_thread(): +@bp.route("/<int:forum_id>/create_thread",methods=("GET","POST")) +def create_thread(forum_id): db = get_db() + forum = db.execute("SELECT * FROM forums WHERE id = ?",(forum_id,)).fetchone() + if forum is None: + flash("that forum doesn't exist") + return redirect(url_for('index')) if g.user is None: flash("you need to be logged in to create a thread") @@ -63,8 +103,8 @@ def create_thread(): if err is None: cur = db.cursor() cur.execute( - "INSERT INTO threads (title,creator,created,updated) VALUES (?,?,current_timestamp,current_timestamp);", - (title,g.user) + "INSERT INTO threads (title,creator,created,updated,forum) VALUES (?,?,current_timestamp,current_timestamp,?);", + (title,g.user,forum_id) ) thread_id = cur.lastrowid cur.execute( @@ -96,7 +136,7 @@ def search(): """, (query,)).fetchall() except OperationalError: flash('your search query was malformed.') - return redirect(url_for("forum.view_forum")) + return redirect(url_for("forum.not_actual_index")) display_thread_id = [ True ] * len(results) last_thread = None diff --git a/apioforum/static/style.css b/apioforum/static/style.css index 6178dd9..62215c7 100644 --- a/apioforum/static/style.css +++ b/apioforum/static/style.css @@ -81,35 +81,36 @@ dt { font-weight: bold } img { max-width: 100% } -nav { float: right; padding: 5px; margin: 2px; border: 1px solid black; display:flex; align-items: center; flex-wrap: wrap } -nav p { margin-left: 15px; margin-top: 0; margin-bottom: 0; margin-right: 10px; padding: 0 } -nav p:first-of-type { margin-left:0.5em } -nav a { color: blue; text-decoration: none } -nav .links { display: flex; } +nav#navbar { float: right; padding: 5px; margin: 2px; border: 1px solid black; display:flex; align-items: center; flex-wrap: wrap } +nav#navbar p { margin-left: 15px; margin-top: 0; margin-bottom: 0; margin-right: 10px; padding: 0 } +nav#navbar p:first-of-type { margin-left:0.5em } +nav#navbar a { color: blue; text-decoration: none } +nav#navbar .links { display: flex; } /* todo: make the navbar less bad */ .flashmsg { border: 1px solid black; background-color: yellow; max-width: max-content; padding: 5px; clear: both;} -.thread-listing:nth-child(even) { background-color: var(--alternating-colour-even) } -.thread-listing:nth-child(odd) { background-color: var(--alternating-colour-odd) } +.listing:nth-child(even) { background-color: var(--alternating-colour-even) } +.listing:nth-child(odd) { background-color: var(--alternating-colour-odd) } -.thread-listing { +.listing { border-left: 1px solid black; border-right: 1px solid black; border-top: 1px solid black; padding: 10px; } -.thread-listing:last-of-type { border-bottom: 1px solid black; } -.thread-listing-main { +.listing:last-of-type { border-bottom: 1px solid black; } +.listing-main { display: flex; align-items: center; } -.thread-listing-title { +.listing-title { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; flex-grow: 1; } + .thread-listing-tags { display: flex; align-items: center; @@ -123,7 +124,7 @@ nav .links { display: flex; } flex-wrap: nowrap; } .thread-listing-creator { margin-right: 5px; } -.thread-preview { +.listing-caption { overflow: hidden; font-size: smaller; white-space: nowrap; @@ -139,7 +140,7 @@ nav .links { display: flex; } /* wide screens */ @media all and (min-width: 600px) { - .thread-listing-title { font-size: larger; } + .listing-title { font-size: larger; } } /* small screens */ @@ -153,22 +154,6 @@ nav .links { display: flex; } } } - -.threadlisting-part { - background-color: inherit; - display: table-cell; - vertical-align: middle; - padding: 3px; - text-align: center; -} - -.threadlisting-part-title { - text-overflow: ellipsis; - overflow: hidden; -} - -.threadlisting-header { font-weight: bold } - .actionbutton::before { content: "["; color: grey; @@ -216,3 +201,17 @@ blockquote { border: 1px solid grey; padding: 4px; } + +.breadcrumbs { + list-style: none; +} + +.breadcrumbs li { + display: inline; +} + +.breadcrumbs li+li::before { + content: "/\00a0"; + padding: 8px; +} + diff --git a/apioforum/templates/base.html b/apioforum/templates/base.html index 33e49f6..9cfd7f3 100644 --- a/apioforum/templates/base.html +++ b/apioforum/templates/base.html @@ -10,7 +10,7 @@ <link rel="icon" href="//gh0.pw/favicon.ico"> </head> <body> - <nav> + <nav id="navbar"> <p style="font-family: monospace;"><b>apio</b><i>forum</i>™</p> <form class="search-form" action="/search"> <input type="search" placeholder="query" name="q"> diff --git a/apioforum/templates/common.html b/apioforum/templates/common.html index 3321085..44cfbce 100644 --- a/apioforum/templates/common.html +++ b/apioforum/templates/common.html @@ -49,3 +49,30 @@ {% macro tag(the_tag) -%} <span class="tag" style="color: {{the_tag.text_colour}}; background-color: {{the_tag.bg_colour}}">{{the_tag.name}}</span> {%- endmacro %} + +{% macro breadcrumb() %} +<nav aria-label="Breadcrumb"> +<ol class="breadcrumbs"> + {{- caller() -}} +</ol> +</nav> +{% endmacro %} + +{% macro forum_bc_entries(forum_id) -%} + {%- for f in forum_path(forum_id) -%} + <li><a href="{{url_for('forum.view_forum',forum_id=f.id)}}">{{ f.name }}</a></li> + {%- endfor %} +{%- endmacro %} + +{% macro forum_breadcrumb(forum) %} + {%- call breadcrumb() %} + {{ forum_bc_entries(forum.id) }} + {% endcall -%} +{% endmacro %} + +{% macro thread_breadcrumb(thread) %} + {%- call breadcrumb() %} + {{ forum_bc_entries(thread.forum) }} + <li>{{ thread.title }}</li> + {% endcall -%} +{% endmacro %} diff --git a/apioforum/templates/create_thread.html b/apioforum/templates/create_thread.html index 9de7312..04b4f42 100644 --- a/apioforum/templates/create_thread.html +++ b/apioforum/templates/create_thread.html @@ -4,7 +4,7 @@ {% endblock %} {% block content %} -<form action="{{url_for('forum.create_thread')}}", method="POST"> +<form method="POST"> <label for="title">thread title</label> <input name="title" id="title"> <br> diff --git a/apioforum/templates/view_forum.html b/apioforum/templates/view_forum.html index b27088d..d075d85 100644 --- a/apioforum/templates/view_forum.html +++ b/apioforum/templates/view_forum.html @@ -1,19 +1,52 @@ {% extends 'base.html' %} -{% from 'common.html' import ts, tag, disp_user, post_url %} -{% block header %}<h1>{% block title %}apioforum{%endblock%}</h1>{%endblock%} +{% from 'common.html' import ts, tag, disp_user, post_url, forum_breadcrumb %} +{% block header %} +<h1>{% block title %}{{forum.name}}{%endblock%}</h1> +{% if forum.id != 1 %} + {{ forum_breadcrumb(forum) }} +{% endif %} +{%endblock%} + {%block content%} -<p>welcome to the apioforum</p> -<p>forum rules: do not be a bad person. do not do bad things.</p> +{% if forum.description %} +{{forum.description|md|safe}} +{% endif %} + +{% if subforums %} +<h2>subforae</h2> +<div class="forum-list"> + {% for subforum in subforums %} + <div class="listing"> + <div class="listing-main"> + <div class="listing-title"> + <a href="{{url_for('forum.view_forum',forum_id=subforum.id)}}"> + {{- subforum.name -}} + </a> + </div> + </div> + <div class="listing-caption"> + {% if subforum.updated %} + last activity {{ts(subforum.updated)}} ago + {% else %} + no threads + {% endif %} + </div> + </div> + {% endfor %} +</div> +{% endif %} + +<h2>threads</h2> {% if g.user %} -<p><a class="actionbutton" href="{{url_for('forum.create_thread')}}">create new thread</a></p> +<p><a class="actionbutton" href="{{url_for('forum.create_thread',forum_id=forum.id)}}">create new thread</a></p> {% else %} <p>please log in to create a new thread</p> {% endif %} <div class="thread-list"> {%for thread in threads%} - <div class="thread-listing"> - <div class="thread-listing-main"> - <div class="thread-listing-title"> + <div class="listing"> + <div class="listing-main"> + <div class="listing-title"> <a href="{{url_for('thread.view_thread',thread_id=thread.id)}}"> {{- thread.title -}} </a> @@ -30,20 +63,21 @@ {{ ts(thread.created) }} </div> </div> - {% if preview_post[thread.id] %} - <div class="thread-preview"> - {{ disp_user(preview_post[thread.id].author) }} + {#{% if thread.mrp_id %}#} + <div class="listing-caption"> + {{ disp_user(thread.mrp_author) }} <span class="thread-preview-ts"> - {{ ts(preview_post[thread.id].created) }} + {{ ts(thread.mrp_created) }} </span> <span class="thread-preview-post"> - <a href="{{post_url(preview_post[thread.id])}}"> - {{ preview_post[thread.id].content[:500]|e }} + <a href="{{url_for('thread.view_thread',thread_id=thread.id)}}#post_{{thread.mrp_id}}"> + {{ thread.mrp_content[:500]|e }} </a> </span> </div> - {% endif %} + {#{% endif %}#} </div> {%endfor%} </div> +</main> {%endblock%} diff --git a/apioforum/templates/view_thread.html b/apioforum/templates/view_thread.html index 7bf253d..d4a43ef 100644 --- a/apioforum/templates/view_thread.html +++ b/apioforum/templates/view_thread.html @@ -1,7 +1,8 @@ -{% from 'common.html' import disp_post,tag %} +{% from 'common.html' import disp_post,tag,thread_breadcrumb %} {% extends 'base.html' %} {% block header %} <h1>{%block title %}{{thread.title}}{% endblock %}</h1> +{{ thread_breadcrumb(thread) }} {% endblock %} {%block content%} |