add: polls

This commit is contained in:
trisua 2025-06-04 17:21:46 -04:00
parent 4dfa09207e
commit 6555324650
29 changed files with 339 additions and 56 deletions

8
Cargo.lock generated
View file

@ -3275,7 +3275,7 @@ dependencies = [
[[package]]
name = "tetratto"
version = "4.5.0"
version = "5.0.0"
dependencies = [
"ammonia",
"async-stripe",
@ -3307,7 +3307,7 @@ dependencies = [
[[package]]
name = "tetratto-core"
version = "4.5.0"
version = "5.0.0"
dependencies = [
"async-recursion",
"base16ct",
@ -3332,7 +3332,7 @@ dependencies = [
[[package]]
name = "tetratto-l10n"
version = "4.5.0"
version = "5.0.0"
dependencies = [
"pathbufd",
"serde",
@ -3341,7 +3341,7 @@ dependencies = [
[[package]]
name = "tetratto-shared"
version = "4.5.0"
version = "5.0.0"
dependencies = [
"ammonia",
"chrono",

View file

@ -1,6 +1,6 @@
[package]
name = "tetratto"
version = "4.5.0"
version = "5.0.0"
edition = "2024"
[features]

View file

@ -678,6 +678,28 @@ button.camo:hover,
color: var(--color-text-lowered);
}
.hover_left_bar {
position: relative;
}
.hover_left_bar::after {
top: 0;
left: 0;
width: 5px;
content: "";
height: 100%;
position: absolute;
background: var(--color-primary);
border-top-left-radius: var(--radius);
border-bottom-left-radius: var(--radius);
opacity: 0%;
transition: opacity 0.15s;
}
.hover_left_bar:hover::after {
opacity: 100%;
}
/* input */
input,
textarea,
@ -707,6 +729,12 @@ select:focus {
color: var(--color-text-raised);
}
.poll_bar {
background-color: var(--color-primary);
border-radius: var(--radius);
height: 25px;
}
/* pillmenu */
.pillmenu {
display: flex;

View file

@ -151,6 +151,13 @@
.querySelector(\"button.primary\")
.classList.add(\"hidden\");
// poll
const poll_data = get_poll_data();
if (!poll_data[0]) {
return alert(poll_data[1]);
}
// create body
const body = new FormData();
@ -167,6 +174,7 @@
community: document.getElementById(
\"community_to_post_to\",
).selectedOptions[0].value,
poll: poll_data[1],
}),
);

View file

@ -11,7 +11,7 @@
(text "{{ text \"communities:label.pinned\" }}")))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in pinned %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {%- endif %} {% endfor %}")))
(text "{% for post in pinned %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts, poll=post[4]) }} {%- endif %} {% endfor %}")))
(text "{%- endif %}")
(div
("class" "card-nest")
@ -22,6 +22,6 @@
(text "{{ text \"communities:label.posts\" }}")))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in feed %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=feed|length) }}"))))
(text "{% for post in feed %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts, poll=post[4]) }} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=feed|length) }}"))))
(text "{% endblock %}")

View file

@ -60,6 +60,13 @@
e.preventDefault();
await trigger(\"atto::debounce\", [\"posts::create\"]);
// poll
const poll_data = get_poll_data();
if (!poll_data[0]) {
return alert(poll_data[1]);
}
// create body
const body = new FormData();
@ -75,6 +82,7 @@
content: e.target.content.value,
community: community ? community : \"{{ config.town_square }}\",
answering,
poll: poll_data[1],
}),
);

View file

