add: close friends stack
This commit is contained in:
parent
407155e6c4
commit
5fafc8d7b9
83 changed files with 479 additions and 213 deletions
56
Cargo.lock
generated
56
Cargo.lock
generated
|
@ -407,7 +407,7 @@ dependencies = [
|
|||
"pathbufd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tetratto-core 15.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tetratto-core 15.0.2",
|
||||
"tetratto-shared 12.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"toml 0.9.5",
|
||||
]
|
||||
|
@ -3350,7 +3350,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tetratto"
|
||||
version = "15.0.0"
|
||||
version = "16.0.0"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"async-stripe",
|
||||
|
@ -3370,7 +3370,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"tera",
|
||||
"tetratto-core 15.0.2",
|
||||
"tetratto-core 16.0.0",
|
||||
"tetratto-l10n 12.0.0",
|
||||
"tetratto-shared 12.0.6",
|
||||
"tokio",
|
||||
|
@ -3379,31 +3379,6 @@ dependencies = [
|
|||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tetratto-core"
|
||||
version = "15.0.2"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"base16ct",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.2",
|
||||
"buckets-core",
|
||||
"emojis",
|
||||
"md-5",
|
||||
"oiseau",
|
||||
"paste",
|
||||
"pathbufd",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tetratto-l10n 12.0.0",
|
||||
"tetratto-shared 12.0.6",
|
||||
"tokio",
|
||||
"toml 0.9.5",
|
||||
"totp-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tetratto-core"
|
||||
version = "15.0.2"
|
||||
|
@ -3430,6 +3405,31 @@ dependencies = [
|
|||
"totp-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tetratto-core"
|
||||
version = "16.0.0"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"base16ct",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.2",
|
||||
"buckets-core",
|
||||
"emojis",
|
||||
"md-5",
|
||||
"oiseau",
|
||||
"paste",
|
||||
"pathbufd",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tetratto-l10n 12.0.0",
|
||||
"tetratto-shared 12.0.6",
|
||||
"tokio",
|
||||
"toml 0.9.5",
|
||||
"totp-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tetratto-l10n"
|
||||
version = "12.0.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "tetratto"
|
||||
version = "15.0.0"
|
||||
version = "16.0.0"
|
||||
edition = "2024"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
|
|
@ -178,6 +178,7 @@ version = "1.0.0"
|
|||
"settings:tab.theme" = "Theme"
|
||||
"settings:tab.sessions" = "Sessions"
|
||||
"settings:tab.grants" = "Grants"
|
||||
"settings:tab.close_friends" = "Close friends"
|
||||
"settings:tab.images" = "Images"
|
||||
"settings:tab.presets" = "Presets"
|
||||
"settings:label.change_password" = "Change password"
|
||||
|
|
|
@ -193,11 +193,13 @@ p {
|
|||
margin-bottom: var(--pad-4);
|
||||
}
|
||||
|
||||
p,
|
||||
span:not(nav *):not(.dropdown *):not(a *):not(button *),
|
||||
input,
|
||||
textarea {
|
||||
font-weight: 300;
|
||||
body:not(.use_system_font) {
|
||||
& p:not(b *),
|
||||
& span:not(.notification, .name, b *, button *, a *, .dropdown *, nav *),
|
||||
& input,
|
||||
& textarea {
|
||||
font-variation-settings: "wght" 325;
|
||||
}
|
||||
}
|
||||
|
||||
.no_p_margin p:last-child {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Chats - {{ config.name }}"))
|
||||
(text "Chats — {{ config.name }}"))
|
||||
(link ("rel" "stylesheet") ("data-turbo-temporary" "true") ("href" "/css/chats.css?v=tetratto-{{ random_cache_breaker }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"chats\", hide_user_menu=true) }}")
|
||||
(nav
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ community.context.display_name }} - {{ config.name }}"))
|
||||
(text "{{ community.context.display_name }} — {{ config.name }}"))
|
||||
|
||||
(meta
|
||||
("name" "og:title")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Create post - {{ config.name }}"))
|
||||
(text "Create post — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -98,13 +98,13 @@
|
|||
("selected" "{% if selected_community == community.id -%}true{% else %}false{%- endif %}")
|
||||
(text "{% if community.context.display_name -%} {{ community.context.display_name }} {% else %} {{ community.title }} {%- endif %}"))
|
||||
(text "{% endfor %}")
|
||||
(text "{% for stack in stacks %}")
|
||||
(text "{% for stack in stacks %} {% if not stack.is_locked or user.id == stack.owner -%}")
|
||||
(option
|
||||
("value" "{{ stack.id }}")
|
||||
("selected" "{% if selected_stack == stack.id -%}true{% else %}false{%- endif %}")
|
||||
("is_stack" "true")
|
||||
(text "{{ stack.name }} (circle)"))
|
||||
(text "{% endfor %}")))
|
||||
(text "{%- endif %} {% endfor %}")))
|
||||
(form
|
||||
("class" "card flex flex_col gap_2")
|
||||
("id" "create_form")
|
||||
|
@ -445,7 +445,7 @@
|
|||
element.setAttribute(\"alt\", `${id}'s avatar`);
|
||||
|
||||
if (id === town_square || is_stack) {
|
||||
element.src = `/api/v1/auth/user/${user_id}/avatar?selector_type=id`;
|
||||
element.src = `${_app_base.service_hosts.buckets}/avatars/${user_id}`;
|
||||
} else {
|
||||
element.src = `/api/v1/communities/${id}/avatar`;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "My communities - {{ config.name }}"))
|
||||
(text "My communities — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"communities\") }}")
|
||||
(main
|
||||
|
@ -19,7 +19,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Question - {{ config.name }}"))
|
||||
(text "Question — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Search communities - {{ config.name }}"))
|
||||
(text "Search communities — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"communities\") }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Community settings - {{ config.name }}"))
|
||||
(text "Community settings — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
@ -145,7 +145,7 @@
|
|||
("required" "")
|
||||
("minlength" "2")))
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))))
|
||||
(div
|
||||
|
@ -168,7 +168,7 @@
|
|||
("class" "flex gap_2 flex_wrap")
|
||||
(button
|
||||
("onclick" "save_context()")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}")))
|
||||
(a
|
||||
|
@ -200,7 +200,7 @@
|
|||
("class" "w_content"))
|
||||
(button
|
||||
("class" "small square big_icon")
|
||||
(text "{{ icon \"check\" }}"))))
|
||||
(icon (text "check")))))
|
||||
(div
|
||||
("class" "card_nest")
|
||||
("ui_ident" "change_banner")
|
||||
|
@ -223,7 +223,7 @@
|
|||
("class" "w_content"))
|
||||
(button
|
||||
("class" "small square big_icon")
|
||||
(text "{{ icon \"check\" }}")))
|
||||
(icon (text "check"))))
|
||||
(span
|
||||
("class" "fade")
|
||||
(text "Use an image of 1100x350px for the best results.")))))
|
||||
|
@ -271,7 +271,7 @@
|
|||
(button
|
||||
("class" "small lowered")
|
||||
("onclick" "update_user_role(document.getElementById('uid').value, document.getElementById('role').value)")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))
|
||||
(div
|
||||
|
@ -294,7 +294,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
|
@ -453,7 +453,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "name")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "name")
|
||||
|
@ -599,7 +599,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
|
@ -677,7 +677,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
("style" "display: contents")
|
||||
(text "{{ self::post(post=post, owner=owner, secondary=secondary, community=community, show_community=show_community, can_manage_post=can_manage_post, repost=repost, expect_repost=true) }}"))
|
||||
(text "{%- endmacro %}")
|
||||
(text "{% macro post_info(post, community) -%}")
|
||||
(text "{% macro post_info(post, community, owner) -%}")
|
||||
; info about the post: edited, date, etc.
|
||||
(text "{% if post.context.edited != 0 -%}")
|
||||
(div
|
||||
|
@ -167,7 +167,7 @@
|
|||
(text "{%- endif %} {% if post.stack -%}")
|
||||
(a
|
||||
("title" "Posted to a stack you're in")
|
||||
("class" "flex items_center flush")
|
||||
("class" "flex items_center flush {% if post.stack == owner.close_friends_stack -%} green {%- endif %}")
|
||||
("style" "color: var(--color-primary)")
|
||||
("href" "/stacks/{{ post.stack }}")
|
||||
(text "{{ icon \"layers\" }}"))
|
||||
|
@ -386,7 +386,7 @@
|
|||
(span
|
||||
; ("class" "name")
|
||||
(text "{{ self::full_username(user=owner) }}"))
|
||||
(text "{{ self::post_info(post=post, community=community) }}")
|
||||
(text "{{ self::post_info(post=post, community=community, owner=owner) }}")
|
||||
(text "{% if post.context.is_pinned or post.context.is_profile_pinned -%} {{ icon \"pin\" }} {%- endif %}"))
|
||||
(text "{% if not dont_show_title and post.title and community and community.context.enable_titles -%}")
|
||||
; post has a title AND whatever is rendering this component wants to see it
|
||||
|
@ -516,7 +516,7 @@
|
|||
(button
|
||||
("class" "green raised")
|
||||
("onclick" "trigger('me::update_notification_read_status', ['{{ notification.id }}', true])")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"notifs:action.mark_as_read\" }}")))
|
||||
(text "{%- endif %}")
|
||||
|
@ -552,7 +552,7 @@
|
|||
(text "{{ self::community_avatar(id=post.community, community=community, size=\"18px\") }}")))
|
||||
(div
|
||||
("class" "flex gap_2")
|
||||
(text "{{ self::post_info(post=post, community=community) }}")))
|
||||
(text "{{ self::post_info(post=post, community=community, owner=owner) }}")))
|
||||
(div
|
||||
("class" "card_nest_horizontal")
|
||||
; author info
|
||||
|
@ -630,7 +630,7 @@
|
|||
("class" "flex justify_between gap_2 w_full")
|
||||
(text "{% if page > 0 -%}")
|
||||
(a
|
||||
("class" "button lowered")
|
||||
("class" "button lowered pagination_previous")
|
||||
("href" "?page={{ page - 1 }}{{ key }}{{ value }}")
|
||||
(text "{{ icon \"arrow-left\" }}")
|
||||
(span
|
||||
|
@ -639,7 +639,7 @@
|
|||
(div)
|
||||
(text "{%- endif %} {% if items != 0 -%}")
|
||||
(a
|
||||
("class" "button lowered")
|
||||
("class" "button lowered pagination_next")
|
||||
("href" "?page={{ page + 1 }}{{ key }}{{ value }}")
|
||||
(span
|
||||
(text "{{ text \"general:link.next\" }}"))
|
||||
|
@ -2168,7 +2168,15 @@
|
|||
("class" "card secondary flex flex_col gap_2")
|
||||
(div
|
||||
("class" "flex items_center gap_2")
|
||||
(text "{{ icon \"list\" }}")
|
||||
(text "{% if stack.is_locked -%}")
|
||||
(span
|
||||
("class" "red")
|
||||
("title" "Locked stack")
|
||||
(icon (text "lock")))
|
||||
(text "{% else %}")
|
||||
(icon (text "list"))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(b
|
||||
(text "{{ stack.name }}")))
|
||||
(span
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ app.title }} - {{ config.name }}"))
|
||||
(text "{{ app.title }} — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -93,7 +93,7 @@
|
|||
("required" "")
|
||||
("minlength" "2")))
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}")))))
|
||||
(div
|
||||
|
@ -118,7 +118,7 @@
|
|||
("required" "")
|
||||
("minlength" "2")))
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}")))))
|
||||
(div
|
||||
|
@ -143,7 +143,7 @@
|
|||
("required" "")
|
||||
("minlength" "2")))
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}")))))
|
||||
(div
|
||||
|
@ -183,7 +183,7 @@
|
|||
(icon (text "external-link")) (text "Docs"))))
|
||||
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}")))))
|
||||
(div
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Developer panel - {{ config.name }}"))
|
||||
(text "Developer panel — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}")
|
||||
(main
|
||||
|
@ -21,7 +21,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ app.title }} - {{ config.name }}"))
|
||||
(text "{{ app.title }} — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Manage product - {{ config.name }}"))
|
||||
(text "Manage product — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Manage advertisement - {{ config.name }}"))
|
||||
(text "Manage advertisement — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ product.title }} - {{ config.name }}"))
|
||||
(text "{{ product.title }} — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "My products - {{ config.name }}"))
|
||||
(text "My products — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"products\") }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Wallet - {{ config.name }}"))
|
||||
(text "Wallet — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"wallet\") }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
; changes to be more github-like instead of retrospring-like
|
||||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ community.context.display_name }} - {{ config.name }}"))
|
||||
(text "{{ community.context.display_name }} — {{ config.name }}"))
|
||||
|
||||
(meta
|
||||
("name" "og:title")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Forge - {{ config.name }}"))
|
||||
(text "Forge — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}")
|
||||
(main
|
||||
|
@ -20,7 +20,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "title")
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
(text "{% else %}")
|
||||
(title (text "{{ journal.title }}"))
|
||||
(text "{%- endif %} {% else %}")
|
||||
(title (text "Journals - {{ config.name }}"))
|
||||
(title (text "Journals — {{ config.name }}"))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(text "{% if note and journal and owner -%}")
|
||||
|
@ -253,7 +253,7 @@
|
|||
("required" "")
|
||||
("minlength" "2")))
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}")))))))
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "My services - {{ config.name }}"))
|
||||
(text "My services — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "My domains - {{ config.name }}"))
|
||||
(text "My domains — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -35,7 +35,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "name")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "name")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "My services - {{ config.name }}"))
|
||||
(text "My services — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "My services - {{ config.name }}"))
|
||||
(text "My services — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -35,7 +35,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "name")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "name")
|
||||
|
|
|
@ -387,4 +387,10 @@
|
|||
(text "{{ icon \"cable\" }}")
|
||||
(span
|
||||
(text "{{ text \"settings:tab.grants\" }}")))
|
||||
(a
|
||||
("data-tab-button" "friends")
|
||||
("href" "#/friends")
|
||||
(text "{{ icon \"book-user\" }}")
|
||||
(span
|
||||
(text "{{ text \"settings:tab.close_friends\" }}")))
|
||||
(text "{%- endmacro %}")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Compose letter - {{ config.name }}"))
|
||||
(text "Compose letter — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Letter - {{ config.name }}"))
|
||||
(text "Letter — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Received mail - {{ config.name }}"))
|
||||
(text "Received mail — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Sent mail - {{ config.name }}"))
|
||||
(text "Sent mail — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Achievements - {{ config.name }}"))
|
||||
(text "Achievements — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"achievements\") }}")
|
||||
(main
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title (text "{{ error_text }} - {{ config.name }}"))
|
||||
(title (text "{{ error_text }} — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
|
||||
(main
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title (text "{{ file_name }} - {{ config.name }}"))
|
||||
(title (text "{{ file_name }} — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Notifications - {{ config.name }}"))
|
||||
(text "Notifications — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"notifications\") }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Requests - {{ config.name }}"))
|
||||
(text "Requests — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"requests\") }}")
|
||||
(main
|
||||
|
@ -83,7 +83,7 @@
|
|||
(button
|
||||
("class" "lowered green")
|
||||
("onclick" "accept_follow_request(event, '{{ request.id }}')")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.accept\" }}")))
|
||||
(button
|
||||
|
@ -114,7 +114,7 @@
|
|||
(button
|
||||
("class" "lowered green")
|
||||
("onclick" "accept_transfer_request(event, '{{ request.id }}', '{{ request.linked_asset }}', {{ request.data.Int32 }})")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.accept\" }}")))
|
||||
(button
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Audit log - {{ config.name }}"))
|
||||
(text "Audit log — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "File report - {{ config.name }}"))
|
||||
(text "File report — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "IP Bans - {{ config.name }}"))
|
||||
(text "IP Bans — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Manage profile - {{ config.name }}"))
|
||||
(text "Manage profile — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -356,7 +356,7 @@
|
|||
(button
|
||||
("class" "small lowered")
|
||||
("onclick" "update_user_role(Number.parseInt(document.getElementById('role').value))")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))
|
||||
(div
|
||||
|
@ -374,7 +374,7 @@
|
|||
(button
|
||||
("class" "small lowered")
|
||||
("onclick" "update_user_secondary_role(Number.parseInt(document.getElementById('secondary_role').value))")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))
|
||||
(div
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Reports - {{ config.name }}"))
|
||||
(text "Reports — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Server stats - {{ config.name }}"))
|
||||
(text "Server stats — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "User warnings - {{ config.name }}"))
|
||||
(text "User warnings — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Post likes - {{ config.name }}"))
|
||||
(text "Post likes — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Post - {{ config.name }}"))
|
||||
(text "Post — {{ config.name }}"))
|
||||
|
||||
(meta
|
||||
("name" "og:title")
|
||||
|
@ -146,7 +146,7 @@
|
|||
("id" "post_context")))
|
||||
(button
|
||||
("onclick" "save_context()")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}")))
|
||||
(script
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Post quotes - {{ config.name }}"))
|
||||
(text "Post quotes — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Post reposts - {{ config.name }}"))
|
||||
(text "Post reposts — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ profile.username }} (banned) - {{ config.name }}"))
|
||||
(text "{{ profile.username }} (banned) — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ profile.username }} - {{ config.name }}"))
|
||||
(text "{{ profile.username }} — {{ config.name }}"))
|
||||
|
||||
(meta
|
||||
("name" "og:title")
|
||||
|
@ -57,7 +57,15 @@
|
|||
(div
|
||||
("class" "card flex gap_2")
|
||||
("id" "user_avatar_and_name")
|
||||
(text "{% if user and close_friends_stack and (user.id in close_friends_stack.users) -%}")
|
||||
(a
|
||||
("style" "border: solid 2px var(--color-yellow); border-radius: calc(var(--radius) / 1.2); height: max-content")
|
||||
("href" "/stacks/{{ close_friends_stack.id }}")
|
||||
(text "{{ components::avatar(id=profile.id, size=\"72px\") }}"))
|
||||
(text "{% else %}")
|
||||
(text "{{ components::avatar(id=profile.id, size=\"72px\") }}")
|
||||
(text "{%- endif %}")
|
||||
|
||||
(div
|
||||
("class" "flex flex_col")
|
||||
(h3
|
||||
|
@ -131,13 +139,21 @@
|
|||
(span
|
||||
(text "{{ text \"auth:label.following\" }}"))))
|
||||
(text "{%- endif %}")
|
||||
(div
|
||||
("class" "flex gap_2")
|
||||
(text "{% if is_following_you -%}")
|
||||
(b
|
||||
("class" "notification chip w_content flex items_center gap_2")
|
||||
(text "{{ icon \"heart\" }}")
|
||||
(span
|
||||
(text "Follows you")))
|
||||
(text "{%- endif %}")))
|
||||
(icon (text "heart"))
|
||||
(span (text "Follows you")))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(text "{% if user and close_friends_stack and (user.id in close_friends_stack.users) -%}")
|
||||
(b
|
||||
("class" "notification chip w_content flex items_center gap_2")
|
||||
(icon (text "heart"))
|
||||
(span (text "Friends")))
|
||||
(text "{%- endif %}"))))
|
||||
(div
|
||||
("class" "card_nest flex flex_col")
|
||||
(div
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ profile.username }} (blocked) - {{ config.name }}"))
|
||||
(text "{{ profile.username }} (blocked) — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ profile.username }} (private profile) - {{ config.name }}"))
|
||||
(text "{{ profile.username }} (private profile) — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Settings - {{ config.name }}"))
|
||||
(text "Settings — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(article
|
||||
("class" "flex flex_row gap_2 content_container")
|
||||
|
@ -280,7 +280,7 @@
|
|||
("required" "")
|
||||
("minlength" "2")))
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))))
|
||||
(div
|
||||
|
@ -335,7 +335,7 @@
|
|||
(button
|
||||
("onclick" "save_settings()")
|
||||
("id" "save_button")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))
|
||||
(div
|
||||
|
@ -442,7 +442,7 @@
|
|||
("minlength" "6")
|
||||
("autocomplete" "off")))
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}")))))))))
|
||||
(div
|
||||
|
@ -959,7 +959,7 @@
|
|||
("class" "w_content"))
|
||||
(button
|
||||
("class" "small square big_icon")
|
||||
(text "{{ icon \"check\" }}")))
|
||||
(icon (text "check"))))
|
||||
(span
|
||||
("class" "fade")
|
||||
(text "Images must be less than 8 MB large. Animated GIFs are
|
||||
|
@ -987,7 +987,7 @@
|
|||
("class" "w_content"))
|
||||
(button
|
||||
("class" "small square big_icon")
|
||||
(text "{{ icon \"check\" }}")))
|
||||
(icon (text "check"))))
|
||||
(span
|
||||
("class" "fade")
|
||||
(text "Use an image of 1100x350px for the best results."))))
|
||||
|
@ -1032,7 +1032,7 @@
|
|||
(button
|
||||
("onclick" "save_settings()")
|
||||
("id" "save_button")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))
|
||||
(div
|
||||
|
@ -1191,7 +1191,7 @@
|
|||
(button
|
||||
("onclick" "save_settings()")
|
||||
("id" "save_button")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))
|
||||
(div
|
||||
|
@ -1304,6 +1304,48 @@
|
|||
(span
|
||||
(text "{{ config.name }} ")
|
||||
(str (text "developer:label.for_developers")))))
|
||||
(div
|
||||
("class" "card w_full lowered hidden flex flex_col gap_2")
|
||||
("data-tab" "friends")
|
||||
(h3 (text "Close friends stack"))
|
||||
(p (text "Your close friends stack is a special stack where you can create posts for only your friends to see!"))
|
||||
(text "{% if user.close_friends_stack -%}")
|
||||
(div
|
||||
("class" "flex gap_2")
|
||||
(a
|
||||
("href" "/stacks/{{ user.close_friends_stack }}/manage#/users")
|
||||
("class" "button")
|
||||
(icon (text "settings"))
|
||||
(text "Manage users"))
|
||||
(a
|
||||
("href" "/communities/intents/post?stack={{ user.close_friends_stack }}")
|
||||
("class" "button raised")
|
||||
(icon (text "plus"))
|
||||
(text "Post")))
|
||||
(text "{% else %}")
|
||||
(button
|
||||
("onclick" "create_close_friends_stack()")
|
||||
(icon (text "plus"))
|
||||
(text "Create stack"))
|
||||
|
||||
(script
|
||||
(text "globalThis.create_close_friends_stack = () => {
|
||||
fetch(\"/api/v1/stacks/close_friends\", {
|
||||
method: \"POST\",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger(\"atto::toast\", [
|
||||
res.ok ? \"success\" : \"error\",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
if (res.ok) {
|
||||
window.location.href = `/stacks/${res.payload}/manage#/users`;
|
||||
}
|
||||
});
|
||||
}"))
|
||||
(text "{%- endif %}"))
|
||||
(script
|
||||
("type" "application/json")
|
||||
("id" "settings_json")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ profile.username }} (warning) - {{ config.name }}"))
|
||||
(text "{{ profile.username }} (warning) — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -26,7 +26,7 @@
|
|||
("class" "card w_full secondary flex gap_2")
|
||||
(button
|
||||
("onclick" "trigger('warnings::accept', ['{{ profile.id }}', '{{ warning_hash }}'])")
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"dialog:action.continue\" }}")))
|
||||
(a
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Add user to stack - {{ config.name }}"))
|
||||
(text "Add user to stack — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"\") }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "{{ stack.name }} - {{ config.name }}"))
|
||||
(text "{{ stack.name }} — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -19,7 +19,7 @@
|
|||
(text "{{ stack.name }}")))
|
||||
(div
|
||||
("class" "flex gap_2")
|
||||
(text "{% if stack.mode == 'Circle' -%}")
|
||||
(text "{% if stack.mode == 'Circle' and (not stack.is_locked or user.id == stack.owner) -%}")
|
||||
; post button for circle stacks
|
||||
(a
|
||||
("href" "/communities/intents/post?stack={{ stack.id }}")
|
||||
|
@ -94,7 +94,7 @@
|
|||
(text "{% set paged = user and user.settings.paged_timelines %}")
|
||||
(script
|
||||
(text "setTimeout(() => {
|
||||
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?stack_id={{ stack.id }}&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
|
||||
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?stack_id={{ stack.id }}&before=$1$&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
|
||||
});"))
|
||||
(text "{%- endif %}"))))
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "My stacks - {{ config.name }}"))
|
||||
(text "My stacks — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -19,7 +19,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "name")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "name")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Stack settings - {{ config.name }}"))
|
||||
(text "Stack settings — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -56,6 +56,7 @@
|
|||
("class" "card")
|
||||
(select
|
||||
("onchange" "save_mode(event)")
|
||||
("disabled" "{{ stack.is_locked }}")
|
||||
(option
|
||||
("value" "Include")
|
||||
("selected" "{% if stack.mode == 'Include' -%}true{% else %}false{%- endif %}")
|
||||
|
@ -105,7 +106,7 @@
|
|||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "name")
|
||||
(text "{{ text \"communities:label.name\" }}"))
|
||||
(str (text "communities:label.name")))
|
||||
(input
|
||||
("type" "text")
|
||||
("name" "name")
|
||||
|
@ -114,9 +115,10 @@
|
|||
("required" "")
|
||||
("minlength" "2")))
|
||||
(button
|
||||
(text "{{ icon \"check\" }}")
|
||||
(icon (text "check"))
|
||||
(span
|
||||
(text "{{ text \"general:action.save\" }}"))))))
|
||||
(text "{% if not stack.is_locked -%}")
|
||||
(div
|
||||
("class" "card_nest")
|
||||
("ui_ident" "danger_zone")
|
||||
|
@ -132,7 +134,8 @@
|
|||
("onclick" "delete_stack()")
|
||||
(text "{{ icon \"trash\" }}")
|
||||
(span
|
||||
(text "{{ text \"general:action.delete\" }}"))))))
|
||||
(text "{{ text \"general:action.delete\" }}")))))
|
||||
(text "{%- endif %}"))
|
||||
(div
|
||||
("class" "card w_full flex flex_col gap_2 hidden")
|
||||
("data-tab" "users")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Latest posts - {{ config.name }}"))
|
||||
(text "Latest posts — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Latest forum posts - {{ config.name }}"))
|
||||
(text "Latest forum posts — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Latest questions - {{ config.name }}"))
|
||||
(text "Latest questions — {{ config.name }}"))
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
("class" "flex flex_col gap_2")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Following - {{ config.name }}"))
|
||||
(text "Following — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
@ -14,7 +14,7 @@
|
|||
(text "{% set paged = user and user.settings.paged_timelines %}")
|
||||
(script
|
||||
(text "setTimeout(() => {
|
||||
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=FollowingPosts&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
|
||||
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=FollowingPosts&before=$1$&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
|
||||
});"))
|
||||
|
||||
(text "{% endblock %}")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Following (questions) - {{ config.name }}"))
|
||||
(text "Following (questions) — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
(text "{% set paged = user and user.settings.paged_timelines %}")
|
||||
(script
|
||||
(text "setTimeout(() => {
|
||||
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=MyCommunities&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
|
||||
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=MyCommunities&before=$1$&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
|
||||
});"))
|
||||
|
||||
(text "{% endblock %}")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "From my communities (questions) - {{ config.name }}"))
|
||||
(text "From my communities (questions) — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Popular - {{ config.name }}"))
|
||||
(text "Popular — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav(selected=\"popular\") }}")
|
||||
(main
|
||||
|
@ -14,7 +14,7 @@
|
|||
(text "{% set paged = user and user.settings.paged_timelines %}")
|
||||
(script
|
||||
(text "setTimeout(() => {
|
||||
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=PopularPosts&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
|
||||
trigger(\"ui::io_data_load\", [\"/_swiss_army_timeline?tl=PopularPosts&before=$1$&page=\", Number.parseInt(\"{{ page }}\") - 1, \"{{ paged }}\" === \"true\"]);
|
||||
});"))
|
||||
|
||||
(text "{% endblock %}")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Popular (questions) - {{ config.name }}"))
|
||||
(text "Popular (questions) — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{% extends \"root.html\" %} {% block head %}")
|
||||
(title
|
||||
(text "Search - {{ config.name }}"))
|
||||
(text "Search — {{ config.name }}"))
|
||||
|
||||
(text "{% endblock %} {% block body %} {{ macros::nav() }}")
|
||||
(main
|
||||
|
|
|
@ -1223,6 +1223,8 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
|
|||
return;
|
||||
}
|
||||
|
||||
const search = new URLSearchParams(window.location.search);
|
||||
|
||||
self.IO_DATA_TMPL = tmpl;
|
||||
self.IO_DATA_PAGE = page;
|
||||
self.IO_DATA_SEEN_IDS = [];
|
||||
|
@ -1230,7 +1232,7 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
|
|||
self.IO_HAS_LOADED_AT_LEAST_ONCE = false;
|
||||
self.IO_DATA_DISCONNECTED = false;
|
||||
self.IO_DATA_DISABLE_RELOAD = false;
|
||||
self.IO_DATA_LOAD_BEFORE = 0;
|
||||
self.IO_DATA_LOAD_BEFORE = search.get("before") || "0";
|
||||
|
||||
if (!paginated_mode) {
|
||||
self.IO_DATA_OBSERVER.observe(self.IO_DATA_MARKER);
|
||||
|
@ -1239,6 +1241,21 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
|
|||
self.IO_DATA_TMPL = self.IO_DATA_TMPL.replace("&page=", "");
|
||||
self.IO_DATA_TMPL += `&paginated=true&page=`;
|
||||
self.io_load_data();
|
||||
|
||||
// update pagination buttons
|
||||
setTimeout(() => {
|
||||
for (const button of document.querySelectorAll(
|
||||
".pagination_previous",
|
||||
)) {
|
||||
button.href += `&before=${self.IO_DATA_LOAD_BEFORE_PREVIOUS}`;
|
||||
}
|
||||
|
||||
for (const button of document.querySelectorAll(
|
||||
".pagination_next",
|
||||
)) {
|
||||
button.href += `&before=${self.IO_DATA_LOAD_BEFORE}`;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
|
@ -1324,6 +1341,7 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
|
|||
].after(self.IO_DATA_MARKER);
|
||||
|
||||
// push ids
|
||||
let first = "";
|
||||
for (const opt of Array.from(
|
||||
document.querySelectorAll(
|
||||
`[ui_ident=list_posts_${self.IO_DATA_PAGE}] option`,
|
||||
|
@ -1335,8 +1353,14 @@ ${option.input_element_type === "textarea" ? `${option.value}</textarea>` : ""}
|
|||
self.IO_DATA_SEEN_IDS.push(v);
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
first = opt.getAttribute("data-created");
|
||||
}
|
||||
|
||||
self.IO_DATA_LOAD_BEFORE = opt.getAttribute("data-created");
|
||||
}
|
||||
|
||||
self.IO_DATA_LOAD_BEFORE_PREVIOUS = first;
|
||||
}, 150);
|
||||
|
||||
// run hooks
|
||||
|
|
|
@ -850,7 +850,7 @@ pub async fn all_request(
|
|||
};
|
||||
|
||||
match data
|
||||
.get_latest_posts(12, props.page, &Some(user.clone()), props.before)
|
||||
.get_latest_posts(12, &Some(user.clone()), props.before)
|
||||
.await
|
||||
{
|
||||
Ok(posts) => {
|
||||
|
|
|
@ -674,6 +674,10 @@ pub fn routes() -> Router {
|
|||
.route("/stacks/{id}/block", post(stacks::block_request))
|
||||
.route("/stacks/{id}/block", delete(stacks::unblock_request))
|
||||
.route("/stacks/{id}", delete(stacks::delete_request))
|
||||
.route(
|
||||
"/stacks/close_friends",
|
||||
post(stacks::create_close_friends_request),
|
||||
)
|
||||
// journals
|
||||
.route("/journals", get(journals::list_request))
|
||||
.route("/journals", post(journals::create_request))
|
||||
|
|
|
@ -91,6 +91,44 @@ pub async fn create_request(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn create_close_friends_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateStacks) {
|
||||
Some(ua) => ua,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
if user.close_friends_stack != 0 {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
let mut stack = UserStack::new("Close friends".to_string(), user.id, Vec::new());
|
||||
stack.mode = StackMode::Circle;
|
||||
stack.is_locked = true;
|
||||
|
||||
match data.create_stack(stack).await {
|
||||
Ok(s) => {
|
||||
if let Err(e) = data
|
||||
.update_user_close_friends_stack(user.id, s.id as i64)
|
||||
.await
|
||||
{
|
||||
return Json(e.into());
|
||||
}
|
||||
|
||||
// ...
|
||||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Stack created".to_string(),
|
||||
payload: s.id.to_string(),
|
||||
})
|
||||
}
|
||||
Err(e) => Json(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn clone_request(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
|
@ -168,6 +206,17 @@ pub async fn update_mode_request(
|
|||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
// make sure stack is not locked
|
||||
let stack = match data.get_stack_by_id(id).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
if stack.is_locked {
|
||||
return Json(Error::NotAllowed.into());
|
||||
}
|
||||
|
||||
// ...
|
||||
match data.update_stack_mode(id, &user, req.mode).await {
|
||||
Ok(_) => Json(ApiReturn {
|
||||
ok: true,
|
||||
|
|
|
@ -317,7 +317,7 @@ pub async fn all_forum_posts_request(
|
|||
let ignore_users = crate::ignore_users_gen!(user!, data);
|
||||
let list = match data
|
||||
.0
|
||||
.get_latest_forum_posts(48, req.page, &Some(user.clone()), req.before)
|
||||
.get_latest_forum_posts(48, req.page, &Some(user.clone()))
|
||||
.await
|
||||
{
|
||||
Ok(l) => match data
|
||||
|
@ -776,17 +776,15 @@ async fn swiss_army_timeline(
|
|||
// everything else
|
||||
match req.tl {
|
||||
DefaultTimelineChoice::AllPosts => {
|
||||
data.0
|
||||
.get_latest_posts(12, req.page, &user, req.before)
|
||||
.await
|
||||
data.0.get_latest_posts(12, &user, req.before).await
|
||||
}
|
||||
DefaultTimelineChoice::PopularPosts => {
|
||||
data.0.get_popular_posts(12, req.page, 604_800_000).await
|
||||
data.0.get_popular_posts(12, req.before, 604_800_000).await
|
||||
}
|
||||
DefaultTimelineChoice::FollowingPosts => {
|
||||
if let Some(ref ua) = user {
|
||||
data.0
|
||||
.get_posts_from_user_following(ua.id, 12, req.page)
|
||||
.get_posts_from_user_following(ua.id, 12, req.before)
|
||||
.await
|
||||
} else {
|
||||
return Err(Html(
|
||||
|
@ -797,7 +795,7 @@ async fn swiss_army_timeline(
|
|||
DefaultTimelineChoice::MyCommunities => {
|
||||
if let Some(ref ua) = user {
|
||||
data.0
|
||||
.get_posts_from_user_communities(ua.id, 12, req.page, ua)
|
||||
.get_posts_from_user_communities(ua.id, 12, req.before, ua)
|
||||
.await
|
||||
} else {
|
||||
return Err(Html(
|
||||
|
|
|
@ -15,6 +15,7 @@ use tetratto_core::model::{
|
|||
auth::{DefaultProfileTabChoice, User},
|
||||
communities::Community,
|
||||
permissions::FinePermission,
|
||||
stacks::UserStack,
|
||||
Error,
|
||||
};
|
||||
use tetratto_shared::hash::hash;
|
||||
|
@ -244,6 +245,7 @@ pub fn profile_context(
|
|||
is_following: bool,
|
||||
is_following_you: bool,
|
||||
is_blocking: bool,
|
||||
close_friends_stack: Option<UserStack>,
|
||||
) {
|
||||
context.insert("profile", &profile);
|
||||
context.insert("communities", &communities);
|
||||
|
@ -253,6 +255,7 @@ pub fn profile_context(
|
|||
context.insert("is_blocking", &is_blocking);
|
||||
context.insert("warning_hash", &hash(profile.settings.warning.clone()));
|
||||
context.insert("applied_configurations", &applied_configurations);
|
||||
context.insert("close_friends_stack", &close_friends_stack);
|
||||
|
||||
context.insert(
|
||||
"is_supporter",
|
||||
|
@ -394,6 +397,14 @@ pub async fn posts_request(
|
|||
is_following,
|
||||
is_following_you,
|
||||
is_blocking,
|
||||
if other_user.close_friends_stack != 0 {
|
||||
match data.0.get_stack_by_id(other_user.close_friends_stack).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// return
|
||||
|
@ -514,6 +525,14 @@ pub async fn replies_request(
|
|||
is_following,
|
||||
is_following_you,
|
||||
is_blocking,
|
||||
if other_user.close_friends_stack != 0 {
|
||||
match data.0.get_stack_by_id(other_user.close_friends_stack).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// return
|
||||
|
@ -630,6 +649,14 @@ pub async fn media_request(
|
|||
is_following,
|
||||
is_following_you,
|
||||
is_blocking,
|
||||
if other_user.close_friends_stack != 0 {
|
||||
match data.0.get_stack_by_id(other_user.close_friends_stack).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// return
|
||||
|
@ -723,6 +750,14 @@ pub async fn shop_request(
|
|||
is_following,
|
||||
is_following_you,
|
||||
is_blocking,
|
||||
if other_user.close_friends_stack != 0 {
|
||||
match data.0.get_stack_by_id(other_user.close_friends_stack).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// return
|
||||
|
@ -821,6 +856,14 @@ pub async fn outbox_request(
|
|||
is_following,
|
||||
is_following_you,
|
||||
is_blocking,
|
||||
if other_user.close_friends_stack != 0 {
|
||||
match data.0.get_stack_by_id(other_user.close_friends_stack).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// return
|
||||
|
@ -934,6 +977,14 @@ pub async fn following_request(
|
|||
is_following,
|
||||
is_following_you,
|
||||
is_blocking,
|
||||
if other_user.close_friends_stack != 0 {
|
||||
match data.0.get_stack_by_id(other_user.close_friends_stack).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// return
|
||||
|
@ -1047,6 +1098,14 @@ pub async fn followers_request(
|
|||
is_following,
|
||||
is_following_you,
|
||||
is_blocking,
|
||||
if other_user.close_friends_stack != 0 {
|
||||
match data.0.get_stack_by_id(other_user.close_friends_stack).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
|
||||
// return
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "tetratto-core"
|
||||
description = "The core behind Tetratto"
|
||||
version = "15.0.2"
|
||||
version = "16.0.0"
|
||||
edition = "2024"
|
||||
readme = "../../README.md"
|
||||
authors.workspace = true
|
||||
|
|
|
@ -130,6 +130,7 @@ impl DataManager {
|
|||
checkouts: serde_json::from_str(&get!(x->32(String)).to_string()).unwrap(),
|
||||
applied_configurations: serde_json::from_str(&get!(x->33(String)).to_string()).unwrap(),
|
||||
last_policy_consent: get!(x->34(i64)) as usize,
|
||||
close_friends_stack: get!(x->35(i64)) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,7 +287,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35)",
|
||||
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -322,7 +323,8 @@ impl DataManager {
|
|||
&(data.coins as i32),
|
||||
&serde_json::to_string(&data.checkouts).unwrap(),
|
||||
&serde_json::to_string(&data.applied_configurations).unwrap(),
|
||||
&(data.last_policy_consent as i64)
|
||||
&(data.last_policy_consent as i64),
|
||||
&(data.close_friends_stack as i64)
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -1138,6 +1140,7 @@ impl DataManager {
|
|||
auto_method!(update_user_checkouts(Vec<String>)@get_user_by_id -> "UPDATE users SET checkouts = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||
auto_method!(update_user_applied_configurations(Vec<(AppliedConfigType, usize)>)@get_user_by_id -> "UPDATE users SET applied_configurations = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||
auto_method!(update_user_last_policy_consent(i64)@get_user_by_id -> "UPDATE users SET last_policy_consent = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||
auto_method!(update_user_close_friends_stack(i64)@get_user_by_id -> "UPDATE users SET close_friends_stack = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||
|
||||
auto_method!(get_user_by_stripe_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE stripe_id = $1" --name="user" --returns=User);
|
||||
auto_method!(update_user_stripe_id(&str)@get_user_by_id -> "UPDATE users SET stripe_id = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||
|
|
|
@ -6,5 +6,6 @@ CREATE TABLE IF NOT EXISTS stacks (
|
|||
users TEXT NOT NULL,
|
||||
privacy TEXT NOT NULL,
|
||||
mode TEXT NOT NULL,
|
||||
sort TEXT NOT NULL
|
||||
sort TEXT NOT NULL,
|
||||
is_locked INT NOT NULL
|
||||
)
|
||||
|
|
|
@ -33,5 +33,6 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
coins INT NOT NULL,
|
||||
checkouts TEXT NOT NULL,
|
||||
applied_configurations TEXT NOT NULL,
|
||||
last_policy_consent BIGINT NOT NULL
|
||||
last_policy_consent BIGINT NOT NULL,
|
||||
close_friends_stack BIGINT NOT NULL
|
||||
)
|
||||
|
|
|
@ -77,3 +77,11 @@ ADD COLUMN IF NOT EXISTS likes INT DEFAULT 0;
|
|||
-- letters dislikes
|
||||
ALTER TABLE letters
|
||||
ADD COLUMN IF NOT EXISTS dislikes INT DEFAULT 0;
|
||||
|
||||
-- users close_friends_stack
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS close_friends_stack BIGINT DEFAULT 0;
|
||||
|
||||
-- stacks is_locked
|
||||
ALTER TABLE stacks
|
||||
ADD COLUMN IF NOT EXISTS is_locked INT DEFAULT 0;
|
||||
|
|
|
@ -1209,12 +1209,12 @@ impl DataManager {
|
|||
/// # Arguments
|
||||
/// * `id` - the ID of the stack the requested posts belong to
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
/// * `before` - the timestamp to pull posts before
|
||||
pub async fn get_posts_by_stack(
|
||||
&self,
|
||||
id: usize,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
before: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
let conn = match self.0.connect().await {
|
||||
Ok(c) => c,
|
||||
|
@ -1223,8 +1223,17 @@ impl DataManager {
|
|||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM posts WHERE stack = $1 AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $2 OFFSET $3",
|
||||
&[&(id as i64), &(batch as i64), &((page * batch) as i64)],
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE stack = $1 AND replying_to = 0 AND is_deleted = 0{} ORDER BY created DESC LIMIT $2",
|
||||
{
|
||||
if before > 0 {
|
||||
format!(" AND created < {before}")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
),
|
||||
&[&(id as i64), &(batch as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
|
@ -1452,12 +1461,12 @@ impl DataManager {
|
|||
///
|
||||
/// # Arguments
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
/// * `before` - the timestamp to pull posts before
|
||||
/// * `cutoff` - the maximum number of milliseconds ago the post could have been created
|
||||
pub async fn get_popular_posts(
|
||||
&self,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
before: usize,
|
||||
cutoff: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
let conn = match self.0.connect().await {
|
||||
|
@ -1467,18 +1476,24 @@ impl DataManager {
|
|||
|
||||
let res = query_rows!(
|
||||
&conn,
|
||||
"SELECT * FROM posts WHERE replying_to = 0 AND NOT context LIKE '%\"is_nsfw\":true%' AND ($1 - created) < $2 ORDER BY likes - dislikes DESC, created ASC LIMIT $3 OFFSET $4",
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE replying_to = 0 AND NOT context LIKE '%\"is_nsfw\":true%' AND ($1 - created) < $2{} ORDER BY (likes - dislikes) DESC, created ASC LIMIT $3",
|
||||
if before > 0 {
|
||||
format!(" AND created < {before}")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
&[
|
||||
&(unix_epoch_timestamp() as i64),
|
||||
&(cutoff as i64),
|
||||
&(batch as i64),
|
||||
&((page * batch) as i64)
|
||||
],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1492,7 +1507,6 @@ impl DataManager {
|
|||
pub async fn get_latest_posts(
|
||||
&self,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
as_user: &Option<User>,
|
||||
before_time: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
|
@ -1518,7 +1532,7 @@ impl DataManager {
|
|||
let res = query_rows!(
|
||||
&conn,
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE replying_to = 0{}{}{} AND NOT context LIKE '%\"full_unlist\":true%' AND topic = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
"SELECT * FROM posts WHERE replying_to = 0{}{}{} AND NOT context LIKE '%\"full_unlist\":true%' AND topic = 0 ORDER BY created DESC LIMIT $1",
|
||||
if before_time > 0 {
|
||||
format!(" AND created < {before_time}")
|
||||
} else {
|
||||
|
@ -1535,12 +1549,12 @@ impl DataManager {
|
|||
""
|
||||
}
|
||||
),
|
||||
&[&(batch as i64), &((page * batch) as i64)],
|
||||
&[&(batch as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1556,7 +1570,6 @@ impl DataManager {
|
|||
batch: usize,
|
||||
page: usize,
|
||||
as_user: &Option<User>,
|
||||
before_time: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
// check if we should hide nsfw posts
|
||||
let mut hide_nsfw: bool = true;
|
||||
|
@ -1574,12 +1587,7 @@ impl DataManager {
|
|||
let res = query_rows!(
|
||||
&conn,
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE replying_to = 0{}{} AND NOT context LIKE '%\"full_unlist\":true%' AND NOT topic = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
if before_time > 0 {
|
||||
format!(" AND created < {before_time}")
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
"SELECT * FROM posts WHERE replying_to = 0{} AND NOT context LIKE '%\"full_unlist\":true%' AND NOT topic = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
if hide_nsfw {
|
||||
" AND NOT context LIKE '%\"is_nsfw\":true%'"
|
||||
} else {
|
||||
|
@ -1590,8 +1598,8 @@ impl DataManager {
|
|||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1602,12 +1610,12 @@ impl DataManager {
|
|||
/// # Arguments
|
||||
/// * `id` - the ID of the user
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
/// * `before` - the timestamp to pull posts before
|
||||
pub async fn get_posts_from_user_communities(
|
||||
&self,
|
||||
id: usize,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
before: usize,
|
||||
user: &User,
|
||||
) -> Result<Vec<Post>> {
|
||||
let memberships = self.get_memberships_by_owner(id).await?;
|
||||
|
@ -1635,20 +1643,25 @@ impl DataManager {
|
|||
let res = query_rows!(
|
||||
&conn,
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE (community = {} {query_string}){} AND NOT context LIKE '%\"full_unlist\":true%' AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
"SELECT * FROM posts WHERE (community = {} {query_string}){}{} AND NOT context LIKE '%\"full_unlist\":true%' AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $1",
|
||||
first.community,
|
||||
if hide_nsfw {
|
||||
" AND NOT context LIKE '%\"is_nsfw\":true%'"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if before > 0 {
|
||||
format!(" AND created < {before}")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
&[&(batch as i64), &((page * batch) as i64)],
|
||||
&[&(batch as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1659,12 +1672,12 @@ impl DataManager {
|
|||
/// # Arguments
|
||||
/// * `id` - the ID of the user
|
||||
/// * `batch` - the limit of posts in each page
|
||||
/// * `page` - the page number
|
||||
/// * `before` - the timestamp to pull posts before
|
||||
pub async fn get_posts_from_user_following(
|
||||
&self,
|
||||
id: usize,
|
||||
batch: usize,
|
||||
page: usize,
|
||||
before: usize,
|
||||
) -> Result<Vec<Post>> {
|
||||
let following = self.get_userfollows_by_initiator_all(id).await?;
|
||||
let mut following = following.iter();
|
||||
|
@ -1688,15 +1701,20 @@ impl DataManager {
|
|||
let res = query_rows!(
|
||||
&conn,
|
||||
&format!(
|
||||
"SELECT * FROM posts WHERE (owner = {id} OR owner = {} {query_string}) AND replying_to = 0 AND is_deleted = 0 ORDER BY created DESC LIMIT $1 OFFSET $2",
|
||||
first.receiver
|
||||
"SELECT * FROM posts WHERE (owner = {id} OR owner = {} {query_string}) AND replying_to = 0 AND is_deleted = 0{} ORDER BY created DESC LIMIT $1",
|
||||
first.receiver,
|
||||
if before > 0 {
|
||||
format!(" AND created < {before}")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
&[&(batch as i64), &((page * batch) as i64)],
|
||||
&[&(batch as i64)],
|
||||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1750,8 +1768,8 @@ impl DataManager {
|
|||
|x| { Self::get_post_from_row(x) }
|
||||
);
|
||||
|
||||
if res.is_err() {
|
||||
return Err(Error::GeneralNotFound("post".to_string()));
|
||||
if let Err(e) = res {
|
||||
return Err(Error::DatabaseError(e.to_string()));
|
||||
}
|
||||
|
||||
Ok(res.unwrap())
|
||||
|
@ -1830,9 +1848,16 @@ impl DataManager {
|
|||
));
|
||||
}
|
||||
|
||||
if !stack.is_locked || data.replying_to.is_some() {
|
||||
if stack.owner != data.owner && !stack.users.contains(&data.owner) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
} else {
|
||||
// only the owner can post in locked stacks UNLESS we're creating a reply
|
||||
if stack.owner != data.owner {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
|
|
@ -23,6 +23,7 @@ impl DataManager {
|
|||
privacy: serde_json::from_str(&get!(x->5(String))).unwrap(),
|
||||
mode: serde_json::from_str(&get!(x->6(String))).unwrap(),
|
||||
sort: serde_json::from_str(&get!(x->7(String))).unwrap(),
|
||||
is_locked: get!(x->8(i32)) == 1,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +57,7 @@ impl DataManager {
|
|||
match stack.sort {
|
||||
StackSort::Created => {
|
||||
self.fill_posts_with_community(
|
||||
self.get_latest_posts(batch, page, &user, 0).await?,
|
||||
self.get_latest_posts(batch, &user, 0).await?,
|
||||
as_user_id,
|
||||
&ignore_users,
|
||||
user,
|
||||
|
@ -184,7 +185,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO stacks VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
"INSERT INTO stacks VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -194,6 +195,7 @@ impl DataManager {
|
|||
&serde_json::to_string(&data.privacy).unwrap(),
|
||||
&serde_json::to_string(&data.mode).unwrap(),
|
||||
&serde_json::to_string(&data.sort).unwrap(),
|
||||
&if data.is_locked { 1 } else { 0 },
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -207,6 +209,10 @@ impl DataManager {
|
|||
pub async fn delete_stack(&self, id: usize, user: &User) -> Result<()> {
|
||||
let stack = self.get_stack_by_id(id).await?;
|
||||
|
||||
if stack.is_locked {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
||||
// check user permission
|
||||
if user.id != stack.owner && !user.permissions.check(FinePermission::MANAGE_STACKS) {
|
||||
return Err(Error::NotAllowed);
|
||||
|
|
|
@ -107,6 +107,12 @@ pub struct User {
|
|||
/// The time in which the user last consented to the site's policies.
|
||||
#[serde(default)]
|
||||
pub last_policy_consent: usize,
|
||||
/// The ID of the user's close friends stack.
|
||||
///
|
||||
/// The user's close friends stack is a circle stack which only allows the owner
|
||||
/// (the user) to post to it.
|
||||
#[serde(default)]
|
||||
pub close_friends_stack: usize,
|
||||
}
|
||||
|
||||
pub type UserConnections =
|
||||
|
@ -430,6 +436,7 @@ impl User {
|
|||
checkouts: Vec::new(),
|
||||
applied_configurations: Vec::new(),
|
||||
last_policy_consent: created,
|
||||
close_friends_stack: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,9 @@ pub struct UserStack {
|
|||
pub privacy: StackPrivacy,
|
||||
pub mode: StackMode,
|
||||
pub sort: StackSort,
|
||||
/// Locked stacks cannot be deleted or have their mode changed. Stacks cannot
|
||||
/// be locked after creation, and must be locked by the server.
|
||||
pub is_locked: bool,
|
||||
}
|
||||
|
||||
impl UserStack {
|
||||
|
@ -74,6 +77,7 @@ impl UserStack {
|
|||
privacy: StackPrivacy::default(),
|
||||
mode: StackMode::default(),
|
||||
sort: StackSort::default(),
|
||||
is_locked: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue