thread page mostly finished

This commit is contained in:
2026-04-15 23:11:24 +03:00
parent 7db111d18b
commit d0daaf4494
13 changed files with 330 additions and 21 deletions

View 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 -%}

View File

@@ -65,3 +65,116 @@
{%- endif -%}
</span>
{%- 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>&lt;/&gt;</code></button>
<button type="button" class="minimal">1.</button>
<button type="button" class="minimal">&bullet;</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&hellip;</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 %}

View File

@@ -19,7 +19,7 @@
<input type="text" placeholder="Username" name="username" autocomplete="username" required>
<input type="password" placeholder="Password" name="password" autocomplete="current-password" required>
<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>
{%- endif -%}
</nav>

View 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&hellip;</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 -%}

View File

@@ -1,10 +1,13 @@
{% from 'common/macros.html' import timestamp, subheader, pager %}
{%- extends 'base.html' -%}
{%- block title -%}browsing topic {{topic.name}}{%- endblock -%}
{%- block content -%}
{%- call() subheader(('Threads in "%s"' % topic.name), topic.description) -%}
<fieldset class="plank even no-shadow minimal thread-actions">
<legend>Actions</legend>
{%- if is_logged_in() -%}
<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>
<form method="GET">
<select name="sort_by">
@@ -14,11 +17,15 @@
<input type="submit" value="Sort">
</form>
</fieldset>
{%- if get_active_user().is_mod() -%}
{%- if is_mod() -%}
<fieldset class="plank even no-shadow minimal thread-actions">
<legend>Moderation actions</legend>
</fieldset>
{%- endif -%}
<fieldset class="plank even no-shadow minimal thread-actions">
<legend>Page</legend>
{{- pager(page, page_count, args=request.args) -}}
</fieldset>
{%- endcall -%}
{%- for thread in threads -%}
<div class="topic-info plank">
@@ -30,9 +37,14 @@
{%- endif -%}
</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>{{thread.posts_count - 1}} {{'repl' | pluralize(thread.posts_count - 1, '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>{{thread.posts_count}} {{'repl' | pluralize(thread.posts_count, 'y', 'ies')}}</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>
{%- 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 -%}

View File

@@ -2,7 +2,7 @@
{%- extends 'base.html' -%}
{%- block content -%}
{%- call() subheader('All topics') -%}
{%- if get_active_user().is_mod() -%}
{%- if is_mod() -%}
<fieldset class="plank even no-shadow minimal thread-actions">
<legend>Moderation actions</legend>
<a href="{{url_for('mod.new_topic')}}" class="linkbutton">New topic</a>