add: user links and location

This commit is contained in:
trisua 2025-08-31 23:41:12 -04:00
parent 5fafc8d7b9
commit 140a11ff72
18 changed files with 442 additions and 222 deletions

91
Cargo.lock generated
View file

@ -1603,6 +1603,7 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown",
"serde",
]
[[package]]
@ -1667,6 +1668,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@ -2379,6 +2389,27 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
@ -2587,7 +2618,7 @@ dependencies = [
"built",
"cfg-if",
"interpolate_name",
"itertools",
"itertools 0.12.1",
"libc",
"libfuzzer-sys",
"log",
@ -3028,6 +3059,51 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_valid"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b615bed66931a7a9809b273937adc8a402d038b1e509d027fcaf62f084d33d1"
dependencies = [
"indexmap",
"itertools 0.13.0",
"num-traits",
"once_cell",
"paste",
"regex",
"serde",
"serde_json",
"serde_valid_derive",
"serde_valid_literal",
"thiserror 1.0.69",
"unicode-segmentation",
]
[[package]]
name = "serde_valid_derive"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fa1a5a21ea5aab06d2e6a6b59837d450fb2be9695be97735a711edfbe79ea07"
dependencies = [
"itertools 0.13.0",
"paste",
"proc-macro-error2",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.104",
]
[[package]]
name = "serde_valid_literal"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd07331596ea967dccf9a35bde71ecd757490e09827b938a5c6226c648e3a25e"
dependencies = [
"paste",
"regex",
]
[[package]]
name = "sha1"
version = "0.10.6"
@ -3214,6 +3290,12 @@ dependencies = [
"unicode-properties",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
@ -3423,6 +3505,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"serde_valid",
"tetratto-l10n 12.0.0",
"tetratto-shared 12.0.6",
"tokio",
@ -4027,6 +4110,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.2.1"

View file

@ -175,6 +175,7 @@ version = "1.0.0"
"settings:tab.general" = "General"
"settings:tab.account" = "Account"
"settings:tab.profile" = "Profile"
"settings:tab.experience" = "Experience"
"settings:tab.theme" = "Theme"
"settings:tab.sessions" = "Sessions"
"settings:tab.grants" = "Grants"

View file

@ -199,6 +199,34 @@ body:not(.use_system_font) {
& input,
& textarea {
font-variation-settings: "wght" 325;
& h1 {
font-variation-settings: "wght" 600;
}
& h2 {
font-variation-settings: "wght" 550;
}
& h3 {
font-variation-settings: "wght" 500;
}
& h4 {
font-variation-settings: "wght" 450;
}
& h5 {
font-variation-settings: "wght" 400;
}
& h6 {
font-variation-settings: "wght" 350;
}
& b {
font-variation-settings: "wght" 500;
}
}
}

View file

@ -1256,6 +1256,11 @@ details summary::-webkit-details-marker {
display: none;
}
details summary.button {
height: max-content;
justify-content: start;
}
details[open] > summary {
position: relative;
color: var(--color-text-lowered) !important;
@ -1288,7 +1293,7 @@ details.accordion {
details.accordion summary {
background: var(--color-lowered);
border-radius: var(--radius);
padding: var(--pad-3) var(--pad-4);
padding: var(--pad-3) var(--pad-4) !important;
margin: 0;
width: 100%;
user-select: none;

View file

@ -147,7 +147,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))))
(str (text "general:action.save")))))))
(div
("class" "card_nest")
("ui_ident" "danger_zone")
@ -170,7 +170,7 @@
("onclick" "save_context()")
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}")))
(str (text "general:action.save"))))
(a
("href" "/community/{{ community.title }}")
("class" "button secondary")
@ -273,7 +273,7 @@
("onclick" "update_user_role(document.getElementById('uid').value, document.getElementById('role').value)")
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))
(str (text "general:action.save")))))
(div
("class" "card flex flex_col gap_2")
("id" "permission_builder"))))

View file

