new sortable list implementation

This commit is contained in:
2025-12-19 19:01:01 +03:00
parent 98bf430604
commit 46704df7d9
16 changed files with 581 additions and 395 deletions

View File

@@ -59,3 +59,9 @@
<path d="M5 11C9.41828 11 13 14.5817 13 19M5 5C12.732 5 19 11.268 19 19M7 18C7 18.5523 6.55228 19 6 19C5.44772 19 5 18.5523 5 18C5 17.4477 5.44772 17 6 17C6.55228 17 7 17.4477 7 18Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{% endmacro %}
{% macro icn_drag(width=24) %}
<svg width="{{width}}px" height="{{width}}px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 10H19M14 19L12 21L10 19M14 5L12 3L10 5M5 14H19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{% endmacro %}

View File

@@ -1,4 +1,8 @@
{% from 'common/icons.html' import icn_image, icn_spoiler, icn_info, icn_lock, icn_warn, icn_error, icn_bookmark, icn_megaphone, icn_rss %}
{% from 'common/icons.html' import icn_image, icn_spoiler, icn_info, icn_lock, icn_warn, icn_error, icn_bookmark, icn_megaphone, icn_rss, icn_drag %}
{%- macro dict_to_attr(attrs) -%}
{%- for key, value in attrs.items() if value is not none -%}{{' '}}{{key}}="{{value}}"{%- endfor -%}
{%- endmacro -%}
{% macro pager(current_page, page_count) %}
{% set left_start = [1, current_page - 5] | max %}
{% set right_end = [page_count, current_page + 5] | min %}
@@ -331,7 +335,9 @@
{% else %}
{% set selected_href = defaults[0].file_path %}
{% endif %}
<bitty-7-0 data-connect="{{ '/static/js/bitties/pyrom-bitty.js' | cachebust }} BadgeEditorBadge" data-listeners="click input submit change" data-receive="deleteBadge">
<li class="sortable-item" data-sortable-list-key="" data-receive="deleteBadge"> {# breaking convention on purpose since this one is special #}
<span class="dragger" draggable="true">{{ icn_drag(24) }}</span>
<bitty-7-0 class="fg" data-connect="{{ '/static/js/bitties/pyrom-bitty.js' | cachebust }} BadgeEditorBadge" data-listeners="click input submit change">
<div class="settings-badge-container">
<div class="settings-badge-select">
<select data-send="badgeUpdatePreview badgeToggleFilePicker" name="badge_choice[]" required>
@@ -358,6 +364,7 @@
<button data-send="deleteBadge" type="button" class="critical" title="Delete">X</button>
</div>
</bitty-7-0>
</li>
{% endmacro %}
{% macro rss_html_content(html) %}
@@ -367,3 +374,20 @@
{% macro rss_button(feed) %}
<a class="linkbutton contain-svg inline icon rss-button" href="{{feed}}" title="it&#39;s actually atom, don&#39;t tell anyone &#59;&#41;">{{ icn_rss(20) }} Subscribe via RSS</a>
{% endmacro %}
{% macro sortable_list(attr=none) %}
<ol class="sortable-list" {% if attr %}{{ dict_to_attr(attr) }}{% endif %}>
{% if caller %}
{{ caller() }}
{% endif %}
</ol>
{% endmacro %}
{% macro sortable_list_item(key, immovable=false, attr=none) %}
<li class="sortable-item{{' immovable' if immovable else ''}}" data-sortable-list-key="{{key}}" {% if attr %}{{ dict_to_attr(attr) }}{% endif %}>
<span class="dragger" draggable="{{ 'true' if not immovable else 'false'}}">{{ icn_drag(24) }}</span>
<div class="sortable-item-inner">
{{ caller() }}
</div>
</li>
{% endmacro %}

View File

@@ -1,18 +1,20 @@
{% extends "base.html" %}
{% from 'common/macros.html' import sortable_list, sortable_list_item %}
{% block content %}
<div class="darkbg">
<h1>Change topics order</h1>
<p>Drag topic titles to reoder them. Press submit when done. The topics will appear to users in the order set here.</p>
<form method="post" id=topics-container>
<p>Drag topic titles to reoder them. Press "Save order" when done. The topics will appear to users in the order set here.</p>
<form method="post">
<input type=submit value="Save order">
{% call() sortable_list() %}
{% for topic in topics %}
<div draggable="true" class="draggable-topic" ondragover="dragOver(event)" ondragstart="dragStart(event)" ondragend="dragEnd()">
<div class="thread-title">{{ topic['name'] }}</div>
{% call() sortable_list_item(key="topics") %}
<div class="thread-title">{{ topic.name }}</div>
<div>{{ topic.description }}</div>
<input type="hidden" name="{{ topic['id'] }}" value="{{ topic['sort-order'] }}" class="topic-input">
</div>
<input type="hidden" name="topics[]" value="{{ topic.id }}" class="topic-input">
{% endcall %}
{% endfor %}
<input type=submit value="Save order">
</form>
{% endcall %}
</form>
</div>
<script src="{{ "/static/js/sort-topics.js" | cachebust }}"></script>
{% endblock %}

View File

@@ -1,35 +1,41 @@
{% extends "base.html" %}
{% from 'common/macros.html' import sortable_list, sortable_list_item %}
{% block title %}managing bookmark collections{% endblock %}
{% block content %}
<div class="darkbg">
<h1>Manage bookmark collections</h1>
<p>Drag collections to reoder them. You cannot move or remove the default collection, but you can rename it.</p>
<div>
<button type="button" id="add-collection-button">Add new collection</button>
<div id="collections-container">
{% for collection in collections | sort(attribute='sort_order') %}
<div class="draggable-collection {{ "default" if collection.is_default else ""}}"
{% if not collection.is_default %}
draggable="true"
ondragover="dragOver(event)"
ondragstart="dragStart(event)"
ondragend="dragEnd()"
{% else %}
id="default-collection"
{% endif %}
data-collection-id="{{ collection.id }}">
<input type="text" class="collection-name" value="{{ collection.name }}" placeholder="Collection name" required autocomplete="off" maxlength="60"><br>
<div>{{ collection.get_threads_count() }} {{ "thread" | pluralize(num=collection.get_threads_count()) }}, {{ collection.get_posts_count() }} {{ "post" | pluralize(num=collection.get_posts_count()) }}</div>
{% if collection.is_default %}
<i>Default collection</i>
{% else %}
<button type="button" class="delete-button critical">Delete</button>
{% endif %}
</div>
<bitty-7-0 data-connect="{{ '/static/js/bitties/pyrom-bitty.js' | cachebust }} CollectionsEditor">
<button type="button" data-send="addCollection">Add new collection</button>
{% set sorted_collections = collections | sort(attribute='sort_order') %}
{% macro collection_inner(collection) %}
<input type="text" class="collection-name" value="{{collection.name}}" placeholder="Collection name" required autocomplete="off" maxlength="60">
<div>{{ collection.get_threads_count() }} {{ "thread" | pluralize(num=collection.get_threads_count()) }}, {{ collection.get_posts_count() }} {{ "post" | pluralize(num=collection.get_posts_count()) }}</div>
{% if collection.is_default %}
<i>Default collection</i>
{% else %}
<button type="button" class="delete-button critical" data-send="deleteCollection">Delete</button>
{% endif %}
{% endmacro %}
{% call() sortable_list(attr={'data-receive': 'addCollection' }) %}
{% call() sortable_list_item(key='collections', immovable=true, attr={'data-collection-id': sorted_collections[0].id, 'data-receive': 'deleteCollection getCollectionData testValidity'}) %}
{{ collection_inner(sorted_collections[0]) }}
{% endcall %}
{% for collection in sorted_collections[1:] %}
{% call() sortable_list_item(key='collections', attr={'data-collection-id': collection.id, 'data-receive': 'deleteCollection getCollectionData testValidity'}) %}
{{ collection_inner(collection) }}
{% endcall %}
{% endfor %}
</div>
<button type="button" id="save-button" data-submit-href="{{ url_for('api.manage_bookmark_collections', user_id=active_user.id) }}">Save</button>
{% endcall %}
<button data-use="saveCollections" type="button" id="save-button" data-submit-href="{{ url_for('api.manage_bookmark_collections', user_id=active_user.id) }}">Save</button>
</div>
</div>
<script src="{{ "/static/js/manage-bookmark-collections.js" | cachebust }}"></script>
<template id="new-collection-template">
{% call() sortable_list_item(key='collections', attr={'data-receive': 'deleteCollection getCollectionData testValidity'}) %}
<input type="text" class="collection-name" value="" placeholder="Collection name" required autocomplete="off" maxlength="60">
<div>0 threads, 0 posts</div>
<button type="button" class="delete-button critical" data-send="deleteCollection">Delete</button>
{% endcall %}
</template>
</bitty-7-0>
{#<script src="{{ "/static/js/manage-bookmark-collections.js" | cachebust }}"></script>#}
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% from 'common/macros.html' import babycode_editor_component, badge_editor_single %}
{% from 'common/macros.html' import babycode_editor_component, badge_editor_single, sortable_list %}
{% extends 'base.html' %}
{% block title %}settings{% endblock %}
{% block content %}
@@ -57,7 +57,7 @@
<legend>Badges</legend>
<a href="{{ url_for('guides.guide_page', category='user-guides', slug='settings', _anchor='badges')}}">Badges help</a>
<bitty-7-0 data-connect="{{ '/static/js/bitties/pyrom-bitty.js' | cachebust }} BadgeEditorForm" data-listeners="click input submit change">
<form data-use="badgeEditorPrepareSubmit" data-init='loadBadgeEditor' data-receive='addBadge' method='post' enctype='multipart/form-data' action='{{ url_for('users.save_badges', username=active_user.username) }}'>
<form data-use="badgeEditorPrepareSubmit" data-init='loadBadgeEditor' method='post' enctype='multipart/form-data' action='{{ url_for('users.save_badges', username=active_user.username) }}'>
<div>Loading badges&hellip;</div>
<div>If badges fail to load, JS may be disabled.</div>
</form>
@@ -72,4 +72,8 @@
<template id='badge-editor-template'>
{{ badge_editor_single(options=uploads) }}
</template>
<template id="badges-list-template">
{{ sortable_list(attr={'data-receive': 'addBadge'}) }}
</template>
{% endblock %}