From 46704df7d9916c08c2ada4e313b3506f204baef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Fri, 19 Dec 2025 19:01:01 +0300 Subject: [PATCH] new sortable list implementation --- app/routes/mod.py | 4 +- app/templates/common/icons.html | 6 + app/templates/common/macros.html | 28 +++- app/templates/mod/sort-topics.html | 20 +-- app/templates/users/bookmark_collections.html | 58 ++++---- app/templates/users/settings.html | 8 +- data/static/css/style.css | 84 ++++++++---- data/static/css/theme-otomotone.css | 91 +++++++----- data/static/css/theme-peachy.css | 89 +++++++----- data/static/css/theme-snow-white.css | 84 ++++++++---- data/static/js/bitties/pyrom-bitty.js | 90 +++++++++++- data/static/js/manage-bookmark-collections.js | 128 ----------------- data/static/js/sort-topics.js | 45 ------ data/static/js/ui.js | 110 ++++++++++++++- sass/_default.scss | 129 +++++++++++------- sass/otomotone.scss | 2 + 16 files changed, 581 insertions(+), 395 deletions(-) delete mode 100644 data/static/js/manage-bookmark-collections.js delete mode 100644 data/static/js/sort-topics.js diff --git a/app/routes/mod.py b/app/routes/mod.py index 0c5199f..b563430 100644 --- a/app/routes/mod.py +++ b/app/routes/mod.py @@ -29,8 +29,10 @@ def sort_topics(): @bp.post("/sort-topics") def sort_topics_post(): + topics_list = request.form.getlist('topics[]') + print(topics_list) with db.transaction(): - for topic_id, new_order in request.form.items(): + for new_order, topic_id in enumerate(topics_list): db.execute("UPDATE topics SET sort_order = ? WHERE id = ?", new_order, topic_id) return redirect(url_for(".sort_topics")) diff --git a/app/templates/common/icons.html b/app/templates/common/icons.html index 6502b1b..48fe083 100644 --- a/app/templates/common/icons.html +++ b/app/templates/common/icons.html @@ -59,3 +59,9 @@ {% endmacro %} + +{% macro icn_drag(width=24) %} + + + +{% endmacro %} diff --git a/app/templates/common/macros.html b/app/templates/common/macros.html index 6260a17..381dac8 100644 --- a/app/templates/common/macros.html +++ b/app/templates/common/macros.html @@ -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 %} - +
  • {# breaking convention on purpose since this one is special #} +{{ icn_drag(24) }} +
    + {% call() sortable_list() %} {% for topic in topics %} -
    -
    {{ topic['name'] }}
    + {% call() sortable_list_item(key="topics") %} +
    {{ topic.name }}
    {{ topic.description }}
    - -
    + + {% endcall %} {% endfor %} - - + {% endcall %} +
    - {% endblock %} diff --git a/app/templates/users/bookmark_collections.html b/app/templates/users/bookmark_collections.html index 85227f6..f5372f4 100644 --- a/app/templates/users/bookmark_collections.html +++ b/app/templates/users/bookmark_collections.html @@ -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 %}

    Manage bookmark collections

    Drag collections to reoder them. You cannot move or remove the default collection, but you can rename it.

    -
    - -
    - {% for collection in collections | sort(attribute='sort_order') %} -
    -
    -
    {{ collection.get_threads_count() }} {{ "thread" | pluralize(num=collection.get_threads_count()) }}, {{ collection.get_posts_count() }} {{ "post" | pluralize(num=collection.get_posts_count()) }}
    - {% if collection.is_default %} - Default collection - {% else %} - - {% endif %} -
    + + + {% set sorted_collections = collections | sort(attribute='sort_order') %} + {% macro collection_inner(collection) %} + +
    {{ collection.get_threads_count() }} {{ "thread" | pluralize(num=collection.get_threads_count()) }}, {{ collection.get_posts_count() }} {{ "post" | pluralize(num=collection.get_posts_count()) }}
    + {% if collection.is_default %} + Default collection + {% else %} + + {% 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 %} -
    - + {% endcall %} +
    -
    - + + +{##} {% endblock %} diff --git a/app/templates/users/settings.html b/app/templates/users/settings.html index 37703f0..884d0f5 100644 --- a/app/templates/users/settings.html +++ b/app/templates/users/settings.html @@ -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 @@ Badges Badges help -
    +
    Loading badges…
    If badges fail to load, JS may be disabled.
    @@ -72,4 +72,8 @@ + + {% endblock %} diff --git a/data/static/css/style.css b/data/static/css/style.css index 47307a1..b53c312 100644 --- a/data/static/css/style.css +++ b/data/static/css/style.css @@ -867,6 +867,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus background-color: rgb(230.2, 235.4, 223.8); } +input:not(form input):invalid { + border: 2px dashed red; +} + textarea { font-family: "Atkinson Hyperlegible Mono", monospace; } @@ -1069,35 +1073,6 @@ textarea { background-color: none; } -.draggable-topic { - cursor: pointer; - user-select: none; - background-color: #c1ceb1; - padding: 20px; - margin: 15px 0; - border-top: 5px outset rgb(217.26, 220.38, 213.42); - border-bottom: 5px outset rgb(135.1928346457, 145.0974015748, 123.0025984252); -} -.draggable-topic.dragged { - background-color: rgb(177, 206, 204.5); -} - -.draggable-collection { - cursor: pointer; - user-select: none; - background-color: #c1ceb1; - padding: 20px; - margin: 15px 0; - border-top: 5px outset rgb(217.26, 220.38, 213.42); - border-bottom: 5px outset rgb(135.1928346457, 145.0974015748, 123.0025984252); -} -.draggable-collection.dragged { - background-color: rgb(177, 206, 204.5); -} -.draggable-collection.default { - background-color: #beb1ce; -} - .editing { background-color: rgb(217.26, 220.38, 213.42); } @@ -1550,3 +1525,54 @@ img.badge-button { padding-right: 20px; } } +ol.sortable-list { + list-style: none; + flex-grow: 1; + margin: 0; +} +ol.sortable-list li { + display: flex; + gap: 10px; + background-color: #c1ceb1; + padding: 20px; + margin: 15px 0; + border-top: 5px outset rgb(217.26, 220.38, 213.42); + border-bottom: 5px outset rgb(135.1928346457, 145.0974015748, 123.0025984252); +} +ol.sortable-list li.dragged { + background-color: rgb(177, 206, 204.5); +} +ol.sortable-list li.immovable { + background-color: #beb1ce; +} +ol.sortable-list li.immovable .dragger { + cursor: not-allowed; +} + +.dragger { + display: flex; + align-items: center; + background-color: rgb(135.1928346457, 145.0974015748, 123.0025984252); + padding: 5px 10px; + cursor: move; +} + +.sortable-item-inner { + display: flex; + gap: 10px; + flex-grow: 1; + flex-direction: column; +} +.sortable-item-inner > * { + flex-grow: 1; +} +.sortable-item-inner.row { + flex-direction: row; +} +.sortable-item-inner:not(.row) > * { + margin-right: auto; +} + +.fg { + flex-grow: 1; +} diff --git a/data/static/css/theme-otomotone.css b/data/static/css/theme-otomotone.css index 2ee7b7e..b142c37 100644 --- a/data/static/css/theme-otomotone.css +++ b/data/static/css/theme-otomotone.css @@ -867,6 +867,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus background-color: #514151; } +input:not(form input):invalid { + border: 2px dashed #d53232; +} + textarea { font-family: "Atkinson Hyperlegible Mono", monospace; } @@ -1069,35 +1073,6 @@ textarea { background-color: #503250; } -.draggable-topic { - cursor: pointer; - user-select: none; - background-color: #9b649b; - padding: 20px; - margin: 15px 0; - border-top: 5px outset #503250; - border-bottom: 5px outset rgb(96.95, 81.55, 96.95); -} -.draggable-topic.dragged { - background-color: #3c283c; -} - -.draggable-collection { - cursor: pointer; - user-select: none; - background-color: #9b649b; - padding: 20px; - margin: 15px 0; - border-top: 5px outset #503250; - border-bottom: 5px outset rgb(96.95, 81.55, 96.95); -} -.draggable-collection.dragged { - background-color: #3c283c; -} -.draggable-collection.default { - background-color: #8a5584; -} - .editing { background-color: #503250; } @@ -1458,7 +1433,7 @@ a.mention:hover, a.mention:visited:hover { .settings-grid fieldset { border: 1px solid black; border-radius: 8px; - background-color: rgb(141.6, 79.65, 141.6); + background-color: #503250; } .hfc { @@ -1479,10 +1454,10 @@ h1 { margin: 10px 0; } .settings-badge-container:has(input:invalid) { - border: 2px dashed red; + border: 2px dashed #d53232; } .settings-badge-container input:invalid { - border: 2px dashed red; + border: 2px dashed #d53232; } .settings-badge-file-picker { @@ -1550,6 +1525,58 @@ img.badge-button { padding-right: 20px; } } +ol.sortable-list { + list-style: none; + flex-grow: 1; + margin: 0; +} +ol.sortable-list li { + display: flex; + gap: 10px; + background-color: #9b649b; + padding: 20px; + margin: 15px 0; + border-top: 5px outset #503250; + border-bottom: 5px outset rgb(96.95, 81.55, 96.95); +} +ol.sortable-list li.dragged { + background-color: #3c283c; +} +ol.sortable-list li.immovable { + background-color: #8a5584; +} +ol.sortable-list li.immovable .dragger { + cursor: not-allowed; +} + +.dragger { + display: flex; + align-items: center; + background-color: rgb(96.95, 81.55, 96.95); + padding: 5px 10px; + cursor: move; +} + +.sortable-item-inner { + display: flex; + gap: 10px; + flex-grow: 1; + flex-direction: column; +} +.sortable-item-inner > * { + flex-grow: 1; +} +.sortable-item-inner.row { + flex-direction: row; +} +.sortable-item-inner:not(.row) > * { + margin-right: auto; +} + +.fg { + flex-grow: 1; +} + #topnav { margin-bottom: 10px; border: 10px solid rgb(40, 40, 40); diff --git a/data/static/css/theme-peachy.css b/data/static/css/theme-peachy.css index 3149f4e..0973f27 100644 --- a/data/static/css/theme-peachy.css +++ b/data/static/css/theme-peachy.css @@ -867,6 +867,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus background-color: rgb(249.8, 201.8, 189); } +input:not(form input):invalid { + border: 2px dashed #f73030; +} + textarea { font-family: "Atkinson Hyperlegible Mono", monospace; } @@ -1069,35 +1073,6 @@ textarea { background-color: #f27a5a; } -.draggable-topic { - cursor: pointer; - user-select: none; - background-color: #f27a5a; - padding: 12px; - margin: 8px 0; - border-top: 5px outset rgb(219.84, 191.04, 183.36); - border-bottom: 5px outset rgb(155.8907865169, 93.2211235955, 76.5092134831); -} -.draggable-topic.dragged { - background-color: #f27a5a; -} - -.draggable-collection { - cursor: pointer; - user-select: none; - background-color: #f27a5a; - padding: 12px; - margin: 8px 0; - border-top: 5px outset rgb(219.84, 191.04, 183.36); - border-bottom: 5px outset rgb(155.8907865169, 93.2211235955, 76.5092134831); -} -.draggable-collection.dragged { - background-color: #f27a5a; -} -.draggable-collection.default { - background-color: #b54444; -} - .editing { background-color: rgb(219.84, 191.04, 183.36); } @@ -1479,10 +1454,10 @@ h1 { margin: 6px 0; } .settings-badge-container:has(input:invalid) { - border: 2px dashed red; + border: 2px dashed #f73030; } .settings-badge-container input:invalid { - border: 2px dashed red; + border: 2px dashed #f73030; } .settings-badge-file-picker { @@ -1550,6 +1525,58 @@ img.badge-button { padding-right: 12px; } } +ol.sortable-list { + list-style: none; + flex-grow: 1; + margin: 0; +} +ol.sortable-list li { + display: flex; + gap: 6px; + background-color: #f27a5a; + padding: 12px; + margin: 8px 0; + border-top: 5px outset rgb(219.84, 191.04, 183.36); + border-bottom: 5px outset rgb(155.8907865169, 93.2211235955, 76.5092134831); +} +ol.sortable-list li.dragged { + background-color: #f27a5a; +} +ol.sortable-list li.immovable { + background-color: #b54444; +} +ol.sortable-list li.immovable .dragger { + cursor: not-allowed; +} + +.dragger { + display: flex; + align-items: center; + background-color: rgb(155.8907865169, 93.2211235955, 76.5092134831); + padding: 3px 6px; + cursor: move; +} + +.sortable-item-inner { + display: flex; + gap: 6px; + flex-grow: 1; + flex-direction: column; +} +.sortable-item-inner > * { + flex-grow: 1; +} +.sortable-item-inner.row { + flex-direction: row; +} +.sortable-item-inner:not(.row) > * { + margin-right: auto; +} + +.fg { + flex-grow: 1; +} + #topnav { border-top-left-radius: 16px; border-top-right-radius: 16px; diff --git a/data/static/css/theme-snow-white.css b/data/static/css/theme-snow-white.css index f70baef..c7b4288 100644 --- a/data/static/css/theme-snow-white.css +++ b/data/static/css/theme-snow-white.css @@ -867,6 +867,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus background-color: rgb(235.4, 239.8, 248.2); } +input:not(form input):invalid { + border: 2px dashed red; +} + textarea { font-family: "Atkinson Hyperlegible Mono", monospace; } @@ -1069,35 +1073,6 @@ textarea { background-color: none; } -.draggable-topic { - cursor: pointer; - user-select: none; - background-color: #ced9ee; - padding: 20px; - margin: 15px 0; - border-top: 5px outset rgb(231.36, 234, 239.04); - border-bottom: 5px outset rgb(136.0836363636, 149.3636363636, 174.7163636364); -} -.draggable-topic.dragged { - background-color: #eecee9; -} - -.draggable-collection { - cursor: pointer; - user-select: none; - background-color: #ced9ee; - padding: 20px; - margin: 15px 0; - border-top: 5px outset rgb(231.36, 234, 239.04); - border-bottom: 5px outset rgb(136.0836363636, 149.3636363636, 174.7163636364); -} -.draggable-collection.dragged { - background-color: #eecee9; -} -.draggable-collection.default { - background-color: #eee3ce; -} - .editing { background-color: rgb(231.36, 234, 239.04); } @@ -1550,3 +1525,54 @@ img.badge-button { padding-right: 20px; } } +ol.sortable-list { + list-style: none; + flex-grow: 1; + margin: 0; +} +ol.sortable-list li { + display: flex; + gap: 10px; + background-color: #ced9ee; + padding: 20px; + margin: 15px 0; + border-top: 5px outset rgb(231.36, 234, 239.04); + border-bottom: 5px outset rgb(136.0836363636, 149.3636363636, 174.7163636364); +} +ol.sortable-list li.dragged { + background-color: #eecee9; +} +ol.sortable-list li.immovable { + background-color: #eee3ce; +} +ol.sortable-list li.immovable .dragger { + cursor: not-allowed; +} + +.dragger { + display: flex; + align-items: center; + background-color: rgb(136.0836363636, 149.3636363636, 174.7163636364); + padding: 5px 10px; + cursor: move; +} + +.sortable-item-inner { + display: flex; + gap: 10px; + flex-grow: 1; + flex-direction: column; +} +.sortable-item-inner > * { + flex-grow: 1; +} +.sortable-item-inner.row { + flex-direction: row; +} +.sortable-item-inner:not(.row) > * { + margin-right: auto; +} + +.fg { + flex-grow: 1; +} diff --git a/data/static/js/bitties/pyrom-bitty.js b/data/static/js/bitties/pyrom-bitty.js index 6ef5eae..9ffb006 100644 --- a/data/static/js/bitties/pyrom-bitty.js +++ b/data/static/js/bitties/pyrom-bitty.js @@ -287,8 +287,7 @@ export class BadgeEditorForm { return; } if (this.#badgeTemplate === undefined){ - this.#badgeTemplate = document.getElementById('badge-editor-template').content; - console.log(this.#badgeTemplate); + this.#badgeTemplate = document.getElementById('badge-editor-template').content.firstElementChild.outerHTML; } el.replaceChildren(); const addButton = ``; @@ -300,14 +299,18 @@ export class BadgeEditorForm { ['DISABLE_IF_MAX', badgeCount === 10 ? 'disabled' : ''], ]; el.appendChild(this.api.makeHTML(controls, subs)); - el.appendChild(badges.value); + + const listTemplate = document.getElementById('badges-list-template').content.firstElementChild.outerHTML; + const list = this.api.makeHTML(listTemplate).firstElementChild; + list.appendChild(badges.value); + el.appendChild(list); } addBadge(ev, el) { if (this.#badgeTemplate === undefined) { return; } - const badge = this.#badgeTemplate.cloneNode(true); + const badge = this.api.makeHTML(this.#badgeTemplate).firstElementChild; el.appendChild(badge); this.api.localTrigger('updateBadgeCount'); } @@ -341,9 +344,7 @@ export class BadgeEditorForm { noUploads.forEach(e => { e.value = null; }) - // console.log(noUploads); el.submit(); - // console.log('would submit now'); } } @@ -463,3 +464,80 @@ export class BadgeEditorBadge { el.setCustomValidity(''); } } + +const getCollectionDataForEl = el => { + const nameInput = el.querySelector(".collection-name"); + const collectionId = el.dataset.collectionId; + + return { + id: collectionId, + name: nameInput.value, + is_new: !('collectionId' in el.dataset), + }; +} + +export class CollectionsEditor { + #collectionTemplate = undefined; + #collectionsData = []; + #removedCollections = []; + #valid = true; + + addCollection(ev, el) { + if (this.#collectionTemplate === undefined) { + this.#collectionTemplate = document.getElementById('new-collection-template').content; + } + // interesting + const newCollection = this.api.makeHTML(this.#collectionTemplate.firstElementChild.outerHTML); + el.appendChild(newCollection); + } + + deleteCollection(ev, el) { + if (!el.contains(ev.sender)) { + return; + } + if ('collectionId' in el.dataset) { + this.#removedCollections.push(el.dataset.collectionId); + } + el.remove(); + } + + async saveCollections(ev, el) { + this.#valid = true; + this.api.localTrigger('testValidity'); + if (!this.#valid) { + return; + } + this.#collectionsData = []; + this.api.localTrigger('getCollectionData'); + + const data = { + collections: this.#collectionsData, + removed_collections: this.#removedCollections, + }; + + const res = await this.api.getJSON(el.prop('submitHref'), [], { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + if (res.error) { + return; + } + + window.location.reload(); + } + + getCollectionData(ev, el) { + this.#collectionsData.push(getCollectionDataForEl(el)); + } + + testValidity(ev, el) { + const input = el.querySelector('input'); + if (!input.validity.valid) { + input.reportValidity(); + this.#valid = false; + } + } +} diff --git a/data/static/js/manage-bookmark-collections.js b/data/static/js/manage-bookmark-collections.js deleted file mode 100644 index 75f1ade..0000000 --- a/data/static/js/manage-bookmark-collections.js +++ /dev/null @@ -1,128 +0,0 @@ -let removedCollections = []; - -document.getElementById("add-collection-button").addEventListener("click", () => { - const container = document.getElementById("collections-container"); - const currentCount = container.querySelectorAll(".draggable-collection").length; - - const newId = `new-${Date.now()}` - const collectionHtml = ` -
    -
    -
    0 threads, 0 posts
    - -
    - `; - container.insertAdjacentHTML('beforeend', collectionHtml); -}) - -document.addEventListener("click", e => { - if (!e.target.classList.contains("delete-button")) { - return; - } - const collectionDiv = e.target.closest(".draggable-collection"); - const collectionId = collectionDiv.dataset.collectionId; - - if (!collectionId.startsWith("new-")) { - removedCollections.push(collectionId); - } - - collectionDiv.remove(); -}) - -document.getElementById("save-button").addEventListener("click", async () => { - const collections = []; - const collectionDivs = document.querySelectorAll(".draggable-collection"); - let isValid = true; - collectionDivs.forEach((collection, index) => { - const collectionId = collection.dataset.collectionId; - const nameInput = collection.querySelector(".collection-name"); - - if (!nameInput.reportValidity()) { - isValid = false; - return; - } - - collections.push({ - id: collectionId, - name: nameInput.value, - is_new: collectionId.startsWith("new-"), - }); - }) - - if (!isValid) { - return; - } - - const data = { - collections: collections, - removed_collections: removedCollections, - }; - - try { - const saveHref = document.getElementById('save-button').dataset.submitHref; - const response = await fetch(saveHref, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - - if (response.ok) { - window.location.reload(); - } else { - console.error("Error saving collections"); - } - - } catch (error) { - console.error("Error saving collections: ", error); - } -}) - -// drag logic -// https://codepen.io/crouchingtigerhiddenadam/pen/qKXgap - -let selected = null; -const container = document.getElementById("collections-container"); -function isBefore(el1, el2) { - let cur; - if (el2.parentNode === el1.parentNode) { - for (cur = el1.previousSibling; cur; cur = cur.previousSibling) { - if (cur === el2) return true; - } - } - return false; -} - -function dragOver(e) { - let target = e.target.closest(".draggable-collection") - - if (!target || target === selected) { - return; - } - - if (isBefore(selected, target)) { - container.insertBefore(selected, target) - } else { - container.insertBefore(selected, target.nextSibling) - } -} - -function dragEnd() { - if (!selected) return; - - selected.classList.remove("dragged") - selected = null; -} - -function dragStart(e) { - e.dataTransfer.effectAllowed = 'move' - e.dataTransfer.setData('text/plain', "") - selected = e.target - selected.classList.add("dragged") -} diff --git a/data/static/js/sort-topics.js b/data/static/js/sort-topics.js deleted file mode 100644 index e288266..0000000 --- a/data/static/js/sort-topics.js +++ /dev/null @@ -1,45 +0,0 @@ -// https://codepen.io/crouchingtigerhiddenadam/pen/qKXgap -let selected = null; -let container = document.getElementById("topics-container") - -function isBefore(el1, el2) { - let cur - if (el2.parentNode === el1.parentNode) { - for (cur = el1.previousSibling; cur; cur = cur.previousSibling) { - if (cur === el2) return true - } - } - return false; -} - -function dragOver(e) { - let target = e.target.closest(".draggable-topic") - - if (!target || target === selected) { - return; - } - - if (isBefore(selected, target)) { - container.insertBefore(selected, target) - } else { - container.insertBefore(selected, target.nextSibling) - } -} - -function dragEnd() { - if (!selected) return; - - selected.classList.remove("dragged") - selected = null; - for (let i = 0; i < container.childElementCount - 1; i++) { - let input = container.children[i].querySelector(".topic-input"); - input.value = i + 1; - } -} - -function dragStart(e) { - e.dataTransfer.effectAllowed = 'move' - e.dataTransfer.setData('text/plain', null) - selected = e.target - selected.classList.add("dragged") -} diff --git a/data/static/js/ui.js b/data/static/js/ui.js index bb5537b..7f6ed1a 100644 --- a/data/static/js/ui.js +++ b/data/static/js/ui.js @@ -90,21 +90,16 @@ document.addEventListener("DOMContentLoaded", () => { minWidth: origMinWidth, minHeight: origMinHeight, } = getComputedStyle(img); - console.log(img, img.naturalWidth, img.naturalHeight, origMinWidth, origMinHeight, origMaxWidth, origMaxHeight) if (img.naturalWidth < parseInt(origMinWidth)) { - console.log(1) img.style.minWidth = img.naturalWidth + "px"; } if (img.naturalHeight < parseInt(origMinHeight)) { - console.log(2) img.style.minHeight = img.naturalHeight + "px"; } if (img.naturalWidth < parseInt(origMaxWidth)) { - console.log(3) img.style.maxWidth = img.naturalWidth + "px"; } if (img.naturalHeight < parseInt(origMaxHeight)) { - console.log(4) img.style.maxHeight = img.naturalHeight + "px"; } } @@ -133,3 +128,108 @@ document.addEventListener("DOMContentLoaded", () => { } }) }); + + +{ + function isBefore(el1, el2) { + if (el2.parentNode === el1.parentNode) { + for (let cur = el1.previousSibling; cur; cur = cur.previousSibling) { + if (cur === el2) return true; + } + } + return false; + } + + let draggedItem = null; + + function sortableItemDragStart(e, item) { + const box = item.getBoundingClientRect(); + const oX = e.clientX - box.left; + const oY = e.clientY - box.top; + draggedItem = item; + item.classList.add('dragged'); + e.dataTransfer.setDragImage(item, oX, oY); + e.dataTransfer.effectAllowed = 'move'; + } + + function sortableItemDragEnd(e, item) { + draggedItem = null; + item.classList.remove('dragged'); + } + + function sortableItemDragOver(e, item) { + const target = e.target.closest('.sortable-item'); + if (!target || target === draggedItem) { + return; + } + const inSameList = draggedItem.dataset.sortableListKey === target.dataset.sortableListKey; + if (!inSameList) { + return; + } + const targetList = draggedItem.closest('.sortable-list'); + if (isBefore(draggedItem, target)) { + targetList.insertBefore(draggedItem, target); + } else { + targetList.insertBefore(draggedItem, target.nextSibling); + } + } + + const listItemsHandled = new Map(); + + const getListItemsHandled = (list) => { + return listItemsHandled.get(list) || new Set(); + } + + function registerSortableList(list) { + list.querySelectorAll('li:not(.immovable)').forEach(item => { + const listItems = getListItemsHandled(list); + listItems.add(item); + listItemsHandled.set(list, listItems); + const dragger = item.querySelector('.dragger'); + dragger.addEventListener('dragstart', e => {sortableItemDragStart(e, item)}); + dragger.addEventListener('dragend', e => {sortableItemDragEnd(e, item)}); + item.addEventListener('dragover', e => {sortableItemDragOver(e, item)}); + }); + + const obs = new MutationObserver(records => { + for (const mutation of records) { + mutation.addedNodes.forEach(node => { + if (!(node instanceof HTMLElement)) { + return; + } + if (!node.classList.contains('sortable-item')) { + return; + } + const listItems = getListItemsHandled(list) + if (listItems.has(node)) { + return; + } + const dragger = node.querySelector('.dragger'); + dragger.addEventListener('dragstart', e => {sortableItemDragStart(e, node)}); + dragger.addEventListener('dragend', e => {sortableItemDragEnd(e, node)}); + node.addEventListener('dragover', e => {sortableItemDragOver(e, node)}); + listItems.add(node); + listItemsHandled.set(list, listItems); + }); + } + }); + obs.observe(list, {childList: true}); + } + + document.querySelectorAll('.sortable-list').forEach(registerSortableList); + + listsObs = new MutationObserver(records => { + for (const mutation of records) { + mutation.addedNodes.forEach(node => { + if (!(node instanceof HTMLElement)) { + return; + } + if (!node.classList.contains('sortable-list')) { + return; + } + registerSortableList(node); + }) + } + }) + listsObs.observe(document.body, {childList: true, subtree: true}); +} diff --git a/sass/_default.scss b/sass/_default.scss index c9478cc..02064d5 100644 --- a/sass/_default.scss +++ b/sass/_default.scss @@ -56,6 +56,7 @@ $PAGE_SIDE_MARGIN: 100px !default; // BORDERS // ************** $DEFAULT_BORDER: 1px solid black !default; +$DEFAULT_BORDER_INVALID: 2px dashed $BUTTON_COLOR_CRITICAL !default; $DEFAULT_BORDER_RADIUS: 4px !default; // other variables can be found before the rule that uses them. they are usually constructed from these basic variables. @@ -709,6 +710,11 @@ input[type="text"], input[type="password"], textarea, select { } } +// lone required inputs managed by js +input:not(form input):invalid { + border: $DEFAULT_BORDER_INVALID; +} + textarea { font-family: "Atkinson Hyperlegible Mono", monospace; } @@ -966,54 +972,6 @@ $topic_locked_background: none !default; background-color: $topic_locked_background; } -$draggable_topic_background: $ACCENT_COLOR !default; -$draggable_topic_dragged_color: $BUTTON_COLOR !default; -$draggable_topic_padding: $BIG_PADDING !default; -$draggable_topic_margin: $MEDIUM_BIG_PADDING 0 !default; -$draggable_topic_border: 5px outset !default; -$draggable_topic_border_top: $draggable_topic_border $LIGHT !default; -$draggable_topic_border_bottom: $draggable_topic_border $DARK_2 !default; -.draggable-topic { - cursor: pointer; - user-select: none; - background-color: $draggable_topic_background; - padding: $draggable_topic_padding; - margin: $draggable_topic_margin; - border-top: $draggable_topic_border_top; - border-bottom: $draggable_topic_border_bottom; - - &.dragged { - background-color: $draggable_topic_dragged_color; - } -} - -$draggable_collection_background: $ACCENT_COLOR !default; -$draggable_collection_dragged_color: $BUTTON_COLOR !default; -$draggable_collection_default_color: $BUTTON_COLOR_2 !default; -$draggable_collection_padding: $BIG_PADDING !default; -$draggable_collection_margin: $MEDIUM_BIG_PADDING 0 !default; -$draggable_collection_border: 5px outset !default; -$draggable_collection_border_top: $draggable_collection_border $LIGHT !default; -$draggable_collection_border_bottom: $draggable_collection_border $DARK_2 !default; -.draggable-collection { - cursor: pointer; - user-select: none; - background-color: $draggable_collection_background; - padding: $draggable_collection_padding; - margin: $draggable_collection_margin; - border-top: $draggable_collection_border_top; - border-bottom: $draggable_collection_border_bottom; - - &.dragged { - background-color: $draggable_collection_dragged_color; - } - - &.default { - background-color: $draggable_collection_default_color; - } -} - - $post_editing_header_color: $LIGHT !default; .editing { background-color: $post_editing_header_color; @@ -1415,6 +1373,7 @@ $settings_grid_gap: $MEDIUM_PADDING !default; $settings_grid_item_min_width: 600px !default; $settings_grid_fieldset_border: 1px solid $DEFAULT_FONT_COLOR_INVERSE !default; $settings_grid_fieldset_border_radius: $DEFAULT_BORDER_RADIUS !default; +$settings_grid_fieldset_background_color: $DARK_1_LIGHTER !default; .settings-grid { display: grid; gap: $settings_grid_gap; @@ -1425,7 +1384,7 @@ $settings_grid_fieldset_border_radius: $DEFAULT_BORDER_RADIUS !default; & fieldset { border: $settings_grid_fieldset_border; border-radius: $settings_grid_fieldset_border_radius; - background-color: $DARK_1_LIGHTER; + background-color: $settings_grid_fieldset_background_color; } } @@ -1440,7 +1399,7 @@ h1 { $settings_badge_container_gap: $SMALL_PADDING !default; $settings_badge_container_border: $DEFAULT_BORDER !default; -$settings_badge_container_border_invalid: 2px dashed red !default; +$settings_badge_container_border_invalid: $DEFAULT_BORDER_INVALID !default; $settings_badge_container_border_radius: $DEFAULT_BORDER_RADIUS !default; $settings_badge_container_padding: $SMALL_PADDING $MEDIUM_PADDING !default; $settings_badge_container_margin: $MEDIUM_PADDING $ZERO_PADDING !default; @@ -1552,3 +1511,73 @@ $rss_button_font_color_active: black !default; padding-right: $guide_section_padding_right_portrait; } } + +$sortable_item_background: $ACCENT_COLOR !default; +$sortable_item_dragged_color: $BUTTON_COLOR !default; +$sortable_item_immovable_color: $BUTTON_COLOR_2 !default; +$sortable_item_padding: $BIG_PADDING !default; +$sortable_item_margin: $MEDIUM_BIG_PADDING 0 !default; +$sortable_item_border: 5px outset !default; +$sortable_item_border_top: $sortable_item_border $LIGHT !default; +$sortable_item_border_bottom: $sortable_item_border $DARK_2 !default; +$sortable_item_gap: $MEDIUM_PADDING !default; +ol.sortable-list { + list-style: none; + flex-grow: 1; + margin: 0; + + li { + display: flex; + gap: $sortable_item_gap; + background-color: $sortable_item_background; + padding: $sortable_item_padding; + margin: $sortable_item_margin; + border-top: $sortable_item_border_top; + border-bottom: $sortable_item_border_bottom; + } + + li.dragged { + background-color: $sortable_item_dragged_color; + } + + li.immovable { + background-color: $sortable_item_immovable_color; + } + + li.immovable .dragger { + cursor: not-allowed; + } +} + +$sortable_item_grabber_padding: $SMALL_PADDING $MEDIUM_PADDING !default; +.dragger { + display: flex; + align-items: center; + background-color: $DARK_2; + padding: $sortable_item_grabber_padding; + cursor: move; +} + +$sortable_item_inner_gap: $MEDIUM_PADDING !default; +.sortable-item-inner { + display: flex; + gap: $sortable_item_inner_gap; + flex-grow: 1; + flex-direction: column; + + & > * { + flex-grow: 1; + } + + &.row { + flex-direction: row; + } + + &:not(.row) > * { + margin-right: auto; + } +} + +.fg { + flex-grow: 1; +} diff --git a/sass/otomotone.scss b/sass/otomotone.scss index bd92a76..5cf8f75 100644 --- a/sass/otomotone.scss +++ b/sass/otomotone.scss @@ -83,6 +83,8 @@ $br: 8px; $mention_font_color: $fc, + $settings_grid_fieldset_background_color: $lightish_accent, + // $settings_badge_container_border_invalid: 2px dashed $crit, );