tetratto/crates/app/src/public/html/communities/settings.html
2025-04-10 18:16:52 -04:00

572 lines
20 KiB
HTML

{% 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 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">{{ icon "check" }}</button>
</div>
<span class="fade"
>Use an image of 1100x350px for the best results.</span
>
</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>
<!-- prettier-ignore -->
<script type="application/json" id="settings_json">{{ community_context_serde|safe }}</script>
<script>
setTimeout(() => {
const ui = 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",
]);
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 %}