From 8025d1075db0779bde635148f6e38303eb29d6c8 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 6 Nov 2022 13:54:02 +0100 Subject: Formatted text with color in matrix, monospace for codeblocks --- src/plugins/Matrix.cpp | 134 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 10 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 0fcc1c3..e4a7bd1 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -18,6 +18,7 @@ #include #include #include "../../include/QuickMedia.hpp" +#include // TODO: Use string assign with string length instead of assigning to c string (which calls strlen) // Show images/videos inline. @@ -598,15 +599,15 @@ namespace QuickMedia { } static std::string message_to_room_description_text(Message *message) { - std::string body = strip(message->body); + std::string body = strip(formatted_text_to_qm_text(message->body.c_str(), message->body.size())); if(message->type == MessageType::REACTION) - return "Reacted with: " + extract_first_line_remove_newline_elipses(body, 150); + return "Reacted with: " + body; else if(message->related_event_type == RelatedEventType::REPLY) - return extract_first_line_remove_newline_elipses(remove_reply_formatting(body), 150); + return body; else if(message->related_event_type == RelatedEventType::EDIT) - return "Edited: " + extract_first_line_remove_newline_elipses(remove_reply_formatting(body), 150); + return "Edited: " + body; else - return extract_first_line_remove_newline_elipses(body, 150); + return body; } void MatrixQuickMedia::update_room_description(RoomData *room, const Messages &new_messages, bool is_initial_sync, bool sync_is_cache) { @@ -672,13 +673,13 @@ namespace QuickMedia { if(!room_desc.empty()) room_desc += '\n'; room_desc += "** " + std::to_string(unread_notification_count) + " unread mention(s) **"; // TODO: Better notification? - room->body_item->set_description_color(get_theme().attention_alert_text_color); + room->body_item->set_description_color(get_theme().attention_alert_text_color, true); } else { room->body_item->set_description_color(get_theme().faded_text_color); } room->body_item->set_description(std::move(room_desc)); if(set_room_as_unread) - room->body_item->set_title_color(get_theme().attention_alert_text_color); + room->body_item->set_title_color(get_theme().attention_alert_text_color, true); room->last_message_read = false; rooms_page->move_room_to_top(room); @@ -1085,8 +1086,8 @@ namespace QuickMedia { body_item->url = notification.event_id; if(!notification.read) { - body_item->set_author_color(get_theme().attention_alert_text_color); - body_item->set_description_color(get_theme().attention_alert_text_color); + body_item->set_author_color(get_theme().attention_alert_text_color, true); + body_item->set_description_color(get_theme().attention_alert_text_color, true); } body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; @@ -2232,6 +2233,115 @@ namespace QuickMedia { return result; } + // Returns -1 if its not a hex value + static int get_hex_value(char c) { + if(c >= '0' && c <= '9') + return c - '0'; + else if(c >= 'a' && c <= 'f') + return 10 + (c - 'a'); + else if(c >= 'A' && c <= 'F') + return 10 + (c - 'A'); + else + return -1; + } + + // Parses hex colors in the format #RRGGBB(AA) + static bool parse_hex_set_color(const char *str, int size, mgl::Color &color) { + if(size == 0) + return false; + + // #RRGGBB(AA), case insensitive hex + if(str[0] != '#') + return false; + + if(size - 1 != 6 && size - 1 != 8) + return false; + + mgl::Color new_color; + for(int i = 1; i < size; i += 2) { + const int c1 = get_hex_value(str[i + 0]); + const int c2 = get_hex_value(str[i + 1]); + if(c1 == -1 || c2 == -1) + return false; + (&new_color.r)[(i - 1)/2] = (c1 << 4) | c2; + } + color = new_color; + return true; + } + + struct FormattedTextParseUserdata { + std::string result; + int mx_reply_depth = 0; + bool inside_font_tag = false; + bool font_tag_has_custom_color = false; + bool inside_code_tag = false; + mgl::Color font_color = mgl::Color(255, 255, 255, 255); + }; + + // TODO: Full proper parsing with tag depth + static int formattext_text_parser_callback(HtmlParser *html_parser, HtmlParseType parse_type, void *userdata) { + FormattedTextParseUserdata &parse_userdata = *(FormattedTextParseUserdata*)userdata; + switch(parse_type) { + case HTML_PARSE_TAG_START: { + if(html_parser->tag_name.size == 2 && memcmp(html_parser->tag_name.data, "br", 2) == 0) + parse_userdata.result += '\n'; + else if(html_parser->tag_name.size == 4 && memcmp(html_parser->tag_name.data, "font", 4) == 0) + parse_userdata.inside_font_tag = true; + else if(html_parser->tag_name.size == 8 && memcmp(html_parser->tag_name.data, "mx-reply", 8) == 0) + ++parse_userdata.mx_reply_depth; + else if(html_parser->tag_name.size == 4 && memcmp(html_parser->tag_name.data, "code", 4) == 0) + parse_userdata.inside_code_tag = true; + break; + } + case HTML_PARSE_TAG_END: { + /*if(html_parser->tag_name.size == 2 && memcmp(html_parser->tag_name.data, "br", 2) == 0) { + parse_userdata.result += '\n'; + } else */if(html_parser->tag_name.size == 4 && memcmp(html_parser->tag_name.data, "font", 4) == 0) { + parse_userdata.inside_font_tag = false; + parse_userdata.font_tag_has_custom_color = false; + } else if(html_parser->tag_name.size == 8 && memcmp(html_parser->tag_name.data, "mx-reply", 8) == 0) { + parse_userdata.mx_reply_depth = std::max(0, parse_userdata.mx_reply_depth - 1); + } else if(html_parser->tag_name.size == 4 && memcmp(html_parser->tag_name.data, "code", 4) == 0) { + parse_userdata.inside_code_tag = false; + } + break; + } + case HTML_PARSE_ATTRIBUTE: { + if(parse_userdata.inside_font_tag && html_parser->attribute_key.size == 5 && memcmp(html_parser->attribute_key.data, "color", 5) == 0) { + if(parse_hex_set_color(html_parser->attribute_value.data, html_parser->attribute_value.size, parse_userdata.font_color)) + parse_userdata.font_tag_has_custom_color = true; + } + break; + } + case HTML_PARSE_TEXT: + case HTML_PARSE_JAVASCRIPT_CODE: { + if(parse_userdata.mx_reply_depth == 0) { + std::string text_to_add(html_parser->text.data, html_parser->text.size); + html_unescape_sequences(text_to_add); + + uint8_t formatted_text_flags = FORMATTED_TEXT_FLAG_NONE; + if(parse_userdata.font_tag_has_custom_color) + formatted_text_flags |= FORMATTED_TEXT_FLAG_COLOR; + if(parse_userdata.inside_code_tag) + formatted_text_flags |= FORMATTED_TEXT_FLAG_MONOSPACE; + + if(formatted_text_flags != FORMATTED_TEXT_FLAG_NONE) + parse_userdata.result += Text::formatted_text(text_to_add, parse_userdata.font_color, formatted_text_flags); + else + parse_userdata.result += std::move(text_to_add); + } + break; + } + } + return 0; + } + + std::string formatted_text_to_qm_text(const char *str, size_t size) { + FormattedTextParseUserdata parse_userdata; + html_parser_parse(str, size, formattext_text_parser_callback, &parse_userdata); + return std::move(parse_userdata.result); + } + std::shared_ptr Matrix::parse_message_event(const rapidjson::Value &event_item_json, RoomData *room_data) { if(!event_item_json.IsObject()) return nullptr; @@ -2612,6 +2722,10 @@ namespace QuickMedia { if(!body_json.IsString()) return nullptr; + const rapidjson::Value *formatted_body_json = &GetMember(*content_json, "formatted_body"); + if(!formatted_body_json->IsString()) + formatted_body_json = &body_json; + auto message = std::make_shared(); std::string prefix; @@ -2678,7 +2792,7 @@ namespace QuickMedia { message->user = user; message->event_id = event_id_str; - message->body = prefix + body_json.GetString(); + message->body = prefix + std::string(formatted_body_json->GetString(), formatted_body_json->GetStringLength()); message->related_event_id = std::move(related_event_id); message->related_event_type = related_event_type; message->timestamp = timestamp; -- cgit v1.2.3