add: ability to generate invite codes in bulk add: better mark as nsfw

ui
This commit is contained in:
trisua 2025-06-23 13:48:16 -04:00
parent 2a77c61bf2
commit 4843688fcf
13 changed files with 126 additions and 90 deletions

View file

@ -168,7 +168,7 @@ version = "1.0.0"
"settings:label.export" = "Export"
"settings:label.manage_blocks" = "Manage blocks"
"settings:label.users" = "Users"
"settings:label.generate_invite" = "Generate invite"
"settings:label.generate_invites" = "Generate invites"
"settings:label.add_to_stack" = "Add to stack"
"settings:tab.security" = "Security"
"settings:tab.blocks" = "Blocks"

View file

@ -218,7 +218,7 @@ pre {
}
code {
padding: var(--pad-1);
padding: 0;
}
pre,

View file

@ -139,7 +139,7 @@
("id" "files_list")
("class" "flex gap-2 flex-wrap"))
(div
("class" "flex justify-between gap-2")
("class" "flex justify-between flex-collapse gap-2")
(text "{{ components::create_post_options() }}")
(div
("class" "flex gap-2")

View file

@ -1399,7 +1399,7 @@
(text "{%- endif %} {%- endmacro %} {% macro create_post_options() -%}")
(div
("class" "flex gap-2")
("class" "flex gap-2 flex-wrap")
(text "{{ components::emoji_picker(element_id=\"content\", render_dialog=true) }} {% if not quoting -%} {% if is_supporter -%} {{ components::file_picker(files_list_id=\"files_list\") }} {%- endif %} {%- endif %}")
(button
@ -1414,7 +1414,20 @@
("title" "More options")
("onclick" "document.getElementById('post_options_dialog').showModal()")
("type" "button")
(text "{{ icon \"ellipsis\" }}")))
(text "{{ icon \"ellipsis\" }}"))
(label
("class" "flex items-center gap-1 button lowered")
("title" "Mark as NSFW/hide from public timelines")
("for" "is_nsfw")
(input
("type" "checkbox")
("name" "is_nsfw")
("id" "is_nsfw")
("checked" "{{ user.settings.auto_unlist }}")
("onchange" "POST_INITIAL_SETTINGS['is_nsfw'] = event.target.checked"))
(span (icon (text "eye-closed")))))
(dialog
("id" "post_options_dialog")
@ -1474,11 +1487,11 @@
window.POST_INITIAL_SETTINGS.reactions_enabled.toString(),
\"checkbox\",
],
[
[\"is_nsfw\", \"Hide from public timelines\"],
window.POST_INITIAL_SETTINGS.is_nsfw.toString(),
\"checkbox\",
],
// [
// [\"is_nsfw\", \"Hide from public timelines\"],
// window.POST_INITIAL_SETTINGS.is_nsfw.toString(),
// \"checkbox\",
// ],
[
[\"content_warning\", \"Content warning\"],
window.POST_INITIAL_SETTINGS.content_warning,

View file

@ -113,21 +113,24 @@
("id" "files_list")
("class" "flex gap-2 flex-wrap"))
(div
("class" "flex flex-wrap w-full gap-2")
(text "{{ components::create_post_options() }}")
("class" "flex w-full justify-between flex-collapse gap-2")
(div
("class" "flex flex-wrap w-full gap-2")
(text "{{ components::create_post_options() }}")
(button
("type" "button")
("class" "red lowered")
("onclick" "trigger('me::remove_question', ['{{ question[0].id }}'])")
(text "{{ text \"general:action.delete\" }}"))
(button
("type" "button")
("class" "red lowered")
("onclick" "trigger('me::ip_block_question', ['{{ question[0].id }}'])")
(text "{{ text \"auth:action.ip_block\" }}")))
(button
("class" "primary")
(text "{{ text \"requests:label.answer\" }}"))
(button
("type" "button")
("class" "red lowered")
("onclick" "trigger('me::remove_question', ['{{ question[0].id }}'])")
(text "{{ text \"general:action.delete\" }}"))
(button
("type" "button")
("class" "red lowered")
("onclick" "trigger('me::ip_block_question', ['{{ question[0].id }}'])")
(text "{{ text \"auth:action.ip_block\" }}")))))
(text "{{ text \"requests:label.answer\" }}")))))
(text "{% endfor %}")))
(text "{{ components::pagination(page=page, items=requests|length, key=\"&id=\", value=profile.id) }}"))

View file

