add: finish ui rewrite
This commit is contained in:
parent
e9846016e6
commit
5dec98d698
119 changed files with 8776 additions and 9350 deletions
|
@ -1,38 +0,0 @@
|
|||
{% extends "root.html" %} {% block head %}
|
||||
<title>Audit log - {{ config.name }}</title>
|
||||
{% endblock %} {% block body %} {{ macros::nav() }}
|
||||
<main class="flex flex-col gap-2">
|
||||
<div class="card-nest w-full">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "scroll" }}
|
||||
<span>{{ text "general:link.audit_log" }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
<!-- prettier-ignore -->
|
||||
{% for item in items %}
|
||||
<div class="card-nest">
|
||||
<a
|
||||
class="card small flex items-center gap-2 flush"
|
||||
href="/api/v1/auth/user/find/{{ item.moderator }}"
|
||||
>
|
||||
<!-- prettier-ignore -->
|
||||
{{ components::avatar(username=item.moderator, selector_type="id") }}
|
||||
<span>{{ item.moderator }}</span>
|
||||
<span class="fade date">{{ item.created }}</span>
|
||||
</a>
|
||||
|
||||
<div class="card secondary">
|
||||
<span class="no_p_margin"
|
||||
>{{ item.content|markdown|safe }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{{ components::pagination(page=page, items=items|length) }}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
37
crates/app/src/public/html/mod/audit_log.lisp
Normal file
37
crates/app/src/public/html/mod/audit_log.lisp
Normal file
|
@ -0,0 +1,37 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Audit log - {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex-col gap-2")
|
||||
(div
|
||||
("class" "card-nest w-full")
|
||||
(div
|
||||
("class" "card small flex items-center gap-2")
|
||||
(text "{{ icon \"scroll\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:link.audit_log\" }}")))
|
||||
(div
|
||||
("class" "card flex flex-col gap-2")
|
||||
(text "{% for item in items %}")
|
||||
(div
|
||||
("class" "card-nest")
|
||||
(a
|
||||
("class" "card small flex items-center gap-2 flush")
|
||||
("href" "/api/v1/auth/user/find/{{ item.moderator }}")
|
||||
(text "{{ components::avatar(username=item.moderator, selector_type=\"id\") }}")
|
||||
(span
|
||||
(text "{{ item.moderator }}"))
|
||||
(span
|
||||
("class" "fade date")
|
||||
(text "{{ item.created }}")))
|
||||
(div
|
||||
("class" "card secondary")
|
||||
(span
|
||||
("class" "no_p_margin")
|
||||
(text "{{ item.content|markdown|safe }}"))))
|
||||
(text "{% endfor %}")
|
||||
(text "{{ components::pagination(page=page, items=items|length) }}"))))
|
||||
|
||||
(text "{% endblock %}")
|
|
@ -1,65 +0,0 @@
|
|||
{% extends "root.html" %} {% block head %}
|
||||
<title>File report - {{ config.name }}</title>
|
||||
{% endblock %} {% block body %} {{ macros::nav() }}
|
||||
<main class="flex flex-col gap-2">
|
||||
<div class="card-nest w-full">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "flag" }}
|
||||
<span>{{ text "general:label.file_report" }}</span>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="card flex flex-col gap-2"
|
||||
onsubmit="create_report_from_form(event)"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="title"
|
||||
>{{ text "communities:label.content" }}</label
|
||||
>
|
||||
<textarea
|
||||
type="text"
|
||||
name="content"
|
||||
id="content"
|
||||
placeholder="content"
|
||||
required
|
||||
minlength="16"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<button class="primary">
|
||||
{{ text "communities:action.create" }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function create_report_from_form(e) {
|
||||
e.preventDefault();
|
||||
fetch("/api/v1/reports", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: e.target.content.value,
|
||||
asset: "{{ asset }}",
|
||||
asset_type: `{{ asset_type }}`,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
if (res.ok) {
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
63
crates/app/src/public/html/mod/file_report.lisp
Normal file
63
crates/app/src/public/html/mod/file_report.lisp
Normal file
|
@ -0,0 +1,63 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "File report - {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex-col gap-2")
|
||||
(div
|
||||
("class" "card-nest w-full")
|
||||
(div
|
||||
("class" "card small flex items-center gap-2")
|
||||
(text "{{ icon \"flag\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:label.file_report\" }}")))
|
||||
(form
|
||||
("class" "card flex flex-col gap-2")
|
||||
("onsubmit" "create_report_from_form(event)")
|
||||
(div
|
||||
("class" "flex flex-col gap-1")
|
||||
(label
|
||||
("for" "title")
|
||||
(text "{{ text \"communities:label.content\" }}"))
|
||||
(textarea
|
||||
("type" "text")
|
||||
("name" "content")
|
||||
("id" "content")
|
||||
("placeholder" "content")
|
||||
("required" "")
|
||||
("minlength" "16")))
|
||||
(button
|
||||
("class" "primary")
|
||||
(text "{{ text \"communities:action.create\" }}")))))
|
||||
|
||||
(script
|
||||
(text "function create_report_from_form(e) {
|
||||
e.preventDefault();
|
||||
fetch(\"/api/v1/reports\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: e.target.content.value,
|
||||
asset: \"{{ asset }}\",
|
||||
asset_type: `{{ asset_type }}`,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
if (res.ok) {
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
}"))
|
||||
|
||||
(text "{% endblock %}")
|
|
@ -1,87 +0,0 @@
|
|||
{% extends "root.html" %} {% block head %}
|
||||
<title>IP Bans - {{ config.name }}</title>
|
||||
{% endblock %} {% block body %} {{ macros::nav() }}
|
||||
<main class="flex flex-col gap-2">
|
||||
<div class="card-nest w-full">
|
||||
<div class="card small flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
{{ icon "ban" }}
|
||||
<span>{{ text "general:link.ip_bans" }}</span>
|
||||
</div>
|
||||
|
||||
<button onclick="prompt_ban_ip()" class="quaternary small">
|
||||
{{ icon "plus" }}
|
||||
<span>{{ text "communities:action.create" }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
<!-- prettier-ignore -->
|
||||
{% for item in items %}
|
||||
<div class="card-nest">
|
||||
<a
|
||||
class="card small flex items-center gap-2 flush"
|
||||
href="/api/v1/auth/user/find/{{ item.moderator }}"
|
||||
>
|
||||
<!-- prettier-ignore -->
|
||||
{{ components::avatar(username=item.moderator, selector_type="id") }}
|
||||
<span>{{ item.moderator }}</span>
|
||||
<span class="fade date">{{ item.created }}</span>
|
||||
</a>
|
||||
|
||||
<div class="card secondary flex flex-col gap-2">
|
||||
<code>{{ item.ip }}</code>
|
||||
<span>{{ item.reason|markdown|safe }}</span>
|
||||
|
||||
<div class="card w-full flex flex-wrap gap-2">
|
||||
<button
|
||||
onclick="remove_ipban('{{ item.ip }}')"
|
||||
class="red quaternary"
|
||||
>
|
||||
{{ icon "trash" }}
|
||||
<span>{{ text "general:action.delete" }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{{ components::pagination(page=page, items=items|length) }}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
async function prompt_ban_ip() {
|
||||
const ip = await trigger("atto::prompt", ["IP address (or prefix):"]);
|
||||
|
||||
if (!ip) {
|
||||
return;
|
||||
}
|
||||
|
||||
trigger("atto::ban_ip", [ip]);
|
||||
}
|
||||
|
||||
async function remove_ipban(ip) {
|
||||
if (
|
||||
!(await trigger("atto::confirm", [
|
||||
"Are you sure you would like to do this?",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/v1/bans/${ip}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
86
crates/app/src/public/html/mod/ip_bans.lisp
Normal file
86
crates/app/src/public/html/mod/ip_bans.lisp
Normal file
|
@ -0,0 +1,86 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "IP Bans - {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex-col gap-2")
|
||||
(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 \"ban\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:link.ip_bans\" }}")))
|
||||
(button
|
||||
("onclick" "prompt_ban_ip()")
|
||||
("class" "quaternary small")
|
||||
(text "{{ icon \"plus\" }}")
|
||||
(span
|
||||
(text "{{ text \"communities:action.create\" }}"))))
|
||||
(div
|
||||
("class" "card flex flex-col gap-2")
|
||||
(text "{% for item in items %}")
|
||||
(div
|
||||
("class" "card-nest")
|
||||
(a
|
||||
("class" "card small flex items-center gap-2 flush")
|
||||
("href" "/api/v1/auth/user/find/{{ item.moderator }}")
|
||||
(text "{{ components::avatar(username=item.moderator, selector_type=\"id\") }}")
|
||||
(span
|
||||
(text "{{ item.moderator }}"))
|
||||
(span
|
||||
("class" "fade date")
|
||||
(text "{{ item.created }}")))
|
||||
(div
|
||||
("class" "card secondary flex flex-col gap-2")
|
||||
(code
|
||||
(text "{{ item.ip }}"))
|
||||
(span
|
||||
(text "{{ item.reason|markdown|safe }}"))
|
||||
(div
|
||||
("class" "card w-full flex flex-wrap gap-2")
|
||||
(button
|
||||
("onclick" "remove_ipban('{{ item.ip }}')")
|
||||
("class" "red quaternary")
|
||||
(text "{{ icon \"trash\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:action.delete\" }}"))))))
|
||||
(text "{% endfor %}")
|
||||
(text "{{ components::pagination(page=page, items=items|length) }}"))))
|
||||
|
||||
(script
|
||||
(text "async function prompt_ban_ip() {
|
||||
const ip = await trigger(\"atto::prompt\", [\"IP address (or prefix):\"]);
|
||||
|
||||
if (!ip) {
|
||||
return;
|
||||
}
|
||||
|
||||
trigger(\"atto::ban_ip\", [ip]);
|
||||
}
|
||||
|
||||
async function remove_ipban(ip) {
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
\"Are you sure you would like to do this?\",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/v1/bans/${ip}`, {
|
||||
method: \"DELETE\",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}"))
|
||||
|
||||
(text "{% endblock %}")
|
|
@ -1,253 +0,0 @@
|
|||
{% extends "root.html" %} {% block head %}
|
||||
<title>Manage profile - {{ config.name }}</title>
|
||||
{% endblock %} {% block body %} {{ macros::nav() }}
|
||||
<main class="flex flex-col gap-2">
|
||||
<div class="card-nest w-full">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "shield" }}
|
||||
<span>{{ text "mod_panel:label.manage_profile" }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card tertiary">
|
||||
<div class="flex flex-col gap-2" id="mod_options">
|
||||
<div
|
||||
class="card w-full flex flex-wrap gap-2"
|
||||
ui_ident="actions"
|
||||
>
|
||||
<a
|
||||
href="/settings?username={{ profile.username }}"
|
||||
class="button quaternary"
|
||||
>
|
||||
{{ icon "settings" }}
|
||||
<span>View settings</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/mod_panel/profile/{{ profile.id }}/warnings"
|
||||
class="button quaternary"
|
||||
>
|
||||
{{ icon "shield-alert" }}
|
||||
<span>View warnings</span>
|
||||
</a>
|
||||
|
||||
<button
|
||||
class="red quaternary"
|
||||
onclick="delete_account(event)"
|
||||
>
|
||||
{{ icon "trash" }}
|
||||
<span>{{ text "settings:label.delete_account" }}</span>
|
||||
</button>
|
||||
|
||||
{% if profile.permissions != 131073 -%}
|
||||
<button
|
||||
class="red quaternary"
|
||||
onclick="update_user_role(131073)"
|
||||
>
|
||||
Ban
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="quaternary" onclick="update_user_role(1)">
|
||||
Unban
|
||||
</button>
|
||||
{%- endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
const ui = ns("ui");
|
||||
const element = document.getElementById("mod_options");
|
||||
|
||||
async function profile_request(do_confirm, path, body) {
|
||||
if (do_confirm) {
|
||||
if (
|
||||
!(await trigger("atto::confirm", [
|
||||
"Are you sure you would like to do this?",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fetch(`/api/v1/auth/user/{{ profile.id }}/${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
globalThis.delete_account = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (
|
||||
!(await trigger("atto::confirm", [
|
||||
"Are you sure you would like to do this?",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("/api/v1/auth/user/{{ profile.id }}", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
password: "",
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
globalThis.update_user_role = async (new_role) => {
|
||||
if (
|
||||
!(await trigger("atto::confirm", [
|
||||
"Are you sure you would like to do this?",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/v1/auth/user/{{ profile.id }}/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,
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
ui.refresh_container(element, ["actions"]);
|
||||
|
||||
setTimeout(() => {
|
||||
ui.refresh_container(element, ["actions"]);
|
||||
|
||||
ui.generate_settings_ui(
|
||||
element,
|
||||
[
|
||||
[
|
||||
["is_verified", "Is verified"],
|
||||
"{{ profile.is_verified }}",
|
||||
"checkbox",
|
||||
],
|
||||
[
|
||||
["role", "Permission level"],
|
||||
"{{ profile.permissions }}",
|
||||
"input",
|
||||
],
|
||||
],
|
||||
null,
|
||||
{
|
||||
is_verified: (value) => {
|
||||
profile_request(false, "verified", {
|
||||
is_verified: value,
|
||||
});
|
||||
},
|
||||
role: (new_role) => {
|
||||
return update_user_role(new_role);
|
||||
},
|
||||
},
|
||||
);
|
||||
}, 100);
|
||||
}, 150);
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-nest w-full">
|
||||
<div class="card small flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
{{ icon "blocks" }}
|
||||
<span
|
||||
>{{ text "mod_panel:label.permissions_level_builder"
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="small quaternary"
|
||||
onclick="update_user_role(Number.parseInt(document.getElementById('role').value))"
|
||||
>
|
||||
{{ icon "check" }}
|
||||
<span>{{ text "general:action.save" }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="card tertiary flex flex-col gap-2"
|
||||
id="permission_builder"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
setTimeout(() => {
|
||||
const get_permissions_html = trigger(
|
||||
"ui::generate_permissions_ui",
|
||||
[
|
||||
{
|
||||
// https://trisuaso.github.io/tetratto/tetratto/model/permissions/struct.FinePermission.html
|
||||
DEFAULT: 1 << 0,
|
||||
ADMINISTRATOR: 1 << 1,
|
||||
MANAGE_COMMUNITIES: 1 << 2,
|
||||
MANAGE_POSTS: 1 << 3,
|
||||
MANAGE_POST_REPLIES: 1 << 4,
|
||||
MANAGE_USERS: 1 << 5,
|
||||
MANAGE_BANS: 1 << 6,
|
||||
MANAGE_WARNINGS: 1 << 7,
|
||||
MANAGE_NOTIFICATIONS: 1 << 8,
|
||||
VIEW_REPORTS: 1 << 9,
|
||||
VIEW_AUDIT_LOG: 1 << 10,
|
||||
MANAGE_MEMBERSHIPS: 1 << 11,
|
||||
MANAGE_REACTIONS: 1 << 12,
|
||||
MANAGE_FOLLOWS: 1 << 13,
|
||||
MANAGE_VERIFIED: 1 << 14,
|
||||
MANAGE_AUDITLOG: 1 << 15,
|
||||
MANAGE_REPORTS: 1 << 16,
|
||||
BANNED: 1 << 17,
|
||||
INFINITE_COMMUNITIES: 1 << 18,
|
||||
SUPPORTER: 1 << 19,
|
||||
MANAGE_REQUESTS: 1 << 20,
|
||||
MANAGE_QUESTIONS: 1 << 21,
|
||||
MANAGE_CHANNELS: 1 << 22,
|
||||
MANAGE_MESSAGES: 1 << 23,
|
||||
MANAGE_UPLOADS: 1 << 24,
|
||||
MANAGE_EMOJIS: 1 << 25,
|
||||
MANAGE_STACKS: 1 << 26,
|
||||
STAFF_BADGE: 1 << 27,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
document.getElementById("permission_builder").innerHTML =
|
||||
get_permissions_html(
|
||||
Number.parseInt("{{ profile.permissions }}"),
|
||||
"permission_builder",
|
||||
);
|
||||
}, 250);
|
||||
</script>
|
||||
</main>
|
||||
{% endblock %}
|
237
crates/app/src/public/html/mod/profile.lisp
Normal file
237
crates/app/src/public/html/mod/profile.lisp
Normal file
|
@ -0,0 +1,237 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Manage profile - {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex-col gap-2")
|
||||
(div
|
||||
("class" "card-nest w-full")
|
||||
(div
|
||||
("class" "card small flex items-center gap-2")
|
||||
(text "{{ icon \"shield\" }}")
|
||||
(span
|
||||
(text "{{ text \"mod_panel:label.manage_profile\" }}")))
|
||||
(div
|
||||
("class" "card tertiary")
|
||||
(div
|
||||
("class" "flex flex-col gap-2")
|
||||
("id" "mod_options")
|
||||
(div
|
||||
("class" "card w-full flex flex-wrap gap-2")
|
||||
("ui_ident" "actions")
|
||||
(a
|
||||
("href" "/settings?username={{ profile.username }}")
|
||||
("class" "button quaternary")
|
||||
(text "{{ icon \"settings\" }}")
|
||||
(span
|
||||
(text "View settings")))
|
||||
(a
|
||||
("href" "/mod_panel/profile/{{ profile.id }}/warnings")
|
||||
("class" "button quaternary")
|
||||
(text "{{ icon \"shield-alert\" }}")
|
||||
(span
|
||||
(text "View warnings")))
|
||||
(button
|
||||
("class" "red quaternary")
|
||||
("onclick" "delete_account(event)")
|
||||
(text "{{ icon \"trash\" }}")
|
||||
(span
|
||||
(text "{{ text \"settings:label.delete_account\" }}")))
|
||||
(text "{% if profile.permissions != 131073 -%}")
|
||||
(button
|
||||
("class" "red quaternary")
|
||||
("onclick" "update_user_role(131073)")
|
||||
(text "Ban"))
|
||||
(text "{% else %}")
|
||||
(button
|
||||
("class" "quaternary")
|
||||
("onclick" "update_user_role(1)")
|
||||
(text "Unban"))
|
||||
(text "{%- endif %}")))
|
||||
(script
|
||||
(text "setTimeout(() => {
|
||||
const ui = ns(\"ui\");
|
||||
const element = document.getElementById(\"mod_options\");
|
||||
|
||||
async function profile_request(do_confirm, path, body) {
|
||||
if (do_confirm) {
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
\"Are you sure you would like to do this?\",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fetch(`/api/v1/auth/user/{{ profile.id }}/${path}`, {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
globalThis.delete_account = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
\"Are you sure you would like to do this?\",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(\"/api/v1/auth/user/{{ profile.id }}\", {
|
||||
method: \"DELETE\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
password: \"\",
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
globalThis.update_user_role = async (new_role) => {
|
||||
if (
|
||||
!(await trigger(\"atto::confirm\", [
|
||||
\"Are you sure you would like to do this?\",
|
||||
]))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/v1/auth/user/{{ profile.id }}/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,
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
ui.refresh_container(element, [\"actions\"]);
|
||||
|
||||
setTimeout(() => {
|
||||
ui.refresh_container(element, [\"actions\"]);
|
||||
|
||||
ui.generate_settings_ui(
|
||||
element,
|
||||
[
|
||||
[
|
||||
[\"is_verified\", \"Is verified\"],
|
||||
\"{{ profile.is_verified }}\",
|
||||
\"checkbox\",
|
||||
],
|
||||
[
|
||||
[\"role\", \"Permission level\"],
|
||||
\"{{ profile.permissions }}\",
|
||||
\"input\",
|
||||
],
|
||||
],
|
||||
null,
|
||||
{
|
||||
is_verified: (value) => {
|
||||
profile_request(false, \"verified\", {
|
||||
is_verified: value,
|
||||
});
|
||||
},
|
||||
role: (new_role) => {
|
||||
return update_user_role(new_role);
|
||||
},
|
||||
},
|
||||
);
|
||||
}, 100);
|
||||
}, 150);"))))
|
||||
(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 quaternary")
|
||||
("onclick" "update_user_role(Number.parseInt(document.getElementById('role').value))")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))
|
||||
(div
|
||||
("class" "card tertiary flex flex-col gap-2")
|
||||
("id" "permission_builder")))
|
||||
(script
|
||||
(text "setTimeout(() => {
|
||||
const get_permissions_html = trigger(
|
||||
\"ui::generate_permissions_ui\",
|
||||
[
|
||||
{
|
||||
// https://trisuaso.github.io/tetratto/tetratto/model/permissions/struct.FinePermission.html
|
||||
DEFAULT: 1 << 0,
|
||||
ADMINISTRATOR: 1 << 1,
|
||||
MANAGE_COMMUNITIES: 1 << 2,
|
||||
MANAGE_POSTS: 1 << 3,
|
||||
MANAGE_POST_REPLIES: 1 << 4,
|
||||
MANAGE_USERS: 1 << 5,
|
||||
MANAGE_BANS: 1 << 6,
|
||||
MANAGE_WARNINGS: 1 << 7,
|
||||
MANAGE_NOTIFICATIONS: 1 << 8,
|
||||
VIEW_REPORTS: 1 << 9,
|
||||
VIEW_AUDIT_LOG: 1 << 10,
|
||||
MANAGE_MEMBERSHIPS: 1 << 11,
|
||||
MANAGE_REACTIONS: 1 << 12,
|
||||
MANAGE_FOLLOWS: 1 << 13,
|
||||
MANAGE_VERIFIED: 1 << 14,
|
||||
MANAGE_AUDITLOG: 1 << 15,
|
||||
MANAGE_REPORTS: 1 << 16,
|
||||
BANNED: 1 << 17,
|
||||
INFINITE_COMMUNITIES: 1 << 18,
|
||||
SUPPORTER: 1 << 19,
|
||||
MANAGE_REQUESTS: 1 << 20,
|
||||
MANAGE_QUESTIONS: 1 << 21,
|
||||
MANAGE_CHANNELS: 1 << 22,
|
||||
MANAGE_MESSAGES: 1 << 23,
|
||||
MANAGE_UPLOADS: 1 << 24,
|
||||
MANAGE_EMOJIS: 1 << 25,
|
||||
MANAGE_STACKS: 1 << 26,
|
||||
STAFF_BADGE: 1 << 27,
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
document.getElementById(\"permission_builder\").innerHTML =
|
||||
get_permissions_html(
|
||||
Number.parseInt(\"{{ profile.permissions }}\"),
|
||||
\"permission_builder\",
|
||||
);
|
||||
}, 250);")))
|
||||
|
||||
(text "{% endblock %}")
|
|
@ -1,81 +0,0 @@
|
|||
{% extends "root.html" %} {% block head %}
|
||||
<title>Reports - {{ config.name }}</title>
|
||||
{% endblock %} {% block body %} {{ macros::nav() }}
|
||||
<main class="flex flex-col gap-2">
|
||||
<div class="card-nest w-full">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "flag" }}
|
||||
<span>{{ text "general:link.reports" }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
<!-- prettier-ignore -->
|
||||
{% for item in items %}
|
||||
<div class="card-nest">
|
||||
<a
|
||||
class="card small flex items-center gap-2 flush"
|
||||
href="/api/v1/auth/user/find/{{ item.owner }}"
|
||||
>
|
||||
<!-- prettier-ignore -->
|
||||
{{ components::avatar(username=item.owner, selector_type="id") }}
|
||||
<span>{{ item.owner }}</span>
|
||||
<span class="fade date">{{ item.created }}</span>
|
||||
</a>
|
||||
|
||||
<div class="card secondary flex flex-col gap-2">
|
||||
<span class="no_p_margin"
|
||||
>{{ item.content|markdown|safe }}</span
|
||||
>
|
||||
|
||||
<div class="card w-full flex flex-wrap gap-2">
|
||||
<button
|
||||
onclick="open_reported_content('{{ item.asset }}', '{{ item.asset_type }}')"
|
||||
>
|
||||
{{ icon "external-link" }}
|
||||
<span
|
||||
>{{ text "mod_panel:label.open_reported_content"
|
||||
}}</span
|
||||
>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onclick="remove_report('{{ item.id }}')"
|
||||
class="red quaternary"
|
||||
>
|
||||
{{ icon "trash" }}
|
||||
<span>{{ text "general:action.delete" }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{{ components::pagination(page=page, items=items|length) }}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function open_reported_content(asset, asset_type) {
|
||||
if (asset_type === "Post") {
|
||||
window.open(`/post/${asset}`);
|
||||
} else if (asset_type === "Community") {
|
||||
window.open(`/community/${asset}`);
|
||||
}
|
||||
}
|
||||
|
||||
function remove_report(id) {
|
||||
fetch(`/api/v1/reports/${id}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
72
crates/app/src/public/html/mod/reports.lisp
Normal file
72
crates/app/src/public/html/mod/reports.lisp
Normal file
|
@ -0,0 +1,72 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Reports - {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex-col gap-2")
|
||||
(div
|
||||
("class" "card-nest w-full")
|
||||
(div
|
||||
("class" "card small flex items-center gap-2")
|
||||
(text "{{ icon \"flag\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:link.reports\" }}")))
|
||||
(div
|
||||
("class" "card flex flex-col gap-2")
|
||||
(text "{% for item in items %}")
|
||||
(div
|
||||
("class" "card-nest")
|
||||
(a
|
||||
("class" "card small flex items-center gap-2 flush")
|
||||
("href" "/api/v1/auth/user/find/{{ item.owner }}")
|
||||
(text "{{ components::avatar(username=item.owner, selector_type=\"id\") }}")
|
||||
(span
|
||||
(text "{{ item.owner }}"))
|
||||
(span
|
||||
("class" "fade date")
|
||||
(text "{{ item.created }}")))
|
||||
(div
|
||||
("class" "card secondary flex flex-col gap-2")
|
||||
(span
|
||||
("class" "no_p_margin")
|
||||
(text "{{ item.content|markdown|safe }}"))
|
||||
(div
|
||||
("class" "card w-full flex flex-wrap gap-2")
|
||||
(button
|
||||
("onclick" "open_reported_content('{{ item.asset }}', '{{ item.asset_type }}')")
|
||||
(text "{{ icon \"external-link\" }}")
|
||||
(span
|
||||
(text "{{ text \"mod_panel:label.open_reported_content\" }}")))
|
||||
(button
|
||||
("onclick" "remove_report('{{ item.id }}')")
|
||||
("class" "red quaternary")
|
||||
(text "{{ icon \"trash\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:action.delete\" }}"))))))
|
||||
(text "{% endfor %}")
|
||||
(text "{{ components::pagination(page=page, items=items|length) }}"))))
|
||||
|
||||
(script
|
||||
(text "function open_reported_content(asset, asset_type) {
|
||||
if (asset_type === \"Post\") {
|
||||
window.open(`/post/${asset}`);
|
||||
} else if (asset_type === \"Community\") {
|
||||
window.open(`/community/${asset}`);
|
||||
}
|
||||
}
|
||||
|
||||
function remove_report(id) {
|
||||
fetch(`/api/v1/reports/${id}`, {
|
||||
method: \"DELETE\",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}"))
|
||||
|
||||
(text "{% endblock %}")
|
|
@ -1,31 +0,0 @@
|
|||
{% extends "root.html" %} {% block head %}
|
||||
<title>Server stats - {{ config.name }}</title>
|
||||
{% endblock %} {% block body %} {{ macros::nav() }}
|
||||
<main class="flex flex-col gap-2">
|
||||
<div class="card-nest w-full">
|
||||
<div class="card small flex items-center gap-2">
|
||||
{{ icon "chart-line" }}
|
||||
<span>{{ text "general:link.stats" }}</span>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-2">
|
||||
<ul>
|
||||
<li>
|
||||
<b>Active user streams:</b>
|
||||
<span>{{ active_users }}</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<b>Active chat subscriptions:</b>
|
||||
<span>{{ active_users_chats }}</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<b>Socket tasks:</b>
|
||||
<span>{{ (active_users_chats + active_users) * 3 }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
34
crates/app/src/public/html/mod/stats.lisp
Normal file
34
crates/app/src/public/html/mod/stats.lisp
Normal file
|
@ -0,0 +1,34 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Server stats - {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex-col gap-2")
|
||||
(div
|
||||
("class" "card-nest w-full")
|
||||
(div
|
||||
("class" "card small flex items-center gap-2")
|
||||
(text "{{ icon \"chart-line\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:link.stats\" }}")))
|
||||
(div
|
||||
("class" "card flex flex-col gap-2")
|
||||
(ul
|
||||
(li
|
||||
(b
|
||||
(text "Active user streams:"))
|
||||
(span
|
||||
(text "{{ active_users }}")))
|
||||
(li
|
||||
(b
|
||||
(text "Active chat subscriptions:"))
|
||||
(span
|
||||
(text "{{ active_users_chats }}")))
|
||||
(li
|
||||
(b
|
||||
(text "Socket tasks:"))
|
||||
(span
|
||||
(text "{{ (active_users_chats + active_users) * 3 }}")))))))
|
||||
|
||||
(text "{% endblock %}")
|
|
@ -1,132 +0,0 @@
|
|||
{% extends "root.html" %} {% block head %}
|
||||
<title>User warnings - {{ config.name }}</title>
|
||||
{% endblock %} {% block body %} {{ macros::nav() }}
|
||||
<main class="flex flex-col gap-2">
|
||||
<div class="card-nest">
|
||||
<div class="card small flex items-center justify-between gap-2">
|
||||
<span class="flex items-center gap-2">
|
||||
{{ icon "gavel" }}
|
||||
<span>{{ text "mod_panel:label.create_warning" }}</span>
|
||||
</span>
|
||||
|
||||
<a
|
||||
href="/mod_panel/profile/{{ profile.id }}"
|
||||
class="button quaternary small red"
|
||||
>
|
||||
{{ icon "x" }}
|
||||
<span>{{ text "dialog:action.cancel" }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="card flex flex-col gap-2"
|
||||
onsubmit="create_warning_from_form(event)"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="content"
|
||||
>{{ text "communities:label.content" }}</label
|
||||
>
|
||||
<textarea
|
||||
type="text"
|
||||
name="content"
|
||||
id="content"
|
||||
placeholder="content"
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="4096"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<button class="primary">
|
||||
{{ text "communities:action.create" }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card-nest">
|
||||
<div class="card small flex items-center justify-between gap-2">
|
||||
<span class="flex items-center gap-2">
|
||||
{{ icon "message-circle-warning" }}
|
||||
<span>{{ text "mod_panel:label.warnings" }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="card flex flex-col gap-4">
|
||||
{% for item in items %}
|
||||
<div class="card-nest">
|
||||
<div class="card small flex items-center justify-between gap-2">
|
||||
<a
|
||||
class="flex items-center gap-2 flush"
|
||||
href="/api/v1/auth/user/find/{{ item.moderator }}"
|
||||
title="Moderator"
|
||||
>
|
||||
<!-- prettier-ignore -->
|
||||
{{ components::avatar(username=item.moderator, selector_type="id") }}
|
||||
<span>{{ item.moderator }}</span>
|
||||
|
||||
<span class="fade date">{{ item.created }}</span>
|
||||
</a>
|
||||
|
||||
<button
|
||||
class="small quaternary red"
|
||||
onclick="remove_warning('{{ item.id }}')"
|
||||
>
|
||||
{{ icon "trash" }}
|
||||
<span>{{ text "general:action.delete" }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card secondary flex flex-col gap-2">
|
||||
<span class="no_p_margin"
|
||||
>{{ item.content|markdown|safe }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{{ components::pagination(page=page, items=items|length) }}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
async function create_warning_from_form(e) {
|
||||
e.preventDefault();
|
||||
await trigger("atto::debounce", ["warnings::create"]);
|
||||
fetch("/api/v1/warnings/{{ profile.id }}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: e.target.content.value,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
if (res.ok) {
|
||||
e.target.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function remove_warning(id) {
|
||||
fetch(`/api/v1/warnings/${id}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
121
crates/app/src/public/html/mod/warnings.lisp
Normal file
121
crates/app/src/public/html/mod/warnings.lisp
Normal file
|
@ -0,0 +1,121 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "User warnings - {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex-col gap-2")
|
||||
(div
|
||||
("class" "card-nest")
|
||||
(div
|
||||
("class" "card small flex items-center justify-between gap-2")
|
||||
(span
|
||||
("class" "flex items-center gap-2")
|
||||
(text "{{ icon \"gavel\" }}")
|
||||
(span
|
||||
(text "{{ text \"mod_panel:label.create_warning\" }}")))
|
||||
(a
|
||||
("href" "/mod_panel/profile/{{ profile.id }}")
|
||||
("class" "button quaternary small red")
|
||||
(text "{{ icon \"x\" }}")
|
||||
(span
|
||||
(text "{{ text \"dialog:action.cancel\" }}"))))
|
||||
(form
|
||||
("class" "card flex flex-col gap-2")
|
||||
("onsubmit" "create_warning_from_form(event)")
|
||||
(div
|
||||
("class" "flex flex-col gap-1")
|
||||
(label
|
||||
("for" "content")
|
||||
(text "{{ text \"communities:label.content\" }}"))
|
||||
(textarea
|
||||
("type" "text")
|
||||
("name" "content")
|
||||
("id" "content")
|
||||
("placeholder" "content")
|
||||
("required" "")
|
||||
("minlength" "2")
|
||||
("maxlength" "4096")))
|
||||
(button
|
||||
("class" "primary")
|
||||
(text "{{ text \"communities:action.create\" }}"))))
|
||||
(div
|
||||
("class" "card-nest")
|
||||
(div
|
||||
("class" "card small flex items-center justify-between gap-2")
|
||||
(span
|
||||
("class" "flex items-center gap-2")
|
||||
(text "{{ icon \"message-circle-warning\" }}")
|
||||
(span
|
||||
(text "{{ text \"mod_panel:label.warnings\" }}"))))
|
||||
(div
|
||||
("class" "card flex flex-col gap-4")
|
||||
(text "{% for item in items %}")
|
||||
(div
|
||||
("class" "card-nest")
|
||||
(div
|
||||
("class" "card small flex items-center justify-between gap-2")
|
||||
(a
|
||||
("class" "flex items-center gap-2 flush")
|
||||
("href" "/api/v1/auth/user/find/{{ item.moderator }}")
|
||||
("title" "Moderator")
|
||||
(text "{{ components::avatar(username=item.moderator, selector_type=\"id\") }}")
|
||||
(span
|
||||
(text "{{ item.moderator }}"))
|
||||
(span
|
||||
("class" "fade date")
|
||||
(text "{{ item.created }}")))
|
||||
(button
|
||||
("class" "small quaternary red")
|
||||
("onclick" "remove_warning('{{ item.id }}')")
|
||||
(text "{{ icon \"trash\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:action.delete\" }}"))))
|
||||
(div
|
||||
("class" "card secondary flex flex-col gap-2")
|
||||
(span
|
||||
("class" "no_p_margin")
|
||||
(text "{{ item.content|markdown|safe }}"))))
|
||||
(text "{% endfor %}")
|
||||
(text "{{ components::pagination(page=page, items=items|length) }}"))))
|
||||
|
||||
(script
|
||||
(text "async function create_warning_from_form(e) {
|
||||
e.preventDefault();
|
||||
await trigger(\"atto::debounce\", [\"warnings::create\"]);
|
||||
fetch(\"/api/v1/warnings/{{ profile.id }}\", {
|
||||
method: \"POST\",
|
||||
headers: {
|
||||
\"Content-Type\": \"application/json\",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: e.target.content.value,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
if (res.ok) {
|
||||
e.target.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function remove_warning(id) {
|
||||
fetch(`/api/v1/warnings/${id}`, {
|
||||
method: \"DELETE\",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
});
|
||||
}"))
|
||||
|
||||
(text "{% endblock %}")
|
Loading…
Add table
Add a link
Reference in a new issue