{% extends "root.html" %} {% block head %} <title>Community settings - {{ config.name }}</title> {% 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 "settings:tab.general" }}</a > <a href="#/profile" data-tab-button="profile" >{{ text "settings:tab.profile" }}</a > <a href="#/members" data-tab-button="members" >{{ text "communities:tab.members" }}</a > </div> <div class="w-full flex flex-col gap-2" data-tab="general"> <div id="manage_fields" class="card tertiary flex flex-col gap-2"> <div class="card-nest" ui_ident="read_access"> <div class="card small"> <b>Read access</b> </div> <div class="card"> <select onchange="save_access(event, 'read')"> <option value="Everybody" selected="{% if community.read_access == 'Everybody' %}true{% else %}false{% endif %}" > Everybody </option> <option value="Joined" selected="{% if community.read_access == 'Joined' %}true{% else %}false{% endif %}" > Joined </option> </select> </div> </div> <div class="card-nest" ui_ident="join_access"> <div class="card small"> <b>Join access</b> </div> <div class="card"> <select onchange="save_access(event, 'join')"> <option value="Everybody" selected="{% if community.join_access == 'Everybody' %}true{% else %}false{% endif %}" > Everybody </option> <option value="Request" selected="{% if community.join_access == 'Request' %}true{% else %}false{% endif %}" > Request </option> <option value="Nobody" selected="{% if community.join_access == 'Nobody' %}true{% else %}false{% endif %}" > Nobody </option> </select> </div> </div> <div class="card-nest" ui_ident="write_access"> <div class="card small"> <b>Post permission</b> </div> <div class="card"> <select onchange="save_access(event, 'write')"> <option value="Everybody" selected="{% if community.write_access == 'Everybody' %}true{% else %}false{% endif %}" > Everybody </option> <option value="Joined" selected="{% if community.write_access == 'Joined' %}true{% else %}false{% endif %}" > Joined </option> <option value="Owner" selected="{% if community.write_access == 'Owner' %}true{% else %}false{% endif %}" > Owner only </option> </select> </div> </div> <div class="card-nest" ui_ident="change_title"> <div class="card small"> <b>{{ text "communities:label.change_title" }}</b> </div> <form class="card flex flex-col gap-2" onsubmit="change_title(event)" > <div class="flex flex-col gap-1"> <label for="new_title" >{{ text "communities:label.new_title" }}</label > <input type="text" name="new_title" id="new_title" placeholder="new_title" required minlength="2" /> </div> <button class="primary"> {{ icon "check" }} <span>{{ text "general:action.save" }}</span> </button> </form> </div> </div> <div class="card-nest" ui_ident="danger_zone"> <div class="card small flex gap-1 items-center red"> {{ icon "skull" }} <b> {{ text "communities:label.danger_zone" }} </b> </div> <div class="card flex flex-wrap gap-2"> <button class="red quaternary" onclick="delete_community()"> {{ icon "trash" }} <span>{{ text "communities:label.delete_community" }}</span> </button> </div> </div> <div class="flex gap-2 flex-wrap"> <button onclick="save_context()"> {{ icon "check" }} <span>{{ text "general:action.save" }}</span> </button> <a href="/community/{{ community.title }}" class="button secondary"> {{ icon "arrow-left" }} <span>{{ text "general:action.back" }}</span> </a> </div> </div> <div class="card tertiary w-full hidden flex flex-col gap-2" data-tab="profile" > <div class="card-nest" ui_ident="change_avatar"> <div class="card small"> <b>{{ text "settings:label.change_avatar" }}</b> </div> <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" class="w-content" /> <button class="primary">{{ icon "check" }}</button> </form> </div> <div class="card-nest" ui_ident="change_banner"> <div class="card small"> <b>{{ text "settings:label.change_banner" }}</b> </div> <form class="card flex gap-2 flex-row flex-wrap items-center" method="post" enctype="multipart/form-data" onsubmit="upload_banner(event)" > <input id="banner_file" name="file" type="file" accept="image/png,image/jpeg,image/avif,image/webp" class="w-content" /> <button class="primary">{{ icon "check" }}</button> </form> </div> </div> <div class="card tertiary w-full hidden flex flex-col gap-2" data-tab="members" > <div class="card-nest"> <div class="card small"> <b>{{ text "communities:label.select_member" }}</b> </div> <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 "communities:label.user_id" }}</label > <input type="number" name="uid" id="uid" placeholder="user id" required minlength="18" /> </div> <button class="primary"> {{ text "communities:action.select" }} </button> </div> </form> </div> <div class="card flex flex-col gap-2 w-full" id="membership_info"></div> </div> </main> <script> setTimeout(() => { const element = document.getElementById("membership_info"); const ui = 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.select_user_from_form = (e) => { e.preventDefault(); fetch( `/api/v1/communities/{{ community.id }}/memberships/${e.target.uid.value}`, ) .then((res) => res.json()) .then((res) => { trigger("atto::toast", [ res.ok ? "success" : "error", res.message, ]); if (!res.ok) { return; } // permissions manager const get_permissions_html = 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, }, ], ); // ... element.innerHTML = `<div class="flex gap-2 flex-wrap" ui_ident="actions"> <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 quaternary" onclick="update_user_role('${e.target.uid.value}', 33)">Ban</button>` : `<button class="quaternary" onclick="update_user_role('${e.target.uid.value}', 5)">Unban</button>`} ${res.payload.role !== 65 ? `<button class="red quaternary" onclick="update_user_role('${e.target.uid.value}', 65)">Send to review</button>` : `<button class="green quaternary" onclick="update_user_role('${e.target.uid.value}', 5)">Accept join request</button>`} <button class="red quaternary" onclick="kick_user('${e.target.uid.value}')">Kick</button> </div> <div class="flex flex-col gap-2" ui_ident="permissions" id="permissions"> ${get_permissions_html(res.payload.role, "permissions")} </div>`; 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> <script> setTimeout(() => { const ui = ns("ui"); const settings = JSON.parse("{{ community_context_serde|safe }}"); 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", ]); ui.generate_settings_ui( document.getElementById("manage_fields"), [ [ ["display_name", "Display title"], "{{ community.context.display_name }}", "input", ], [ ["description", "Description"], "{{ community.context.description }}", "textarea", ], [ ["is_nsfw", "Mark as NSFW"], "{{ community.context.is_nsfw }}", "checkbox", ], ], settings, ); }, 250); </script> {% endblock %}