(text "{% extends \"root.html\" %} {% block head %}") (title (text "Community settings - {{ config.name }}")) (text "{% endblock %} {% block body %} {{ macros::nav() }}") (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 %}")) (div ("class" "w-full flex flex-col gap-2") ("data-tab" "general") (div ("id" "manage_fields") ("class" "card lowered flex flex-col gap-2") (div ("class" "card-nest") ("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 ("class" "card-nest") ("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 ("class" "card-nest") ("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 ("class" "card-nest") ("ui_ident" "change_title") (div ("class" "card small") (b (text "{{ text \"communities:label.change_title\" }}"))) (form ("class" "card flex flex-col gap-2") ("onsubmit" "change_title(event)") (div ("class" "flex flex-col gap-1") (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 ("class" "primary") (text "{{ icon \"check\" }}") (span (text "{{ text \"general:action.save\" }}")))))) (div ("class" "card-nest") ("ui_ident" "danger_zone") (div ("class" "card small flex gap-1 items-center red") (text "{{ icon \"skull\" }}") (b (text "{{ text \"communities:label.danger_zone\" }}"))) (div ("class" "card flex flex-wrap gap-2") (button ("class" "red lowered") ("onclick" "delete_community()") (text "{{ icon \"trash\" }}") (span (text "{{ text \"communities:label.delete_community\" }}"))))) (div ("class" "flex gap-2 flex-wrap") (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 ("class" "card lowered w-full hidden flex flex-col gap-2") ("data-tab" "images") (div ("class" "card-nest") ("ui_ident" "change_avatar") (div ("class" "card small") (b (text "{{ text \"settings:label.change_avatar\" }}"))) (form ("class" "card flex gap-2 flex-row flex-wrap items-center") ("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") ("class" "w-content")) (button ("class" "primary") (text "{{ icon \"check\" }}")))) (div ("class" "card-nest") ("ui_ident" "change_banner") (div ("class" "card small") (b (text "{{ text \"settings:label.change_banner\" }}"))) (form ("class" "card flex flex-col gap-2") ("method" "post") ("enctype" "multipart/form-data") ("onsubmit" "upload_banner(event)") (div ("class" "flex gap-2 flex-row flex-wrap items-center") (input ("id" "banner_file") ("name" "file") ("type" "file") ("accept" "image/png,image/jpeg,image/avif,image/webp") ("class" "w-content")) (button ("class" "primary") (text "{{ icon \"check\" }}"))) (span ("class" "fade") (text "Use an image of 1100x350px for the best results."))))) (div ("class" "card lowered w-full hidden flex flex-col gap-2") ("data-tab" "members") (div ("class" "card-nest") (div ("class" "card small") (b (text "{{ text \"communities:label.select_member\" }}"))) (form ("class" "card flex-col gap-2") ("onsubmit" "select_user_from_form(event)") (div ("class" "flex flex-col gap-1") (div ("class" "flex flex-col gap-1") (label ("for" "uid") (text "{{ text \"communities:label.user_id\" }}")) (input ("type" "number") ("name" "uid") ("id" "uid") ("placeholder" "user id") ("required" "") ("minlength" "18"))) (button ("class" "primary") (text "{{ text \"communities:action.select\" }}"))))) (div ("class" "card flex flex-col gap-2 w-full") ("id" "membership_info")) (div ("class" "card-nest w-full") (div ("class" "card small flex items-center justify-between gap-2") (div ("class" "flex items-center gap-2") (text "{{ icon \"blocks\" }}") (span (text "{{ text \"mod_panel:label.permissions_level_builder\" }}"))) (button ("class" "small lowered") ("onclick" "update_user_role(document.getElementById('uid').value, document.getElementById('role').value)") (text "{{ icon \"check\" }}") (span (text "{{ text \"general:action.save\" }}")))) (div ("class" "card flex flex-col gap-2") ("id" "permission_builder")))) (text "{% if can_manage_channels -%}") (div ("class" "card lowered w-full hidden flex flex-col gap-2") ("data-tab" "channels") (div ("class" "card-nest") (div ("class" "card small") (b (text "{{ text \"communities:action.create_channel\" }}"))) (form ("class" "card flex flex-col gap-2") ("onsubmit" "create_channel_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"))) (button ("class" "primary") (text "{{ text \"communities:action.create\" }}")))) (text "{% for channel in channels %}") (div ("class" "card-nest") (div ("class" "card small") (b (text "{{ channel.position }} ")) (text "{{ channel.title }}")) (div ("class" "card flex gap-2") (button ("class" "red lowered small") ("onclick" "delete_channel('{{ channel.id }}')") (text "{{ text \"general:action.delete\" }}")) (button ("class" "lowered small") ("onclick" "update_channel_position('{{ channel.id }}')") (text "{{ text \"chats:action.move\" }}")) (button ("class" "lowered small") ("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 ("class" "card lowered w-full hidden flex flex-col gap-2") ("data-tab" "emojis") (text "{{ components::supporter_ad(body=\"Become a supporter to upload GIF animated emojis!\") }}") (div ("class" "card-nest") ("ui_ident" "change_banner") (div ("class" "card small flex items-center gap-2") (text "{{ icon \"upload\" }}") (b (text "{{ text \"communities:label.upload\" }}"))) (form ("class" "card flex flex-col gap-2") ("onsubmit" "upload_emoji(event)") (div ("class" "flex flex-col gap-1") (label ("for" "name") (text "{{ text \"communities:label.name\" }}")) (input ("type" "text") ("name" "name") ("id" "name") ("placeholder" "name") ("required" "") ("minlength" "2") ("maxlength" "32"))) (div ("class" "flex flex-col gap-1") (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") ("class" "w-full"))) (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 ("class" "card secondary flex flex-wrap gap-2 items-center justify-between") (div ("class" "flex gap-2 items-center") (img ("src" "/api/v1/communities/{{ community.id }}/emojis/{{ emoji.name }}") ("alt" "{{ emoji.name }}") ("class" "emoji") ("loading" "lazy")) (b (text "{{ emoji.name }}"))) (div ("class" "flex gap-2") (button ("class" "lowered small") ("onclick" "rename_emoji('{{ emoji.id }}')") (text "{{ icon \"pencil\" }}") (span (text "{{ text \"chats:action.rename\" }}"))) (button ("class" "lowered small red") ("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, ]); }); };")) (text "{%- endif %}")) (script (text "setTimeout(async () => { const element = document.getElementById(\"membership_info\"); const ui = await ns(\"ui\"); 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\", [ \"Are you sure you would like to do this?\\n\\nThis action is PERMANENT!\", ])) ) { 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) => { trigger(\"atto::toast\", [ res.ok ? \"success\" : \"error\", res.message, ]); if (!res.ok) { return; } // permissions manager const get_permissions_html = await trigger( \"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, }, ], ); // ... element.innerHTML = `
Open user profile ${res.payload.role !== 33 ? `` : ``} ${res.payload.role !== 65 ? `` : ``}
`; document.getElementById(\"permission_builder\").innerHTML = get_permissions_html(res.payload.role, \"permission_builder\"); 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\"); 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\", ] ]; // {% if not community.is_forge %} settings_fields.push([ [ \"enable_questions\", \"Allow users to ask questions in this community\", ], \"{{ 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\", ]); // {% endif %} ui.generate_settings_ui( document.getElementById(\"manage_fields\"), settings_fields, settings, ); }, 250);")) (text "{% endblock %}")