summaryrefslogtreecommitdiff
path: root/html.c
diff options
context:
space:
mode:
Diffstat (limited to 'html.c')
-rw-r--r--html.c781
1 files changed, 693 insertions, 88 deletions
diff --git a/html.c b/html.c
index df5b508..3f5435c 100644
--- a/html.c
+++ b/html.c
@@ -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 58xs_str *_replace_shortnames(xs_str *s, const xs_list *tag, int ems,
58xs_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
143xs_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
133xs_str *actor_name(xs_dict *actor, const char *proxy) 150xs_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
165xs_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
148xs_str *format_text_with_emoji(snac *user, const char *text, int ems, const char *proxy) 193xs_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
460void 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. */
494xs_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
402xs_html *html_note(snac *user, const char *summary, 538xs_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
2222static 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
2054xs_html *html_entry(snac *user, xs_dict *msg, int read_only, 2246xs_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)
3003xs_str *html_timeline(snac *user, const xs_list *list, int read_only, 3383xs_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
3326xs_html *html_people_list(snac *user, xs_list *list, const char *header, const char *t, const char *proxy) 3710xs_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? */
3981xs_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
3590xs_str *html_notifications(snac *user, int skip, int show) 4082xs_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&#xFE0F;)", type, emoji); 4198 wrk = xs_fmt("%s (%s&#xFE0F;)", 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");