Add poll support

This commit is contained in:
blankie 2023-11-24 17:37:26 +11:00
parent 3c28b81405
commit dc3758004b
Signed by: blankie
GPG Key ID: CC15FC822C7F61F5
5 changed files with 91 additions and 0 deletions

View File

@ -71,6 +71,19 @@ void from_json(const json& j, Media& media) {
}
}
void from_json(const json& j, PollOption& option) {
j.at("title").get_to(option.title);
j.at("votes_count").get_to(option.votes_count);
}
void from_json(const json& j, Poll& poll) {
poll.expires_at = parse_rfc3339(j.at("expires_at").get_ref<const std::string&>());
j.at("expired").get_to(poll.expired);
j.at("voters_count").get_to(poll.voters_count);
j.at("options").get_to(poll.options);
j.at("emojis").get_to(poll.emojis);
}
void from_json(const json& j, Post& post) {
j.at("id").get_to(post.id);
post.created_at = parse_rfc3339(j.at("created_at").get_ref<const std::string&>());
@ -98,6 +111,9 @@ void from_json(const json& j, Post& post) {
j.at("account").get_to(post.account);
j.at("media_attachments").get_to(post.media_attachments);
j.at("emojis").get_to(post.emojis);
if (!j.at("poll").is_null()) {
post.poll = j["poll"].get<Poll>();
}
}
void from_json(const json& j, PostContext& context) {

View File

@ -58,6 +58,18 @@ struct Media {
std::optional<std::string> description;
};
struct PollOption {
std::string title;
uint64_t votes_count;
};
struct Poll {
time_t expires_at;
bool expired;
uint64_t voters_count;
std::vector<PollOption> options;
std::vector<Emoji> emojis;
};
struct Post {
std::string id;
time_t created_at;
@ -74,6 +86,7 @@ struct Post {
Account account;
std::vector<Media> media_attachments;
std::vector<Emoji> emojis;
std::optional<Poll> poll;
};
struct PostContext {
@ -85,5 +98,7 @@ void from_json(const nlohmann::json& j, Emoji& emoji);
void from_json(const nlohmann::json& j, AccountField& field);
void from_json(const nlohmann::json& j, Account& account);
void from_json(const nlohmann::json& j, Media& media);
void from_json(const nlohmann::json& j, PollOption& option);
void from_json(const nlohmann::json& j, Poll& poll);
void from_json(const nlohmann::json& j, Post& post);
void from_json(const nlohmann::json& j, PostContext& context);

View File

@ -125,6 +125,25 @@ svg {
height: 40px;
}
/* POLL */
.poll-option {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.poll-percentage {
margin-right: 0.5em;
}
.poll-bar {
height: 0.5em;
display: block;
background-color: lightgray;
border-radius: 10em;
margin-top: 0.25em;
}
.poll-bar[width="0%"] {
width: 0.5em;
}
/* ERROR PAGE */
.error {
text-align: center;

View File

@ -8,6 +8,7 @@ void home_route(const httplib::Request& req, httplib::Response& res) {
Element("ul", {
Element("li", {Element("a", {{"href", get_origin(req) + "/vt.social/@lina"}}, {"Asahi Lina (朝日リナ) // nullptr::live"})}),
Element("li", {Element("a", {{"href", get_origin(req) + "/vt.social/@LucydiaLuminous/111290028216105435"}}, {"\"I love kids and their creativity. So I was explain…\""})}),
Element("li", {Element("a", {{"href", get_origin(req) + "/ruby.social/@CoralineAda/109951421922797743"}}, {"\"My partner just said \"I'm starting to think nostal…\""})}),
}),
Element("hr"),
Element("p", {

View File

@ -25,6 +25,7 @@ struct PostStatus {
};
static Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, bool main_post, const std::optional<PostStatus>& post_status);
static inline Element serialize_media(const Media& media);
static inline Element serialize_poll(const httplib::Request& req, const Poll& poll);
class CurlUrlException : public std::exception {
public:
@ -370,6 +371,7 @@ static Element serialize_post(const httplib::Request& req, const std::string& se
const char* time_badge = post.edited_at < 0 ? "" : " (edited)";
Element contents("div", {{"class", "post-contents"}}, {preprocess_html(req, server, post.emojis, post.content)});
Element post_attachments("div", {{"class", "post-attachments"}}, {});
post_attachments.nodes.reserve(post.media_attachments.size());
for (const Media& media : post.media_attachments) {
@ -377,6 +379,10 @@ static Element serialize_post(const httplib::Request& req, const std::string& se
}
contents.nodes.push_back(std::move(post_attachments));
if (post.poll) {
contents.nodes.push_back(serialize_poll(req, *post.poll));
}
if (post.sensitive) {
std::string spoiler_text = !post.spoiler_text.empty() ? post.spoiler_text : "See more";
contents = Element("details", {
@ -453,3 +459,37 @@ static inline Element serialize_media(const Media& media) {
return element;
}
static inline Element serialize_poll(const httplib::Request& req, const Poll& poll) {
Element div("div");
auto pick_form = [](uint64_t count, const char* singular, const char* plural) {
return count == 1 ? singular : plural;
};
div.nodes.reserve(poll.options.size() + 1);
for (const PollOption& option : poll.options) {
std::string percentage = poll.voters_count
? std::to_string(option.votes_count * 100 / poll.voters_count) + '%'
: "0%";
div.nodes.push_back(Element("div", {{"class", "poll-option"}, {"title", std::to_string(option.votes_count) + pick_form(option.votes_count, " vote", " votes")}}, {
Element("b", {{"class", "poll-percentage"}}, {percentage}), " ", preprocess_html(req, poll.emojis, option.title),
Element("object", {{"class", "poll-bar"}, {"width", percentage}}, {}),
}));
}
Element p("p", {
std::to_string(poll.voters_count), " ", pick_form(poll.voters_count, "voter", "voters"),
" / ",
});
if (poll.expired) {
p.nodes.push_back("Expired");
} else {
p.nodes.push_back("Expires in ");
p.nodes.push_back(relative_time(current_time(), poll.expires_at));
}
div.nodes.push_back(std::move(p));
return div;
}