@ -95,7 +95,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}")))))
(str (text "general:action.save"))))))
(div
("class" "card_nest")
(div
@ -120,7 +120,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}")))))
(str (text "general:action.save"))))))
(div
("class" "card_nest")
(div
@ -145,7 +145,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}")))))
(str (text "general:action.save"))))))
(div
("class" "card_nest")
(div
@ -185,7 +185,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}")))))
(str (text "general:action.save"))))))
(div
("class" "card_nest")
(div

View file

@ -255,7 +255,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}")))))))
(str (text "general:action.save"))))))))
; users should also be able to manage the journal's sub directories here
(details

View file

@ -354,7 +354,7 @@
(str (text "forge:tab.tickets"))))
(text "{%- endmacro %}")
(text "{% macro profile_settings_nav_options() -%}")
(text "{% macro user_settings_nav_options() -%}")
(a
("data-tab-button" "account")
("class" "active")
@ -368,6 +368,12 @@
(text "{{ icon \"user-round\" }}")
(span
(text "{{ text \"settings:tab.profile\" }}")))
(a
("data-tab-button" "experience")
("href" "#/experience")
(text "{{ icon \"settings-2\" }}")
(span
(text "{{ text \"settings:tab.experience\" }}")))
(a
("data-tab-button" "theme")
("href" "#/theme")
@ -393,4 +399,10 @@
(text "{{ icon \"book-user\" }}")
(span
(text "{{ text \"settings:tab.close_friends\" }}")))
(a
("data-tab-button" "presets")
("href" "#/presets")
(icon (text "cooking-pot"))
(span
(str (text "settings:tab.presets"))))
(text "{%- endmacro %}")

View file

@ -358,7 +358,7 @@
("onclick" "update_user_role(Number.parseInt(document.getElementById('role').value))")
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))
(str (text "general:action.save")))))
(div
("class" "card lowered flex flex_col gap_2")
("id" "permission_builder")))
@ -376,7 +376,7 @@
("onclick" "update_user_secondary_role(Number.parseInt(document.getElementById('secondary_role').value))")
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))
(str (text "general:action.save")))))
(div
("class" "card lowered flex flex_col gap_2")
("id" "secondary_permission_builder")))

View file