@ -222,6 +222,9 @@
(text "{%- endif %} {%- endif %}"))
(text "{{ self::post_media(upload_ids=post.uploads) }}")))
(text "{%- endif %}")
(text "{% if poll -%} {{ self::poll(post=post, poll=poll) }} {%- endif %}")
(div
("class" "flex flex-wrap gap-2 fade")
(text "{% for tag in post.context.tags %}")
@ -1205,6 +1208,14 @@
(div
("class" "flex gap-2")
(text "{{ components::emoji_picker(element_id=\"content\", render_dialog=true) }} {% if not quoting -%} {% if is_supporter -%} {{ components::file_picker(files_list_id=\"files_list\") }} {%- endif %} {%- endif %}")
(button
("class" "small square quaternary")
("title" "Add poll")
("onclick" "document.getElementById('poll_options_dialog').showModal()")
("type" "button")
(text "{{ icon \"list-todo\" }}"))
(button
("class" "small square quaternary")
("title" "More options")
@ -1323,4 +1334,155 @@
}
};"))))
; poll data manager function and dialog
;
; `get_poll_data` returns `[bool, string | PollData]`, where the string in arg 1
; represents an error message if arg 0 is `false`
(script
(text "window.POLL_OPTION_A = \"\";
window.POLL_OPTION_B = \"\";
window.POLL_OPTION_C = \"\";
window.POLL_OPTION_D = \"\";
window.get_poll_data = () => {
if (!POLL_OPTION_A && !POLL_OPTION_B) {
return [true, null];
}
if (POLL_OPTION_A && !POLL_OPTION_B || POLL_OPTION_B && !POLL_OPTION_A) {
return [false, \"At least 2 options are required for a poll\"];
}
return [true, {
option_a: POLL_OPTION_A,
option_b: POLL_OPTION_B,
option_c: POLL_OPTION_C,
option_d: POLL_OPTION_D
}];
}"))
(dialog
("id" "poll_options_dialog")
(div
("class" "inner flex flex-col gap-2")
(div
("id" "poll_options")
("class" "flex flex-col gap-2")
(b (text "Attach poll"))
(div
("class" "card flex flex-col gap-2")
(span
(b (text "Option A "))
(span ("class" "fade red") (text "(required)")))
(input ("type" "text") ("placeholder" "option A") ("onchange" "window.POLL_OPTION_A = event.target.value")))
(div
("class" "card flex flex-col gap-2")
(span
(b (text "Option B "))
(span ("class" "fade red") (text "(required)")))
(input ("type" "text") ("placeholder" "option B") ("onchange" "window.POLL_OPTION_B = event.target.value")))
(div
("class" "card flex flex-col gap-2")
(b (text "Option C"))
(input ("type" "text") ("placeholder" "option A") ("onchange" "window.POLL_OPTION_C = event.target.value")))
(div
("class" "card flex flex-col gap-2")
(b (text "Option D"))
(input ("type" "text") ("placeholder" "option D") ("onchange" "window.POLL_OPTION_D = event.target.value"))))
(hr)
(div
("class" "flex justify-between")
(div)
(div
("class" "flex gap-2")
(button
("class" "bold red quaternary")
("onclick" "document.getElementById('poll_options_dialog').close()")
("type" "button")
(text "{{ icon \"x\" }} {{ text \"dialog:action.close\" }}"))))))
(text "{%- endmacro %}")
(text "{% macro poll(post, poll) -%}")
(div
("class" "card tertiary w-full flex flex-col gap-2")
(text "{% set total = poll[0].votes_a + poll[0].votes_b + poll[0].votes_c + poll[0].votes_d %}")
(text "{% if poll[1] -%}")
; already voted, show results
(span ("class" "fade") (text "You've already voted!"))
; option a
(div
("class" "card w-full flex flex-col gap-2")
(span (text "{{ poll[0].option_a }} ({{ poll[0].votes_a }} votes)"))
(div ("class" "poll_bar") ("style" "width: {{ (poll[0].votes_a / total) * 100 }}%")))
; option b
(div
("class" "card w-full flex flex-col gap-2")
(span (text "{{ poll[0].option_b }} ({{ poll[0].votes_b }} votes)"))
(div ("class" "poll_bar") ("style" "width: {{ (poll[0].votes_b / total) * 100 }}%")))
; option c
(text "{% if poll[0].option_c -%}")
(div
("class" "card w-full flex flex-col gap-2")
(span (text "{{ poll[0].option_c }} ({{ poll[0].votes_c }} votes)"))
(div ("class" "poll_bar") ("style" "width: {{ (poll[0].votes_c / total) * 100 }}%")))
(text "{%- endif %}")
; option d
(text "{% if poll[0].option_d -%}")
(div
("class" "card w-full flex flex-col gap-2")
(span (text "{{ poll[0].option_d }} ({{ poll[0].votes_d }} votes)"))
(div ("class" "poll_bar") ("style" "width: {{ (poll[0].votes_d / total) * 100 }}%")))
(text "{%- endif %}")
(text "{% else %}")
; not voted yet, just show options so user can vote
; option a
(button
("class" "hover_left_bar tertiary justify-start w-full")
("onclick" "trigger('me::vote', ['{{ post.id }}', 'A'])")
(icon (text "tally-1"))
(text "{{ poll[0].option_a }}"))
; option b
(button
("class" "hover_left_bar tertiary justify-start w-full")
("onclick" "trigger('me::vote', ['{{ post.id }}', 'B'])")
(icon (text "tally-2"))
(text "{{ poll[0].option_b }}"))
; option c
(text "{% if poll[0].option_c -%}")
(button
("class" "hover_left_bar tertiary justify-start w-full")
("onclick" "trigger('me::vote', ['{{ post.id }}', 'C'])")
(icon (text "tally-3"))
(text "{{ poll[0].option_c }}"))
(text "{%- endif %}")
; option d
(text "{% if poll[0].option_d -%}")
(button
("class" "hover_left_bar tertiary justify-start w-full")
("onclick" "trigger('me::vote', ['{{ post.id }}', 'D'])")
(icon (text "tally-4"))
(text "{{ poll[0].option_d }}"))
(text "{%- endif %}")
(text "{%- endif %}")
; show expiration date + totals
(div
("class" "flex w-full flex-wrap gap-2")
(span ("class" "notification chip") (text "{{ total }} votes"))))
(text "{%- endmacro %}")

View file

@ -179,6 +179,13 @@
e.preventDefault();
await trigger(\"atto::debounce\", [\"posts::create\"]);
// poll
const poll_data = get_poll_data();
if (!poll_data[0]) {
return alert(poll_data[1]);
}
// create body
const body = new FormData();
@ -194,6 +201,7 @@
content: e.target.content.value,
community: \"{{ config.town_square }}\",
answering,
poll: poll_data[1],
}),
);

View file

@ -15,7 +15,7 @@
(text "{%- endif %}")
(div
("style" "display: contents;")
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts) }} {%- endif %}"))
(text "{% if post.context.repost and post.context.repost.reposting -%} {{ components::repost(repost=reposting, post=post, owner=owner, community=community, show_community=true, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post, owner=owner, question=question, community=community, show_community=true, can_manage_post=can_manage_posts, poll=poll) }} {%- endif %}"))
(text "{% if user and post.context.comments_enabled -%}")
(div
("class" "card-nest")
@ -273,13 +273,20 @@
(text "{{ text \"communities:label.replies\" }}")))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in replies %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false) }} {% endfor %} {{ components::pagination(page=page, items=replies|length) }}"))))
(text "{% for post in replies %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, poll=post[4]) }} {% endfor %} {{ components::pagination(page=page, items=replies|length) }}"))))
(script
(text "async function create_reply_from_form(e) {
e.preventDefault();
await trigger(\"atto::debounce\", [\"posts::create\"]);
// poll
const poll_data = get_poll_data();
if (!poll_data[0]) {
return alert(poll_data[1]);
}
// create body
const body = new FormData();
@ -295,6 +302,7 @@
content: e.target.content.value,
community: \"{{ community.id }}\",
replying_to: \"{{ post.id }}\",
poll: poll_data[1],
}),
);

