add bookmarks view

This commit is contained in:
2026-06-02 17:58:06 +03:00
parent edfa2e232f
commit 2c8bc6dca8
15 changed files with 223 additions and 46 deletions

View File

@@ -1,6 +1,6 @@
from flask import Blueprint, render_template, request, url_for from flask import Blueprint, render_template, request, url_for
from ..auth import get_active_user, is_logged_in, hard_login_required from ..auth import get_active_user, is_logged_in, hard_login_required
from ..models import BookmarkCollections, BookmarkedPosts, BookmarkedThreads from ..models import BookmarkCollections, BookmarkedPosts, BookmarkedThreads, Threads, Posts
from functools import wraps from functools import wraps
bp = Blueprint('hyperapi', __name__, url_prefix='/hyperapi/') bp = Blueprint('hyperapi', __name__, url_prefix='/hyperapi/')
@@ -24,6 +24,14 @@ def get_bookmark_dropdown():
except ValueError: except ValueError:
return 'error', 400 return 'error', 400
is_thread = concept_kind == 'thread' is_thread = concept_kind == 'thread'
if is_thread:
target_thread = Threads.find({'id': concept_id})
if not target_thread:
return 'This thread no longer exists. Please refresh the page.', 404
else:
target_post = Posts.find({'id': concept_id})
if not target_post:
return 'This post no longer exists. Please refresh the page.', 404
collections = BookmarkCollections.get_for_user(user.id) collections = BookmarkCollections.get_for_user(user.id)
in_collection = None in_collection = None
note = '' note = ''
@@ -54,6 +62,9 @@ def bookmark_thread():
bt.delete() bt.delete()
return '', 204 return '', 204
if not Threads.find({'id': thread_id}):
return 'error', 404
target_collection = BookmarkCollections.find({'id': target_collection_id}) target_collection = BookmarkCollections.find({'id': target_collection_id})
note = request.form.get('note', '') note = request.form.get('note', '')
if not target_collection: if not target_collection:
@@ -91,6 +102,9 @@ def bookmark_post():
bp.delete() bp.delete()
return '', 204 return '', 204
if not Posts.find({'id': post_id}):
return 'error', 404
target_collection = BookmarkCollections.find({'id': target_collection_id}) target_collection = BookmarkCollections.find({'id': target_collection_id})
note = request.form.get('note', '') note = request.form.get('note', '')
if not target_collection: if not target_collection:

View File

@@ -445,8 +445,9 @@ def inbox(username):
@login_required @login_required
@redirect_to_own @redirect_to_own
def bookmarks(username): def bookmarks(username):
username = username.lower() user = get_active_user()
return 'stub' collections = BookmarkCollections.get_for_user(user.id)
return render_template('users/bookmarks.html', collections=collections)
@bp.get('/<username>/bookmarks/collections/') @bp.get('/<username>/bookmarks/collections/')
@login_required @login_required

View File