@ -148,7 +148,7 @@
("onclick" "save_context()")
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}")))
(str (text "general:action.save"))))
(script
(text "setTimeout(async () => {
const ui = await ns(\"ui\");
@ -286,7 +286,7 @@
("class" "flex gap_2")
(text "{{ components::emoji_picker(element_id=\"new_content\", render_dialog=false) }}")
(button
(text "{{ text \"general:action.save\" }}")))))
(str (text "general:action.save"))))))
(script
(text "async function edit_post_from_form(e) {
e.preventDefault();

View file

@ -115,7 +115,7 @@
("class" "fade")
(text "{{ profile.username }}"))))
(div
("class" "card flex flex_col items_center gap_2")
("class" "card flex flex_col items_center small gap_2")
("id" "social")
(text "{% if profile.settings.status -%}")
(p
@ -159,7 +159,20 @@
(div
("id" "bio")
("class" "card small no_p_margin")
(text "{{ profile.settings.biography|markdown|safe }}"))
(text "{{ profile.settings.biography|markdown|safe }}")
(text "{% if profile.settings.location|length > 0 -%}")
(span ("class" "flex items_center gap_2") (icon (text "map-pin")) (text "{{ profile.settings.location }}"))
(text "{%- endif %}")
(text "{% for link in profile.settings.links -%}")
(span
("class" "flex items_center gap_2")
(icon (text "link"))
(a
("href" "{{ link[1] }}")
(text "{{ link[0] }}")))
(text "{%- endfor %}"))
(div
("class" "card flex flex_col gap_2")
(text "{% if user -%}")

View file

@ -7,7 +7,7 @@
; nav desktop
(menu
("class" "desktop col")
(text "{{ macros::profile_settings_nav_options() }}"))
(text "{{ macros::user_settings_nav_options() }}"))
; content
(main
@ -35,7 +35,7 @@
(span ("class" "current_tab_text") (text "account")))
(div
("class" "inner left")
(text "{{ macros::profile_settings_nav_options() }}"))))
(text "{{ macros::user_settings_nav_options() }}"))))
; ...
(div
@ -43,25 +43,11 @@
("data-tab" "presets")
(div
("class" "card lowered flex flex_col gap_2")
(a
("href" "#/account")
("class" "button secondary")
(icon (text "arrow-left"))
(span
(str (text "general:action.back"))))
(div
("class" "card_nest")
(div
("class" "card flex items_center gap_2 small")
(icon (text "cooking-pot"))
(span
(str (text "settings:tab.presets"))))
(div
("class" "card flex flex_col gap_2 secondary")
(p (text "Not sure where to start? Try some settings presets!"))
(details
("class" "w_full accordion")
(summary
("class" "button raised")
(icon (text "rss"))
(text "Microblogging"))
@ -77,6 +63,7 @@
(details
("class" "w_full accordion")
(summary
("class" "button raised")
(icon (text "message-circle-heart"))
(text "Q&A"))
@ -92,6 +79,7 @@
(details
("class" "w_full accordion")
(summary
("class" "button raised")
(icon (text "key"))
(text "Private"))
@ -107,6 +95,7 @@
(details
("class" "w_full accordion")
(summary
("class" "button raised")
(icon (text "eye-closed"))
(text "NSFW"))
@ -117,7 +106,7 @@
(button
("onclick" "apply_preset(PRESET_NSFW)")
(icon (text "settings"))
(str (text "general:action.apply")))))))))
(str (text "general:action.apply")))))))
(div
("class" "w_full flex flex_col gap_2")
@ -180,61 +169,6 @@
(span
(text "{{ text \"settings:tab.billing\" }}"))))
(text "{%- endif %}")
(div
("class" "card_nest")
("ui_ident" "home_timeline")
(div
("class" "card small")
(b
(text "Home timeline")))
(div
("class" "card")
(select
("onchange" "window.SETTING_SET_FUNCTIONS[0]('default_timeline', event.target.selectedOptions[0].value.startsWith('{') ? JSON.parse(event.target.selectedOptions[0].value) : event.target.selectedOptions[0].value)")
(option
("value" "MyCommunities")
("selected" "{% if home == '/' -%}true{% else %}false{%- endif %}")
(text "My communities"))
(option
("value" "MyCommunitiesQuestions")
("selected" "{% if home == '/questions' -%}true{% else %}false{%- endif %}")
(text "My communities (questions)"))
(option
("value" "PopularPosts")
("selected" "{% if home == '/popular' -%}true{% else %}false{%- endif %}")
(text "Popular"))
(option
("value" "PopularQuestions")
("selected" "{% if home == '/popular/questions' -%}true{% else %}false{%- endif %}")
(text "Popular (questions)"))
(option
("value" "FollowingPosts")
("selected" "{% if home == '/following' -%}true{% else %}false{%- endif %}")
(text "Following"))
(option
("value" "FollowingQuestions")
("selected" "{% if home == '/following/questions' -%}true{% else %}false{%- endif %}")
(text "Following (questions)"))
(option
("value" "AllPosts")
("selected" "{% if home == '/all' -%}true{% else %}false{%- endif %}")
(text "All"))
(option
("value" "AllQuestions")
("selected" "{% if home == '/all/questions' -%}true{% else %}false{%- endif %}")
(text "All (questions)"))
(text "{% for stack in stacks %}")
(text "<option
value='{\"Stack\":\"{{ stack.id }}\"}'
selected=\"{% if home is ending_with(stack.id|as_str) -%}true{% else %}false{%- endif %}\"
>
{{ stack.name }} (stack)
</option>")
(text "{% endfor %}"))
(span
("class" "fade")
(text "This represents the timeline the home button takes you to."))))
(div
("class" "card_nest desktop")
("ui_ident" "notifications")
@ -282,7 +216,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))))
(str (text "general:action.save")))))))
(div
("class" "card_nest")
("ui_ident" "delete_account")
@ -337,7 +271,7 @@
("id" "save_button")
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))
(str (text "general:action.save")))))
(div
("class" "w_full flex flex_col gap_2 hidden")
("data-tab" "account/security")
@ -444,7 +378,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}")))))))))
(str (text "general:action.save"))))))))))
(div
("class" "w_full flex flex_col gap_2 hidden")
("data-tab" "account/following")
@ -991,13 +925,13 @@
(span
("class" "fade")
(text "Use an image of 1100x350px for the best results."))))
(div
("class" "card_nest")
("ui_ident" "default_profile_page")
(div
("class" "card small")
(b
(text "Default profile tab")))
(b (text "Default profile tab")))
(div
("class" "card")
(select
@ -1013,10 +947,36 @@
(span
("class" "fade")
(text "This represents the timeline that is shown on your profile by default."))))
(div
("class" "card_nest")
("ui_ident" "user_links")
(div
("class" "card small")
(b (text "My links")))
(div
("class" "card flex flex_col gap_2")
(button
("onclick" "add_link()")
(icon (text "plus"))
(text "Add link"))
(ul ("id" "user_links")))))
(button
("onclick" "save_settings()")
("id" "save_button")
(icon (text "check"))
(span
(str (text "general:action.save")))))
(div
("class" "w_full hidden flex flex_col gap_2")
("data-tab" "experience")
(div
("class" "card lowered flex flex_col gap_2")
("id" "experience_settings")
(div
("class" "flex flex_col gap_2")
("ui_ident" "show_presets")
(hr ("class" "margin"))
(div
("class" "card_nest")
(div
@ -1028,13 +988,67 @@
(p
(text "Quickly set up your account with ")
(a ("href" "/settings#/presets") (text "settings presets"))
(text "!"))))))
(text "!")))))
(div
("class" "card_nest")
("ui_ident" "home_timeline")
(div
("class" "card small")
(b
(text "Home timeline")))
(div
("class" "card")
(select
("onchange" "window.SETTING_SET_FUNCTIONS[0]('default_timeline', event.target.selectedOptions[0].value.startsWith('{') ? JSON.parse(event.target.selectedOptions[0].value) : event.target.selectedOptions[0].value)")
(option
("value" "MyCommunities")
("selected" "{% if home == '/' -%}true{% else %}false{%- endif %}")
(text "My communities"))
(option
("value" "MyCommunitiesQuestions")
("selected" "{% if home == '/questions' -%}true{% else %}false{%- endif %}")
(text "My communities (questions)"))
(option
("value" "PopularPosts")
("selected" "{% if home == '/popular' -%}true{% else %}false{%- endif %}")
(text "Popular"))
(option
("value" "PopularQuestions")
("selected" "{% if home == '/popular/questions' -%}true{% else %}false{%- endif %}")
(text "Popular (questions)"))
(option
("value" "FollowingPosts")
("selected" "{% if home == '/following' -%}true{% else %}false{%- endif %}")
(text "Following"))
(option
("value" "FollowingQuestions")
("selected" "{% if home == '/following/questions' -%}true{% else %}false{%- endif %}")
(text "Following (questions)"))
(option
("value" "AllPosts")
("selected" "{% if home == '/all' -%}true{% else %}false{%- endif %}")
(text "All"))
(option
("value" "AllQuestions")
("selected" "{% if home == '/all/questions' -%}true{% else %}false{%- endif %}")
(text "All (questions)"))
(text "{% for stack in stacks %}")
(text "<option
value='{\"Stack\":\"{{ stack.id }}\"}'
selected=\"{% if home is ending_with(stack.id|as_str) -%}true{% else %}false{%- endif %}\"
>
{{ stack.name }} (stack)
</option>")
(text "{% endfor %}"))
(span
("class" "fade")
(text "This represents the timeline the home button takes you to.")))))
(button
("onclick" "save_settings()")
("id" "save_button")
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))
(str (text "general:action.save")))))
(div
("class" "card w_full lowered hidden flex flex_col gap_2")
("data-tab" "sessions")
@ -1193,7 +1207,7 @@
("id" "save_button")
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))
(str (text "general:action.save")))))
(div
("class" "card w_full lowered hidden flex flex_col gap_2")
("data-tab" "grants")
@ -1595,7 +1609,7 @@
`data:image/png;base64,${qr}`;
document.getElementById(
\"totp_recovery_codes\",
).innerText = recovery_codes.join(\"\n\");
).innerText = recovery_codes.join(\"\\n\");
document.getElementById(\"totp_stuff\").style.display =
\"contents\";
@ -1787,12 +1801,13 @@
document.getElementById(\"account_settings\");
const profile_settings =
document.getElementById(\"profile_settings\");
const experience_settings =
document.getElementById(\"experience_settings\");
const theme_settings = document.getElementById(\"theme_settings\");
ui.refresh_container(account_settings, [
\"supporter_ad\",
\"account_settings_tabs\",
\"home_timeline\",
\"notifications\",
\"change_username\",
\"delete_account\",
@ -1802,6 +1817,11 @@
\"change_avatar\",
\"change_banner\",
\"default_profile_page\",
\"user_links\",
]);
ui.refresh_container(experience_settings, [
\"supporter_ad\",
\"home_timeline\",
\"show_presets\",
]);
ui.refresh_container(theme_settings, [
@ -1814,7 +1834,7 @@
]);
ui.generate_settings_ui(
account_settings,
profile_settings,
[
[
[\"display_name\", \"Display name\"],
@ -1835,50 +1855,23 @@
'<span class=\"fade\">This biography is only shown to users you are not following while your account is private.</span>',
},
],
[
[\"location\", \"Location\"],
\"{{ profile.settings.location }}\",
\"input\",
],
[[\"status\", \"Status\"], settings.status, \"textarea\"],
[
[\"warning\", \"Profile warning\"],
settings.warning,
\"textarea\",
],
[[\"muted\", \"Muted phrases\"], settings.muted.join(\"\\n\"), \"textarea\", {
embed_html:
'<span class=\"fade\">Muted phrases should all be on new lines.</span>',
}],
[[], \"Accessibility\", \"title\"],
[
[\"large_text\", \"Increase UI text size\"],
\"{{ profile.settings.large_text }}\",
\"checkbox\",
],
[
[\"use_system_font\", \"Always use system font instead\"],
\"{{ profile.settings.use_system_font }}\",
\"checkbox\",
],
[
[\"paged_timelines\", \"Make timelines paged instead of infinitely scrolled\"],
\"{{ profile.settings.paged_timelines }}\",
\"checkbox\",
],
[
[\"auto_clear_notifs\", \"Automatically clear all notifications when you open the notifications page\"],
\"{{ profile.settings.auto_clear_notifs }}\",
\"checkbox\",
],
],
settings,
{
muted: (new_muted) => {
settings.muted = new_muted
.split(\"\\n\")
.map((t) => t.trim());
},
},
);
ui.generate_settings_ui(
profile_settings,
experience_settings,
[
[[], \"Privacy\", \"title\"],
[
@ -1988,6 +1981,10 @@
\"{{ profile.settings.hide_username_badges }}\",
\"checkbox\",
],
[[\"muted\", \"Muted phrases\"], settings.muted.join(\"\\n\"), \"textarea\", {
embed_html:
'<span class=\"fade\">Muted phrases should all be on new lines.</span>',
}],
[[], \"Questions\", \"title\"],
[
[
@ -2074,8 +2071,36 @@
\"{{ profile.settings.disable_achievements }}\",
\"checkbox\",
],
[[], \"Accessibility\", \"title\"],
[
[\"large_text\", \"Increase UI text size\"],
\"{{ profile.settings.large_text }}\",
\"checkbox\",
],
[
[\"use_system_font\", \"Always use system font instead\"],
\"{{ profile.settings.use_system_font }}\",
\"checkbox\",
],
[
[\"paged_timelines\", \"Make timelines paged instead of infinitely scrolled\"],
\"{{ profile.settings.paged_timelines }}\",
\"checkbox\",
],
[
[\"auto_clear_notifs\", \"Automatically clear all notifications when you open the notifications page\"],
\"{{ profile.settings.auto_clear_notifs }}\",
\"checkbox\",
],
],
settings,
{
muted: (new_muted) => {
settings.muted = new_muted
.split(\"\\n\")
.map((t) => t.trim());
},
},
);
const can_use_custom_css =
@ -2351,5 +2376,40 @@
anchor.click();
anchor.remove();
};
// links
function render_links() {
document.getElementById(\"user_links\").innerHTML = \"\";
let i = 0;
for (const link of settings.links) {
document.getElementById(\"user_links\").innerHTML += `<li id=\"link_${i}\"><span>${link[0]}</span> (<a href=\"${link[1]}\">${link[1]}</a>) (<a class=\"red\" href=\"javascript:remove_link(${i})\">delete</a>)</li>`;
i += 1;
}
}
globalThis.add_link = async () => {
const label = await trigger(\"atto::prompt\", [\"Link label:\"]);
if (!label) {
return;
}
const url = await trigger(\"atto::prompt\", [\"Link URL:\"]);
if (!url) {
return;
}
settings.links.push([label, url]);
render_links();
}
globalThis.remove_link = (idx) => {
settings.links.splice(idx, 1);
document.getElementById(`link_${idx}`).remove();
}
render_links();
});"))))
(text "{% endblock %}")

View file

@ -9,7 +9,7 @@
("class" "card_nest")
(div
("class" "card small flex items_center gap_2")
(text "{{ components::avatar(id=add_user.username, size=\"24px\") }}")
(text "{{ components::avatar(id=add_user.id, size=\"24px\") }}")
(text "{{ components::full_username(user=add_user) }}"))
(div
("class" "card flex flex_col gap_2")

View file

@ -117,7 +117,7 @@
(button
(icon (text "check"))
(span
(text "{{ text \"general:action.save\" }}"))))))
(str (text "general:action.save")))))))
(text "{% if not stack.is_locked -%}")
(div
("class" "card_nest")

View file

@ -153,28 +153,8 @@ pub async fn update_user_settings_request(
}
// check lengths
if req.display_name.len() > 32 {
return Json(Error::DataTooLong("display name".to_string()).into());
}
if req.warning.len() > 2048 {
return Json(Error::DataTooLong("warning".to_string()).into());
}
if req.status.len() > 256 {
return Json(Error::DataTooLong("status".to_string()).into());
}
if req.biography.len() > 4096 {
return Json(Error::DataTooLong("warning".to_string()).into());
}
if req.mail_signature.len() > 2048 {
return Json(Error::DataTooLong("mail signature".to_string()).into());
}
if req.forum_signature.len() > 2048 {
return Json(Error::DataTooLong("forum signature".to_string()).into());
if let Err(e) = req.verify_values() {
return Json(e.into());
}
// check percentage themes

View file

@ -50,3 +50,4 @@ oiseau = { version = "0.1.2", default-features = false, features = [
paste = { version = "1.0.15", optional = true }
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
buckets-core = "1.0.4"
serde_valid = "1.0.5"

View file

@ -709,7 +709,8 @@ impl DataManager {
self.cache_clear_user(&other_user).await;
// create audit log entry
// create audit log entry (if we aren't the user that is being updated)
if user.id != other_user.id {
self.create_audit_log_entry(AuditLogEntry::new(
user.id,
format!(
@ -718,6 +719,7 @@ impl DataManager {
),
))
.await?;
}
// ...
Ok(())

View file

@ -1,4 +1,6 @@
use std::collections::HashMap;
use crate::model::{Error, Result};
use super::{
oauth::AuthGrant,
permissions::{FinePermission, SecondaryPermission},
@ -10,6 +12,7 @@ use tetratto_shared::{
snow::Snowflake,
unix_epoch_timestamp,
};
use serde_valid::Validate;
/// `(ip, token, creation timestamp)`
pub type Token = (String, String, usize);
@ -187,13 +190,16 @@ impl Default for DefaultProfileTabChoice {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[derive(Clone, Debug, Serialize, Deserialize, Default, Validate)]
pub struct UserSettings {
#[serde(default)]
#[validate(max_length = 32)]
pub display_name: String,
#[serde(default)]
#[validate(max_length = 4096)]
pub biography: String,
#[serde(default)]
#[validate(max_length = 2048)]
pub warning: String,
#[serde(default)]
pub private_profile: bool,
@ -303,6 +309,7 @@ pub struct UserSettings {
pub private_mails: bool,
/// The user's status. Shows over connection info.
#[serde(default)]
#[validate(max_length = 256)]
pub status: String,
/// The mime type of the user's banner.
#[serde(default = "mime_avif")]
@ -365,9 +372,11 @@ pub struct UserSettings {
pub hide_social_follows: bool,
/// The signature automatically attached to new mail letters.
#[serde(default)]
#[validate(max_length = 2048)]
pub mail_signature: String,
/// The signature automatically attached to new forum posts.
#[serde(default)]
#[validate(max_length = 2048)]
pub forum_signature: String,
/// If coin transfer requests are disabled.
#[serde(default)]
@ -381,6 +390,26 @@ pub struct UserSettings {
/// If the user's system font is always used over Lexend.
#[serde(default)]
pub use_system_font: bool,
/// The user's location. This isn't actually verified or anything, so it can really
/// be whatever the user wants.
#[serde(default)]
#[validate(max_length = 128)]
pub location: String,
/// External links for the user's other profiles on other websites.
#[serde(default)]
#[validate(max_items = 5)]
#[validate(unique_items)]
pub links: Vec<(String, String)>,
}
impl UserSettings {
pub fn verify_values(&self) -> Result<()> {
if let Err(e) = self.validate() {
return Err(Error::MiscError(e.to_string()));
}
Ok(())
}
}
fn mime_avif() -> String {