diff --git a/font_awesome.h b/font_awesome.h new file mode 100644 index 0000000..ec65ce5 --- /dev/null +++ b/font_awesome.h @@ -0,0 +1,10 @@ +/* + * The icons here are from the free edition of Font Awesome, which are created + * by Fonticons, Inc. and is available under the CC-BY 4.0 license. + * + * https://fontawesome.com/license/free#icons + * https://creativecommons.org/licenses/by/4.0/ + */ + +const char fa_retweet[] = R"EOF()EOF"; +const char fa_reply[] = R"EOF()EOF"; diff --git a/routes/css.cpp b/routes/css.cpp index 9bc0f45..9b7f856 100644 --- a/routes/css.cpp +++ b/routes/css.cpp @@ -49,13 +49,17 @@ a:hover { color: var(--bright-accent-color); } -/* CUSTOM EMOJI */ -.custom_emoji { +/* CUSTOM EMOJI and FONT AWESOME ICONS */ +.custom_emoji, svg { height: 1em; width: 1em; /* https://stackoverflow.com/a/489394 */ vertical-align: middle; } +/* FONT AWESOME ICONS */ +svg { + filter: invert(1); +} /* POST */ .post { diff --git a/servehelper.cpp b/servehelper.cpp index 015eb7e..c083bbb 100644 --- a/servehelper.cpp +++ b/servehelper.cpp @@ -4,6 +4,7 @@ #include #include +#include "font_awesome.h" #include "config.h" #include "models.h" #include "timeutils.h" @@ -17,6 +18,12 @@ static inline void preprocess_link(const httplib::Request& req, const std::strin static inline bool should_fix_link(lxb_dom_element_t* element, const std::string& cls); static inline lxb_dom_node_t* emojify(lxb_dom_node_t* child, const std::vector& emojis); static inline std::vector emojify(lxb_dom_document_t* document, std::string str, const std::vector& emojis); + +struct PostStatus { + const char* icon_html; + Node info_node; +}; +static Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, const std::optional& post_status); static inline Element serialize_media(const Media& media); class CurlUrlException : public std::exception { @@ -169,47 +176,20 @@ Element serialize_post(const httplib::Request& req, const std::string& server, c using namespace std::string_literals; if (post.reblog) { - return serialize_post(req, server, *post.reblog); + PostStatus post_status = { + fa_retweet, + preprocess_html(req, post.account.emojis, post.account.display_name + " boosted"), + }; + return serialize_post(req, server, *post.reblog, post_status); + } else if (post.in_reply_to_id && post.in_reply_to_account_id && post.account.id == *post.in_reply_to_account_id) { + PostStatus post_status = { + fa_reply, + preprocess_html(req, post.account.emojis, "Replied to "s + post.account.display_name), + }; + return serialize_post(req, server, post, post_status); + } else { + return serialize_post(req, server, post, std::nullopt); } - - std::string time_title = post.edited_at < 0 - ? full_time(post.created_at) - : "Created: "s + full_time(post.created_at) + "\nEdited: " + full_time(post.edited_at); - const char* time_badge = post.edited_at < 0 ? "" : " (edited)"; - - Element contents("div", {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) { - post_attachments.nodes.push_back(serialize_media(media)); - } - contents.nodes.push_back(std::move(post_attachments)); - - if (post.sensitive) { - std::string spoiler_text = !post.spoiler_text.empty() ? post.spoiler_text : "See more"; - contents = Element("details", { - Element("summary", {preprocess_html(req, post.emojis, std::move(spoiler_text))}), - std::move(contents), - }); - } - - Element div("div", {{"class", "post"}}, { - Element("div", {{"class", "post-header"}}, { - Element("a", {{"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false)}}, { - Element("img", {{"class", "post-avatar"}, {"alt", "User profile picture"}, {"src", post.account.avatar_static}}, {}), - Element("span", { - Element("b", {preprocess_html(req, post.account.emojis, post.account.display_name)}), - Element("br"), "@", post.account.acct(), - }), - }), - Element("a", {{"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false) + '/' + post.id}, {"title", time_title}}, { - Element("time", {{"datetime", to_rfc3339(post.created_at)}}, {relative_time(post.created_at, current_time()), time_badge}), - }), - }), - - contents, - }); - return div; } blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::string& domain_name, const std::vector& emojis, const blankie::html::HTMLString& str) { @@ -375,6 +355,55 @@ static inline std::vector emojify(lxb_dom_document_t* document, return res; } +static Element serialize_post(const httplib::Request& req, const std::string& server, const Post& post, const std::optional& post_status) { + using namespace std::string_literals; + + std::string time_title = post.edited_at < 0 + ? full_time(post.created_at) + : "Created: "s + full_time(post.created_at) + "\nEdited: " + full_time(post.edited_at); + const char* time_badge = post.edited_at < 0 ? "" : " (edited)"; + + Element contents("div", {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) { + post_attachments.nodes.push_back(serialize_media(media)); + } + contents.nodes.push_back(std::move(post_attachments)); + + if (post.sensitive) { + std::string spoiler_text = !post.spoiler_text.empty() ? post.spoiler_text : "See more"; + contents = Element("details", { + Element("summary", {preprocess_html(req, post.emojis, std::move(spoiler_text))}), + std::move(contents), + }); + } + + Element div("div", {{"class", "post"}}, { + Element("div", {{"class", "post-header"}}, { + Element("a", {{"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false)}}, { + Element("img", {{"class", "post-avatar"}, {"alt", "User profile picture"}, {"src", post.account.avatar_static}}, {}), + Element("span", { + Element("b", {preprocess_html(req, post.account.emojis, post.account.display_name)}), + Element("br"), "@", post.account.acct(), + }), + }), + Element("a", {{"href", get_origin(req) + '/' + server + "/@" + post.account.acct(false) + '/' + post.id}, {"title", time_title}}, { + Element("time", {{"datetime", to_rfc3339(post.created_at)}}, {relative_time(post.created_at, current_time()), time_badge}), + }), + }), + + contents, + }); + if (post_status) { + div.nodes.insert(div.nodes.begin(), Element("p", { + blankie::html::HTMLString(post_status->icon_html), " ", post_status->info_node, + })); + } + + return div; +} + static inline Element serialize_media(const Media& media) { Element element = [&]() { if (media.type == "image") {