View file

@ -64,6 +64,6 @@
(text "{{ text \"communities:label.quotes\" }}")))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in list %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false) }} {% endfor %} {{ components::pagination(page=page, items=list|length, key=\"quotes\", value=\"true\") }}"))))
(text "{% for post in list %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, poll=post[4]) }} {% endfor %} {{ components::pagination(page=page, items=list|length, key=\"quotes\", value=\"true\") }}"))))
(text "{% endblock %}")

View file

@ -23,6 +23,6 @@
(text "{%- endif %}"))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in posts %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=posts|length) }}")))
(text "{% for post in posts %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=posts|length) }}")))
(text "{% endblock %}")

View file

@ -13,7 +13,7 @@
(text "{{ text \"communities:label.pinned\" }}")))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in pinned %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self) }} {%- endif %} {%- endif %} {% endfor %}")))
(text "{% for post in pinned %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")))
(text "{%- endif %} {{ macros::profile_nav(selected=\"posts\") }}")
(div
@ -41,6 +41,6 @@
(text "{%- endif %}"))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in posts %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=posts|length, key=\"&tag=\", value=tag) }}")))
(text "{% for post in posts %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=posts|length, key=\"&tag=\", value=tag) }}")))
(text "{% endblock %}")

View file

@ -23,6 +23,6 @@
(text "{%- endif %}"))
(div
("class" "card flex flex-col gap-4")
(text "{% for post in posts %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=posts|length) }}")))
(text "{% for post in posts %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=posts|length) }}")))
(text "{% endblock %}")

