diff options
Diffstat (limited to 'html.c')
| -rw-r--r-- | html.c | 781 |
1 files changed, 693 insertions, 88 deletions
| @@ -1,5 +1,5 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ | 2 | /* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ |
| 3 | 3 | ||
| 4 | #include "xs.h" | 4 | #include "xs.h" |
| 5 | #include "xs_io.h" | 5 | #include "xs_io.h" |
| @@ -16,6 +16,7 @@ | |||
| 16 | #include "xs_url.h" | 16 | #include "xs_url.h" |
| 17 | #include "xs_random.h" | 17 | #include "xs_random.h" |
| 18 | #include "xs_http.h" | 18 | #include "xs_http.h" |
| 19 | #include "xs_list_tools.h" | ||
| 19 | 20 | ||
| 20 | #include "snac.h" | 21 | #include "snac.h" |
| 21 | 22 | ||
| @@ -54,9 +55,10 @@ int login(snac *user, const xs_dict *headers) | |||
| 54 | return logged_in; | 55 | return logged_in; |
| 55 | } | 56 | } |
| 56 | 57 | ||
| 57 | 58 | xs_str *_replace_shortnames(xs_str *s, const xs_list *tag, int ems, | |
| 58 | xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *proxy) | 59 | const char *proxy, const xs_list *cl, const char *act_o) |
| 59 | /* replaces all the :shortnames: with the emojis in tag */ | 60 | /* replace but also adds a class list and an actor in its alt text. |
| 61 | * Used for emoji reactions */ | ||
| 60 | { | 62 | { |
| 61 | if (!xs_is_null(tag)) { | 63 | if (!xs_is_null(tag)) { |
| 62 | xs *tag_list = NULL; | 64 | xs *tag_list = NULL; |
| @@ -69,11 +71,17 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p | |||
| 69 | tag_list = xs_dup(tag); | 71 | tag_list = xs_dup(tag); |
| 70 | } | 72 | } |
| 71 | 73 | ||
| 72 | xs *style = xs_fmt("height: %dem; width: %dem; vertical-align: middle;", ems, ems); | 74 | xs *style = xs_fmt("max-height: %dem; max-width: %dem;", ems, ems); |
| 73 | xs *class = xs_fmt("snac-emoji snac-emoji-%d-em", ems); | 75 | xs *class = xs_fmt("snac-emoji snac-emoji-%d-em", ems); |
| 76 | if (cl) { | ||
| 77 | xs *l = xs_join(cl, " "); | ||
| 78 | class = xs_str_cat(class, " ", l); | ||
| 79 | } | ||
| 74 | 80 | ||
| 75 | const xs_dict *v; | ||
| 76 | int c = 0; | 81 | int c = 0; |
| 82 | const xs_val *v; | ||
| 83 | |||
| 84 | c = 0; | ||
| 77 | 85 | ||
| 78 | xs_set rep_emoji; | 86 | xs_set rep_emoji; |
| 79 | xs_set_init(&rep_emoji); | 87 | xs_set_init(&rep_emoji); |
| @@ -100,6 +108,8 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p | |||
| 100 | if (!xs_is_string(mt)) | 108 | if (!xs_is_string(mt)) |
| 101 | mt = xs_mime_by_ext(u); | 109 | mt = xs_mime_by_ext(u); |
| 102 | 110 | ||
| 111 | xs *act = act_o ? xs_fmt("%s\n%s", n, act_o) : xs_fmt("%s", n); | ||
| 112 | |||
| 103 | if (strcmp(mt, "image/svg+xml") == 0 && !xs_is_true(xs_dict_get(srv_config, "enable_svg"))) | 113 | if (strcmp(mt, "image/svg+xml") == 0 && !xs_is_true(xs_dict_get(srv_config, "enable_svg"))) |
| 104 | s = xs_replace_i(s, n, ""); | 114 | s = xs_replace_i(s, n, ""); |
| 105 | else { | 115 | else { |
| @@ -108,8 +118,8 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p | |||
| 108 | xs_html *img = xs_html_sctag("img", | 118 | xs_html *img = xs_html_sctag("img", |
| 109 | xs_html_attr("loading", "lazy"), | 119 | xs_html_attr("loading", "lazy"), |
| 110 | xs_html_attr("src", url), | 120 | xs_html_attr("src", url), |
| 111 | xs_html_attr("alt", n), | 121 | xs_html_attr("alt", act), |
| 112 | xs_html_attr("title", n), | 122 | xs_html_attr("title", act), |
| 113 | xs_html_attr("class", class), | 123 | xs_html_attr("class", class), |
| 114 | xs_html_attr("style", style)); | 124 | xs_html_attr("style", style)); |
| 115 | 125 | ||
| @@ -130,6 +140,13 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p | |||
| 130 | } | 140 | } |
| 131 | 141 | ||
| 132 | 142 | ||
| 143 | xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *proxy) | ||
| 144 | /* replaces all the :shortnames: with the emojis in tag */ | ||
| 145 | { | ||
| 146 | return _replace_shortnames(s, tag, ems, proxy, NULL, NULL); | ||
| 147 | } | ||
| 148 | |||
| 149 | |||
| 133 | xs_str *actor_name(xs_dict *actor, const char *proxy) | 150 | xs_str *actor_name(xs_dict *actor, const char *proxy) |
| 134 | /* gets the actor name */ | 151 | /* gets the actor name */ |
| 135 | { | 152 | { |
| @@ -145,6 +162,34 @@ xs_str *actor_name(xs_dict *actor, const char *proxy) | |||
| 145 | } | 162 | } |
| 146 | 163 | ||
| 147 | 164 | ||
| 165 | xs_str *actor_pronouns(xs_dict *actor) | ||
| 166 | /* gets the actor name */ | ||
| 167 | { | ||
| 168 | const xs_list *attachment; | ||
| 169 | const xs_dict *d; | ||
| 170 | const char *v; | ||
| 171 | const char *pronouns = ""; | ||
| 172 | xs_str *ret; | ||
| 173 | |||
| 174 | if (xs_is_list((attachment = xs_dict_get(actor, "attachment")))) { | ||
| 175 | xs_list_foreach(attachment, d) { | ||
| 176 | xs *prop = xs_utf8_to_lower(xs_dict_get(d, "name")); | ||
| 177 | /* make sure that we are reading the correct metadata */ | ||
| 178 | if (strlen(prop) == 8 && strcmp(prop, "pronouns") == 0) { | ||
| 179 | /* safeguard from NULL values */ | ||
| 180 | v = xs_dict_get(d, "value"); | ||
| 181 | pronouns = v ? v : pronouns; | ||
| 182 | break; | ||
| 183 | } | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | /* strip all HTML tags */ | ||
| 188 | ret = xs_regex_replace(pronouns, "</?[^>]+>", ""); | ||
| 189 | return ret; | ||
| 190 | } | ||
| 191 | |||
| 192 | |||
| 148 | xs_str *format_text_with_emoji(snac *user, const char *text, int ems, const char *proxy) | 193 | xs_str *format_text_with_emoji(snac *user, const char *text, int ems, const char *proxy) |
| 149 | /* needed when we have local text with no tags attached */ | 194 | /* needed when we have local text with no tags attached */ |
| 150 | { | 195 | { |
| @@ -178,6 +223,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 178 | int fwer = 0; | 223 | int fwer = 0; |
| 179 | 224 | ||
| 180 | xs *name = actor_name(actor, proxy); | 225 | xs *name = actor_name(actor, proxy); |
| 226 | xs *pronouns = actor_pronouns(actor); | ||
| 181 | 227 | ||
| 182 | /* get the avatar */ | 228 | /* get the avatar */ |
| 183 | if ((v = xs_dict_get(actor, "icon")) != NULL) { | 229 | if ((v = xs_dict_get(actor, "icon")) != NULL) { |
| @@ -205,23 +251,35 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 205 | anchored link to the people page instead of the actor url */ | 251 | anchored link to the people page instead of the actor url */ |
| 206 | if (fwer || fwing) { | 252 | if (fwer || fwing) { |
| 207 | xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); | 253 | xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); |
| 208 | href = xs_fmt("%s/people#%s", user->actor, md5); | 254 | href = xs_fmt("%s/people/%s", user->actor, md5); |
| 209 | } | 255 | } |
| 210 | } | 256 | } |
| 211 | 257 | ||
| 212 | if (href == NULL) | 258 | if (href == NULL) |
| 213 | href = xs_dup(actor_id); | 259 | href = xs_dup(actor_id); |
| 214 | 260 | ||
| 261 | xs_html *name_link = xs_html_tag("a", | ||
| 262 | xs_html_attr("href", href), | ||
| 263 | xs_html_attr("class", "p-author h-card snac-author"), | ||
| 264 | xs_html_raw(name)); /* name is already html-escaped */ | ||
| 265 | |||
| 266 | if (*pronouns) { | ||
| 267 | xs_html_add(name_link, | ||
| 268 | xs_html_text(" ["), | ||
| 269 | xs_html_tag("span", | ||
| 270 | xs_html_attr("class", "snac-pronouns"), | ||
| 271 | xs_html_attr("title", "user's pronouns"), | ||
| 272 | xs_html_raw(pronouns)), | ||
| 273 | xs_html_text("]")); | ||
| 274 | } | ||
| 275 | |||
| 215 | xs_html_add(actor_icon, | 276 | xs_html_add(actor_icon, |
| 216 | xs_html_sctag("img", | 277 | xs_html_sctag("img", |
| 217 | xs_html_attr("loading", "lazy"), | 278 | xs_html_attr("loading", "lazy"), |
| 218 | xs_html_attr("class", "snac-avatar"), | 279 | xs_html_attr("class", "snac-avatar"), |
| 219 | xs_html_attr("src", avatar), | 280 | xs_html_attr("src", avatar), |
| 220 | xs_html_attr("alt", "[?]")), | 281 | xs_html_attr("alt", "[?]")), |
| 221 | xs_html_tag("a", | 282 | name_link); |
| 222 | xs_html_attr("href", href), | ||
| 223 | xs_html_attr("class", "p-author h-card snac-author"), | ||
| 224 | xs_html_raw(name))); /* name is already html-escaped */ | ||
| 225 | 283 | ||
| 226 | if (!xs_is_null(url)) { | 284 | if (!xs_is_null(url)) { |
| 227 | xs *md5 = xs_md5_hex(url, strlen(url)); | 285 | xs *md5 = xs_md5_hex(url, strlen(url)); |
| @@ -234,6 +292,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 234 | xs_html_text("»"))); | 292 | xs_html_text("»"))); |
| 235 | } | 293 | } |
| 236 | 294 | ||
| 295 | |||
| 237 | if (strcmp(xs_dict_get(actor, "type"), "Service") == 0) { | 296 | if (strcmp(xs_dict_get(actor, "type"), "Service") == 0) { |
| 238 | xs_html_add(actor_icon, | 297 | xs_html_add(actor_icon, |
| 239 | xs_html_text(" "), | 298 | xs_html_text(" "), |
| @@ -398,6 +457,83 @@ xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, | |||
| 398 | return actor_icon; | 457 | return actor_icon; |
| 399 | } | 458 | } |
| 400 | 459 | ||
| 460 | void html_note_render_visibility(snac* user, xs_html *form, const int scope) | ||
| 461 | { | ||
| 462 | // scopes aren't sorted by value unfortunately, so simple math won't work to limit them. using map-like thing here | ||
| 463 | static const int public_scopes[5] = {SCOPE_PUBLIC, SCOPE_UNLISTED, SCOPE_FOLLOWERS, SCOPE_MENTIONED, -1}; | ||
| 464 | static const int unlisted_scopes[4] = {SCOPE_UNLISTED, SCOPE_FOLLOWERS, SCOPE_MENTIONED, -1}; | ||
| 465 | static const int followers_scopes[3] = {SCOPE_FOLLOWERS, SCOPE_MENTIONED, -1}; | ||
| 466 | static const int mentioned_scopes[2] = {SCOPE_MENTIONED, -1}; | ||
| 467 | static const int * const scopes[4] = { public_scopes, mentioned_scopes, unlisted_scopes, followers_scopes}; | ||
| 468 | static const char * const scopes_tags[4] = { "public", "mentioned", "unlisted", "followers"}; | ||
| 469 | static const char * const scopes_names[4] = { "Public", "Direct Message", "Unlisted", "Followers-only"}; | ||
| 470 | |||
| 471 | xs_html *paragraph = xs_html_tag("p", xs_html_text(L("Visibility: "))); | ||
| 472 | const int* to_render = scopes[scope]; | ||
| 473 | for( int i = 0; to_render[i] != -1; i++ ){ | ||
| 474 | const int scope_i = to_render[i]; | ||
| 475 | const char* value = scopes_tags[scope_i]; | ||
| 476 | const char* name = scopes_names[scope_i]; | ||
| 477 | xs_html_add(paragraph, | ||
| 478 | xs_html_tag("label", | ||
| 479 | xs_html_sctag("input", | ||
| 480 | xs_html_attr("type", "radio"), | ||
| 481 | xs_html_attr("name", "visibility"), | ||
| 482 | xs_html_attr("value", value), | ||
| 483 | xs_html_attr(scope == scope_i ? "checked" : "", NULL)), | ||
| 484 | xs_html_text(" "), | ||
| 485 | xs_html_text(L(name)), | ||
| 486 | xs_html_text(" ")) | ||
| 487 | ); | ||
| 488 | } | ||
| 489 | xs_html_add(form, paragraph); | ||
| 490 | } | ||
| 491 | |||
| 492 | /* html_note but moddled for emoji's needs. here and not bellow, since the | ||
| 493 | * other one is already so complex. */ | ||
| 494 | xs_html *html_emoji(snac *user, const char *summary, | ||
| 495 | const char *div_id, const char *form_id, | ||
| 496 | const char* placeholder, const char *post_id, | ||
| 497 | const char* eid) | ||
| 498 | { | ||
| 499 | xs *action = xs_fmt("%s/admin/action", user->actor); | ||
| 500 | |||
| 501 | xs_html *form; | ||
| 502 | const int react = eid == NULL ? 0 : 1; | ||
| 503 | |||
| 504 | xs_html *note = xs_html_tag("div", | ||
| 505 | xs_html_tag("details", | ||
| 506 | xs_html_tag("summary", | ||
| 507 | xs_html_text(summary)), | ||
| 508 | xs_html_tag("p", NULL), | ||
| 509 | xs_html_tag("div", | ||
| 510 | xs_html_attr("class", "snac-note"), | ||
| 511 | xs_html_attr("id", div_id), | ||
| 512 | form = xs_html_tag("form", | ||
| 513 | xs_html_attr("autocomplete", "off"), | ||
| 514 | xs_html_attr("method", "post"), | ||
| 515 | xs_html_attr("action", action), | ||
| 516 | xs_html_attr("enctype", "multipart/form-data"), | ||
| 517 | xs_html_attr("id", form_id), | ||
| 518 | xs_html_sctag("input", | ||
| 519 | xs_html_attr("type", "hidden"), | ||
| 520 | xs_html_attr("name", "id"), | ||
| 521 | xs_html_attr("value", post_id)), | ||
| 522 | xs_html_sctag("input", | ||
| 523 | xs_html_attr("type", react ? "hidden" : "text"), | ||
| 524 | xs_html_attr("name", "eid"), | ||
| 525 | xs_html_attr(react ? "value" : "placeholder", react ? eid : placeholder)), | ||
| 526 | xs_html_text(" "), | ||
| 527 | xs_html_sctag("input", | ||
| 528 | xs_html_attr("type", "submit"), | ||
| 529 | xs_html_attr("name", "action"), | ||
| 530 | xs_html_attr("eid", "action"), | ||
| 531 | xs_html_attr("value", react ? L("EmojiUnreact") : L("EmojiReact"))), | ||
| 532 | xs_html_text(" "), | ||
| 533 | xs_html_tag("p", NULL))))); | ||
| 534 | |||
| 535 | return note; | ||
| 536 | } | ||
| 401 | 537 | ||
| 402 | xs_html *html_note(snac *user, const char *summary, | 538 | xs_html *html_note(snac *user, const char *summary, |
| 403 | const char *div_id, const char *form_id, | 539 | const char *div_id, const char *form_id, |
| @@ -455,46 +591,8 @@ xs_html *html_note(snac *user, const char *summary, | |||
| 455 | xs_html_attr("type", "hidden"), | 591 | xs_html_attr("type", "hidden"), |
| 456 | xs_html_attr("name", "to"), | 592 | xs_html_attr("name", "to"), |
| 457 | xs_html_attr("value", actor_id))); | 593 | xs_html_attr("value", actor_id))); |
| 458 | else { | 594 | if (edit_id == NULL) |
| 459 | xs_html_add(form, | 595 | html_note_render_visibility(user, form, scope); |
| 460 | xs_html_tag("p", | ||
| 461 | xs_html_text(L("Visibility: ")), | ||
| 462 | xs_html_tag("label", | ||
| 463 | xs_html_sctag("input", | ||
| 464 | xs_html_attr("type", "radio"), | ||
| 465 | xs_html_attr("name", "visibility"), | ||
| 466 | xs_html_attr("value", "public"), | ||
| 467 | xs_html_attr(scope == SCOPE_PUBLIC ? "checked" : "", NULL)), | ||
| 468 | xs_html_text(" "), | ||
| 469 | xs_html_text(L("Public"))), | ||
| 470 | xs_html_text(" "), | ||
| 471 | xs_html_tag("label", | ||
| 472 | xs_html_sctag("input", | ||
| 473 | xs_html_attr("type", "radio"), | ||
| 474 | xs_html_attr("name", "visibility"), | ||
| 475 | xs_html_attr("value", "unlisted"), | ||
| 476 | xs_html_attr(scope == SCOPE_UNLISTED ? "checked" : "", NULL)), | ||
| 477 | xs_html_text(" "), | ||
| 478 | xs_html_text(L("Unlisted"))), | ||
| 479 | xs_html_text(" "), | ||
| 480 | xs_html_tag("label", | ||
| 481 | xs_html_sctag("input", | ||
| 482 | xs_html_attr("type", "radio"), | ||
| 483 | xs_html_attr("name", "visibility"), | ||
| 484 | xs_html_attr("value", "followers"), | ||
| 485 | xs_html_attr(scope == SCOPE_FOLLOWERS ? "checked" : "", NULL)), | ||
| 486 | xs_html_text(" "), | ||
| 487 | xs_html_text(L("Followers-only"))), | ||
| 488 | xs_html_text(" "), | ||
| 489 | xs_html_tag("label", | ||
| 490 | xs_html_sctag("input", | ||
| 491 | xs_html_attr("type", "radio"), | ||
| 492 | xs_html_attr("name", "visibility"), | ||
| 493 | xs_html_attr("value", "mentioned"), | ||
| 494 | xs_html_attr(scope == SCOPE_MENTIONED ? "checked" : "", NULL)), | ||
| 495 | xs_html_text(" "), | ||
| 496 | xs_html_text(L("Direct Message"))))); | ||
| 497 | } | ||
| 498 | 596 | ||
| 499 | if (redir) | 597 | if (redir) |
| 500 | xs_html_add(form, | 598 | xs_html_add(form, |
| @@ -1363,6 +1461,28 @@ xs_html *html_top_controls(snac *user) | |||
| 1363 | xs_html_attr("value", L("Like"))), | 1461 | xs_html_attr("value", L("Like"))), |
| 1364 | xs_html_text(" "), | 1462 | xs_html_text(" "), |
| 1365 | xs_html_text(L("(by URL)"))), | 1463 | xs_html_text(L("(by URL)"))), |
| 1464 | xs_html_tag("form", | ||
| 1465 | xs_html_attr("autocomplete", "off"), | ||
| 1466 | xs_html_attr("method", "post"), | ||
| 1467 | xs_html_attr("action", ops_action), | ||
| 1468 | xs_html_sctag("input", | ||
| 1469 | xs_html_attr("type", "text"), | ||
| 1470 | xs_html_attr("name", "eid"), | ||
| 1471 | xs_html_attr("required", "required"), | ||
| 1472 | xs_html_attr("placeholder", ":neocat:")), | ||
| 1473 | xs_html_text(" "), | ||
| 1474 | xs_html_sctag("input", | ||
| 1475 | xs_html_attr("type", "text"), | ||
| 1476 | xs_html_attr("name", "id"), | ||
| 1477 | xs_html_attr("required", "required"), | ||
| 1478 | xs_html_attr("placeholder", "https:/" "/fedi.example.com/bob/...")), | ||
| 1479 | xs_html_text(" "), | ||
| 1480 | xs_html_sctag("input", | ||
| 1481 | xs_html_attr("type", "submit"), | ||
| 1482 | xs_html_attr("name", "action"), | ||
| 1483 | xs_html_attr("value", L("EmojiReact"))), | ||
| 1484 | xs_html_text(" "), | ||
| 1485 | xs_html_text(L("(by URL)"))), | ||
| 1366 | xs_html_tag("p", NULL))); | 1486 | xs_html_tag("p", NULL))); |
| 1367 | 1487 | ||
| 1368 | /** user settings **/ | 1488 | /** user settings **/ |
| @@ -1787,6 +1907,38 @@ xs_html *html_top_controls(snac *user) | |||
| 1787 | xs_html_attr("class", "button"), | 1907 | xs_html_attr("class", "button"), |
| 1788 | xs_html_attr("value", L("Update hashtags"))))))); | 1908 | xs_html_attr("value", L("Update hashtags"))))))); |
| 1789 | 1909 | ||
| 1910 | xs *muted_words_action = xs_fmt("%s/admin/muted-words", user->actor); | ||
| 1911 | xs *muted_words = xs_join(xs_dict_get_def(user->config, | ||
| 1912 | "muted_words", xs_stock(XSTYPE_LIST)), "\n"); | ||
| 1913 | |||
| 1914 | xs_html_add(top_controls, | ||
| 1915 | xs_html_tag("details", | ||
| 1916 | xs_html_tag("summary", | ||
| 1917 | xs_html_text(L("Muted words..."))), | ||
| 1918 | xs_html_tag("p", | ||
| 1919 | xs_html_text(L("One word per line, partial matches count"))), | ||
| 1920 | xs_html_tag("div", | ||
| 1921 | xs_html_attr("class", "snac-muted-words"), | ||
| 1922 | xs_html_tag("form", | ||
| 1923 | xs_html_attr("autocomplete", "off"), | ||
| 1924 | xs_html_attr("method", "post"), | ||
| 1925 | xs_html_attr("action", muted_words_action), | ||
| 1926 | xs_html_attr("enctype", "multipart/form-data"), | ||
| 1927 | |||
| 1928 | xs_html_tag("textarea", | ||
| 1929 | xs_html_attr("name", "muted_words"), | ||
| 1930 | xs_html_attr("cols", "40"), | ||
| 1931 | xs_html_attr("rows", "4"), | ||
| 1932 | xs_html_attr("placeholder", "nascar\nsuperbowl\nFIFA"), | ||
| 1933 | xs_html_text(muted_words)), | ||
| 1934 | |||
| 1935 | xs_html_tag("br", NULL), | ||
| 1936 | |||
| 1937 | xs_html_sctag("input", | ||
| 1938 | xs_html_attr("type", "submit"), | ||
| 1939 | xs_html_attr("class", "button"), | ||
| 1940 | xs_html_attr("value", L("Update muted words"))))))); | ||
| 1941 | |||
| 1790 | return top_controls; | 1942 | return top_controls; |
| 1791 | } | 1943 | } |
| 1792 | 1944 | ||
| @@ -1898,7 +2050,7 @@ xs_html *html_entry_controls(snac *user, const char *actor, | |||
| 1898 | xs_html_attr("name", "redir"), | 2050 | xs_html_attr("name", "redir"), |
| 1899 | xs_html_attr("value", redir)))); | 2051 | xs_html_attr("value", redir)))); |
| 1900 | 2052 | ||
| 1901 | if (!xs_startswith(id, user->actor)) { | 2053 | if (!is_msg_mine(user, id)) { |
| 1902 | if (xs_list_in(likes, user->md5) == -1) { | 2054 | if (xs_list_in(likes, user->md5) == -1) { |
| 1903 | /* not already liked; add button */ | 2055 | /* not already liked; add button */ |
| 1904 | xs_html_add(form, | 2056 | xs_html_add(form, |
| @@ -2026,6 +2178,22 @@ xs_html *html_entry_controls(snac *user, const char *actor, | |||
| 2026 | xs_html_tag("p", NULL)); | 2178 | xs_html_tag("p", NULL)); |
| 2027 | } | 2179 | } |
| 2028 | 2180 | ||
| 2181 | if (!xs_is_true(xs_dict_get(srv_config, "disable_emojireact"))) { /** emoji react **/ | ||
| 2182 | /* the post textarea */ | ||
| 2183 | xs *div_id = xs_fmt("%s_reply", md5); | ||
| 2184 | xs *form_id = xs_fmt("%s_reply_form", md5); | ||
| 2185 | xs *e_react = emoji_reacted(user, id); | ||
| 2186 | |||
| 2187 | xs_html_add(controls, xs_html_tag("div", | ||
| 2188 | xs_html_tag("p", NULL), | ||
| 2189 | html_emoji( | ||
| 2190 | user, L("Emoji react..."), | ||
| 2191 | div_id, form_id, | ||
| 2192 | ":neocat:", id, | ||
| 2193 | e_react)), | ||
| 2194 | xs_html_tag("p", NULL)); | ||
| 2195 | } | ||
| 2196 | |||
| 2029 | { /** reply **/ | 2197 | { /** reply **/ |
| 2030 | /* the post textarea */ | 2198 | /* the post textarea */ |
| 2031 | xs *ct = build_mentions(user, msg); | 2199 | xs *ct = build_mentions(user, msg); |
| @@ -2051,6 +2219,30 @@ xs_html *html_entry_controls(snac *user, const char *actor, | |||
| 2051 | } | 2219 | } |
| 2052 | 2220 | ||
| 2053 | 2221 | ||
| 2222 | static const xs_str* words_in_content(const xs_list *words, const xs_val *content) | ||
| 2223 | /* returns a word that matches any of the words in content */ | ||
| 2224 | { | ||
| 2225 | if (!xs_is_list(words) || !xs_is_string(content)) { | ||
| 2226 | return NULL; | ||
| 2227 | } | ||
| 2228 | xs *c = xs_split(content, " "); | ||
| 2229 | xs *sc = xs_list_sort(c, NULL); | ||
| 2230 | |||
| 2231 | const xs_str *wv; | ||
| 2232 | const xs_str *cv; | ||
| 2233 | xs_list_foreach(words, wv) { | ||
| 2234 | xs_list_foreach(sc, cv) { | ||
| 2235 | xs_tolower_i((xs_str*)cv); | ||
| 2236 | if(xs_str_in(cv, wv) != -1){ | ||
| 2237 | return wv; | ||
| 2238 | } | ||
| 2239 | } | ||
| 2240 | } | ||
| 2241 | |||
| 2242 | return NULL; | ||
| 2243 | } | ||
| 2244 | |||
| 2245 | |||
| 2054 | xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | 2246 | xs_html *html_entry(snac *user, xs_dict *msg, int read_only, |
| 2055 | int level, const char *md5, int hide_children) | 2247 | int level, const char *md5, int hide_children) |
| 2056 | { | 2248 | { |
| @@ -2262,7 +2454,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2262 | } | 2454 | } |
| 2263 | 2455 | ||
| 2264 | if (!read_only && (fwers || fwing)) | 2456 | if (!read_only && (fwers || fwing)) |
| 2265 | href = xs_fmt("%s/people#%s", user->actor, p); | 2457 | href = xs_fmt("%s/people/%s", user->actor, p); |
| 2266 | else | 2458 | else |
| 2267 | href = xs_dup(id); | 2459 | href = xs_dup(id); |
| 2268 | 2460 | ||
| @@ -2345,6 +2537,17 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2345 | xs_html_text(v), | 2537 | xs_html_text(v), |
| 2346 | xs_html_text(L(" [SENSITIVE CONTENT]")))); | 2538 | xs_html_text(L(" [SENSITIVE CONTENT]")))); |
| 2347 | } | 2539 | } |
| 2540 | else | ||
| 2541 | if (user && | ||
| 2542 | /* muted_words is all lowercase and sorted for performance */ | ||
| 2543 | (v = words_in_content(xs_dict_get(user->config, "muted_words"), | ||
| 2544 | xs_dict_get(msg, "content"))) != NULL) { | ||
| 2545 | snac_debug(user, 1, xs_fmt("word %s muted by user preferences: %s", v, id)); | ||
| 2546 | snac_content = xs_html_tag("details", | ||
| 2547 | xs_html_tag("summary", | ||
| 2548 | xs_html_text(L("Muted: ")), | ||
| 2549 | xs_html_text(v))); | ||
| 2550 | } | ||
| 2348 | else { | 2551 | else { |
| 2349 | snac_content = xs_html_tag("div", NULL); | 2552 | snac_content = xs_html_tag("div", NULL); |
| 2350 | } | 2553 | } |
| @@ -2352,6 +2555,182 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2352 | xs_html_add(snac_content_wrap, | 2555 | xs_html_add(snac_content_wrap, |
| 2353 | snac_content); | 2556 | snac_content); |
| 2354 | 2557 | ||
| 2558 | /* add all emoji reacts */ | ||
| 2559 | int is_emoji = 0; | ||
| 2560 | if (!xs_is_true(xs_dict_get(srv_config, "disable_emojireact"))) { | ||
| 2561 | int c = 0; | ||
| 2562 | const xs_dict *k; | ||
| 2563 | xs *ls = xs_list_new(); | ||
| 2564 | xs *sfrl = xs_dict_new(); | ||
| 2565 | xs *rl = object_get_emoji_reacts(id); | ||
| 2566 | |||
| 2567 | while (xs_list_next(rl, &v, &c)) { | ||
| 2568 | xs *m = NULL; | ||
| 2569 | if (valid_status(object_get_by_md5(v, &m))) { | ||
| 2570 | const char *content = xs_dict_get(m, "content"); | ||
| 2571 | const char *actor = xs_dict_get(m, "actor"); | ||
| 2572 | const xs_list *contentl = xs_dict_get(sfrl, content); | ||
| 2573 | |||
| 2574 | if ((user && is_muted(user, actor)) || is_instance_blocked(actor)) | ||
| 2575 | continue; | ||
| 2576 | |||
| 2577 | xs *actors = xs_list_new(); | ||
| 2578 | actors = xs_list_append(actors, actor); | ||
| 2579 | char me = actor && user && strcmp(actor, user->actor) == 0; | ||
| 2580 | int count = 1; | ||
| 2581 | |||
| 2582 | if (contentl) { | ||
| 2583 | count = atoi(xs_list_get(contentl, 0)) + 1; | ||
| 2584 | const xs_list *actorsc = xs_list_get(contentl, 1); | ||
| 2585 | if (strncmp(xs_list_get(contentl, 2), "1", 1) == 0) | ||
| 2586 | me = 1; | ||
| 2587 | |||
| 2588 | if (xs_list_in(actorsc, actor) != -1) { | ||
| 2589 | xs_free(actors); | ||
| 2590 | actors = xs_dup(actorsc); | ||
| 2591 | } | ||
| 2592 | else | ||
| 2593 | actors = xs_list_cat(actors, actorsc); | ||
| 2594 | } | ||
| 2595 | |||
| 2596 | xs *fl = xs_list_new(); | ||
| 2597 | xs *c1 = xs_fmt("%d", count); | ||
| 2598 | xs *c2 = xs_fmt("%d", me); | ||
| 2599 | fl = xs_list_append(fl, c1, actors, c2); | ||
| 2600 | sfrl = xs_dict_append(sfrl, content, fl); | ||
| 2601 | } | ||
| 2602 | } | ||
| 2603 | |||
| 2604 | c = 0; | ||
| 2605 | |||
| 2606 | while (xs_list_next(rl, &k, &c)) { | ||
| 2607 | xs *m = NULL; | ||
| 2608 | if (valid_status(object_get_by_md5(k, &m))) { | ||
| 2609 | const xs_dict *tag = xs_dict_get(m, "tag"); | ||
| 2610 | const xs_dict *ide = xs_dict_get(m, "id"); | ||
| 2611 | |||
| 2612 | const char *content = xs_dict_get(m, "content"); | ||
| 2613 | const char *shortname; | ||
| 2614 | shortname = xs_dict_get(m, "content"); | ||
| 2615 | |||
| 2616 | const xs_list *items = xs_dict_get(sfrl, content); | ||
| 2617 | |||
| 2618 | if (!xs_is_null(items)) { | ||
| 2619 | const char *nb = xs_list_get(items, 0); | ||
| 2620 | const xs_list *actors = xs_list_get(items, 1); | ||
| 2621 | const char me = *xs_list_get(items, 2) == '1'; | ||
| 2622 | |||
| 2623 | is_emoji = 1; | ||
| 2624 | |||
| 2625 | xs *al = xs_join(actors, ",\n\t"); | ||
| 2626 | xs *act = atoi(nb) > 1 ? | ||
| 2627 | xs_fmt("%d different actors \n\t%s", atoi(nb), al) : | ||
| 2628 | xs_dup(xs_dict_get(m, "actor")); | ||
| 2629 | |||
| 2630 | xs *class = xs_list_new(); | ||
| 2631 | class = xs_list_append(class, "snac-reaction"); | ||
| 2632 | |||
| 2633 | xs_html *ret = NULL; | ||
| 2634 | if (tag && shortname) { | ||
| 2635 | xs *cl = xs_list_new(); | ||
| 2636 | cl = xs_list_append(cl, "snac-reaction-image"); | ||
| 2637 | xs *emoji = _replace_shortnames(xs_dup(shortname), tag, 2, proxy, cl, act); | ||
| 2638 | |||
| 2639 | emoji = xs_strip_chars_i(emoji, ":"); | ||
| 2640 | |||
| 2641 | if (me) | ||
| 2642 | class = xs_list_append(class, "snac-reacted"); | ||
| 2643 | |||
| 2644 | xs *l1 = xs_join(class, " "); | ||
| 2645 | ret = xs_html_tag("button", | ||
| 2646 | xs_html_attr("type", "submit"), | ||
| 2647 | xs_html_attr("name", "action"), | ||
| 2648 | xs_html_attr("value", me ? L("EmojiReact") : L("EmojiUnreact")), | ||
| 2649 | xs_html_raw(emoji), | ||
| 2650 | xs_html_tag("span", | ||
| 2651 | xs_html_raw(nb), | ||
| 2652 | xs_html_attr("style", "padding-left: 5px;")), | ||
| 2653 | xs_html_attr("title", act), | ||
| 2654 | xs_html_attr("class", l1)); | ||
| 2655 | |||
| 2656 | if (!(ide && xs_startswith(ide, srv_baseurl))) | ||
| 2657 | xs_html_add(ret, xs_html_attr("disabled", "true")); | ||
| 2658 | } | ||
| 2659 | else if (shortname) { | ||
| 2660 | xs *sn = xs_dup(shortname); | ||
| 2661 | const char *sna = sn; | ||
| 2662 | unsigned int utf = xs_utf8_dec((const char **)&sna); | ||
| 2663 | |||
| 2664 | if (xs_is_emoji(utf)) { | ||
| 2665 | const char *style = "font-size: large;"; | ||
| 2666 | if (me) | ||
| 2667 | class = xs_list_append(class, "snac-reacted"); | ||
| 2668 | xs *l1 = xs_join(class, " "); | ||
| 2669 | xs *s1 = xs_fmt("&#%d", utf); | ||
| 2670 | ret = xs_html_tag("button", | ||
| 2671 | xs_html_attr("type", "submit"), | ||
| 2672 | xs_html_attr("name", "action"), | ||
| 2673 | xs_html_attr("value", me ? L("EmojiUnreact") : L("EmojiReact")), | ||
| 2674 | xs_html_raw(s1), | ||
| 2675 | xs_html_tag("span", | ||
| 2676 | xs_html_raw(nb), | ||
| 2677 | xs_html_attr("style", "font-size: initial; padding-left: 5px;")), | ||
| 2678 | xs_html_attr("title", act), | ||
| 2679 | xs_html_attr("class", l1), | ||
| 2680 | xs_html_attr("style", style)); | ||
| 2681 | } | ||
| 2682 | } | ||
| 2683 | if (ret) { | ||
| 2684 | xs *s1; | ||
| 2685 | if (user) { | ||
| 2686 | xs *action = xs_fmt("%s/admin/action", user->actor); | ||
| 2687 | xs *form_id = xs_fmt("%s_reply_form", md5); | ||
| 2688 | |||
| 2689 | xs_html *form = | ||
| 2690 | xs_html_tag("form", | ||
| 2691 | xs_html_attr("autocomplete", "off"), | ||
| 2692 | xs_html_attr("method", "post"), | ||
| 2693 | xs_html_attr("action", action), | ||
| 2694 | xs_html_attr("enctype", "multipart/form-data"), | ||
| 2695 | xs_html_attr("style", "display: inline-flex;" | ||
| 2696 | "vertical-align: middle;"), | ||
| 2697 | xs_html_attr("id", form_id), | ||
| 2698 | xs_html_sctag("input", | ||
| 2699 | xs_html_attr("type", "hidden"), | ||
| 2700 | xs_html_attr("name", "id"), | ||
| 2701 | xs_html_attr("value", id)), | ||
| 2702 | xs_html_sctag("input", | ||
| 2703 | xs_html_attr("type", "hidden"), | ||
| 2704 | xs_html_attr("name", "eid"), | ||
| 2705 | xs_html_attr("value", shortname)), | ||
| 2706 | ret); | ||
| 2707 | s1 = xs_html_render(form); | ||
| 2708 | } | ||
| 2709 | else | ||
| 2710 | s1 = xs_html_render(ret); | ||
| 2711 | |||
| 2712 | ls = xs_list_append(ls, s1); | ||
| 2713 | sfrl = xs_dict_del(sfrl, content); | ||
| 2714 | } | ||
| 2715 | } | ||
| 2716 | } | ||
| 2717 | } | ||
| 2718 | |||
| 2719 | c = 0; | ||
| 2720 | |||
| 2721 | xs_html *emoji_div; | ||
| 2722 | if (xs_list_len(ls) > 0) { | ||
| 2723 | emoji_div = xs_html_tag("div", xs_html_text(L("Emoji reactions: ")), | ||
| 2724 | xs_html_attr("class", "snac-reaction-div")); | ||
| 2725 | |||
| 2726 | while (ls != NULL && xs_list_next(ls, &k, &c)) | ||
| 2727 | xs_html_add(emoji_div, xs_html_raw(k)); | ||
| 2728 | |||
| 2729 | xs_html_add(snac_content_wrap, emoji_div); | ||
| 2730 | } | ||
| 2731 | |||
| 2732 | } | ||
| 2733 | |||
| 2355 | { | 2734 | { |
| 2356 | /** build the content string **/ | 2735 | /** build the content string **/ |
| 2357 | const char *content = xs_dict_get(msg, "content"); | 2736 | const char *content = xs_dict_get(msg, "content"); |
| @@ -2378,7 +2757,8 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2378 | 2757 | ||
| 2379 | c = xs_replace_i(c, "<br><br>", "<p>"); | 2758 | c = xs_replace_i(c, "<br><br>", "<p>"); |
| 2380 | 2759 | ||
| 2381 | c = xs_str_cat(c, "<p>"); | 2760 | if (is_emoji == 0) |
| 2761 | c = xs_str_cat(c, "<p>"); | ||
| 2382 | 2762 | ||
| 2383 | /* replace the :shortnames: */ | 2763 | /* replace the :shortnames: */ |
| 2384 | c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2, proxy); | 2764 | c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2, proxy); |
| @@ -2426,7 +2806,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2426 | if (read_only) | 2806 | if (read_only) |
| 2427 | closed = 1; /* non-identified page; show as closed */ | 2807 | closed = 1; /* non-identified page; show as closed */ |
| 2428 | else | 2808 | else |
| 2429 | if (user && xs_startswith(id, user->actor)) | 2809 | if (user && is_msg_mine(user, id)) |
| 2430 | closed = 1; /* we questioned; closed for us */ | 2810 | closed = 1; /* we questioned; closed for us */ |
| 2431 | else | 2811 | else |
| 2432 | if (user && was_question_voted(user, id)) | 2812 | if (user && was_question_voted(user, id)) |
| @@ -2634,7 +3014,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2634 | xs_html_attr("title", name)))); | 3014 | xs_html_attr("title", name)))); |
| 2635 | } | 3015 | } |
| 2636 | else | 3016 | else |
| 2637 | if (xs_startswith(type, "video/")) { | 3017 | if (xs_startswith(type, "video/") || strcmp(type, "Video") == 0) { |
| 2638 | xs_html_add(content_attachments, | 3018 | xs_html_add(content_attachments, |
| 2639 | xs_html_tag("video", | 3019 | xs_html_tag("video", |
| 2640 | xs_html_attr("preload", "none"), | 3020 | xs_html_attr("preload", "none"), |
| @@ -3003,7 +3383,7 @@ xs_html *html_footer(const snac *user) | |||
| 3003 | xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | 3383 | xs_str *html_timeline(snac *user, const xs_list *list, int read_only, |
| 3004 | int skip, int show, int show_more, | 3384 | int skip, int show, int show_more, |
| 3005 | const char *title, const char *page, | 3385 | const char *title, const char *page, |
| 3006 | int utl, const char *error) | 3386 | int utl, const char *error, int terse) |
| 3007 | /* returns the HTML for the timeline */ | 3387 | /* returns the HTML for the timeline */ |
| 3008 | { | 3388 | { |
| 3009 | xs_list *p = (xs_list *)list; | 3389 | xs_list *p = (xs_list *)list; |
| @@ -3031,7 +3411,11 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 3031 | 3411 | ||
| 3032 | if (user) { | 3412 | if (user) { |
| 3033 | head = html_user_head(user, desc, alternate); | 3413 | head = html_user_head(user, desc, alternate); |
| 3034 | body = html_user_body(user, read_only); | 3414 | |
| 3415 | if (terse) | ||
| 3416 | body = xs_html_tag("body", NULL); | ||
| 3417 | else | ||
| 3418 | body = html_user_body(user, read_only); | ||
| 3035 | } | 3419 | } |
| 3036 | else { | 3420 | else { |
| 3037 | head = html_instance_head(); | 3421 | head = html_instance_head(); |
| @@ -3251,7 +3635,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 3251 | 3635 | ||
| 3252 | if (list && user && read_only) { | 3636 | if (list && user && read_only) { |
| 3253 | /** history **/ | 3637 | /** history **/ |
| 3254 | if (xs_type(xs_dict_get(srv_config, "disable_history")) != XSTYPE_TRUE) { | 3638 | if (xs_type(xs_dict_get(srv_config, "disable_history")) != XSTYPE_TRUE && !terse) { |
| 3255 | xs_html *ul = xs_html_tag("ul", NULL); | 3639 | xs_html *ul = xs_html_tag("ul", NULL); |
| 3256 | 3640 | ||
| 3257 | xs_html *history = xs_html_tag("div", | 3641 | xs_html *history = xs_html_tag("div", |
| @@ -3323,13 +3707,19 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 3323 | } | 3707 | } |
| 3324 | 3708 | ||
| 3325 | 3709 | ||
| 3326 | xs_html *html_people_list(snac *user, xs_list *list, const char *header, const char *t, const char *proxy) | 3710 | xs_html *html_people_list(snac *user, xs_list *list, const char *header, const char *t, const char *proxy, int do_count) |
| 3327 | { | 3711 | { |
| 3328 | xs_html *snac_posts; | 3712 | xs_html *snac_posts; |
| 3713 | xs *header_cnt; | ||
| 3714 | if (do_count) | ||
| 3715 | header_cnt = xs_fmt("%s - %d\n", header, xs_list_len(list)); | ||
| 3716 | else | ||
| 3717 | header_cnt = xs_fmt("%s\n", header); | ||
| 3718 | |||
| 3329 | xs_html *people = xs_html_tag("div", | 3719 | xs_html *people = xs_html_tag("div", |
| 3330 | xs_html_tag("h2", | 3720 | xs_html_tag("h2", |
| 3331 | xs_html_attr("class", "snac-header"), | 3721 | xs_html_attr("class", "snac-header"), |
| 3332 | xs_html_text(header)), | 3722 | xs_html_text(header_cnt)), |
| 3333 | snac_posts = xs_html_tag("details", | 3723 | snac_posts = xs_html_tag("details", |
| 3334 | xs_html_attr("open", NULL), | 3724 | xs_html_attr("open", NULL), |
| 3335 | xs_html_tag("summary", | 3725 | xs_html_tag("summary", |
| @@ -3570,12 +3960,12 @@ xs_str *html_people(snac *user) | |||
| 3570 | 3960 | ||
| 3571 | if (xs_list_len(pending) || xs_is_true(xs_dict_get(user->config, "approve_followers"))) { | 3961 | if (xs_list_len(pending) || xs_is_true(xs_dict_get(user->config, "approve_followers"))) { |
| 3572 | xs_html_add(lists, | 3962 | xs_html_add(lists, |
| 3573 | html_people_list(user, pending, L("Pending follow confirmations"), "p", proxy)); | 3963 | html_people_list(user, pending, L("Pending follow confirmations"), "p", proxy, 1)); |
| 3574 | } | 3964 | } |
| 3575 | 3965 | ||
| 3576 | xs_html_add(lists, | 3966 | xs_html_add(lists, |
| 3577 | html_people_list(user, wing, L("People you follow"), "i", proxy), | 3967 | html_people_list(user, wing, L("People you follow"), "i", proxy, 1), |
| 3578 | html_people_list(user, wers, L("People that follow you"), "e", proxy)); | 3968 | html_people_list(user, wers, L("People that follow you"), "e", proxy, 1)); |
| 3579 | 3969 | ||
| 3580 | xs_html *html = xs_html_tag("html", | 3970 | xs_html *html = xs_html_tag("html", |
| 3581 | html_user_head(user, NULL, NULL), | 3971 | html_user_head(user, NULL, NULL), |
| @@ -3586,6 +3976,108 @@ xs_str *html_people(snac *user) | |||
| 3586 | return xs_html_render_s(html, "<!DOCTYPE html>\n"); | 3976 | return xs_html_render_s(html, "<!DOCTYPE html>\n"); |
| 3587 | } | 3977 | } |
| 3588 | 3978 | ||
| 3979 | /* Filter list to display only posts by actor. We'll probably show | ||
| 3980 | fewer than show posts. Should we try harder to find some? */ | ||
| 3981 | xs_str *html_people_one(snac *user, const char *actor, const xs_list *list, | ||
| 3982 | int skip, int show, int show_more, const char *page) | ||
| 3983 | { | ||
| 3984 | const char *proxy = NULL; | ||
| 3985 | xs_list *p = (xs_list *)list; | ||
| 3986 | const char *v; | ||
| 3987 | |||
| 3988 | if (xs_is_true(xs_dict_get(srv_config, "proxy_media"))) | ||
| 3989 | proxy = user->actor; | ||
| 3990 | |||
| 3991 | xs_html *body = html_user_body(user, 0); | ||
| 3992 | |||
| 3993 | xs_html *lists = xs_html_tag("div", | ||
| 3994 | xs_html_attr("class", "snac-posts")); | ||
| 3995 | |||
| 3996 | xs *foll = xs_list_append(xs_list_new(), actor); | ||
| 3997 | |||
| 3998 | xs_html_add(lists, | ||
| 3999 | html_people_list(user, foll, L("Contact's posts"), "p", proxy, 0)); | ||
| 4000 | |||
| 4001 | xs_html_add(body, lists); | ||
| 4002 | |||
| 4003 | while (xs_list_iter(&p, &v)) { | ||
| 4004 | xs *msg = NULL; | ||
| 4005 | int status; | ||
| 4006 | |||
| 4007 | status = timeline_get_by_md5(user, v, &msg); | ||
| 4008 | |||
| 4009 | if (!valid_status(status)) | ||
| 4010 | continue; | ||
| 4011 | |||
| 4012 | const char *id = xs_dict_get(msg, "id"); | ||
| 4013 | const char *by = get_atto(msg); | ||
| 4014 | xs *actor_md5 = NULL; | ||
| 4015 | xs_list *boosts = NULL; | ||
| 4016 | xs_list *likes = NULL; | ||
| 4017 | xs_list *reacts = NULL; | ||
| 4018 | /* Besides actor's posts, also show actor's boosts, and also | ||
| 4019 | posts by user with likes or reacts by actor. I.e., any | ||
| 4020 | actor's actions that user could have seen in the timeline | ||
| 4021 | or in notifications. */ | ||
| 4022 | if (!(by && strcmp(actor, by) == 0) && | ||
| 4023 | xs_list_in((boosts = object_announces(id)), | ||
| 4024 | (actor_md5 = xs_md5_hex(actor, strlen(actor)))) == -1 && | ||
| 4025 | (!(by && strcmp(user->actor, by) == 0) || | ||
| 4026 | (xs_list_in((likes = object_likes(id)), actor_md5) == -1 && | ||
| 4027 | xs_list_in((reacts = object_get_emoji_reacts(id)), actor_md5) == -1))) | ||
| 4028 | continue; | ||
| 4029 | |||
| 4030 | xs_html *entry = html_entry(user, msg, 0, 0, v, 1); | ||
| 4031 | |||
| 4032 | if (entry != NULL) | ||
| 4033 | xs_html_add(lists, | ||
| 4034 | entry); | ||
| 4035 | } | ||
| 4036 | |||
| 4037 | if (show_more) { | ||
| 4038 | xs *m = NULL; | ||
| 4039 | xs *m10 = NULL; | ||
| 4040 | xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show); | ||
| 4041 | |||
| 4042 | xs *url = xs_dup(user == NULL ? srv_baseurl : user->actor); | ||
| 4043 | |||
| 4044 | if (page != NULL) | ||
| 4045 | url = xs_str_cat(url, page); | ||
| 4046 | |||
| 4047 | if (xs_str_in(url, "?") != -1) | ||
| 4048 | m = xs_fmt("%s&%s", url, ss); | ||
| 4049 | else | ||
| 4050 | m = xs_fmt("%s?%s", url, ss); | ||
| 4051 | |||
| 4052 | m10 = xs_fmt("%s0", m); | ||
| 4053 | |||
| 4054 | xs_html *more_links = xs_html_tag("p", | ||
| 4055 | xs_html_tag("a", | ||
| 4056 | xs_html_attr("href", url), | ||
| 4057 | xs_html_attr("name", "snac-more"), | ||
| 4058 | xs_html_text(L("Back to top"))), | ||
| 4059 | xs_html_text(" - "), | ||
| 4060 | xs_html_tag("a", | ||
| 4061 | xs_html_attr("href", m), | ||
| 4062 | xs_html_attr("name", "snac-more"), | ||
| 4063 | xs_html_text(L("More..."))), | ||
| 4064 | xs_html_text(" - "), | ||
| 4065 | xs_html_tag("a", | ||
| 4066 | xs_html_attr("href", m10), | ||
| 4067 | xs_html_attr("name", "snac-more"), | ||
| 4068 | xs_html_text(L("More (x 10)...")))); | ||
| 4069 | |||
| 4070 | xs_html_add(body, | ||
| 4071 | more_links); | ||
| 4072 | } | ||
| 4073 | |||
| 4074 | xs_html *html = xs_html_tag("html", | ||
| 4075 | html_user_head(user, NULL, NULL), | ||
| 4076 | xs_html_add(body, | ||
| 4077 | html_footer(user))); | ||
| 4078 | |||
| 4079 | return xs_html_render_s(html, "<!DOCTYPE html>\n"); | ||
| 4080 | } | ||
| 3589 | 4081 | ||
| 3590 | xs_str *html_notifications(snac *user, int skip, int show) | 4082 | xs_str *html_notifications(snac *user, int skip, int show) |
| 3591 | { | 4083 | { |
| @@ -3654,6 +4146,9 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 3654 | if (xs_is_string(id2) && xs_set_add(&rep, id2) != 1) | 4146 | if (xs_is_string(id2) && xs_set_add(&rep, id2) != 1) |
| 3655 | continue; | 4147 | continue; |
| 3656 | 4148 | ||
| 4149 | if (strcmp(type, "EmojiReact") == 0 && xs_is_true(xs_dict_get(srv_config, "disable_emojireact"))) | ||
| 4150 | continue; | ||
| 4151 | |||
| 3657 | object_get(id, &obj); | 4152 | object_get(id, &obj); |
| 3658 | 4153 | ||
| 3659 | const char *msg_id = NULL; | 4154 | const char *msg_id = NULL; |
| @@ -3689,9 +4184,18 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 3689 | if (strcmp(type, "EmojiReact") == 0 || strcmp(type, "Like") == 0) { | 4184 | if (strcmp(type, "EmojiReact") == 0 || strcmp(type, "Like") == 0) { |
| 3690 | const char *content = xs_dict_get_path(noti, "msg.content"); | 4185 | const char *content = xs_dict_get_path(noti, "msg.content"); |
| 3691 | 4186 | ||
| 4187 | xs *cd = xs_dup(content); | ||
| 4188 | const char *sna = cd; | ||
| 4189 | const xs_dict *tag = xs_dict_get_path(noti, "msg.tag"); | ||
| 4190 | unsigned int utf = xs_utf8_dec((const char **)&sna); | ||
| 4191 | |||
| 4192 | int isEmoji = 0; | ||
| 4193 | if (xs_is_emoji(utf) || (tag && xs_list_len(tag) > 0)) | ||
| 4194 | isEmoji = 1; | ||
| 4195 | |||
| 3692 | if (xs_type(content) == XSTYPE_STRING) { | 4196 | if (xs_type(content) == XSTYPE_STRING) { |
| 3693 | xs *emoji = replace_shortnames(xs_dup(content), xs_dict_get_path(noti, "msg.tag"), 1, proxy); | 4197 | xs *emoji = replace_shortnames(xs_dup(content), xs_dict_get_path(noti, "msg.tag"), 1, proxy); |
| 3694 | wrk = xs_fmt("%s (%s️)", type, emoji); | 4198 | wrk = xs_fmt("%s (%s️)", isEmoji ? "EmojiReact" : "Like", emoji); |
| 3695 | label = wrk; | 4199 | label = wrk; |
| 3696 | } | 4200 | } |
| 3697 | } | 4201 | } |
| @@ -3909,6 +4413,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 3909 | int cache = 1; | 4413 | int cache = 1; |
| 3910 | int save = 1; | 4414 | int save = 1; |
| 3911 | int proxy = 0; | 4415 | int proxy = 0; |
| 4416 | int terse = 0; | ||
| 3912 | const char *v; | 4417 | const char *v; |
| 3913 | 4418 | ||
| 3914 | const xs_dict *q_vars = xs_dict_get(req, "q_vars"); | 4419 | const xs_dict *q_vars = xs_dict_get(req, "q_vars"); |
| @@ -3921,6 +4426,9 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 3921 | return HTTP_STATUS_NOT_FOUND; | 4426 | return HTTP_STATUS_NOT_FOUND; |
| 3922 | } | 4427 | } |
| 3923 | 4428 | ||
| 4429 | if (!xs_is_null(xs_dict_get(q_vars, "terse"))) | ||
| 4430 | terse = 1; | ||
| 4431 | |||
| 3924 | if (strcmp(v, "share-bridge") == 0) { | 4432 | if (strcmp(v, "share-bridge") == 0) { |
| 3925 | /* temporary redirect for a post */ | 4433 | /* temporary redirect for a post */ |
| 3926 | const char *login = xs_dict_get(q_vars, "login"); | 4434 | const char *login = xs_dict_get(q_vars, "login"); |
| @@ -3988,8 +4496,12 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 3988 | cache = 0; | 4496 | cache = 0; |
| 3989 | 4497 | ||
| 3990 | int skip = 0; | 4498 | int skip = 0; |
| 4499 | const char *max_show_default = "50"; | ||
| 4500 | int max_show = xs_number_get(xs_dict_get_def(srv_config, "max_timeline_entries", | ||
| 4501 | max_show_default)); | ||
| 3991 | int def_show = xs_number_get(xs_dict_get_def(srv_config, "def_timeline_entries", | 4502 | int def_show = xs_number_get(xs_dict_get_def(srv_config, "def_timeline_entries", |
| 3992 | xs_dict_get_def(srv_config, "max_timeline_entries", "50"))); | 4503 | xs_dict_get_def(srv_config, "max_timeline_entries", |
| 4504 | max_show_default))); | ||
| 3993 | int show = def_show; | 4505 | int show = def_show; |
| 3994 | 4506 | ||
| 3995 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) | 4507 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) |
| @@ -4015,13 +4527,15 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4015 | /* a show of 0 has no sense */ | 4527 | /* a show of 0 has no sense */ |
| 4016 | if (show == 0) | 4528 | if (show == 0) |
| 4017 | show = def_show; | 4529 | show = def_show; |
| 4530 | if (show > max_show) | ||
| 4531 | show = max_show; | ||
| 4018 | 4532 | ||
| 4019 | if (p_path == NULL) { /** public timeline **/ | 4533 | if (p_path == NULL) { /** public timeline **/ |
| 4020 | xs *h = xs_str_localtime(0, "%Y-%m.html"); | 4534 | xs *h = xs_str_localtime(0, "%Y-%m.html"); |
| 4021 | 4535 | ||
| 4022 | if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) { | 4536 | if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) { |
| 4023 | /** empty public timeline for private users **/ | 4537 | /** empty public timeline for private users **/ |
| 4024 | *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL, "", 1, error); | 4538 | *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL, "", 1, error, terse); |
| 4025 | *b_size = strlen(*body); | 4539 | *b_size = strlen(*body); |
| 4026 | status = HTTP_STATUS_OK; | 4540 | status = HTTP_STATUS_OK; |
| 4027 | } | 4541 | } |
| @@ -4044,7 +4558,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4044 | xs *pins = pinned_list(&snac); | 4558 | xs *pins = pinned_list(&snac); |
| 4045 | pins = xs_list_cat(pins, list); | 4559 | pins = xs_list_cat(pins, list); |
| 4046 | 4560 | ||
| 4047 | *body = html_timeline(&snac, pins, 1, skip, show, more, NULL, "", 1, error); | 4561 | *body = html_timeline(&snac, pins, 1, skip, show, more, NULL, "", 1, error, terse); |
| 4048 | 4562 | ||
| 4049 | *b_size = strlen(*body); | 4563 | *b_size = strlen(*body); |
| 4050 | status = HTTP_STATUS_OK; | 4564 | status = HTTP_STATUS_OK; |
| @@ -4060,9 +4574,15 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4060 | status = HTTP_STATUS_UNAUTHORIZED; | 4574 | status = HTTP_STATUS_UNAUTHORIZED; |
| 4061 | } | 4575 | } |
| 4062 | else { | 4576 | else { |
| 4063 | const char *q = xs_dict_get(q_vars, "q"); | 4577 | const char *q = NULL; |
| 4578 | xs *cq = xs_dup(xs_dict_get(q_vars, "q")); | ||
| 4064 | xs *url_acct = NULL; | 4579 | xs *url_acct = NULL; |
| 4065 | 4580 | ||
| 4581 | if (xs_is_string(cq)) { | ||
| 4582 | cq = xs_strip_i(cq); | ||
| 4583 | q = cq; | ||
| 4584 | } | ||
| 4585 | |||
| 4066 | /* searching for an URL? */ | 4586 | /* searching for an URL? */ |
| 4067 | if (q && xs_match(q, "https://*|http://*")) { | 4587 | if (q && xs_match(q, "https://*|http://*")) { |
| 4068 | /* may by an actor; try a webfinger */ | 4588 | /* may by an actor; try a webfinger */ |
| @@ -4126,11 +4646,11 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4126 | actor_add(actor, actor_obj); | 4646 | actor_add(actor, actor_obj); |
| 4127 | 4647 | ||
| 4128 | /* create a people list with only one element */ | 4648 | /* create a people list with only one element */ |
| 4129 | l = xs_list_append(xs_list_new(), actor); | 4649 | l = xs_list_append(l, actor); |
| 4130 | 4650 | ||
| 4131 | xs *title = xs_fmt(L("Search results for account %s"), q); | 4651 | xs *title = xs_fmt(L("Search results for account %s"), q); |
| 4132 | 4652 | ||
| 4133 | page = html_people_list(&snac, l, title, "wf", NULL); | 4653 | page = html_people_list(&snac, l, title, "wf", NULL, 1); |
| 4134 | } | 4654 | } |
| 4135 | } | 4655 | } |
| 4136 | 4656 | ||
| @@ -4168,7 +4688,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4168 | xs *title = xs_fmt(xs_list_len(tl) ? | 4688 | xs *title = xs_fmt(xs_list_len(tl) ? |
| 4169 | L("Search results for tag %s") : L("Nothing found for tag %s"), q); | 4689 | L("Search results for tag %s") : L("Nothing found for tag %s"), q); |
| 4170 | 4690 | ||
| 4171 | *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0, error); | 4691 | *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0, error, terse); |
| 4172 | *b_size = strlen(*body); | 4692 | *b_size = strlen(*body); |
| 4173 | status = HTTP_STATUS_OK; | 4693 | status = HTTP_STATUS_OK; |
| 4174 | } | 4694 | } |
| @@ -4193,7 +4713,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4193 | title = xs_fmt(L("Nothing found for '%s'"), q); | 4713 | title = xs_fmt(L("Nothing found for '%s'"), q); |
| 4194 | 4714 | ||
| 4195 | *body = html_timeline(&snac, tl, 0, skip, tl_len, to || tl_len == show, | 4715 | *body = html_timeline(&snac, tl, 0, skip, tl_len, to || tl_len == show, |
| 4196 | title, page, 0, error); | 4716 | title, page, 0, error, terse); |
| 4197 | *b_size = strlen(*body); | 4717 | *b_size = strlen(*body); |
| 4198 | status = HTTP_STATUS_OK; | 4718 | status = HTTP_STATUS_OK; |
| 4199 | } | 4719 | } |
| @@ -4220,7 +4740,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4220 | xs *list = timeline_list(&snac, "private", skip, show, &more); | 4740 | xs *list = timeline_list(&snac, "private", skip, show, &more); |
| 4221 | 4741 | ||
| 4222 | *body = html_timeline(&snac, list, 0, skip, show, | 4742 | *body = html_timeline(&snac, list, 0, skip, show, |
| 4223 | more, NULL, "/admin", 1, error); | 4743 | more, NULL, "/admin", 1, error, terse); |
| 4224 | 4744 | ||
| 4225 | *b_size = strlen(*body); | 4745 | *b_size = strlen(*body); |
| 4226 | status = HTTP_STATUS_OK; | 4746 | status = HTTP_STATUS_OK; |
| @@ -4247,7 +4767,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4247 | xs *list0 = xs_list_append(xs_list_new(), md5); | 4767 | xs *list0 = xs_list_append(xs_list_new(), md5); |
| 4248 | xs *list = timeline_top_level(&snac, list0); | 4768 | xs *list = timeline_top_level(&snac, list0); |
| 4249 | 4769 | ||
| 4250 | *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1, error); | 4770 | *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1, error, terse); |
| 4251 | *b_size = strlen(*body); | 4771 | *b_size = strlen(*body); |
| 4252 | status = HTTP_STATUS_OK; | 4772 | status = HTTP_STATUS_OK; |
| 4253 | } | 4773 | } |
| @@ -4266,6 +4786,37 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4266 | } | 4786 | } |
| 4267 | } | 4787 | } |
| 4268 | else | 4788 | else |
| 4789 | if (xs_startswith(p_path, "people/")) { /** a single actor **/ | ||
| 4790 | if (!login(&snac, req)) { | ||
| 4791 | *body = xs_dup(uid); | ||
| 4792 | status = HTTP_STATUS_UNAUTHORIZED; | ||
| 4793 | } | ||
| 4794 | else { | ||
| 4795 | xs *actor_dict = NULL; | ||
| 4796 | const char *actor_id = NULL; | ||
| 4797 | xs *actor = NULL; | ||
| 4798 | xs_list *page_lst = xs_split_n(p_path, "?", 2); | ||
| 4799 | xs *page = xs_str_cat(xs_str_new("/"), xs_list_get(page_lst, 0)); | ||
| 4800 | xs_list *l = xs_split_n(page, "/", 3); | ||
| 4801 | const char *actor_md5 = xs_list_get(l, 2); | ||
| 4802 | |||
| 4803 | if (valid_status(object_get_by_md5(actor_md5, &actor_dict)) && | ||
| 4804 | (actor_id = xs_dict_get(actor_dict, "id")) != NULL && | ||
| 4805 | valid_status(actor_get(actor_id, &actor))) { | ||
| 4806 | int more = 0; | ||
| 4807 | xs *list = timeline_simple_list(&snac, "private", skip, show, &more); | ||
| 4808 | |||
| 4809 | *body = html_people_one(&snac, actor_id, list, skip, show, more, page); | ||
| 4810 | *b_size = strlen(*body); | ||
| 4811 | status = HTTP_STATUS_OK; | ||
| 4812 | } | ||
| 4813 | else { | ||
| 4814 | *body = xs_dup(uid); | ||
| 4815 | status = HTTP_STATUS_NOT_FOUND; | ||
| 4816 | } | ||
| 4817 | } | ||
| 4818 | } | ||
| 4819 | else | ||
| 4269 | if (strcmp(p_path, "notifications") == 0) { /** the list of notifications **/ | 4820 | if (strcmp(p_path, "notifications") == 0) { /** the list of notifications **/ |
| 4270 | if (!login(&snac, req)) { | 4821 | if (!login(&snac, req)) { |
| 4271 | *body = xs_dup(uid); | 4822 | *body = xs_dup(uid); |
| @@ -4288,7 +4839,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4288 | xs *next = timeline_instance_list(skip + show, 1); | 4839 | xs *next = timeline_instance_list(skip + show, 1); |
| 4289 | 4840 | ||
| 4290 | *body = html_timeline(&snac, list, 0, skip, show, | 4841 | *body = html_timeline(&snac, list, 0, skip, show, |
| 4291 | xs_list_len(next), L("Showing instance timeline"), "/instance", 0, error); | 4842 | xs_list_len(next), L("Showing instance timeline"), "/instance", 0, error, terse); |
| 4292 | *b_size = strlen(*body); | 4843 | *b_size = strlen(*body); |
| 4293 | status = HTTP_STATUS_OK; | 4844 | status = HTTP_STATUS_OK; |
| 4294 | } | 4845 | } |
| @@ -4303,7 +4854,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4303 | xs *list = pinned_list(&snac); | 4854 | xs *list = pinned_list(&snac); |
| 4304 | 4855 | ||
| 4305 | *body = html_timeline(&snac, list, 0, skip, show, | 4856 | *body = html_timeline(&snac, list, 0, skip, show, |
| 4306 | 0, L("Pinned posts"), "", 0, error); | 4857 | 0, L("Pinned posts"), "", 0, error, terse); |
| 4307 | *b_size = strlen(*body); | 4858 | *b_size = strlen(*body); |
| 4308 | status = HTTP_STATUS_OK; | 4859 | status = HTTP_STATUS_OK; |
| 4309 | } | 4860 | } |
| @@ -4318,7 +4869,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4318 | xs *list = bookmark_list(&snac); | 4869 | xs *list = bookmark_list(&snac); |
| 4319 | 4870 | ||
| 4320 | *body = html_timeline(&snac, list, 0, skip, show, | 4871 | *body = html_timeline(&snac, list, 0, skip, show, |
| 4321 | 0, L("Bookmarked posts"), "", 0, error); | 4872 | 0, L("Bookmarked posts"), "", 0, error, terse); |
| 4322 | *b_size = strlen(*body); | 4873 | *b_size = strlen(*body); |
| 4323 | status = HTTP_STATUS_OK; | 4874 | status = HTTP_STATUS_OK; |
| 4324 | } | 4875 | } |
| @@ -4333,7 +4884,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4333 | xs *list = draft_list(&snac); | 4884 | xs *list = draft_list(&snac); |
| 4334 | 4885 | ||
| 4335 | *body = html_timeline(&snac, list, 0, skip, show, | 4886 | *body = html_timeline(&snac, list, 0, skip, show, |
| 4336 | 0, L("Post drafts"), "", 0, error); | 4887 | 0, L("Post drafts"), "", 0, error, terse); |
| 4337 | *b_size = strlen(*body); | 4888 | *b_size = strlen(*body); |
| 4338 | status = HTTP_STATUS_OK; | 4889 | status = HTTP_STATUS_OK; |
| 4339 | } | 4890 | } |
| @@ -4348,7 +4899,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4348 | xs *list = scheduled_list(&snac); | 4899 | xs *list = scheduled_list(&snac); |
| 4349 | 4900 | ||
| 4350 | *body = html_timeline(&snac, list, 0, skip, show, | 4901 | *body = html_timeline(&snac, list, 0, skip, show, |
| 4351 | 0, L("Scheduled posts"), "", 0, error); | 4902 | 0, L("Scheduled posts"), "", 0, error, terse); |
| 4352 | *b_size = strlen(*body); | 4903 | *b_size = strlen(*body); |
| 4353 | status = HTTP_STATUS_OK; | 4904 | status = HTTP_STATUS_OK; |
| 4354 | } | 4905 | } |
| @@ -4374,7 +4925,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4374 | xs *title = xs_fmt(L("Showing timeline for list '%s'"), name); | 4925 | xs *title = xs_fmt(L("Showing timeline for list '%s'"), name); |
| 4375 | 4926 | ||
| 4376 | *body = html_timeline(&snac, ttl, 0, skip, show, | 4927 | *body = html_timeline(&snac, ttl, 0, skip, show, |
| 4377 | xs_list_len(next), title, base, 1, error); | 4928 | xs_list_len(next), title, base, 1, error, terse); |
| 4378 | *b_size = strlen(*body); | 4929 | *b_size = strlen(*body); |
| 4379 | status = HTTP_STATUS_OK; | 4930 | status = HTTP_STATUS_OK; |
| 4380 | } | 4931 | } |
| @@ -4394,7 +4945,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4394 | 4945 | ||
| 4395 | list = xs_list_append(list, md5); | 4946 | list = xs_list_append(list, md5); |
| 4396 | 4947 | ||
| 4397 | *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL, "", 1, error); | 4948 | *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL, "", 1, error, terse); |
| 4398 | *b_size = strlen(*body); | 4949 | *b_size = strlen(*body); |
| 4399 | status = HTTP_STATUS_OK; | 4950 | status = HTTP_STATUS_OK; |
| 4400 | } | 4951 | } |
| @@ -4582,8 +5133,8 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 4582 | xs *msg = msg_admiration(&snac, id, *action == 'L' ? "Like" : "Announce"); | 5133 | xs *msg = msg_admiration(&snac, id, *action == 'L' ? "Like" : "Announce"); |
| 4583 | 5134 | ||
| 4584 | if (msg != NULL) { | 5135 | if (msg != NULL) { |
| 5136 | timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, *action == 'L' ? 1 : 0, msg); | ||
| 4585 | enqueue_message(&snac, msg); | 5137 | enqueue_message(&snac, msg); |
| 4586 | timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, *action == 'L' ? 1 : 0); | ||
| 4587 | 5138 | ||
| 4588 | status = HTTP_STATUS_SEE_OTHER; | 5139 | status = HTTP_STATUS_SEE_OTHER; |
| 4589 | } | 5140 | } |
| @@ -4858,6 +5409,9 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 4858 | /* overwrite object, not updating the indexes */ | 5409 | /* overwrite object, not updating the indexes */ |
| 4859 | object_add_ow(edit_id, msg); | 5410 | object_add_ow(edit_id, msg); |
| 4860 | 5411 | ||
| 5412 | /* index tags */ | ||
| 5413 | tag_index(edit_id, msg); | ||
| 5414 | |||
| 4861 | /* update message */ | 5415 | /* update message */ |
| 4862 | c_msg = msg_update(&snac, msg); | 5416 | c_msg = msg_update(&snac, msg); |
| 4863 | } | 5417 | } |
| @@ -4891,12 +5445,36 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 4891 | 5445 | ||
| 4892 | status = HTTP_STATUS_SEE_OTHER; | 5446 | status = HTTP_STATUS_SEE_OTHER; |
| 4893 | 5447 | ||
| 5448 | if (strcmp(action, L("EmojiUnreact")) == 0) { /** **/ | ||
| 5449 | const char *eid = xs_dict_get(p_vars, "eid"); | ||
| 5450 | |||
| 5451 | if (eid != NULL) { | ||
| 5452 | xs *n_msg = msg_emoji_unreact(&snac, id, eid); | ||
| 5453 | |||
| 5454 | if (n_msg != NULL) | ||
| 5455 | enqueue_message(&snac, n_msg); | ||
| 5456 | } | ||
| 5457 | } | ||
| 5458 | else | ||
| 5459 | if (strcmp(action, L("EmojiReact")) == 0) { /** **/ | ||
| 5460 | xs *eid = xs_dup(xs_dict_get(p_vars, "eid")); | ||
| 5461 | |||
| 5462 | eid = xs_strip_chars_i(eid, ":"); | ||
| 5463 | |||
| 5464 | const xs_dict *ret = msg_emoji_init(&snac, id, eid); | ||
| 5465 | /* fails if either invalid or already reacted */ | ||
| 5466 | if (!ret) | ||
| 5467 | ret = msg_emoji_unreact(&snac, id, eid); | ||
| 5468 | if (!ret) | ||
| 5469 | status = HTTP_STATUS_NOT_FOUND; | ||
| 5470 | } | ||
| 5471 | else | ||
| 4894 | if (strcmp(action, L("Like")) == 0) { /** **/ | 5472 | if (strcmp(action, L("Like")) == 0) { /** **/ |
| 4895 | xs *msg = msg_admiration(&snac, id, "Like"); | 5473 | xs *msg = msg_admiration(&snac, id, "Like"); |
| 4896 | 5474 | ||
| 4897 | if (msg != NULL) { | 5475 | if (msg != NULL) { |
| 5476 | timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 1, msg); | ||
| 4898 | enqueue_message(&snac, msg); | 5477 | enqueue_message(&snac, msg); |
| 4899 | timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 1); | ||
| 4900 | } | 5478 | } |
| 4901 | } | 5479 | } |
| 4902 | else | 5480 | else |
| @@ -4904,8 +5482,8 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 4904 | xs *msg = msg_admiration(&snac, id, "Announce"); | 5482 | xs *msg = msg_admiration(&snac, id, "Announce"); |
| 4905 | 5483 | ||
| 4906 | if (msg != NULL) { | 5484 | if (msg != NULL) { |
| 5485 | timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 0, msg); | ||
| 4907 | enqueue_message(&snac, msg); | 5486 | enqueue_message(&snac, msg); |
| 4908 | timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 0); | ||
| 4909 | } | 5487 | } |
| 4910 | } | 5488 | } |
| 4911 | else | 5489 | else |
| @@ -5022,7 +5600,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 5022 | } | 5600 | } |
| 5023 | else { | 5601 | else { |
| 5024 | /* delete an entry */ | 5602 | /* delete an entry */ |
| 5025 | if (xs_startswith(id, snac.actor) && !is_draft(&snac, id)) { | 5603 | if (is_msg_mine(&snac, id) && !is_draft(&snac, id)) { |
| 5026 | /* it's a post by us: generate a delete */ | 5604 | /* it's a post by us: generate a delete */ |
| 5027 | xs *msg = msg_delete(&snac, id); | 5605 | xs *msg = msg_delete(&snac, id); |
| 5028 | 5606 | ||
| @@ -5351,6 +5929,33 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 5351 | 5929 | ||
| 5352 | status = HTTP_STATUS_SEE_OTHER; | 5930 | status = HTTP_STATUS_SEE_OTHER; |
| 5353 | } | 5931 | } |
| 5932 | else | ||
| 5933 | if (p_path && strcmp(p_path, "admin/muted-words") == 0) { | ||
| 5934 | const char *words = xs_dict_get(p_vars, "muted_words"); | ||
| 5935 | |||
| 5936 | if (xs_is_string(words)) { | ||
| 5937 | xs *new_words = xs_list_new(); | ||
| 5938 | xs *l = xs_split(words, "\n"); | ||
| 5939 | const char *v; | ||
| 5940 | |||
| 5941 | xs_list_foreach(l, v) { | ||
| 5942 | xs *s1 = xs_strip_i(xs_dup(v)); | ||
| 5943 | s1 = xs_replace_i(s1, " ", ""); | ||
| 5944 | |||
| 5945 | if (*s1 == '\0') | ||
| 5946 | continue; | ||
| 5947 | |||
| 5948 | xs *s2 = xs_utf8_to_lower(s1); | ||
| 5949 | |||
| 5950 | new_words = xs_list_insert_sorted(new_words, s2); | ||
| 5951 | } | ||
| 5952 | |||
| 5953 | snac.config = xs_dict_set(snac.config, "muted_words", new_words); | ||
| 5954 | user_persist(&snac, 0); | ||
| 5955 | } | ||
| 5956 | |||
| 5957 | status = HTTP_STATUS_SEE_OTHER; | ||
| 5958 | } | ||
| 5354 | 5959 | ||
| 5355 | if (status == HTTP_STATUS_SEE_OTHER) { | 5960 | if (status == HTTP_STATUS_SEE_OTHER) { |
| 5356 | const char *hard_redir = xs_dict_get(p_vars, "hard-redir"); | 5961 | const char *hard_redir = xs_dict_get(p_vars, "hard-redir"); |