@@ -136,10 +136,16 @@
</div> </div>
{%- endmacro %} {%- endmacro %}
{% macro bookmark_button(kind, id, text='Bookmark') -%}
<button autocomplete='off' data-r="enhance" data-s="showBookmarkMenu" disabled title="This feature requires JavaScript to be enabled." data-concept-kind="{{kind}}" data-concept-id="{{id}}">{{icn_bookmark(24)}}{{text}}&hellip;</button>
{%- endmacro %}
{% macro full_post( {% macro full_post(
post, render_sig=true, is_latest=false, post, render_sig=true, is_latest=false,
show_toolbar=true, is_editing=false, thread=none, show_toolbar=true, is_editing=false, thread=none,
show_reactions=true, show_thread=false, allow_reacting=true show_reactions=true, show_thread=false, allow_reacting=true,
tb_edit=true, tb_quote=true, tb_delete=true, tb_bookmark=true,
bookmark_btn='Bookmark', tb_pretext=''
) -%} ) -%}
{%- if is_logged_in() -%} {%- if is_logged_in() -%}
{%- set can_delete = post.user_id == get_active_user().id or is_mod() -%} {%- set can_delete = post.user_id == get_active_user().id or is_mod() -%}
@@ -169,6 +175,9 @@
<div class="post-content"> <div class="post-content">
<div class="plank even minimal secondary-bg no-shadow post-info"> <div class="plank even minimal secondary-bg no-shadow post-info">
<span> <span>
{%- if tb_pretext -%}
<span>{{tb_pretext}} &bullet; </span>
{%- endif -%}
<a href="{{get_post_url(post.id, _anchor=true)}}"> <a href="{{get_post_url(post.id, _anchor=true)}}">
{%- if post.edited_at <= post.created_at -%} {%- if post.edited_at <= post.created_at -%}
<i>Posted on {{timestamp(post.created_at)}}</i> <i>Posted on {{timestamp(post.created_at)}}</i>
@@ -181,17 +190,19 @@
{%- endif -%} {%- endif -%}
</span> </span>
{%- if show_toolbar -%} {%- if show_toolbar -%}
<span class="thread-actions"> <span class="subheader-actions">
{%- if owns -%} {%- if owns and tb_edit -%}
<a class="linkbutton" href="{{url_for('posts.edit', post_id=post.id, _anchor='babycode-content')}}">Edit</a> <a class="linkbutton" href="{{url_for('posts.edit', post_id=post.id, _anchor='babycode-content')}}">Edit</a>
{%- endif -%} {%- endif -%}
{%- if can_reply -%} {%- if can_reply and tb_quote -%}
<button autocomplete='off' data-r="enhance" data-s="babycodeEditorQuote" disabled title="This feature requires JavaScript to be enabled." data-quote="{{post.original_markup}}" data-poster-name="{{ post.display_name if post.display_name else post.username }}">Quote</button> <button autocomplete='off' data-r="enhance" data-s="babycodeEditorQuote" disabled title="This feature requires JavaScript to be enabled." data-quote="{{post.original_markup}}" data-poster-name="{{ post.display_name if post.display_name else post.username }}">Quote</button>
{%- endif -%} {%- endif -%}
{%- if can_delete -%} {%- if can_delete and tb_delete -%}
<a class="linkbutton critical" href="{{url_for('posts.delete', post_id=post.id)}}">Delete</a> <a class="linkbutton critical" href="{{url_for('posts.delete', post_id=post.id)}}">Delete</a>
{%- endif -%} {%- endif -%}
<button autocomplete='off' data-r="enhance" data-s="showBookmarkMenu" disabled title="This feature requires JavaScript to be enabled." data-concept-kind="post" data-concept-id="{{post.id}}">{{icn_bookmark(24)}}Bookmark&hellip;</button> {%- if tb_bookmark -%}
{{ bookmark_button('post', post.id, bookmark_btn) }}
{%- endif -%}
</span> </span>
{%- endif -%} {%- endif -%}
</div> </div>
@@ -229,6 +240,18 @@
</div> </div>
{%- endmacro %} {%- endmacro %}
{% macro bookmark_menu() -%}
{%- if is_logged_in() -%}
<div id="bookmark-popover" data-r="showBookmarkMenu" class="plank even" popover>
<div class="bookmark-menu-header">
<span>Bookmark collections</span>
<a href="{{url_for('users.bookmarks', username=get_active_user().username)}}">View bookmarks</a>
</div>
<div class="bookmark-menu-inner" data-r="fillBookmarkMenu">Loading&hellip;</div>
</div>
{%- endif -%}
{%- endmacro %}
{% macro infobox(message, kind=InfoboxKind.INFO) -%} {% macro infobox(message, kind=InfoboxKind.INFO) -%}
<div class="infobox plank top contain-svg horizontal {{InfoboxHTMLClass[kind]}}"> <div class="infobox plank top contain-svg horizontal {{InfoboxHTMLClass[kind]}}">
{%- if kind == InfoboxKind.INFO -%} {%- if kind == InfoboxKind.INFO -%}

View File

