tetratto/crates/app/src/public/html/communities/settings.lisp

1210 lines
46 KiB
Common Lisp
Raw Normal View History

2025-06-01 12:25:33 -04:00
(text "{% extends \"root.html\" %} {% block head %}")
(title
(text "Community settings - {{ config.name }}"))
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
(main
2025-08-03 23:24:57 -04:00
("class" "flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
(div
2025-08-04 12:12:04 -04:00
("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 %}")))
2025-06-01 12:25:33 -04:00
(div
2025-08-03 23:24:57 -04:00
("class" "w_full flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("data-tab" "general")
(div
("id" "manage_fields")
2025-08-03 23:24:57 -04:00
("class" "card lowered flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
("ui_ident" "read_access")
(div
("class" "card small")
(b
(text "Read access")))
(div
("class" "card")
(select
("onchange" "save_access(event, 'read')")
(option
("value" "Everybody")
("selected" "{% if community.read_access == 'Everybody' -%}true{% else %}false{%- endif %}")
(text "Everybody"))
(option
("value" "Joined")
("selected" "{% if community.read_access == 'Joined' -%}true{% else %}false{%- endif %}")
(text "Joined")))))
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
("ui_ident" "join_access")
(div
("class" "card small")
(b
(text "Join access")))
(div
("class" "card")
(select
("onchange" "save_access(event, 'join')")
(option
("value" "Everybody")
("selected" "{% if community.join_access == 'Everybody' -%}true{% else %}false{%- endif %}")
(text "Everybody"))
(option
("value" "Request")
("selected" "{% if community.join_access == 'Request' -%}true{% else %}false{%- endif %}")
(text "Request"))
(option
("value" "Nobody")
("selected" "{% if community.join_access == 'Nobody' -%}true{% else %}false{%- endif %}")
(text "Nobody")))))
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
("ui_ident" "write_access")
(div
("class" "card small")
(b
(text "Post permission")))
(div
("class" "card")
(select
("onchange" "save_access(event, 'write')")
(option
("value" "Everybody")
("selected" "{% if community.write_access == 'Everybody' -%}true{% else %}false{%- endif %}")
(text "Everybody"))
(option
("value" "Joined")
("selected" "{% if community.write_access == 'Joined' -%}true{% else %}false{%- endif %}")
(text "Joined"))
(option
("value" "Owner")
("selected" "{% if community.write_access == 'Owner' -%}true{% else %}false{%- endif %}")
(text "Owner only")))))
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
("ui_ident" "change_title")
(div
("class" "card small")
(b
(text "{{ text \"communities:label.change_title\" }}")))
(form
2025-08-03 23:24:57 -04:00
("class" "card flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("onsubmit" "change_title(event)")
(div
2025-08-03 23:24:57 -04:00
("class" "flex flex_col gap_1")
2025-06-01 12:25:33 -04:00
(label
("for" "new_title")
(text "{{ text \"communities:label.new_title\" }}"))
(input
("type" "text")
("name" "new_title")
("id" "new_title")
("placeholder" "new_title")
("required" "")
("minlength" "2")))
(button
(text "{{ icon \"check\" }}")
(span
(text "{{ text \"general:action.save\" }}"))))))
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
("ui_ident" "danger_zone")
(div
2025-08-03 23:24:57 -04:00
("class" "card small flex gap_1 items_center red")
2025-06-01 12:25:33 -04:00
(text "{{ icon \"skull\" }}")
(b
(text "{{ text \"communities:label.danger_zone\" }}")))
(div
2025-08-03 23:24:57 -04:00
("class" "card flex flex_wrap gap_2")
2025-06-01 12:25:33 -04:00
(button
("class" "red lowered")
2025-06-01 12:25:33 -04:00
("onclick" "delete_community()")
(text "{{ icon \"trash\" }}")
(span
(text "{{ text \"communities:label.delete_community\" }}")))))
(div
2025-08-03 23:24:57 -04:00
("class" "flex gap_2 flex_wrap")
2025-06-01 12:25:33 -04:00
(button
("onclick" "save_context()")
(text "{{ icon \"check\" }}")
(span
(text "{{ text \"general:action.save\" }}")))
(a
("href" "/community/{{ community.title }}")
("class" "button secondary")
(text "{{ icon \"arrow-left\" }}")
(span
(text "{{ text \"general:action.back\" }}")))))
(div
2025-08-03 23:24:57 -04:00
("class" "card lowered w_full hidden flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("data-tab" "images")
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
("ui_ident" "change_avatar")
(div
("class" "card small")
(b
(text "{{ text \"settings:label.change_avatar\" }}")))
(form
2025-08-03 23:24:57 -04:00
("class" "card flex gap_2 flex_row flex_wrap items_center")
2025-06-01 12:25:33 -04:00
("method" "post")
("enctype" "multipart/form-data")
("onsubmit" "upload_avatar(event)")
(input
("id" "avatar_file")
("name" "file")
("type" "file")
("accept" "image/png,image/jpeg,image/avif,image/webp,image/gif")
2025-08-03 23:24:57 -04:00
("class" "w_content"))
2025-06-01 12:25:33 -04:00
(button
(text "{{ icon \"check\" }}"))))
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
("ui_ident" "change_banner")
(div
("class" "card small")
(b
(text "{{ text \"settings:label.change_banner\" }}")))
(form
2025-08-03 23:24:57 -04:00
("class" "card flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("method" "post")
("enctype" "multipart/form-data")
("onsubmit" "upload_banner(event)")
(div
2025-08-03 23:24:57 -04:00
("class" "flex gap_2 flex_row flex_wrap items_center")
2025-06-01 12:25:33 -04:00
(input
("id" "banner_file")
("name" "file")
("type" "file")
("accept" "image/png,image/jpeg,image/avif,image/webp")
2025-08-03 23:24:57 -04:00
("class" "w_content"))
2025-06-01 12:25:33 -04:00
(button
(text "{{ icon \"check\" }}")))
(span
("class" "fade")
(text "Use an image of 1100x350px for the best results.")))))
(div
2025-08-03 23:24:57 -04:00
("class" "card lowered w_full hidden flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("data-tab" "members")
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
(div
("class" "card small")
(b
(text "{{ text \"communities:label.select_member\" }}")))
(form
2025-08-03 23:24:57 -04:00
("class" "card flex_col gap_2")
2025-06-01 12:25:33 -04:00
("onsubmit" "select_user_from_form(event)")
(div
2025-08-03 23:24:57 -04:00
("class" "flex flex_col gap_1")
2025-06-01 12:25:33 -04:00
(div
2025-08-03 23:24:57 -04:00
("class" "flex flex_col gap_1")
2025-06-01 12:25:33 -04:00
(label
("for" "uid")
(text "{{ text \"communities:label.user_id\" }}"))
(input
("type" "number")
("name" "uid")
("id" "uid")
("placeholder" "user id")
("required" "")
("minlength" "18")))
(button
(text "{{ text \"communities:action.select\" }}")))))
(div
2025-08-03 23:24:57 -04:00
("class" "card flex flex_col gap_2 w_full")
2025-06-02 22:50:12 -04:00
("id" "membership_info"))
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest w_full")
2025-06-02 22:50:12 -04:00
(div
2025-08-03 23:24:57 -04:00
("class" "card small flex items_center justify_between gap_2")
2025-06-02 22:50:12 -04:00
(div
2025-08-03 23:24:57 -04:00
("class" "flex items_center gap_2")
2025-06-02 22:50:12 -04:00
(text "{{ icon \"blocks\" }}")
(span
(text "{{ text \"mod_panel:label.permissions_level_builder\" }}")))
(button
("class" "small lowered")
2025-06-02 22:50:12 -04:00
("onclick" "update_user_role(document.getElementById('uid').value, document.getElementById('role').value)")
(text "{{ icon \"check\" }}")
(span
(text "{{ text \"general:action.save\" }}"))))
(div
2025-08-03 23:24:57 -04:00
("class" "card flex flex_col gap_2")
2025-06-02 22:50:12 -04:00
("id" "permission_builder"))))
2025-06-01 12:25:33 -04:00
(text "{% if can_manage_channels -%}")
(div
2025-08-03 23:24:57 -04:00
("class" "card lowered w_full hidden flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("data-tab" "channels")
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
(div
("class" "card small")
(b
(text "{{ text \"communities:action.create_channel\" }}")))
(form
2025-08-03 23:24:57 -04:00
("class" "card flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("onsubmit" "create_channel_from_form(event)")
(div
2025-08-03 23:24:57 -04:00
("class" "flex flex_col gap_1")
2025-06-01 12:25:33 -04:00
(label
("for" "title")
(text "{{ text \"communities:label.name\" }}"))
(input
("type" "text")
("name" "title")
("id" "title")
("placeholder" "name")
("required" "")
("minlength" "2")
("maxlength" "32")))
(button
(text "{{ text \"communities:action.create\" }}"))))
(text "{% for channel in channels %}")
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
(div
("class" "card small")
(b
(text "{{ channel.position }} "))
(text "{{ channel.title }}"))
(div
2025-08-03 23:24:57 -04:00
("class" "card flex gap_2")
2025-06-01 12:25:33 -04:00
(button
("class" "red lowered small")
2025-06-01 12:25:33 -04:00
("onclick" "delete_channel('{{ channel.id }}')")
(text "{{ text \"general:action.delete\" }}"))
(button
("class" "lowered small")
2025-06-01 12:25:33 -04:00
("onclick" "update_channel_position('{{ channel.id }}')")
(text "{{ text \"chats:action.move\" }}"))
(button
("class" "lowered small")
2025-06-01 12:25:33 -04:00
("onclick" "update_channel_title('{{ channel.id }}')")
(text "{{ text \"chats:action.rename\" }}"))))
(text "{% endfor %}"))
(script
(text "globalThis.delete_channel = async (id) => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
]))
) {
return;
}
fetch(`/api/v1/channels/${id}`, {
method: \"DELETE\",
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.update_channel_position = async (id) => {
await trigger(\"atto::debounce\", [\"channels::move\"]);
const position = Number.parseInt(
await trigger(\"atto::prompt\", [
\"New channel position (number):\",
]),
);
if (!position && position !== 0) {
return alert(\"Must be a number!\");
}
fetch(`/api/v1/channels/${id}/move`, {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
position,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.update_channel_title = async (id) => {
await trigger(\"atto::debounce\", [\"channels::update_title\"]);
const title = await trigger(\"atto::prompt\", [\"New channel title:\"]);
if (!title) {
return;
}
fetch(`/api/v1/channels/${id}/title`, {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
title,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
async function create_channel_from_form(e) {
e.preventDefault();
await trigger(\"atto::debounce\", [\"channels::create\"]);
fetch(\"/api/v1/channels\", {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
title: e.target.title.value,
community: \"{{ community.id }}\",
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
if (res.ok) {
window.location.reload();
}
});
}"))
(text "{%- endif %} {% if can_manage_emojis -%}")
(div
2025-08-03 23:24:57 -04:00
("class" "card lowered w_full hidden flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("data-tab" "emojis")
(text "{{ components::supporter_ad(body=\"Become a supporter to upload GIF animated emojis!\") }}")
(div
2025-08-03 23:24:57 -04:00
("class" "card_nest")
2025-06-01 12:25:33 -04:00
("ui_ident" "change_banner")
(div
2025-08-03 23:24:57 -04:00
("class" "card small flex items_center gap_2")
2025-06-01 12:25:33 -04:00
(text "{{ icon \"upload\" }}")
(b
(text "{{ text \"communities:label.upload\" }}")))
(form
2025-08-03 23:24:57 -04:00
("class" "card flex flex_col gap_2")
2025-06-01 12:25:33 -04:00
("onsubmit" "upload_emoji(event)")
(div
2025-08-03 23:24:57 -04:00
("class" "flex flex_col gap_1")
2025-06-01 12:25:33 -04:00
(label
("for" "name")
(text "{{ text \"communities:label.name\" }}"))
(input
("type" "text")
("name" "name")
("id" "name")
("placeholder" "name")
("required" "")
("minlength" "2")
("maxlength" "32")))
(div
2025-08-03 23:24:57 -04:00
("class" "flex flex_col gap_1")
2025-06-01 12:25:33 -04:00
(label
("for" "file")
(text "{{ text \"communities:label.file\" }}"))
(input
("id" "banner_file")
("name" "file")
("type" "file")
("accept" "image/png,image/jpeg,image/avif,image/webp")
2025-08-03 23:24:57 -04:00
("class" "w_full")))
2025-06-01 12:25:33 -04:00
(button
(text "{{ text \"communities:action.create\" }}"))
(span
("class" "fade")
(text "Emojis can be a maximum of 256 KiB, or 512x512px (width x
height)."))))
(text "{% for emoji in emojis %}")
(div
2025-08-03 23:24:57 -04:00
("class" "card secondary flex flex_wrap gap_2 items_center justify_between")
2025-06-01 12:25:33 -04:00
(div
2025-08-03 23:24:57 -04:00
("class" "flex gap_2 items_center")
2025-06-01 12:25:33 -04:00
(img
("src" "/api/v1/communities/{{ community.id }}/emojis/{{ emoji.name }}")
("alt" "{{ emoji.name }}")
("class" "emoji")
("loading" "lazy"))
(b
(text "{{ emoji.name }}")))
(div
2025-08-03 23:24:57 -04:00
("class" "flex gap_2")
2025-06-01 12:25:33 -04:00
(button
("class" "lowered small")
2025-06-01 12:25:33 -04:00
("onclick" "rename_emoji('{{ emoji.id }}')")
(text "{{ icon \"pencil\" }}")
(span
(text "{{ text \"chats:action.rename\" }}")))
(button
("class" "lowered small red")
2025-06-01 12:25:33 -04:00
("onclick" "remove_emoji('{{ emoji.id }}')")
(text "{{ icon \"x\" }}")
(span
(text "{{ text \"stacks:label.remove\" }}")))))
(text "{% endfor %}"))
(script
(text "globalThis.upload_emoji = (e) => {
e.preventDefault();
e.target.querySelector(\"button\").style.display = \"none\";
fetch(
`/api/v1/communities/{{ community.id }}/emojis/${e.target.name.value}`,
{
method: \"POST\",
body: e.target.file.files[0],
},
)
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
e.target.querySelector(\"button\").removeAttribute(\"style\");
});
alert(\"Emoji upload in progress. Please wait!\");
};
globalThis.rename_emoji = async (id) => {
const name = await trigger(\"atto::prompt\", [\"New emoji name:\"]);
if (!name) {
return;
}
fetch(`/api/v1/emojis_id/${id}/name`, {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
name,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.remove_emoji = async (id) => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this? This action is permanent.\",
]))
) {
return;
}
fetch(`/api/v1/emojis_id/${id}`, {
method: \"DELETE\",
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};"))
2025-08-04 12:12:04 -04:00
(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" "{{ id }}-description")
2025-08-04 12:12:04 -04:00
(str (text "communities:label.description")))
(input
("type" "text")
("name" "description")
("id" "{{ id }}-description")
2025-08-04 12:12:04 -04:00
("placeholder" "description")
("value" "{{ topic.description }}")
("required" "")
("minlength" "2")
("maxlength" "256")))
(div
("class" "flex flex_col gap_1")
(label
("for" "{{ id }}-color")
2025-08-04 12:12:04 -04:00
(str (text "communities:label.color")))
(input
("type" "color")
("name" "color")
("id" "{{ id }}-color")
2025-08-04 12:12:04 -04:00
("placeholder" "color")
("required" "")
("value" "{{ topic.color }}")
("style" "width: 8rem")))
(div
("class" "flex flex_col gap_1")
(label
("for" "{{ id }}-position")
2025-08-04 12:12:04 -04:00
(str (text "communities:label.position")))
(input
("type" "number")
("name" "position")
("id" "{{ id }}-position")
2025-08-04 12:12:04 -04:00
("placeholder" "position")
("required" "")
("value" "{{ topic.position }}")
("min" "0")
("max" "256")))
(div
("class" "flex flex_col gap_1")
(label
("for" "{{ id }}-write_access")
(text "Post permission"))
(select
("name" "write_access")
("id" "{{ id }}-write_access")
(option
("value" "Everybody")
("selected" "{% if topic.write_access == 'Everybody' -%}true{% else %}false{%- endif %}")
(text "Everybody"))
(option
("value" "Joined")
("selected" "{% if topic.write_access == 'Joined' -%}true{% else %}false{%- endif %}")
(text "Joined"))
(option
("value" "Owner")
("selected" "{% if topic.write_access == 'Owner' -%}true{% else %}false{%- endif %}")
(text "Owner only"))))
2025-08-04 12:12:04 -04:00
(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),
write_access: e.target.write_access.selectedOptions[0].value,
2025-08-04 12:12:04 -04:00
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
}"))
2025-06-02 22:50:12 -04:00
(text "{%- endif %}"))
2025-06-01 12:25:33 -04:00
(script
(text "setTimeout(async () => {
2025-06-01 12:25:33 -04:00
const element = document.getElementById(\"membership_info\");
const ui = await ns(\"ui\");
2025-06-01 12:25:33 -04:00
const uid = new URLSearchParams(window.location.search).get(\"uid\");
if (uid) {
document.getElementById(\"uid\").value = uid;
}
globalThis.update_user_role = async (uid, new_role) => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
]))
) {
return;
}
fetch(
`/api/v1/communities/{{ community.id }}/memberships/${uid}/role`,
{
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
role: Number.parseInt(new_role),
}),
},
)
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.kick_user = async (uid, new_role) => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
]))
) {
return;
}
fetch(`/api/v1/communities/{{ community.id }}/memberships/${uid}`, {
method: \"DELETE\",
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.transfer_ownership = async (uid) => {
if (
!(await trigger(\"atto::confirm\", [
2025-06-11 13:14:49 -04:00
\"Are you sure you would like to do this?\\n\\nThis action is PERMANENT!\",
2025-06-01 12:25:33 -04:00
]))
) {
return;
}
fetch(`/api/v1/communities/{{ community.id }}/transfer_ownership`, {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
user: uid,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.select_user_from_form = (e) => {
e.preventDefault();
fetch(
`/api/v1/communities/{{ community.id }}/memberships/${e.target.uid.value}`,
)
.then((res) => res.json())
.then(async (res) => {
2025-06-01 12:25:33 -04:00
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
if (!res.ok) {
return;
}
// permissions manager
const get_permissions_html = await trigger(
2025-06-01 12:25:33 -04:00
\"ui::generate_permissions_ui\",
[
{
// https://trisuaso.github.io/tetratto/tetratto/model/communities_permissions/struct.CommunityPermission.html
DEFAULT: 1 << 0,
ADMINISTRATOR: 1 << 1,
MEMBER: 1 << 2,
MANAGE_POSTS: 1 << 3,
MANAGE_ROLES: 1 << 4,
BANNED: 1 << 5,
REQUESTED: 1 << 6,
MANAGE_PINS: 1 << 7,
MANAGE_COMMUNITY: 1 << 8,
MANAGE_QUESTIONS: 1 << 9,
MANAGE_CHANNELS: 1 << 10,
MANAGE_MESSAGES: 1 << 11,
MANAGE_EMOJIS: 1 << 12,
},
],
);
// ...
2025-08-03 23:24:57 -04:00
element.innerHTML = `<div class=\"flex gap_2 flex_wrap\" ui_ident=\"actions\">
2025-06-01 12:25:33 -04:00
<a target=\"_blank\" class=\"button\" href=\"/api/v1/auth/user/find/${e.target.uid.value}\">Open user profile</a>
${res.payload.role !== 33 ? `<button class=\"red lowered\" onclick=\"update_user_role('${e.target.uid.value}', 33)\">Ban</button>` : `<button class=\"lowered\" onclick=\"update_user_role('${e.target.uid.value}', 5)\">Unban</button>`}
${res.payload.role !== 65 ? `<button class=\"red lowered\" onclick=\"update_user_role('${e.target.uid.value}', 65)\">Send to review</button>` : `<button class=\"green lowered\" onclick=\"update_user_role('${e.target.uid.value}', 5)\">Accept join request</button>`}
<button class=\"red lowered\" onclick=\"kick_user('${e.target.uid.value}')\">Kick</button>
<button class=\"red lowered\" onclick=\"transfer_ownership('${e.target.uid.value}')\">Transfer ownership</button>
2025-06-01 12:25:33 -04:00
</div>`;
document.getElementById(\"permission_builder\").innerHTML =
get_permissions_html(res.payload.role, \"permission_builder\");
2025-06-01 12:25:33 -04:00
ui.refresh_container(element, [\"actions\", \"permissions\"]);
ui.generate_settings_ui(
element,
[
[
[\"role\", \"Permission level\"],
res.payload.role,
\"input\",
],
],
null,
{
role: (new_role) => {
const [matching, _] =
all_matching_permissions(new_role);
document.getElementById(
\"permissions\",
).innerHTML = get_permissions_html(
rebuild_role(matching),
\"permissions\",
);
return update_user_role(
e.target.uid.value,
new_role,
);
},
},
);
});
};
}, 250);"))
(script
("type" "application/json")
("id" "settings_json")
(text "{{ community.context|json_encode()|safe }}"))
(script
(text "setTimeout(async () => {
const ui = await ns(\"ui\");
2025-06-01 12:25:33 -04:00
const settings = JSON.parse(
document.getElementById(\"settings_json\").innerHTML,
);
globalThis.upload_avatar = (e) => {
e.preventDefault();
e.target.querySelector(\"button\").style.display = \"none\";
fetch(\"/api/v1/communities/{{ community.id }}/upload/avatar\", {
method: \"POST\",
body: e.target.file.files[0],
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
e.target.querySelector(\"button\").removeAttribute(\"style\");
});
alert(\"Avatar upload in progress. Please wait!\");
};
globalThis.upload_banner = (e) => {
e.preventDefault();
e.target.querySelector(\"button\").style.display = \"none\";
fetch(\"/api/v1/communities/{{ community.id }}/upload/banner\", {
method: \"POST\",
body: e.target.file.files[0],
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
e.target.querySelector(\"button\").removeAttribute(\"style\");
});
alert(\"Banner upload in progress. Please wait!\");
};
globalThis.save_context = () => {
fetch(\"/api/v1/communities/{{ community.id }}/context\", {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
context: settings,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.save_access = (event, mode) => {
const selected = event.target.selectedOptions[0];
fetch(`/api/v1/communities/{{ community.id }}/access/${mode}`, {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
access: selected.value,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.change_title = async (e) => {
e.preventDefault();
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this?\",
]))
) {
return;
}
fetch(\"/api/v1/communities/{{ community.id }}/title\", {
method: \"POST\",
headers: {
\"Content-Type\": \"application/json\",
},
body: JSON.stringify({
title: e.target.new_title.value,
}),
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
globalThis.delete_community = async () => {
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this? This action is permanent.\",
]))
) {
return;
}
fetch(`/api/v1/communities/{{ community.id }}`, {
method: \"DELETE\",
})
.then((res) => res.json())
.then((res) => {
trigger(\"atto::toast\", [
res.ok ? \"success\" : \"error\",
res.message,
]);
});
};
ui.refresh_container(document.getElementById(\"manage_fields\"), [
\"read_access\",
\"join_access\",
\"write_access\",
\"change_title\",
\"change_avatar\",
\"change_banner\",
]);
const settings_fields = [
[
[\"display_name\", \"Display title\"],
\"{{ community.context.display_name }}\",
\"input\",
],
[
[\"description\", \"Description\"],
settings.description,
\"textarea\",
],
[
[\"is_nsfw\", \"Mark as NSFW\"],
\"{{ community.context.is_nsfw }}\",
\"checkbox\",
]
];
2025-06-09 18:13:53 -04:00
// {% if not community.is_forge %}
settings_fields.push([
2025-06-01 12:25:33 -04:00
[
\"enable_questions\",
\"Allow users to ask questions in this community\",
2025-06-01 12:25:33 -04:00
],
\"{{ community.context.enable_questions }}\",
\"checkbox\",
]);
settings_fields.push([
[
\"enable_titles\",
\"Allow users to attach a title to their posts\",
],
\"{{ community.context.enable_titles }}\",
\"checkbox\",
]);
settings_fields.push([
[
\"require_titles\",
\"Require users to attach a title to their posts\",
],
\"{{ community.context.require_titles }}\",
\"checkbox\",
]);
2025-06-09 18:13:53 -04:00
// {% endif %}
ui.generate_settings_ui(
document.getElementById(\"manage_fields\"),
settings_fields,
2025-06-01 12:25:33 -04:00
settings,
);
}, 250);"))
(text "{% endblock %}")