+
{% endmacro %}
{% macro rss_html_content(html) %}
@@ -367,3 +374,20 @@
{% macro rss_button(feed) %}
{% endmacro %}
+
+{% macro sortable_list(attr=none) %}
+
+{% if caller %}
+{{ caller() }}
+{% endif %}
+
+{% endmacro %}
+
+{% macro sortable_list_item(key, immovable=false, attr=none) %}
+
+ {{ icn_drag(24) }}
+
+ {{ caller() }}
+
+
+{% endmacro %}
diff --git a/app/templates/mod/sort-topics.html b/app/templates/mod/sort-topics.html
index 7e3dff3..babb91f 100644
--- a/app/templates/mod/sort-topics.html
+++ b/app/templates/mod/sort-topics.html
@@ -1,18 +1,20 @@
{% extends "base.html" %}
+{% from 'common/macros.html' import sortable_list, sortable_list_item %}
{% block content %}
Change topics order
-
Drag topic titles to reoder them. Press submit when done. The topics will appear to users in the order set here.
-
+ {% 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 %}
+
-
-
+
+{% call() sortable_list_item(key='collections', attr={'data-receive': 'deleteCollection getCollectionData testValidity'}) %}
+
+ 0 threads, 0 posts
+
+{% 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 help
-
@@ -72,4 +72,8 @@
{{ badge_editor_single(options=uploads) }}
+
+
+{{ sortable_list(attr={'data-receive': 'addBadge'}) }}
+
{% 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,
);