@ -77,7 +77,7 @@
(text "{% if config.security.enable_invite_codes -%}")
(a
("data-tab-button" "account/invites")
("href" "#/account/invites")
("href" "?page=0#/account/invites")
(text "{{ icon \"ticket\" }}")
(span
(text "{{ text \"settings:tab.invites\" }}")))
@ -538,10 +538,12 @@
(text "{{ text \"settings:tab.invites\" }}")))
(div
("class" "card flex flex-col gap-2 secondary")
(pre ("id" "invite_codes_output") ("class" "hidden") (code))
(button
("onclick" "generate_invite_code()")
("onclick" "generate_invite_codes()")
(icon (text "plus"))
(str (text "settings:label.generate_invite")))
(str (text "settings:label.generate_invites")))
(text "{{ components::supporter_ad(body=\"Become a supporter to generate up to 48 invite codes! You can currently have 2 maximum.\") }} {% for code in invites %}")
(div
@ -555,8 +557,10 @@
(b (text "{{ code[1].code }}"))
(text "{%- endif %}"))
(text "{% endfor %}")
(text "{{ components::pagination(page=page, items=invites|length, key=\"#/account/invites\") }}")
(script
(text "globalThis.generate_invite_code = async () => {
(text "globalThis.generate_invite_codes = async () => {
await trigger(\"atto::debounce\", [\"invites::create\"]);
if (
!(await trigger(\"atto::confirm\", [
\"Are you sure you would like to do this? This action is permanent.\",
@ -565,7 +569,16 @@
return;
}
fetch(`/api/v1/invite`, {
const count = Number.parseInt(await trigger(\"atto::prompt\", [\"Count (1-48):\"]));
if (!count) {
return;
}
document.getElementById(\"invite_codes_output\").classList.remove(\"hidden\");
document.getElementById(\"invite_codes_output\").children[0].innerText = \"Working...\";
fetch(`/api/v1/invites/${count}`, {
method: \"POST\",
})
.then((res) => res.json())
@ -576,7 +589,7 @@
]);
if (res.ok) {
alert(res.payload);
document.getElementById(\"invite_codes_output\").children[0].innerText = res.payload;
}
});
};"))))))

View file

@ -832,8 +832,9 @@ pub async fn refresh_grant_request(
/// Generate an invite code.
///
/// Does not support third-party grants.
pub async fn generate_invite_code_request(
pub async fn generate_invite_codes_request(
jar: CookieJar,
Path(count): Path<usize>,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
@ -846,15 +847,25 @@ pub async fn generate_invite_code_request(
return Json(Error::NotAllowed.into());
}
match data
.create_invite_code(InviteCode::new(user.id), &user)
.await
{
Ok(x) => Json(ApiReturn {
ok: true,
message: "Code generated".to_string(),
payload: Some(x.code),
}),
Err(e) => Json(e.into()),
if count > 48 {
return Json(Error::DataTooLong("count".to_string()).into());
}
let mut out_string = String::new();
for _ in 0..count {
match data
.create_invite_code(InviteCode::new(user.id), &user)
.await
{
Ok(x) => out_string += &(x.code + "\n"),
Err(_) => break,
}
}
Json(ApiReturn {
ok: true,
message: "Success".to_string(),
payload: Some(out_string),
})
}

View file

@ -441,10 +441,7 @@ pub async fn posts_request(
};
check_user_blocked_or_private!(Some(&user), other_user, data, @api);
match data
.get_posts_by_user(id, 12, props.page, &Some(user.clone()))
.await
{
match data.get_posts_by_user(id, 12, props.page).await {
Ok(posts) => {
let ignore_users = crate::ignore_users_gen!(user!, #data);
Json(ApiReturn {

View file

@ -37,7 +37,10 @@ pub fn routes() -> Router {
.route("/util/proxy", get(util::proxy_request))
.route("/util/lang", get(util::set_langfile_request))
.route("/util/ip", get(util::ip_test_request))
.route("/invite", post(auth::profile::generate_invite_code_request))
.route(
"/invites/{count}",
post(auth::profile::generate_invite_codes_request),
)
// reactions
.route("/reactions", post(reactions::create_request))
.route("/reactions/{id}", get(reactions::get_request))

View file

@ -625,12 +625,10 @@ pub async fn swiss_army_timeline_request(
check_user_blocked_or_private!(user, other_user, data, jar);
if req.tag.is_empty() {
data.0
.get_posts_by_user(req.user_id, 12, req.page, &user)
.await
data.0.get_posts_by_user(req.user_id, 12, req.page).await
} else {
data.0
.get_posts_by_user_tag(req.user_id, &req.tag, 12, req.page, &user)
.get_posts_by_user_tag(req.user_id, &req.tag, 12, req.page)
.await
}
} else {

View file

@ -101,7 +101,11 @@ pub async fn settings_request(
}
};
let invites = match data.0.get_invite_codes_by_owner(profile.id).await {
let invites = match data
.0
.get_invite_codes_by_owner(profile.id, 12, req.page)
.await
{
Ok(l) => match data.0.fill_invite_codes(l).await {
Ok(l) => l,
Err(e) => {