Add custom emoji support

This commit is contained in:
blankie 2023-11-23 11:16:56 +11:00
parent 462dbb1b10
commit a090aeabcd
Signed by: blankie
GPG Key ID: CC15FC822C7F61F5
4 changed files with 75 additions and 11 deletions

View File

@ -41,6 +41,11 @@ a:hover {
color: var(--bright-accent-color); color: var(--bright-accent-color);
} }
.custom_emoji {
height: 1em;
width: 1em;
}
/* ERROR PAGE */ /* ERROR PAGE */
.error { .error {
text-align: center; text-align: center;
@ -79,6 +84,7 @@ a:hover {
.user_page-user_description { .user_page-user_description {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-gap: 1em;
} }
} }

View File

@ -8,7 +8,7 @@ static const char* sorting_method_suffixes[3] = {"", "/with_replies", "/media"};
static inline PostSortingMethod get_sorting_method(const std::string& method); static inline PostSortingMethod get_sorting_method(const std::string& method);
static inline Element user_header(const httplib::Request& req, const Account& account, PostSortingMethod sorting_method); static inline Element user_header(const httplib::Request& req, const Account& account, PostSortingMethod sorting_method);
static inline Element user_link_field(const httplib::Request& req, const std::string& domain_name, const AccountField& field); static inline Element user_link_field(const httplib::Request& req, const Account& account, const AccountField& field);
static inline Element sorting_method_link(const httplib::Request& req, const Account& account, PostSortingMethod current_method, PostSortingMethod new_method); static inline Element sorting_method_link(const httplib::Request& req, const Account& account, PostSortingMethod current_method, PostSortingMethod new_method);
@ -59,7 +59,7 @@ static inline Element user_header(const httplib::Request& req, const Account& ac
Element user_links("table", {{"class", "user_page-user_links"}}, {}); Element user_links("table", {{"class", "user_page-user_links"}}, {});
user_links.nodes.reserve(account.fields.size()); user_links.nodes.reserve(account.fields.size());
for (const AccountField& i : account.fields) { for (const AccountField& i : account.fields) {
user_links.nodes.push_back(user_link_field(req, account.domain_name, i)); user_links.nodes.push_back(user_link_field(req, account, i));
} }
Element header("header", { Element header("header", {
@ -82,7 +82,7 @@ static inline Element user_header(const httplib::Request& req, const Account& ac
}), }),
Element("div", {{"class", "user_page-user_description"}}, { Element("div", {{"class", "user_page-user_description"}}, {
Element("div", {{"class", "user_page-user_bio"}}, {preprocess_html(req, account.domain_name, account.note_html)}), Element("div", {{"class", "user_page-user_bio"}}, {preprocess_html(req, account.domain_name, account.emojis, account.note_html)}),
std::move(user_links), std::move(user_links),
}), }),
@ -96,12 +96,12 @@ static inline Element user_header(const httplib::Request& req, const Account& ac
return header; return header;
} }
static inline Element user_link_field(const httplib::Request& req, const std::string& domain_name, const AccountField& field) { static inline Element user_link_field(const httplib::Request& req, const Account& account, const AccountField& field) {
using namespace std::string_literals; using namespace std::string_literals;
Element tr("tr", { Element tr("tr", {
Element("th", {field.name}), Element("th", {field.name}),
Element("td", {preprocess_html(req, domain_name, field.value_html)}), Element("td", {preprocess_html(req, account.domain_name, account.emojis, field.value_html)}),
}); });
if (field.verified_at >= 0) { if (field.verified_at >= 0) {
struct tm verified_at; struct tm verified_at;

View File

@ -5,13 +5,16 @@
#include <curl/curl.h> #include <curl/curl.h>
#include "config.h" #include "config.h"
#include "models.h"
#include "servehelper.h" #include "servehelper.h"
#include "lxb_wrapper.h" #include "lxb_wrapper.h"
#include "routes/routes.h" #include "routes/routes.h"
static inline void preprocess_html(const httplib::Request& req, const std::string& domain_name, lxb_dom_element_t* element); static inline void preprocess_html(const httplib::Request& req, const std::string& domain_name, const std::vector<Emoji>& emojis, lxb_dom_element_t* element);
static inline void preprocess_link(const httplib::Request& req, const std::string& domain_name, lxb_dom_element_t* element); static inline void preprocess_link(const httplib::Request& req, const std::string& domain_name, lxb_dom_element_t* element);
static inline bool should_fix_link(lxb_dom_element_t* element); static inline bool should_fix_link(lxb_dom_element_t* element);
static inline lxb_dom_node_t* emojify(lxb_dom_node_t* child, const std::vector<Emoji>& emojis);
static inline std::vector<lxb_dom_node*> emojify(lxb_dom_document_t* document, std::string str, const std::vector<Emoji>& emojis);
class CurlUrlException : public std::exception { class CurlUrlException : public std::exception {
public: public:
@ -159,15 +162,15 @@ bool should_send_304(const httplib::Request& req, uint64_t hash) {
return pos != std::string::npos && (pos == 0 || header[pos - 1] != '/'); return pos != std::string::npos && (pos == 0 || header[pos - 1] != '/');
} }
blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::string& domain_name, const blankie::html::HTMLString& str) { blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::string& domain_name, const std::vector<Emoji>& emojis, const blankie::html::HTMLString& str) {
LXB::HTML::Document document(str.str); LXB::HTML::Document document(str.str);
preprocess_html(req, domain_name, document.body_element()); preprocess_html(req, domain_name, emojis, document.body_element());
return blankie::html::HTMLString(document.serialize()); return blankie::html::HTMLString(document.serialize());
} }
static inline void preprocess_html(const httplib::Request& req, const std::string& domain_name, lxb_dom_element_t* element) { static inline void preprocess_html(const httplib::Request& req, const std::string& domain_name, const std::vector<Emoji>& emojis, lxb_dom_element_t* element) {
const char* tag_name = reinterpret_cast<const char*>(lxb_dom_element_tag_name(element, nullptr)); const char* tag_name = reinterpret_cast<const char*>(lxb_dom_element_tag_name(element, nullptr));
if (strncmp(tag_name, "A", 2) == 0) { if (strncmp(tag_name, "A", 2) == 0) {
@ -179,7 +182,9 @@ static inline void preprocess_html(const httplib::Request& req, const std::strin
lxb_dom_node_t* child = lxb_dom_node_first_child(lxb_dom_interface_node(element)); lxb_dom_node_t* child = lxb_dom_node_first_child(lxb_dom_interface_node(element));
while (child) { while (child) {
if (child->type == LXB_DOM_NODE_TYPE_ELEMENT) { if (child->type == LXB_DOM_NODE_TYPE_ELEMENT) {
preprocess_html(req, domain_name, lxb_dom_interface_element(child)); preprocess_html(req, domain_name, emojis, lxb_dom_interface_element(child));
} else if (child->type == LXB_DOM_NODE_TYPE_TEXT) {
child = emojify(child, emojis);
} }
child = lxb_dom_node_next(child); child = lxb_dom_node_next(child);
@ -247,3 +252,55 @@ static inline bool should_fix_link(lxb_dom_element_t* element) {
child = lxb_dom_node_next(child); child = lxb_dom_node_next(child);
return child == nullptr; return child == nullptr;
} }
static inline lxb_dom_node_t* emojify(lxb_dom_node_t* child, const std::vector<Emoji>& emojis) {
size_t text_content_len;
const char* text_content = reinterpret_cast<const char*>(lxb_dom_node_text_content(child, &text_content_len));
std::vector<lxb_dom_node_t*> nodes = emojify(child->owner_document, std::string(text_content, text_content_len), emojis);
for (lxb_dom_node_t* node : nodes) {
lxb_dom_node_insert_after(child, node);
lxb_dom_node_destroy(child);
child = node;
}
return child;
}
static std::regex shortcode_re(":([a-zA-Z0-9_]+):");
static inline std::vector<lxb_dom_node_t*> emojify(lxb_dom_document_t* document, std::string str, const std::vector<Emoji>& emojis) {
std::string buf;
std::smatch sm;
std::vector<lxb_dom_node*> res;
while (std::regex_search(str, sm, shortcode_re)) {
buf += sm.prefix();
std::string group_0 = sm.str(0);
auto emoji = std::find_if(emojis.begin(), emojis.end(), [&](const Emoji& i) { return i.shortcode == sm.str(1); });
if (emoji != emojis.end()) {
res.push_back(lxb_dom_interface_node(lxb_dom_document_create_text_node(document, reinterpret_cast<const lxb_char_t*>(buf.data()), buf.size())));
buf.clear();
lxb_dom_element_t* img = lxb_dom_element_create(document, reinterpret_cast<const lxb_char_t*>("img"), 3, nullptr, 0, nullptr, 0, nullptr, 0, false);
lxb_dom_element_set_attribute(img, reinterpret_cast<const lxb_char_t*>("class"), 5, reinterpret_cast<const lxb_char_t*>("custom_emoji"), 12);
lxb_dom_element_set_attribute(img, reinterpret_cast<const lxb_char_t*>("alt"), 3, reinterpret_cast<const lxb_char_t*>(group_0.data()), group_0.size());
lxb_dom_element_set_attribute(img, reinterpret_cast<const lxb_char_t*>("title"), 5, reinterpret_cast<const lxb_char_t*>(group_0.data()), group_0.size());
lxb_dom_element_set_attribute(img, reinterpret_cast<const lxb_char_t*>("src"), 3, reinterpret_cast<const lxb_char_t*>(emoji->url.data()), emoji->url.size());
res.push_back(lxb_dom_interface_node(img));
} else {
buf += group_0;
}
str = sm.suffix();
}
if (!str.empty()) {
buf += std::move(str);
}
if (!buf.empty()) {
res.push_back(lxb_dom_interface_node(lxb_dom_document_create_text_node(document, reinterpret_cast<const lxb_char_t*>(buf.data()), buf.size())));
}
return res;
}

View File

@ -4,6 +4,7 @@
#include <httplib/httplib.h> #include <httplib/httplib.h>
#include "blankie/serializer.h" #include "blankie/serializer.h"
struct Emoji; // forward declaration from models.h
using Element = blankie::html::Element; using Element = blankie::html::Element;
using Node = blankie::html::Node; using Node = blankie::html::Node;
@ -18,4 +19,4 @@ std::string get_origin(const httplib::Request& req);
std::string proxy_mastodon_url(const httplib::Request& req, const std::string& url_str); std::string proxy_mastodon_url(const httplib::Request& req, const std::string& url_str);
bool should_send_304(const httplib::Request& req, uint64_t hash); bool should_send_304(const httplib::Request& req, uint64_t hash);
blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::string& domain_name, const blankie::html::HTMLString& str); blankie::html::HTMLString preprocess_html(const httplib::Request& req, const std::string& domain_name, const std::vector<Emoji>& emojis, const blankie::html::HTMLString& str);