View file

@ -32,6 +32,6 @@
("href" "/stacks/{{ stack.id }}/manage#/users")
(text "add a user to this stack"))
(text "!"))
(text "{%- endif %} {% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}"))))
(text "{%- endif %} {% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}"))))
(text "{% endblock %}")

View file

@ -30,6 +30,6 @@
(text "{%- endif %}")
(div
("class" "card w-full flex flex-col gap-2")
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[3]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}")))
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}")))
(text "{% endblock %}")

View file

@ -8,6 +8,6 @@
(text "{{ macros::timelines_nav(selected=\"following\") }} {{ macros::timelines_secondary_nav(posts=\"/following\", questions=\"/following/questions\") }}")
(div
("class" "card w-full flex flex-col gap-2")
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}")))
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}")))
(text "{% endblock %}")

View file

@ -27,7 +27,7 @@
(text "{% else %}")
(div
("class" "card w-full flex flex-col gap-2")
(text "{% for post in list %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2]) }} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}"))
(text "{% for post in list %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}"))
(text "{%- endif %}"))
(text "{% endblock %}")

View file

@ -8,6 +8,6 @@
(text "{{ macros::timelines_nav(selected=\"popular\") }} {{ macros::timelines_secondary_nav(posts=\"/popular\", questions=\"/popular/questions\") }}")
(div
("class" "card w-full flex flex-col gap-2")
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}")))
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=list|length) }}")))
(text "{% endblock %}")

View file

@ -56,6 +56,6 @@
(text "{{ icon \"circle-help\" }}"))
(text "{%- endif %}"))))
(text "{%- endif %}")
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2]) }} {%- endif %} {%- endif %} {% endfor %} {% if profile -%} {{ components::pagination(page=page, items=list|length, key=\"&profile=\" ~ profile.id, value=\"&query=\" ~ query) }} {% else %} {{ components::pagination(page=page, items=list|length, key=\"&query=\" ~ query) }} {%- endif %}"))))
(text "{% for post in list %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %} {% if profile -%} {{ components::pagination(page=page, items=list|length, key=\"&profile=\" ~ profile.id, value=\"&query=\" ~ query) }} {% else %} {{ components::pagination(page=page, items=list|length, key=\"&query=\" ~ query) }} {%- endif %}"))))
(text "{% endblock %}")

View file

