thread page mostly finished
This commit is contained in:
@@ -202,12 +202,14 @@ def create_app():
|
|||||||
from app.routes.users import bp as users_bp
|
from app.routes.users import bp as users_bp
|
||||||
from app.routes.guides import bp as guides_bp
|
from app.routes.guides import bp as guides_bp
|
||||||
from app.routes.mod import bp as mod_bp
|
from app.routes.mod import bp as mod_bp
|
||||||
|
from app.routes.posts import bp as posts_bp
|
||||||
app.register_blueprint(app_bp)
|
app.register_blueprint(app_bp)
|
||||||
app.register_blueprint(topics_bp)
|
app.register_blueprint(topics_bp)
|
||||||
app.register_blueprint(threads_bp)
|
app.register_blueprint(threads_bp)
|
||||||
app.register_blueprint(users_bp)
|
app.register_blueprint(users_bp)
|
||||||
app.register_blueprint(guides_bp)
|
app.register_blueprint(guides_bp)
|
||||||
app.register_blueprint(mod_bp)
|
app.register_blueprint(mod_bp)
|
||||||
|
app.register_blueprint(posts_bp)
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
from .schema import create as create_tables
|
from .schema import create as create_tables
|
||||||
|
|||||||
@@ -206,9 +206,9 @@ class Topics(Model):
|
|||||||
class Threads(Model):
|
class Threads(Model):
|
||||||
table = 'threads'
|
table = 'threads'
|
||||||
|
|
||||||
def get_posts(self, limit, offset):
|
def get_posts(self, per_page, page):
|
||||||
q = Posts.FULL_POSTS_QUERY + ' WHERE posts.thread_id = ? ORDER BY posts.created_at ASC LIMIT ? OFFSET ?'
|
q = Posts.FULL_POSTS_QUERY + ' WHERE posts.thread_id = ? ORDER BY posts.created_at ASC LIMIT ? OFFSET ?'
|
||||||
return db.query(q, self.id, limit, offset)
|
return db.query(q, self.id, per_page, (page - 1) * per_page)
|
||||||
|
|
||||||
def get_posts_rss(self):
|
def get_posts_rss(self):
|
||||||
q = Posts.FULL_POSTS_QUERY + ' WHERE posts.thread_id = ?'
|
q = Posts.FULL_POSTS_QUERY + ' WHERE posts.thread_id = ?'
|
||||||
|
|||||||
@@ -13,3 +13,15 @@ def new_topic():
|
|||||||
@bp.get('/topics/sort')
|
@bp.get('/topics/sort')
|
||||||
def sort_topics():
|
def sort_topics():
|
||||||
return 'stub'
|
return 'stub'
|
||||||
|
|
||||||
|
@bp.post('/threads/<int:thread_id>/move')
|
||||||
|
def move_thread(thread_id):
|
||||||
|
return 'stub'
|
||||||
|
|
||||||
|
@bp.post('/threads/<int:thread_id>/lock')
|
||||||
|
def lock_thread(thread_id):
|
||||||
|
return 'stub'
|
||||||
|
|
||||||
|
@bp.post('/threads/<int:thread_id>/sticky')
|
||||||
|
def sticky_thread(thread_id):
|
||||||
|
return 'stub'
|
||||||
|
|||||||
7
app/routes/posts.py
Normal file
7
app/routes/posts.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('posts', __name__, url_prefix='/posts/')
|
||||||
|
|
||||||
|
@bp.get('/<int:post_id>/edit')
|
||||||
|
def edit(post_id):
|
||||||
|
return 'stub'
|
||||||
@@ -1,9 +1,45 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint, redirect, url_for, render_template, request, abort
|
||||||
|
|
||||||
|
from ..models import Threads, Posts, Topics, Users, Reactions
|
||||||
|
import math
|
||||||
|
|
||||||
bp = Blueprint('threads', __name__, url_prefix='/threads/')
|
bp = Blueprint('threads', __name__, url_prefix='/threads/')
|
||||||
|
|
||||||
@bp.get('/<slug>')
|
@bp.get('/<slug>')
|
||||||
def thread(slug):
|
def thread(slug):
|
||||||
|
thread = Threads.find({'slug': slug})
|
||||||
|
if not thread:
|
||||||
|
abort(404)
|
||||||
|
topic = Topics.find({'id': thread.topic_id})
|
||||||
|
started_by = Users.find({'id': thread.user_id})
|
||||||
|
PER_PAGE = 10
|
||||||
|
posts_count = Posts.count({'thread_id': thread.id})
|
||||||
|
page_count = max(1, math.ceil(posts_count / PER_PAGE))
|
||||||
|
page = 1
|
||||||
|
after = request.args.get('after', default=None)
|
||||||
|
if after is not None:
|
||||||
|
try:
|
||||||
|
after_id = int(after)
|
||||||
|
post_position = Posts.count([
|
||||||
|
('thread_id', '=', thread.id),
|
||||||
|
('id', '<=', after_id),
|
||||||
|
])
|
||||||
|
page = math.ceil((post_position) / PER_PAGE)
|
||||||
|
except ValueError:
|
||||||
|
abort(404)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
page = max(1, min(int(request.args.get('page', default=1)), page_count))
|
||||||
|
except ValueError:
|
||||||
|
abort(404)
|
||||||
|
return render_template('threads/thread.html', thread=thread, posts=thread.get_posts(PER_PAGE, page), page=page, page_count=page_count, topic=topic, started_by=started_by, topics=Topics.get_list(), Reactions=Reactions)
|
||||||
|
|
||||||
|
@bp.post('/<slug>/reply')
|
||||||
|
def reply(slug):
|
||||||
|
return 'stub'
|
||||||
|
|
||||||
|
@bp.get('/<slug>/feed.atom')
|
||||||
|
def feed(slug):
|
||||||
return 'stub'
|
return 'stub'
|
||||||
|
|
||||||
@bp.get('/new')
|
@bp.get('/new')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask import Blueprint, redirect, url_for, render_template, request, session
|
from flask import Blueprint, redirect, url_for, render_template, request, session, abort
|
||||||
|
|
||||||
from ..models import Topics, Threads
|
from ..models import Topics, Threads
|
||||||
import math
|
import math
|
||||||
@@ -12,15 +12,18 @@ def all_topics():
|
|||||||
|
|
||||||
@bp.get('/<slug>')
|
@bp.get('/<slug>')
|
||||||
def topic(slug):
|
def topic(slug):
|
||||||
t = Topics.find({'slug': slug})
|
topic = Topics.find({'slug': slug})
|
||||||
if not t:
|
if not topic:
|
||||||
return 'stub'
|
abort(404)
|
||||||
sort_by = request.args.get('sort_by', default=session.get('sort_by', default='activity'))
|
sort_by = request.args.get('sort_by', default=session.get('sort_by', default='activity'))
|
||||||
PER_PAGE = 10
|
PER_PAGE = 10
|
||||||
threads_count = Threads.count({'topic_id': t.id})
|
threads_count = Threads.count({'topic_id': topic.id})
|
||||||
page_count = max(1, math.ceil(threads_count / PER_PAGE))
|
page_count = max(1, math.ceil(threads_count / PER_PAGE))
|
||||||
page = max(1, min(int(request.args.get('page', default=1)), page_count))
|
try:
|
||||||
return render_template('topics/topic.html', topic=t, threads=t.get_threads(PER_PAGE, page, sort_by), sort_by=sort_by, page=page, page_count=page_count)
|
page = max(1, min(int(request.args.get('page', default=1)), page_count))
|
||||||
|
except ValueError:
|
||||||
|
abort(404)
|
||||||
|
return render_template('topics/topic.html', topic=topic, threads=topic.get_threads(PER_PAGE, page, sort_by), sort_by=sort_by, page=page, page_count=page_count)
|
||||||
|
|
||||||
@bp.get('/<slug>/feed.atom')
|
@bp.get('/<slug>/feed.atom')
|
||||||
def feed(slug):
|
def feed(slug):
|
||||||
|
|||||||
8
app/templates/common/404.html
Normal file
8
app/templates/common/404.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{%- from 'common/macros.html' import subheader -%}
|
||||||
|
{%- extends 'base.html' -%}
|
||||||
|
{%- block title -%}Not found{%- endblock -%}
|
||||||
|
{%- block content -%}
|
||||||
|
{%- call() subheader('404 Not Found') -%}
|
||||||
|
<span>The requested URL was not found.</span>
|
||||||
|
{%- endcall -%}
|
||||||
|
{%- endblock -%}
|
||||||
@@ -65,3 +65,116 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</span>
|
</span>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro tabs(prefix='', labels = []) -%}
|
||||||
|
<div class="tab-container">
|
||||||
|
<div class="tab-bar" role="tablist">
|
||||||
|
{%- for tab_label in labels -%}
|
||||||
|
<button type="button" class="tab-button" role="tab" aria-selected="{{'true' if loop.index0==0 else 'false'}}" id="{{prefix+'-'+(tab_label | lower)+'-tab'}}" aria-controls="{{prefix+'-'+(tab_label | lower)+'-content'}}">{{tab_label}}</button>
|
||||||
|
{%- endfor -%}
|
||||||
|
</div>
|
||||||
|
{%- for tab_label in labels -%}
|
||||||
|
<div class="plank secondary-bg even no-shadow tab-content {{'hidden' if loop.index0!=0 else ''}}" role="tabpanel" aria-labelledby="{{prefix+'-'+(tab_label | lower)+'-tab'}}" id="{{prefix+'-'+(tab_label | lower)+'-content'}}">
|
||||||
|
{{- caller(loop.index0) -}}
|
||||||
|
</div>
|
||||||
|
{%- endfor -%}
|
||||||
|
</div>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro babycode_editor_component(
|
||||||
|
placeholder='Post content',
|
||||||
|
prefill=''
|
||||||
|
) -%}
|
||||||
|
{%- call(idx) tabs(prefix='babycode', labels=['Write', 'Preview']) -%}
|
||||||
|
{%- if idx == 0 -%}
|
||||||
|
<span class="babycode-editor-controls">
|
||||||
|
<span class="button-row">
|
||||||
|
<button type="button" class="minimal"><b>B</b></button>
|
||||||
|
<button type="button" class="minimal"><i>i</i></button>
|
||||||
|
<button type="button" class="minimal"><s>S</s></button>
|
||||||
|
<button type="button" class="minimal"><u>U</u></button>
|
||||||
|
<button type="button" class="minimal"><code>://</code></button>
|
||||||
|
<button type="button" class="minimal"><code></></code></button>
|
||||||
|
<button type="button" class="minimal">1.</button>
|
||||||
|
<button type="button" class="minimal">•</button>
|
||||||
|
<button type="button" class="minimal"><img src="/static/emoji/angry.png" class="emoji"></button>
|
||||||
|
</span>
|
||||||
|
<a href="##">babycode help</a>
|
||||||
|
</span>
|
||||||
|
<textarea name="babycode-content" class="babycode-editor" placeholder="{{placeholder}}" required>{{ prefill }}</textarea>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endcall -%}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro full_post(
|
||||||
|
post, render_sig=true, is_latest=false,
|
||||||
|
show_toolbar=true, is_editing=false, thread=none,
|
||||||
|
show_reactions=true
|
||||||
|
) -%}
|
||||||
|
{%- if is_logged_in() -%}
|
||||||
|
{%- set can_delete = post.user_id == get_active_user().id or is_mod() -%}
|
||||||
|
{%- else -%}
|
||||||
|
{%- set show_toolbar = false -%}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- set owns = is_logged_in() and post.user_id == get_active_user().id -%}
|
||||||
|
{%- set can_reply = (is_logged_in()) and (not thread.locked or is_mod()) -%}
|
||||||
|
<div class="usercard plank even contrast-bg minimal no-shadow">
|
||||||
|
<div class="usercard-inner">
|
||||||
|
<img src="{{post.avatar_path}}" class="avatar">
|
||||||
|
<div class="usercard-rest">
|
||||||
|
<a href="{{url_for('users.user_page', username=post.username)}}">{{post.display_name if post.display_name else post.username}}</a>
|
||||||
|
<abbr title="mention">@{{post.username}}</abbr>
|
||||||
|
<i>{{post.status}}</i>
|
||||||
|
{%- set badges=post.badges_json | fromjson -%}
|
||||||
|
<div class="badges-container">
|
||||||
|
{%- for badge in badges -%}
|
||||||
|
{%- if badge.link -%}<a href="{{badge.link}}">{%- endif -%}
|
||||||
|
<img src="{{badge.file_path}}" alt="{{badge.label}}" title="{{badge.label}}" class="badge-button">
|
||||||
|
{%- if badge.link -%}</a>{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="post-content">
|
||||||
|
<div class="plank even minimal secondary-bg no-shadow post-info">
|
||||||
|
<a href="{{get_post_url(post.id, _anchor=true)}}"><i>Posted on {{timestamp(post.created_at)}}</i></a>
|
||||||
|
{%- if show_toolbar -%}
|
||||||
|
<span class="thread-actions">
|
||||||
|
{%- if owns -%}
|
||||||
|
<a class="linkbutton" href="{{url_for('posts.edit', post_id=post.id)}}">Edit</a>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- if can_reply -%}
|
||||||
|
<button>Quote</button>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- if can_delete -%}
|
||||||
|
<button class="critical">Delete</button>
|
||||||
|
{%- endif -%}
|
||||||
|
<button>Bookmark…</button>
|
||||||
|
</span>
|
||||||
|
{%- endif -%}
|
||||||
|
</div>
|
||||||
|
<div class="plank even no-shadow post-content-inner minimal">{{post.content | safe}}
|
||||||
|
{%- if render_sig and post.signature_rendered -%}
|
||||||
|
<div class="post-signature">{{post.signature_rendered | safe}}</div>
|
||||||
|
{%- endif -%}
|
||||||
|
</div>
|
||||||
|
{%- if show_reactions -%}
|
||||||
|
<div class="plank even secondary-bg minimal no-shadow">
|
||||||
|
<span class="button-row">
|
||||||
|
{%- for reaction in Reactions.for_post(post.id) -%}
|
||||||
|
{% set reactors = Reactions.get_users(post.id, reaction.reaction_text) | map(attribute='username') | list %}
|
||||||
|
{% set reactors_trimmed = reactors[:10] %}
|
||||||
|
{% set reactors_str = reactors_trimmed | join (',\n') %}
|
||||||
|
{% if reactors | count > 10 %}
|
||||||
|
{% set reactors_str = reactors_str + '\n...and many others' %}
|
||||||
|
{% endif %}
|
||||||
|
{% set has_reacted = get_active_user() is not none and get_active_user().username in reactors %}
|
||||||
|
<button {{'disabled' if not is_logged_in() else ''}} title="{{reactors_str}}" class="minimal {{'alt' if has_reacted else ''}}"><img src="/static/emoji/{{reaction.reaction_text}}.png">{{reaction.c}}</button>
|
||||||
|
{%- endfor -%}
|
||||||
|
</span>
|
||||||
|
{%- if is_logged_in() -%}<button>Add reaction</button>{%- endif -%}
|
||||||
|
</div>
|
||||||
|
{%- endif -%}
|
||||||
|
</div>
|
||||||
|
{%- endmacro %}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<input type="text" placeholder="Username" name="username" autocomplete="username" required>
|
<input type="text" placeholder="Username" name="username" autocomplete="username" required>
|
||||||
<input type="password" placeholder="Password" name="password" autocomplete="current-password" required>
|
<input type="password" placeholder="Password" name="password" autocomplete="current-password" required>
|
||||||
<input type="submit" value="Log in">
|
<input type="submit" value="Log in">
|
||||||
<a href="{{url_for('users.sign_up')}}" class="linkbutton">Sign up</a>
|
<a href="{{url_for('users.sign_up')}}" class="linkbutton alt">Sign up</a>
|
||||||
</form>
|
</form>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
66
app/templates/threads/thread.html
Normal file
66
app/templates/threads/thread.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{%- from 'common/macros.html' import subheader, timestamp, pager, babycode_editor_component -%}
|
||||||
|
{%- from 'common/macros.html' import full_post with context -%}
|
||||||
|
{%- extends 'base.html' -%}
|
||||||
|
{%- block title -%}{%- endblock -%}
|
||||||
|
{%- block content -%}
|
||||||
|
{%- set td -%}
|
||||||
|
Started by <a href="{{url_for('users.user_page', username=started_by.username)}}">{{started_by.get_readable_name()}}</a> in topic <a href="{{url_for('topics.topic', slug=topic.slug)}}">{{topic.name}}</a>
|
||||||
|
{%- endset -%}
|
||||||
|
{%- call() subheader(thread.title, td) -%}
|
||||||
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
|
<legend>Actions</legend>
|
||||||
|
{%- if is_logged_in() -%}
|
||||||
|
<button>Subscribe</button>
|
||||||
|
<button>Bookmark…</button>
|
||||||
|
{%- endif -%}
|
||||||
|
<a href="{{url_for('threads.feed', slug=thread.slug)}}" class="linkbutton rss">Subscribe via RSS</a>
|
||||||
|
</fieldset>
|
||||||
|
{%- if is_mod() -%}
|
||||||
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
|
<legend>Moderation actions</legend>
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="lock" value="{{not thread.locked()}}">
|
||||||
|
<input type="hidden" name="sticky" value="{{not thread.stickied()}}">
|
||||||
|
<input type="submit" class="warn" value="{{'Unlock' if thread.locked() else 'Lock'}}" formaction="{{url_for('mod.lock_thread', thread_id=thread.id)}}">
|
||||||
|
<input type="submit" class="warn" value="{{'Unsticky' if thread.stickied() else 'Sticky'}}" formaction="{{url_for('mod.sticky_thread', thread_id=thread.id)}}">
|
||||||
|
</form>
|
||||||
|
<form class="horizontal wrap" method="POST" action="{{url_for('mod.move_thread', thread_id=thread.id)}}">
|
||||||
|
<select name="new_topic_id" id="new_topic_id">
|
||||||
|
{%- for t in topics -%}
|
||||||
|
<option value="{{t.id}}" {{'selected disabled' if t.id == topic.id else ''}} autocomplete="off">{{t.name}}</option>
|
||||||
|
{%- endfor -%}
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="Move" class="warn">
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
|
<legend>Page</legend>
|
||||||
|
{{- pager(page, page_count) -}}
|
||||||
|
</fieldset>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endcall -%}
|
||||||
|
<main>
|
||||||
|
{%- for post in posts -%}
|
||||||
|
<article id="post-{{post.id}}" class="post plank">
|
||||||
|
{{full_post(post)}}
|
||||||
|
</article>
|
||||||
|
{%- endfor -%}
|
||||||
|
</main>
|
||||||
|
<div class="plank secondary-bg">
|
||||||
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
|
<legend>Page</legend>
|
||||||
|
{{- pager(page, page_count) -}}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{%- if is_logged_in() -%}
|
||||||
|
<form action="{{url_for('threads.reply', slug=thread.slug)}}" method="POST" class="plank post-edit-form">
|
||||||
|
<h2 class="info">Reply to "{{thread.title}}"</h2>
|
||||||
|
{{- babycode_editor_component() -}}
|
||||||
|
<span>
|
||||||
|
<input type="checkbox" checked name="subscribe" id="subscribe">
|
||||||
|
<label for="subscribe">Subscribe to thread</label>
|
||||||
|
</span>
|
||||||
|
<span><input type="submit" value="Post reply"></span>
|
||||||
|
</form>
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endblock -%}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
{% from 'common/macros.html' import timestamp, subheader, pager %}
|
{% from 'common/macros.html' import timestamp, subheader, pager %}
|
||||||
{%- extends 'base.html' -%}
|
{%- extends 'base.html' -%}
|
||||||
|
{%- block title -%}browsing topic {{topic.name}}{%- endblock -%}
|
||||||
{%- block content -%}
|
{%- block content -%}
|
||||||
{%- call() subheader(('Threads in "%s"' % topic.name), topic.description) -%}
|
{%- call() subheader(('Threads in "%s"' % topic.name), topic.description) -%}
|
||||||
<fieldset class="plank even no-shadow minimal thread-actions">
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
<legend>Actions</legend>
|
<legend>Actions</legend>
|
||||||
|
{%- if is_logged_in() -%}
|
||||||
<a href="{{url_for('threads.new', topic_id=topic.id)}}" class="linkbutton">New thread</a>
|
<a href="{{url_for('threads.new', topic_id=topic.id)}}" class="linkbutton">New thread</a>
|
||||||
|
{%- endif -%}
|
||||||
<a href="{{url_for('topics.feed', slug=topic.slug)}}" class="linkbutton rss">Subscribe via RSS</a>
|
<a href="{{url_for('topics.feed', slug=topic.slug)}}" class="linkbutton rss">Subscribe via RSS</a>
|
||||||
<form method="GET">
|
<form method="GET">
|
||||||
<select name="sort_by">
|
<select name="sort_by">
|
||||||
@@ -14,11 +17,15 @@
|
|||||||
<input type="submit" value="Sort">
|
<input type="submit" value="Sort">
|
||||||
</form>
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{%- if get_active_user().is_mod() -%}
|
{%- if is_mod() -%}
|
||||||
<fieldset class="plank even no-shadow minimal thread-actions">
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
<legend>Moderation actions</legend>
|
<legend>Moderation actions</legend>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
|
<legend>Page</legend>
|
||||||
|
{{- pager(page, page_count, args=request.args) -}}
|
||||||
|
</fieldset>
|
||||||
{%- endcall -%}
|
{%- endcall -%}
|
||||||
{%- for thread in threads -%}
|
{%- for thread in threads -%}
|
||||||
<div class="topic-info plank">
|
<div class="topic-info plank">
|
||||||
@@ -30,9 +37,14 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</div>
|
</div>
|
||||||
<span>Started by <a href="{{url_for('users.user_page', username=thread.started_by)}}">{{thread.started_by_display_name if thread.started_by_display_name else thread.started_by}}</a> on {{timestamp(thread.created_at)}}</span>
|
<span>Started by <a href="{{url_for('users.user_page', username=thread.started_by)}}">{{thread.started_by_display_name if thread.started_by_display_name else thread.started_by}}</a> on {{timestamp(thread.created_at)}}</span>
|
||||||
<span>{{thread.posts_count - 1}} {{'repl' | pluralize(thread.posts_count - 1, 'y', 'ies')}}</span>
|
<span>{{thread.posts_count}} {{'repl' | pluralize(thread.posts_count, 'y', 'ies')}}</span>
|
||||||
<span>Latest reply by <a href="{{get_post_url(thread.latest_post_id, _anchor=true)}}">{{thread.latest_post_display_name if thread.latest_post_display_name else thread.latest_post_username}} on {{timestamp(thread.latest_post_created_at)}}</a></span>
|
<span>Latest post by <a href="{{get_post_url(thread.latest_post_id, _anchor=true)}}">{{thread.latest_post_display_name if thread.latest_post_display_name else thread.latest_post_username}} on {{timestamp(thread.latest_post_created_at)}}</a>{{' (OP)' if thread.posts_count == 1 else ''}}</span>
|
||||||
</div>
|
</div>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{{pager(page, page_count, args=request.args)}}
|
<div class="plank secondary-bg">
|
||||||
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
|
<legend>Page</legend>
|
||||||
|
{{- pager(page, page_count, args=request.args) -}}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
{%- endblock -%}
|
{%- endblock -%}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{%- extends 'base.html' -%}
|
{%- extends 'base.html' -%}
|
||||||
{%- block content -%}
|
{%- block content -%}
|
||||||
{%- call() subheader('All topics') -%}
|
{%- call() subheader('All topics') -%}
|
||||||
{%- if get_active_user().is_mod() -%}
|
{%- if is_mod() -%}
|
||||||
<fieldset class="plank even no-shadow minimal thread-actions">
|
<fieldset class="plank even no-shadow minimal thread-actions">
|
||||||
<legend>Moderation actions</legend>
|
<legend>Moderation actions</legend>
|
||||||
<a href="{{url_for('mod.new_topic')}}" class="linkbutton">New topic</a>
|
<a href="{{url_for('mod.new_topic')}}" class="linkbutton">New topic</a>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ button, .linkbutton, input[type="submit"] {
|
|||||||
box-shadow: inset 0px 2px 5px 3px var(--inset-color);
|
box-shadow: inset 0px 2px 5px 3px var(--inset-color);
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: default;
|
user-select: none;
|
||||||
|
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
display: inline flex;
|
display: inline flex;
|
||||||
@@ -142,13 +142,14 @@ button, .linkbutton, input[type="submit"] {
|
|||||||
background: linear-gradient(var(--top-color) 0%, var(--top-color2) 25%, var(--hover-color) 26%, var(--hover-color) 80%, var(--bottom-color) 100%);
|
background: linear-gradient(var(--top-color) 0%, var(--top-color2) 25%, var(--hover-color) 26%, var(--hover-color) 80%, var(--bottom-color) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:is(:active, .active) {
|
&:is(:active, .active, [aria-selected='true']) {
|
||||||
background: linear-gradient(var(--active-color) 0%, var(--active-color) 50%, var(--main-color) 100%);
|
background: linear-gradient(var(--active-color) 0%, var(--active-color) 50%, var(--main-color) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background: var(--disabled-color);
|
background: var(--disabled-color);
|
||||||
--inset-color: #fff3;
|
--inset-color: #fff3;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +159,7 @@ button, .linkbutton, input[type="submit"] {
|
|||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
&.active {
|
&[aria-selected='true'] {
|
||||||
padding-top: calc(var(--base-padding) * 1.5);
|
padding-top: calc(var(--base-padding) * 1.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,6 +456,7 @@ footer {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: var(--base-padding);
|
gap: var(--base-padding);
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-group {
|
.actions-group {
|
||||||
@@ -502,9 +504,20 @@ footer {
|
|||||||
grid-template-rows: min-content 1fr min-content;
|
grid-template-rows: min-content 1fr min-content;
|
||||||
&> * {
|
&> * {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 54px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post-signature {
|
||||||
|
margin-top: auto;
|
||||||
|
border-top: 2px dotted gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.badge-button {
|
.badge-button {
|
||||||
min-width: 88px;
|
min-width: 88px;
|
||||||
min-height: 31px;
|
min-height: 31px;
|
||||||
@@ -577,6 +590,7 @@ footer {
|
|||||||
color: var(--font-color-anti);
|
color: var(--font-color-anti);
|
||||||
padding: var(--base-padding);
|
padding: var(--base-padding);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.babycode-big {
|
.babycode-big {
|
||||||
@@ -635,11 +649,44 @@ summary {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.emoji {
|
.emoji {
|
||||||
max-width: 15px;
|
max-width: 15px;
|
||||||
max-height: 15px;
|
max-height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.mention {
|
||||||
|
--mention-color: var(--bg-color-contrast);
|
||||||
|
--hover-color: hsl(from var(--mention-color) h calc(s * 0.7) calc(l * 1.1));
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: var(--base-padding);
|
||||||
|
background-color: var(--mention-color);
|
||||||
|
color: black;
|
||||||
|
border: 1px dashed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.me {
|
||||||
|
--mention-color: hsl(from var(--bg-color-contrast) calc(h + 90) s l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
#wrapper {
|
#wrapper {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@@ -660,12 +707,15 @@ summary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
max-width: 40%;
|
max-width: 180px;
|
||||||
max-height: 40%;
|
max-height: 180px;
|
||||||
|
min-width: 140px;
|
||||||
|
min-height: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.usercard-inner {
|
.usercard-inner {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-title-counter {
|
.thread-title-counter {
|
||||||
|
|||||||
Reference in New Issue
Block a user