@@ -5,7 +5,7 @@
{%- block content -%} {%- block content -%}
{%- call() subheader("Delete post", "Are you sure you want to delete this post? This action can not be undone.") -%} {%- call() subheader("Delete post", "Are you sure you want to delete this post? This action can not be undone.") -%}
<form method="POST"> <form method="POST">
<fieldset class="plank minimal even no-shadow thread-actions"> <fieldset class="plank minimal even no-shadow subheader-actions">
<legend>Please confirm</legend> <legend>Please confirm</legend>
<a href="{{get_post_url(post.id, _anchor=true)}}" class="linkbutton">Cancel</a> <a href="{{get_post_url(post.id, _anchor=true)}}" class="linkbutton">Cancel</a>
<input type="submit" value="Delete" class="critical"> <input type="submit" value="Delete" class="critical">

View File

@@ -1,6 +1,6 @@
{%- from 'common/macros.html' import subheader, timestamp, pager, babycode_editor_component -%} {%- from 'common/macros.html' import subheader, timestamp, pager, babycode_editor_component -%}
{%- from 'common/icons.html' import icn_bookmark -%} {%- from 'common/icons.html' import icn_bookmark -%}
{%- from 'common/macros.html' import full_post with context -%} {%- from 'common/macros.html' import full_post, bookmark_menu with context -%}
{%- extends 'base.html' -%} {%- extends 'base.html' -%}
{%- block title -%}{{thread.title}}{%- endblock -%} {%- block title -%}{{thread.title}}{%- endblock -%}
{%- block content -%} {%- block content -%}
@@ -19,7 +19,7 @@
</ul> </ul>
{%- endset -%} {%- endset -%}
{%- call() subheader(thread.title, td) -%} {%- call() subheader(thread.title, td) -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Actions</legend> <legend>Actions</legend>
{%- if is_logged_in() -%} {%- if is_logged_in() -%}
{%- if thread.user_id == get_active_user().id -%} {%- if thread.user_id == get_active_user().id -%}
@@ -35,7 +35,7 @@
<a href="{{url_for('threads.feed', thread_id=thread.id)}}" class="linkbutton rss">Subscribe via RSS</a> <a href="{{url_for('threads.feed', thread_id=thread.id)}}" class="linkbutton rss">Subscribe via RSS</a>
</fieldset> </fieldset>
{%- if is_mod() -%} {%- if is_mod() -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Moderation actions</legend> <legend>Moderation actions</legend>
{%- if thread.user_id != get_active_user().id -%} {%- if thread.user_id != get_active_user().id -%}
<a class="linkbutton warn" href="{{url_for('threads.edit', thread_id=thread.id)}}">Rename</a> <a class="linkbutton warn" href="{{url_for('threads.edit', thread_id=thread.id)}}">Rename</a>
@@ -56,7 +56,7 @@
<input type="submit" value="Move" class="warn"> <input type="submit" value="Move" class="warn">
</form> </form>
</fieldset> </fieldset>
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Page</legend> <legend>Page</legend>
{{- pager(page, page_count) -}} {{- pager(page, page_count) -}}
</fieldset> </fieldset>
@@ -70,7 +70,7 @@
{%- endfor -%} {%- endfor -%}
</main> </main>
<div class="plank secondary-bg"> <div class="plank secondary-bg">
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Page</legend> <legend>Page</legend>
{{- pager(page, page_count) -}} {{- pager(page, page_count) -}}
</fieldset> </fieldset>
@@ -83,15 +83,7 @@
<button>Stop updates</button> <button>Stop updates</button>
</span> </span>
</div> </div>
{%- if is_logged_in() -%} {{ bookmark_menu() }}
<div id="bookmark-popover" data-r="showBookmarkMenu" class="plank even" popover>
<div class="bookmark-menu-header">
<span>Bookmark collections</span>
<a href="{{url_for('users.bookmarks', username=get_active_user().username)}}">View bookmarks</a>
</div>
<div class="bookmark-menu-inner" data-r="fillBookmarkMenu">Loading&hellip;</div>
</div>
{%- endif -%}
{%- if is_logged_in() and get_active_user().can_post_to_thread_or_topic(thread) -%} {%- if is_logged_in() and get_active_user().can_post_to_thread_or_topic(thread) -%}
<form action="{{url_for('threads.reply', thread_id=thread.id)}}" method="POST" class="plank post-edit-form" data-listen="submit" data-r="clearThreadDraft" data-s="clearThreadDraft"> <form action="{{url_for('threads.reply', thread_id=thread.id)}}" method="POST" class="plank post-edit-form" data-listen="submit" data-r="clearThreadDraft" data-s="clearThreadDraft">
<h2 class="info">Reply to "{{thread.title}}"</h2> <h2 class="info">Reply to "{{thread.title}}"</h2>

View File

@@ -12,7 +12,7 @@
</ul> </ul>
{%- endset -%} {%- endset -%}
{%- call() subheader(('Threads in "%s"' % topic.name), td) -%} {%- call() subheader(('Threads in "%s"' % topic.name), td) -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Actions</legend> <legend>Actions</legend>
{%- if is_logged_in() and get_active_user().can_post_to_thread_or_topic(topic) -%} {%- if is_logged_in() and get_active_user().can_post_to_thread_or_topic(topic) -%}
<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>
@@ -27,7 +27,7 @@
</form> </form>
</fieldset> </fieldset>
{%- if is_mod() -%} {%- if is_mod() -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Moderation actions</legend> <legend>Moderation actions</legend>
<a href="{{url_for('mod.edit_topic', topic_id=topic.id)}}" class="linkbutton">Edit</a> <a href="{{url_for('mod.edit_topic', topic_id=topic.id)}}" class="linkbutton">Edit</a>
<form action="{{url_for('mod.lock_topic', topic_id=topic.id)}}" method="POST"> <form action="{{url_for('mod.lock_topic', topic_id=topic.id)}}" method="POST">
@@ -37,7 +37,7 @@
</fieldset> </fieldset>
{%- endif -%} {%- endif -%}
{%- if threads | length > 0 -%} {%- if threads | length > 0 -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Page</legend> <legend>Page</legend>
{{- pager(page, page_count, args=request.args) -}} {{- pager(page, page_count, args=request.args) -}}
</fieldset> </fieldset>
@@ -77,7 +77,7 @@
{%- endfor -%} {%- endfor -%}
{%- if threads | length > 0 -%} {%- if threads | length > 0 -%}
<div class="plank secondary-bg"> <div class="plank secondary-bg">
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Page</legend> <legend>Page</legend>
{{- pager(page, page_count, args=request.args) -}} {{- pager(page, page_count, args=request.args) -}}
</fieldset> </fieldset>

View File

@@ -4,7 +4,7 @@
{%- block content -%} {%- block content -%}
{%- call() subheader('All topics') -%} {%- call() subheader('All topics') -%}
{%- if is_mod() -%} {%- if is_mod() -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-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>
<a href="{{url_for('mod.index', _anchor='sort-topics')}}" class="linkbutton">Sort topics</a> <a href="{{url_for('mod.index', _anchor='sort-topics')}}" class="linkbutton">Sort topics</a>

View File

@@ -0,0 +1,56 @@
{%- from 'common/macros.html' import full_post, bookmark_menu with context -%}
{%- from 'common/macros.html' import subheader, bookmark_button -%}
{%- extends 'base.html' -%}
{%- block title -%}bookmarks"{%- endblock -%}
{%- block content -%}
<bitty-8 data-connect="/static/js/bits/bookmark-menu.js"></bitty-8>
<bitty-8 data-connect="/static/js/bits/bookmarks.js"></bitty-8>
{%- call() subheader('Your bookmarks') -%}
<fieldset class="plank even no-shadow minimal subheader-actions js-only" data-r="enhance">
<legend>Actions</legend>
<a href="{{url_for('users.bookmark_collections', username=get_active_user().username)}}" class="linkbutton">Manage collections</a>
</fieldset>
{%- endcall -%}
<div class="plank">
{%- for collection in collections -%}
{%- set thread_count = collection.get_threads_count() -%}
{%- set post_count = collection.get_posts_count() -%}
<details class="separated" data-id="{{collection.id}}" data-r="restoreCollectionDetails setCollectionDetails" data-s="setCollectionDetails">
<summary class="plank secondary-bg no-shadow even">{{collection.name}} ({{thread_count}} {{'thread' | pluralize(num=thread_count)}}, {{post_count}} {{'post' | pluralize(num=post_count)}})</summary>
{%- if thread_count > 0 -%}
<details class="inner" data-id="{{collection.id}}" data-r="restoreThreadDetails setThreadDetails" data-s="setThreadDetails">
<summary class="plank no-shadow even">Threads</summary>
<table class="three-cols">
<thead>
<tr>
<th class="plank even no-shadow contrast-bg">Title</th>
<th class="plank even no-shadow contrast-bg">Memo</th>
<th class="plank even no-shadow contrast-bg">Manage</th>
</tr>
</thead>
<tbody>
{%- for bt in collection.get_threads() -%}
{%- set thread = bt.get_thread() -%}
<tr>
<td class="plank even no-shadow minimal secondary-bg"><a href="{{url_for('threads.thread_by_id', thread_id=thread.id)}}">{{thread.title}}</a></td>
<td class="plank even no-shadow minimal secondary-bg">{{bt.note}}</td>
<td class="plank even no-shadow minimal secondary-bg">{{bookmark_button('thread', id=thread.id, text='Manage')}}</td>
</tr>
{%- endfor -%}
</tbody>
</table>
</details>
{%- endif -%}
{%- if post_count > 0 -%}
<details class="inner" data-id="{{collection.id}}" data-r="restorePostDetails setPostDetails" data-s="setPostDetails">
<summary class="plank no-shadow even">Posts</summary>
{%- for bp in collection.get_posts() -%}
<div class="post plank no-shadow even">{{ full_post(bp.get_post().get_full_post_view(), render_sig=false, show_thread=true, show_reactions=false, tb_edit=false, tb_quote=false, tb_delete=false, bookmark_btn='Manage', tb_pretext=('memo: ' + bp.note) if bp.note else '') }}</div>
{%- endfor -%}
</details>
{%- endif -%}
</details>
{%- endfor -%}
</div>
{{ bookmark_menu() }}
{%- endblock -%}

View File

@@ -2,7 +2,7 @@
{%- macro collection_item(name='', can_delete=true, id=-1, thread_count=0, post_count=0) -%} {%- macro collection_item(name='', can_delete=true, id=-1, thread_count=0, post_count=0) -%}
<input name="name[]" type="text" autocomplete="off" value="{{name}}" required maxlength=60 placeholder="Collection name"> <input name="name[]" type="text" autocomplete="off" value="{{name}}" required maxlength=60 placeholder="Collection name">
<input type="hidden" name="id[]" value="{{ 'new' if id == -1 else id}}" autocomplete="off"> <input type="hidden" name="id[]" value="{{ 'new' if id == -1 else id}}" autocomplete="off">
<span>{{thread_count}} {{'thread' | pluralize(num=thread_count)}}, {{post_count}} {{'post' | pluralize(num=post_count)}} </span> <span>{{thread_count}} {{'thread' | pluralize(num=thread_count)}}, {{post_count}} {{'post' | pluralize(num=post_count)}}</span>
{%- if not can_delete -%} {%- if not can_delete -%}
<i>Default collection</i> <i>Default collection</i>
{%- else -%} {%- else -%}
@@ -20,7 +20,7 @@ Drag collections to reoder them. You cannot move or remove the default collectio
<div data-r="enhanceHide">This page requires JS enabled to work correctly.</div> <div data-r="enhanceHide">This page requires JS enabled to work correctly.</div>
{%- endset -%} {%- endset -%}
{%- call() subheader('Manage bookmark collections', sh) -%} {%- call() subheader('Manage bookmark collections', sh) -%}
<fieldset class="plank even no-shadow minimal thread-actions js-only" data-r="enhance"> <fieldset class="plank even no-shadow minimal subheader-actions js-only" data-r="enhance">
<legend>Actions</legend> <legend>Actions</legend>
<button data-s="addCollection">Add new collection</button> <button data-s="addCollection">Add new collection</button>
<input type="submit" class="alt" value="Save collections" form="collections-form"> <input type="submit" class="alt" value="Save collections" form="collections-form">

View File

@@ -8,7 +8,7 @@
{%- endset -%} {%- endset -%}
{%- call() subheader("%s's posts" % target_user.get_readable_name(), td) -%} {%- call() subheader("%s's posts" % target_user.get_readable_name(), td) -%}
{%- if posts -%} {%- if posts -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Page</legend> <legend>Page</legend>
{{- pager(page, page_count) -}} {{- pager(page, page_count) -}}
</fieldset> </fieldset>
@@ -19,7 +19,7 @@
<div class="post plank">{{full_post(post, show_toolbar=false, show_thread=true, allow_reacting=false)}}</div> <div class="post plank">{{full_post(post, show_toolbar=false, show_thread=true, allow_reacting=false)}}</div>
{%- endfor -%} {%- endfor -%}
<div class="plank"> <div class="plank">
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Page</legend> <legend>Page</legend>
{{- pager(page, page_count) -}} {{- pager(page, page_count) -}}
</fieldset> </fieldset>

View File

@@ -8,7 +8,7 @@
{%- endset -%} {%- endset -%}
{%- call() subheader("%s's started threads" % target_user.get_readable_name(), td) -%} {%- call() subheader("%s's started threads" % target_user.get_readable_name(), td) -%}
{%- if threads -%} {%- if threads -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Page</legend> <legend>Page</legend>
{{- pager(page, page_count) -}} {{- pager(page, page_count) -}}
</fieldset> </fieldset>
@@ -22,7 +22,7 @@
</div> </div>
{%- endfor -%} {%- endfor -%}
<div class="plank"> <div class="plank">
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Page</legend> <legend>Page</legend>
{{- pager(page, page_count) -}} {{- pager(page, page_count) -}}
</fieldset> </fieldset>

View File

@@ -7,7 +7,7 @@
{%- if is_logged_in() -%} {%- if is_logged_in() -%}
{%- if target_user.id == get_active_user().id -%} {%- if target_user.id == get_active_user().id -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Actions</legend> <legend>Actions</legend>
<form action="{{url_for('users.log_out')}}" method="POST"> <form action="{{url_for('users.log_out')}}" method="POST">
<input type="submit" class="warn" value="Log out"> <input type="submit" class="warn" value="Log out">
@@ -16,9 +16,9 @@
{%- endif -%} {%- endif -%}
{%- if get_active_user().is_mod() and target_user.id != get_active_user().id and target_user.permission < get_active_user().permission -%} {%- if get_active_user().is_mod() and target_user.id != get_active_user().id and target_user.permission < get_active_user().permission -%}
<fieldset class="plank even no-shadow minimal thread-actions"> <fieldset class="plank even no-shadow minimal subheader-actions">
<legend>Moderation actions</legend> <legend>Moderation actions</legend>
<form class="thread-actions" method="POST"> <form class="subheader-actions" method="POST">
{{csrf_input() | safe}} {{csrf_input() | safe}}
{%- if target_user.is_guest() -%} {%- if target_user.is_guest() -%}
<input class="warn" type="submit" value="Approve user" formaction="{{url_for('mod.make_user_regular', user_id=target_user.id)}}"> <input class="warn" type="submit" value="Approve user" formaction="{{url_for('mod.make_user_regular', user_id=target_user.id)}}">

View File

@@ -465,7 +465,7 @@ footer {
gap: var(--base-padding); gap: var(--base-padding);
} }
.thread-actions { .subheader-actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--base-padding); gap: var(--base-padding);
@@ -662,12 +662,12 @@ details {
} }
} }
&:not([open]) summary::before { &:not([open]) > summary::before {
content: '▶'; content: '▶';
padding-inline: var(--base-padding); padding-inline: var(--base-padding);
} }
&[open] summary::before { &[open] > summary::before {
content: '▼'; content: '▼';
padding-inline: var(--base-padding); padding-inline: var(--base-padding);
} }
@@ -677,6 +677,10 @@ details.separated {
margin: 0.5em 0; margin: 0.5em 0;
} }
details.inner {
margin-inline: var(--base-padding);
}
.avatar-form { .avatar-form {
display: flex; display: flex;
gap: var(--huge-padding); gap: var(--huge-padding);
@@ -724,6 +728,25 @@ details.separated {
} }
} }
table {
border-collapse: collapse;
width: 100%;
&.three-cols > thead > tr > th {
&:nth-child(1) {
width: 70%;
}
&:nth-child(2) {
width: 25%;
}
&:nth-child(3) {
width: 15%;
}
}
}
/* babycode tags */ /* babycode tags */
.inline-code { .inline-code {

View File

@@ -7,9 +7,9 @@ async function getHTML(endpoint, options = {}) {
const params = new URLSearchParams(query); const params = new URLSearchParams(query);
const res = await fetch(`${endpoint}?${params}`, options); const res = await fetch(`${endpoint}?${params}`, options);
if (!res.ok) { // if (!res.ok) {
console.error(res); // console.error(res);
} // }
return { body: await res.text(), status: res.status }; return { body: await res.text(), status: res.status };
} }
@@ -81,7 +81,7 @@ export async function bookmarkMenuSubmit(ev, _, el) {
const status = (await getHTML(url, options)).status; const status = (await getHTML(url, options)).status;
if (status !== 204) { if (status !== 204) {
b.trigger('bookmarkMenuShowError'); b.send({ status: status }, 'bookmarkMenuShowError');
return; return;
} }
@@ -99,11 +99,17 @@ export function bookmarkMenuResetSavedButton(_, __, el) {
el.value = 'Save'; el.value = 'Save';
} }
export function bookmarkMenuShowError(_, __, el) { export function bookmarkMenuShowError(payload, _, el) {
if (el === undefined) { if (el === undefined) {
return; return;
} }
if (payload.status === 404) {
el.innerText = 'This thread or post no longer exists. Please refresh the page.';
} else {
el.innerText = 'Something went wrong. Try again later.';
}
if (el.classList.contains('hidden')) { if (el.classList.contains('hidden')) {
el.classList.remove('hidden'); el.classList.remove('hidden');
setTimeout(() => { b.trigger('bookmarkMenuHideError') }, 4000); setTimeout(() => { b.trigger('bookmarkMenuHideError') }, 4000);

View File

@@ -0,0 +1,62 @@
export const b = {
init: 'restoreCollectionDetails restoreThreadDetails restorePostDetails',
}
const COLLECTION_DETAILS_KEY = 'collectionsOpen';
const THREAD_DETAILS_KEY = 'threadsOpen';
const POST_DETAILS_KEY = 'postsOpen';
let collectionDetailsData = {};
let collectionThreadDetailsData = {};
let collectionPostDetailsData = {};
async function setDetailsData(obj, key, id, isOpen) {
obj[id] = isOpen;
await b.savePageData(obj, key);
}
export async function restoreCollectionDetails(_, __, el) {
collectionDetailsData = await b.loadPageData(COLLECTION_DETAILS_KEY, {});
el.open = collectionDetailsData[el.dataset.id] === true;
}
export async function setCollectionDetails(ev, sender, el) {
if (el !== sender) {
return;
}
if (ev.target !== el.querySelector('summary')) {
return;
}
console.log(!el.open);
await setDetailsData(collectionDetailsData, COLLECTION_DETAILS_KEY, el.dataset.id, !el.open);
}
export async function restoreThreadDetails(_, __, el) {
collectionThreadDetailsData = await b.loadPageData(THREAD_DETAILS_KEY, {});
el.open = collectionThreadDetailsData[el.dataset.id] === true;
}
export async function setThreadDetails(ev, sender, el) {
if (el !== sender) {
return;
}
if (ev.target !== el.querySelector('summary')) {
return;
}
await setDetailsData(collectionThreadDetailsData, THREAD_DETAILS_KEY, el.dataset.id, !el.open);
}
export async function restorePostDetails(_, __, el) {
collectionPostDetailsData = await b.loadPageData(POST_DETAILS_KEY, {});
el.open = collectionPostDetailsData[el.dataset.id] === true;
}
export async function setPostDetails(ev, sender, el) {
if (el !== sender) {
return;
}
if (ev.target !== el.querySelector('summary')) {
return;
}
await setDetailsData(collectionPostDetailsData, POST_DETAILS_KEY, el.dataset.id, !el.open);
}