@ -104,6 +104,35 @@
});
});
self.define("vote", async (_, id, option) => {
if (
!(await trigger("atto::confirm", [
"Are you sure you want to do this?",
]))
) {
return;
}
fetch(`/api/v1/posts/${id}/poll_vote`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
option,
}),
})
.then((res) => res.json())
.then((res) => {
trigger("atto::toast", [
res.ok ? "success" : "error",
res.message,
]);
window.location.href = `/post/${id}`;
});
});
self.define("react", async (_, element, asset, asset_type, is_like) => {
await trigger("atto::debounce", ["reactions::toggle"]);
fetch("/api/v1/reactions", {

View file

@ -717,6 +717,12 @@ pub async fn post_request(
Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
};
// check poll
let poll = match data.0.get_post_poll(&post, &user).await {
Ok(q) => q,
Err(e) => return Err(Html(render_error(e, &jar, &data, &user).await)),
};
// check permissions
let (can_read, can_manage_pins) = check_permissions!(community, jar, data, user);
@ -755,6 +761,7 @@ pub async fn post_request(
context.insert("post", &post);
context.insert("reposting", &reposting);
context.insert("question", &question);
context.insert("poll", &poll);
context.insert("replies", &feed);
context.insert("page", &props.page);
context.insert(

View file

@ -1,6 +1,6 @@
[package]
name = "tetratto-core"
version = "4.5.0"
version = "5.0.0"
edition = "2024"
[features]

View file

@ -21,7 +21,7 @@ impl DataManager {
id: get!(x->0(i64)) as usize,
owner: get!(x->1(i64)) as usize,
created: get!(x->2(i64)) as usize,
expires: get!(x->3(i64)) as usize,
expires: get!(x->3(i32)) as usize,
option_a: get!(x->4(String)),
option_b: get!(x->5(String)),
option_c: get!(x->6(String)),
@ -74,15 +74,15 @@ impl DataManager {
&(data.id as i64),
&(data.owner as i64),
&(data.created as i64),
&(data.expires as i64),
&(data.expires as i32),
&data.option_a,
&data.option_b,
&data.option_c,
&data.option_d,
&(data.votes_a as i64),
&(data.votes_b as i64),
&(data.votes_c as i64),
&(data.votes_d as i64),
&(data.votes_a as i32),
&(data.votes_b as i32),
&(data.votes_c as i32),
&(data.votes_d as i32),
]
);
@ -120,6 +120,18 @@ impl DataManager {
self.2.remove(format!("atto.poll:{}", id)).await;
// remove votes
let res = execute!(
&conn,
"DELETE FROM pollvotes WHERE poll_id = $1",
&[&(id as i64)]
);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
// ...
Ok(())
}
@ -127,15 +139,15 @@ impl DataManager {
self.2.remove(format!("atto.poll:{}", poll.id)).await;
}
auto_method!(incr_votes_a_count()@get_poll_by_id -> "UPDATE users SET votes_a = votes_a + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --incr);
auto_method!(decr_votes_a_count()@get_poll_by_id -> "UPDATE users SET votes_a = votes_a - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --decr=votes_a);
auto_method!(incr_votes_a_count()@get_poll_by_id -> "UPDATE polls SET votes_a = votes_a + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --incr);
auto_method!(decr_votes_a_count()@get_poll_by_id -> "UPDATE polls SET votes_a = votes_a - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --decr=votes_a);
auto_method!(incr_votes_b_count()@get_poll_by_id -> "UPDATE users SET votes_b = votes_b + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --incr);
auto_method!(decr_votes_b_count()@get_poll_by_id -> "UPDATE users SET votes_b = votes_b - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --decr=votes_b);
auto_method!(incr_votes_b_count()@get_poll_by_id -> "UPDATE polls SET votes_b = votes_b + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --incr);
auto_method!(decr_votes_b_count()@get_poll_by_id -> "UPDATE polls SET votes_b = votes_b - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --decr=votes_b);
auto_method!(incr_votes_c_count()@get_poll_by_id -> "UPDATE users SET votes_a = votes_d + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --incr);
auto_method!(decr_votes_c_count()@get_poll_by_id -> "UPDATE users SET votes_a = votes_d - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --decr=votes_c);
auto_method!(incr_votes_c_count()@get_poll_by_id -> "UPDATE polls SET votes_c = votes_d + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --incr);
auto_method!(decr_votes_c_count()@get_poll_by_id -> "UPDATE polls SET votes_c = votes_d - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --decr=votes_c);
auto_method!(incr_votes_d_count()@get_poll_by_id -> "UPDATE users SET votes_a = votes_d + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --incr);
auto_method!(decr_votes_d_count()@get_poll_by_id -> "UPDATE users SET votes_a = votes_d - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --decr=votes_d);
auto_method!(incr_votes_d_count()@get_poll_by_id -> "UPDATE polls SET votes_d = votes_d + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --incr);
auto_method!(decr_votes_d_count()@get_poll_by_id -> "UPDATE polls SET votes_d = votes_d - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_poll --decr=votes_d);
}

View file

@ -1,6 +1,6 @@
use super::*;
use crate::cache::Cache;
use crate::model::communities::PollVote;
use crate::model::communities::{PollOption, PollVote};
use crate::model::moderation::AuditLogEntry;
use crate::model::{Error, Result, auth::User, permissions::FinePermission};
use crate::{auto_method, execute, get, query_row, params};
@ -45,7 +45,7 @@ impl DataManager {
let res = query_row!(
&conn,
"SELECT * FROM pollvotes WHERE id = $1 AND poll_id = $2",
"SELECT * FROM pollvotes WHERE owner = $1 AND poll_id = $2",
&[&(id as i64), &(poll_id as i64)],
|x| { Ok(Self::get_pollvote_from_row(x)) }
);
@ -95,7 +95,7 @@ impl DataManager {
&(data.owner as i64),
&(data.created as i64),
&(data.poll_id as i64),
&(vote_u8 as i64),
&(vote_u8 as i32),
]
);
@ -104,10 +104,20 @@ impl DataManager {
}
// update poll
self.incr_votes_a_count(poll.id).await?;
self.incr_votes_b_count(poll.id).await?;
self.incr_votes_c_count(poll.id).await?;
self.incr_votes_d_count(poll.id).await?;
match data.vote {
PollOption::A => {
self.incr_votes_a_count(poll.id).await?;
}
PollOption::B => {
self.incr_votes_b_count(poll.id).await?;
}
PollOption::C => {
self.incr_votes_c_count(poll.id).await?;
}
PollOption::D => {
self.incr_votes_d_count(poll.id).await?;
}
};
// ...
Ok(data.id)

View file

@ -245,7 +245,7 @@ impl DataManager {
let user = if let Some(ua) = user {
ua
} else {
return Err(Error::MiscError("Could not get user for pull".to_string()));
return Ok(None);
};
if post.poll_id != 0 {
@ -259,7 +259,7 @@ impl DataManager {
Err(_) => return Err(Error::MiscError("Invalid poll ID attached".to_string())),
}))
} else {
return Err(Error::MiscError("Invalid poll ID attached".to_string()));
Ok(None)
}
}
@ -374,15 +374,10 @@ impl DataManager {
Community,
Option<(User, Post)>,
Option<(Question, User)>,
Option<(Poll, bool)>,
)>,
> {
let mut out: Vec<(
Post,
User,
Community,
Option<(User, Post)>,
Option<(Question, User)>,
)> = Vec::new();
let mut out = Vec::new();
let mut seen_before: HashMap<(usize, usize), (User, Community)> = HashMap::new();
let mut seen_user_follow_statuses: HashMap<(usize, usize), bool> = HashMap::new();
@ -403,6 +398,7 @@ impl DataManager {
community.to_owned(),
self.get_post_reposting(&post, ignore_users, user).await,
self.get_post_question(&post, ignore_users).await?,
self.get_post_poll(&post, user).await?,
));
} else {
let ua = self.get_user_by_id(owner).await?;
@ -450,6 +446,7 @@ impl DataManager {
community,
self.get_post_reposting(&post, ignore_users, user).await,
self.get_post_question(&post, ignore_users).await?,
self.get_post_poll(&post, user).await?,
));
}
}
@ -1423,7 +1420,7 @@ impl DataManager {
let res = execute!(
&conn,
"INSERT INTO posts VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, null, $13)",
"INSERT INTO posts VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, DEFAULT, $13)",
params![
&(data.id as i64),
&(data.created as i64),
@ -1541,6 +1538,11 @@ impl DataManager {
self.delete_upload(upload).await?;
}
// remove poll
if y.poll_id != 0 {
self.delete_poll(y.poll_id, user).await?;
}
// return
Ok(())
}

View file

@ -1,6 +1,6 @@
use super::*;
use crate::cache::Cache;
use crate::model::communities::{Community, Post, Question};
use crate::model::communities::{Community, Poll, Post, Question};
use crate::model::stacks::{StackMode, StackSort};
use crate::model::{
Error, Result,
@ -51,6 +51,7 @@ impl DataManager {
Community,
Option<(User, Post)>,
Option<(Question, User)>,
Option<(Poll, bool)>,
)>,
> {
let stack = self.get_stack_by_id(id).await?;

View file

@ -425,7 +425,7 @@ impl Poll {
/// Poll option (selectors) are stored in the database as numbers 0 to 3.
///
/// This enum allows us to convert from these numbers into letters.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PollOption {
A,
B,

View file

@ -1,6 +1,6 @@
[package]
name = "tetratto-l10n"
version = "4.5.0"
version = "5.0.0"
edition = "2024"
authors.workspace = true
repository.workspace = true

View file

@ -1,6 +1,6 @@
[package]
name = "tetratto-shared"
version = "4.5.0"
version = "5.0.0"
edition = "2024"
authors.workspace = true
repository.workspace = true