add: forums ui

This commit is contained in:
trisua 2025-08-04 12:12:04 -04:00
parent 2be87c397d
commit 9ec52abfe4
24 changed files with 770 additions and 64 deletions

View file

@ -163,7 +163,8 @@
(text "{{ text \"communities:action.create\" }}"))))))
(text "{% if not quoting -%}")
(script
(text "async function create_post_from_form(e) {
(text "globalThis.SEARCH_PARAMS = new URLSearchParams(window.location.search);
async function create_post_from_form(e) {
e.preventDefault();
await trigger(\"atto::debounce\", [\"posts::create\"]);
@ -204,6 +205,7 @@
content: e.target.content.value,
community: !is_selected_stack ? selected_community : \"0\",
stack: is_selected_stack ? selected_community : \"0\",
topic: !is_selected_stack ? SEARCH_PARAMS.get(\"topic\") || \"0\" : \"0\",
poll: poll_data[1],
title: e.target.title.value,
}),
@ -457,7 +459,7 @@
check_community_supports_title({
target: document.getElementById(\"community_to_post_to\"),
});
}, 150);
}, 250);
window.cancel_create_post = async () => {
if (

View file

@ -23,5 +23,4 @@
(div
("class" "card flex flex_col gap_4")
(text "{% for post in feed %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts, poll=post[4]) }} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=feed|length) }}"))))
(text "{% endblock %}")

View file

@ -6,41 +6,52 @@
(main
("class" "flex flex_col gap_2")
(div
("class" "pillmenu")
(a
("href" "#/general")
("data-tab-button" "general")
("class" "active")
(text "{{ icon \"settings\" }}")
(span
(text "{{ text \"settings:tab.general\" }}")))
(a
("href" "#/images")
("data-tab-button" "images")
(text "{{ icon \"image\" }}")
(span
(text "{{ text \"settings:tab.images\" }}")))
(a
("href" "#/members")
("data-tab-button" "members")
(text "{{ icon \"users-round\" }}")
(span
(text "{{ text \"communities:tab.members\" }}")))
(text "{% if can_manage_channels -%}")
(a
("href" "#/channels")
("data-tab-button" "channels")
(text "{{ icon \"rss\" }}")
(span
(text "{{ text \"communities:tab.channels\" }}")))
(text "{%- endif %} {% if can_manage_emojis -%}")
(a
("href" "#/emojis")
("data-tab-button" "emojis")
(text "{{ icon \"smile\" }}")
(span
(text "{{ text \"communities:tab.emojis\" }}")))
(text "{%- endif %}"))
("class" "pillmenu rows w-full")
(div
("class" "row")
(a
("href" "#/general")
("data-tab-button" "general")
("class" "active")
(text "{{ icon \"settings\" }}")
(span
(text "{{ text \"settings:tab.general\" }}")))
(a
("href" "#/images")
("data-tab-button" "images")
(text "{{ icon \"image\" }}")
(span
(text "{{ text \"settings:tab.images\" }}")))
(a
("href" "#/members")
("data-tab-button" "members")
(text "{{ icon \"users-round\" }}")
(span
(text "{{ text \"communities:tab.members\" }}"))))
(div
("class" "row")
(text "{% if can_manage_channels -%}")
(a
("href" "#/channels")
("data-tab-button" "channels")
(text "{{ icon \"rss\" }}")
(span
(text "{{ text \"communities:tab.channels\" }}")))
(text "{%- endif %} {% if community.is_forum -%}")
(a
("href" "#/topics")
("data-tab-button" "topics")
(icon (text "list"))
(span
(str (text "communities:tab.topics"))))
(text "{%- endif %} {% if can_manage_emojis -%}")
(a
("href" "#/emojis")
("data-tab-button" "emojis")
(text "{{ icon \"smile\" }}")
(span
(text "{{ text \"communities:tab.emojis\" }}")))
(text "{%- endif %}")))
(div
("class" "w_full flex flex_col gap_2")
("data-tab" "general")
@ -564,6 +575,235 @@
]);
});
};"))
(text "{%- endif %}")
(text "{% if community.is_forum -%}")
(script ("type" "application/json") ("id" "community_topics") (text "{{ community.topics | json_encode() | safe }}"))
(div
("class" "card lowered w_full hidden flex flex_col gap_2")
("data-tab" "topics")
(div
("class" "card_nest")
(div
("class" "card small")
(b
(str (text "communities:action.create_topic"))))
(form
("class" "card flex flex_col gap_2")
("onsubmit" "create_topic_from_form(event)")
(div
("class" "flex flex_col gap_1")
(label
("for" "title")
(text "{{ text \"communities:label.name\" }}"))
(input
("type" "text")
("name" "title")
("id" "title")
("placeholder" "name")
("required" "")
("minlength" "2")
("maxlength" "32")))
(div
("class" "flex flex_col gap_1")
(label
("for" "description")
(str (text "communities:label.description")))
(input
("type" "text")
("name" "description")
("id" "description")
("placeholder" "description")
("required" "")
("minlength" "2")
("maxlength" "256")))
(div
("class" "flex flex_col gap_1")
(label
("for" "color")
(str (text "communities:label.color")))
(input
("type" "color")
("name" "color")
("id" "color")
("placeholder" "color")
("required" "")
("style" "width: 8rem")))
(div
("class" "flex flex_col gap_1")
(label
("for" "position")
(str (text "communities:label.position")))
(input
("type" "number")
("name" "position")
("id" "position")
("placeholder" "position")
("required" "")
("value" "0")
("min" "0")
("max" "256")))
(button
(text "{{ text \"communities:action.create\" }}"))))
(text "{% for id, topic in community.topics %}")
(div
("class" "card_nest")
(div
("class" "card small flex justify_between gap_2")
(div
("class" "flex gap_2")
(b
(text "{{ topic.position }} "))
(text "{{ topic.title }}"))
(button
("class" "red lowered small")
("onclick" "delete_topic('{{ id }}')")
(icon (text "trash"))
(str (text "general:action.delete"))))
(div
("class" "card flex flex_col gap_2")
(details
("class" "accordion")
(summary ("class" "flex items_center gap_2") (icon (text "pencil")) (str (text "general:label.edit")))
(form
("class" "inner flex flex_col gap_2")
("style" "background: var(--color-super-raised)")
("onsubmit" "update_topic_from_form(event, '{{ id }}')")
(div
("class" "flex flex_col gap_1")
(label
("for" "title")
(text "{{ text \"communities:label.name\" }}"))
(input
("type" "text")
("name" "title")
("id" "title")
("placeholder" "name")
("value" "{{ topic.title }}")
("required" "")
("minlength" "2")
("maxlength" "32")))
(div
("class" "flex flex_col gap_1")
(label
("for" "description")
(str (text "communities:label.description")))
(input
("type" "text")
("name" "description")
("id" "description")
("placeholder" "description")
("value" "{{ topic.description }}")
("required" "")
("minlength" "2")
("maxlength" "256")))
(div
("class" "flex flex_col gap_1")
(label
("for" "color")
(str (text "communities:label.color")))
(input
("type" "color")
("name" "color")
("id" "color")
("placeholder" "color")
("required" "")
("value" "{{ topic.color }}")
("style" "width: 8rem")))
(div
("class" "flex flex_col gap_1")
(label
("for" "position")
(str (text "communities:label.position")))
(input
("type" "number")
("name" "position")
("id" "position")
("placeholder" "position")
("required" "")
("value" "{{ topic.position }}")
("min" "0")
("max" "256")))
(button
(icon (text "check"))
(str (text "general:action.save")))))))
(text "{% endfor %}"))
(script
(text "globalThis.delete_topic = async (id) => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
]))
) {
return;
}
fetch(`/api/v1/communities/{{ community.id }}/topics/${id}`, {
method: \"DELETE\",
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
async function create_topic_from_form(e) {
e.preventDefault();
await trigger(\"atto::debounce\", [\"topics::create\"]);
fetch(\"/api/v1/communities/{{ community.id }}/topics\", {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
title: e.target.title.value,
description: e.target.description.value,
color: e.target.color.value,
position: Number.parseInt(e.target.position.value),
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
if (res.ok) {
e.target.reset();
window.location.reload();
}
});
}
async function update_topic_from_form(e, id) {
e.preventDefault();
await trigger(\"atto::debounce\", [\"topics::update\"]);
fetch(`/api/v1/communities/{{ community.id }}/topics/${id}`, {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
title: e.target.title.value,
description: e.target.description.value,
color: e.target.color.value,
position: Number.parseInt(e.target.position.value),
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
}"))
(text "{%- endif %}"))
(script

View file

@ -0,0 +1,42 @@
(text "{% import \"components.html\" as components %} {% extends \"communities/base.html\" %} {% block content %}")
(div
("class" "flex flex_col gap_4 w_full")
(text "{{ macros::community_nav(community=community, selected=\"posts\") }}")
(div
("class" "card_nest")
(div
("class" "card small flex justify_between gap_2")
(text "{{ components::topic_display(id=topic_id, topic=topic, community=community, show_description=false) }}")
(div
("class" "flex gap_2")
(a
("href" "/communities/intents/post?community={{ community.id }}&topic={{ topic_id }}")
("class" "button small lowered")
("data-turbo" "false")
(icon (text "plus"))
(span
(str (text "general:action.post"))))
(a
("href" "/community/{{ community.title }}")
("class" "button lowered small")
(icon (text "arrow-left"))
(str (text "general:action.back")))))
(div
("class" "card flex flex_col gap_4")
(span ("class" "no_p_margin") (text "{{ topic.description|markdown|safe }}"))
(hr)
(div
("class" "w_full")
("style" "overflow: auto")
(table
("class" "w_full")
(thead
(th (text "Title"))
(th (text "Replies"))
(th (text "Score"))
(th (text "Created")))
(tbody
(text "{% for post in pinned %} {{ components::topic_post_display(post=post[0], owner=post[1], is_pinned=true) }} {% endfor %}")
(text "{% for post in feed %} {{ components::topic_post_display(post=post[0], owner=post[1]) }} {% endfor %}"))))
(text "{{ components::pagination(page=page, items=feed|length) }}"))))
(text "{% endblock %}")

View file

@ -0,0 +1,19 @@
(text "{% import \"components.html\" as components %} {% extends \"communities/base.html\" %} {% block content %}")
(div
("class" "flex flex_col gap_4 w_full")
(text "{{ macros::community_nav(community=community, selected=\"posts\") }}")
(div
("class" "card_nest")
(div
("class" "card small flex gap_2 items_center")
(icon (text "list"))
(span
(str (text "communities:label.topics"))))
(div
("class" "card flex flex_col gap_4")
(text "{% for topic in topics_sorted %}")
(div
("class" "card lowered w_full flex flex_col gap_2")
(text "{{ components::topic_display(id=topic[0], topic=topic[1], community=community) }}"))
(text "{% endfor %}"))))
(text "{% endblock %}")

View file

@ -543,8 +543,14 @@
(text "{{ self::avatar(username=owner.username, size=\"24px\", selector_type=\"username\") }}"))
(span
("class" "name")
(text "{{ self::full_username(user=owner) }}")))
(text "{{ self::post_info(post=post, community=community) }}"))
(text "{{ self::full_username(user=owner) }}"))
(a
("href" "/community/{{ community.title }}/topic/{{ post.topic }}")
("class" "flush flex gap_1 items_center smaller_avatar")
(text "{{ self::community_avatar(id=post.community, community=community, size=\"18px\") }}")))
(div
("class" "flex gap_2")
(text "{{ self::post_info(post=post, community=community) }}")))
(div
("class" "card_nest_horizontal")
; author info
@ -2066,6 +2072,7 @@
(text "{{ icon \"message-circle\" }}")
(span
(text "{{ text \"communities:label.chats\" }}")))
(text "{% if not community.is_forum -%}")
(a
("href" "/communities/intents/post?community={{ community.id }}")
("class" "button lowered")
@ -2073,6 +2080,7 @@
(text "{{ icon \"plus\" }}")
(span
(text "{{ text \"general:action.post\" }}")))
(text "{%- endif %}")
(text "{%- endif %} {% if can_manage_community or is_manager -%}")
(a
("href" "/community/{{ community.id }}/manage")
@ -2606,3 +2614,39 @@
(icon (text "trash")))
(text "{%- endif %}"))))
(text "{%- endmacro %}")
(text "{% macro topic_display(id, topic, community, show_description=true) -%}")
(div
("class" "flex items_center gap_2")
(svg
("width" "12")
("height" "12")
("viewBox" "0 0 12 12")
("style" "fill: {% if topic.color == \"#000000\" -%} var(--color-primary) {%- else -%} {{ topic.color }} {%- endif %}; margin-top: 3.5px")
(circle
("cx" "6")
("cy" "6")
("r" "6")))
(a
("href" "/community/{{ community.title }}/topic/{{ id }}")
("class" "flush")
(b (text "{{ topic.title }}"))))
(text "{% if show_description -%}")
(span ("class" "no_p_margin") (text "{{ topic.description|markdown|safe }}"))
(text "{%- endif %}")
(text "{%- endmacro %}")
(text "{% macro topic_post_display(post, owner, is_pinned=false) -%}")
(tr
(td
("class" "flex gap_1")
(a
("href" "/post/{{ post.id }}")
(text "{% if is_pinned -%}Sticky: {% endif %}")
(text "{{ post.title }}"))
(span ("class" "fade") (text "by"))
(text "{{ self::full_username(user=owner) }}"))
(td (text "{{ post.comment_count }}"))
(td (text "{{ ((post.likes + 1) / (post.dislikes + 1))|round(method=\"ceil\", precision=2) }}"))
(td (span ("class" "date") (text "{{ post.created }}"))))
(text "{%- endmacro %}")

View file

@ -354,6 +354,7 @@
content: e.target.content.value,
community: \"{{ community.id }}\",
stack: \"{{ post.stack }}\",
topic: \"{{ post.topic }}\",
replying_to: \"{{ post.id }}\",
poll: poll_data[1],
}),