summaryrefslogtreecommitdiff
path: root/html.c
diff options
context:
space:
mode:
Diffstat (limited to 'html.c')
-rw-r--r--html.c333
1 files changed, 312 insertions, 21 deletions
diff --git a/html.c b/html.c
index 20ad839..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
@@ -161,6 +162,34 @@ xs_str *actor_name(xs_dict *actor, const char *proxy)
161} 162}
162 163
163 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
164xs_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)
165/* needed when we have local text with no tags attached */ 194/* needed when we have local text with no tags attached */
166{ 195{
@@ -194,6 +223,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
194 int fwer = 0; 223 int fwer = 0;
195 224
196 xs *name = actor_name(actor, proxy); 225 xs *name = actor_name(actor, proxy);
226 xs *pronouns = actor_pronouns(actor);
197 227
198 /* get the avatar */ 228 /* get the avatar */
199 if ((v = xs_dict_get(actor, "icon")) != NULL) { 229 if ((v = xs_dict_get(actor, "icon")) != NULL) {
@@ -221,23 +251,35 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
221 anchored link to the people page instead of the actor url */ 251 anchored link to the people page instead of the actor url */
222 if (fwer || fwing) { 252 if (fwer || fwing) {
223 xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); 253 xs *md5 = xs_md5_hex(actor_id, strlen(actor_id));
224 href = xs_fmt("%s/people#%s", user->actor, md5); 254 href = xs_fmt("%s/people/%s", user->actor, md5);
225 } 255 }
226 } 256 }
227 257
228 if (href == NULL) 258 if (href == NULL)
229 href = xs_dup(actor_id); 259 href = xs_dup(actor_id);
230 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
231 xs_html_add(actor_icon, 276 xs_html_add(actor_icon,
232 xs_html_sctag("img", 277 xs_html_sctag("img",
233 xs_html_attr("loading", "lazy"), 278 xs_html_attr("loading", "lazy"),
234 xs_html_attr("class", "snac-avatar"), 279 xs_html_attr("class", "snac-avatar"),
235 xs_html_attr("src", avatar), 280 xs_html_attr("src", avatar),
236 xs_html_attr("alt", "[?]")), 281 xs_html_attr("alt", "[?]")),
237 xs_html_tag("a", 282 name_link);
238 xs_html_attr("href", href),
239 xs_html_attr("class", "p-author h-card snac-author"),
240 xs_html_raw(name))); /* name is already html-escaped */
241 283
242 if (!xs_is_null(url)) { 284 if (!xs_is_null(url)) {
243 xs *md5 = xs_md5_hex(url, strlen(url)); 285 xs *md5 = xs_md5_hex(url, strlen(url));
@@ -250,6 +292,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
250 xs_html_text("»"))); 292 xs_html_text("»")));
251 } 293 }
252 294
295
253 if (strcmp(xs_dict_get(actor, "type"), "Service") == 0) { 296 if (strcmp(xs_dict_get(actor, "type"), "Service") == 0) {
254 xs_html_add(actor_icon, 297 xs_html_add(actor_icon,
255 xs_html_text(" "), 298 xs_html_text(" "),
@@ -1864,6 +1907,38 @@ xs_html *html_top_controls(snac *user)
1864 xs_html_attr("class", "button"), 1907 xs_html_attr("class", "button"),
1865 xs_html_attr("value", L("Update hashtags"))))))); 1908 xs_html_attr("value", L("Update hashtags")))))));
1866 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
1867 return top_controls; 1942 return top_controls;
1868} 1943}
1869 1944
@@ -2103,7 +2178,7 @@ xs_html *html_entry_controls(snac *user, const char *actor,
2103 xs_html_tag("p", NULL)); 2178 xs_html_tag("p", NULL));
2104 } 2179 }
2105 2180
2106 { /** emoji react **/ 2181 if (!xs_is_true(xs_dict_get(srv_config, "disable_emojireact"))) { /** emoji react **/
2107 /* the post textarea */ 2182 /* the post textarea */
2108 xs *div_id = xs_fmt("%s_reply", md5); 2183 xs *div_id = xs_fmt("%s_reply", md5);
2109 xs *form_id = xs_fmt("%s_reply_form", md5); 2184 xs *form_id = xs_fmt("%s_reply_form", md5);
@@ -2144,6 +2219,30 @@ xs_html *html_entry_controls(snac *user, const char *actor,
2144} 2219}
2145 2220
2146 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
2147xs_html *html_entry(snac *user, xs_dict *msg, int read_only, 2246xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2148 int level, const char *md5, int hide_children) 2247 int level, const char *md5, int hide_children)
2149{ 2248{
@@ -2355,7 +2454,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2355 } 2454 }
2356 2455
2357 if (!read_only && (fwers || fwing)) 2456 if (!read_only && (fwers || fwing))
2358 href = xs_fmt("%s/people#%s", user->actor, p); 2457 href = xs_fmt("%s/people/%s", user->actor, p);
2359 else 2458 else
2360 href = xs_dup(id); 2459 href = xs_dup(id);
2361 2460
@@ -2438,6 +2537,17 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2438 xs_html_text(v), 2537 xs_html_text(v),
2439 xs_html_text(L(" [SENSITIVE CONTENT]")))); 2538 xs_html_text(L(" [SENSITIVE CONTENT]"))));
2440 } 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 }
2441 else { 2551 else {
2442 snac_content = xs_html_tag("div", NULL); 2552 snac_content = xs_html_tag("div", NULL);
2443 } 2553 }
@@ -2447,7 +2557,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2447 2557
2448 /* add all emoji reacts */ 2558 /* add all emoji reacts */
2449 int is_emoji = 0; 2559 int is_emoji = 0;
2450 { 2560 if (!xs_is_true(xs_dict_get(srv_config, "disable_emojireact"))) {
2451 int c = 0; 2561 int c = 0;
2452 const xs_dict *k; 2562 const xs_dict *k;
2453 xs *ls = xs_list_new(); 2563 xs *ls = xs_list_new();
@@ -2460,6 +2570,10 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2460 const char *content = xs_dict_get(m, "content"); 2570 const char *content = xs_dict_get(m, "content");
2461 const char *actor = xs_dict_get(m, "actor"); 2571 const char *actor = xs_dict_get(m, "actor");
2462 const xs_list *contentl = xs_dict_get(sfrl, content); 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
2463 xs *actors = xs_list_new(); 2577 xs *actors = xs_list_new();
2464 actors = xs_list_append(actors, actor); 2578 actors = xs_list_append(actors, actor);
2465 char me = actor && user && strcmp(actor, user->actor) == 0; 2579 char me = actor && user && strcmp(actor, user->actor) == 0;
@@ -3593,13 +3707,19 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
3593} 3707}
3594 3708
3595 3709
3596xs_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)
3597{ 3711{
3598 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
3599 xs_html *people = xs_html_tag("div", 3719 xs_html *people = xs_html_tag("div",
3600 xs_html_tag("h2", 3720 xs_html_tag("h2",
3601 xs_html_attr("class", "snac-header"), 3721 xs_html_attr("class", "snac-header"),
3602 xs_html_text(header)), 3722 xs_html_text(header_cnt)),
3603 snac_posts = xs_html_tag("details", 3723 snac_posts = xs_html_tag("details",
3604 xs_html_attr("open", NULL), 3724 xs_html_attr("open", NULL),
3605 xs_html_tag("summary", 3725 xs_html_tag("summary",
@@ -3840,12 +3960,12 @@ xs_str *html_people(snac *user)
3840 3960
3841 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"))) {
3842 xs_html_add(lists, 3962 xs_html_add(lists,
3843 html_people_list(user, pending, L("Pending follow confirmations"), "p", proxy)); 3963 html_people_list(user, pending, L("Pending follow confirmations"), "p", proxy, 1));
3844 } 3964 }
3845 3965
3846 xs_html_add(lists, 3966 xs_html_add(lists,
3847 html_people_list(user, wing, L("People you follow"), "i", proxy), 3967 html_people_list(user, wing, L("People you follow"), "i", proxy, 1),
3848 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));
3849 3969
3850 xs_html *html = xs_html_tag("html", 3970 xs_html *html = xs_html_tag("html",
3851 html_user_head(user, NULL, NULL), 3971 html_user_head(user, NULL, NULL),
@@ -3856,6 +3976,108 @@ xs_str *html_people(snac *user)
3856 return xs_html_render_s(html, "<!DOCTYPE html>\n"); 3976 return xs_html_render_s(html, "<!DOCTYPE html>\n");
3857} 3977}
3858 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}
3859 4081
3860xs_str *html_notifications(snac *user, int skip, int show) 4082xs_str *html_notifications(snac *user, int skip, int show)
3861{ 4083{
@@ -3924,6 +4146,9 @@ xs_str *html_notifications(snac *user, int skip, int show)
3924 if (xs_is_string(id2) && xs_set_add(&rep, id2) != 1) 4146 if (xs_is_string(id2) && xs_set_add(&rep, id2) != 1)
3925 continue; 4147 continue;
3926 4148
4149 if (strcmp(type, "EmojiReact") == 0 && xs_is_true(xs_dict_get(srv_config, "disable_emojireact")))
4150 continue;
4151
3927 object_get(id, &obj); 4152 object_get(id, &obj);
3928 4153
3929 const char *msg_id = NULL; 4154 const char *msg_id = NULL;
@@ -4271,8 +4496,12 @@ int html_get_handler(const xs_dict *req, const char *q_path,
4271 cache = 0; 4496 cache = 0;
4272 4497
4273 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));
4274 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",
4275 xs_dict_get_def(srv_config, "max_timeline_entries", "50"))); 4503 xs_dict_get_def(srv_config, "max_timeline_entries",
4504 max_show_default)));
4276 int show = def_show; 4505 int show = def_show;
4277 4506
4278 if ((v = xs_dict_get(q_vars, "skip")) != NULL) 4507 if ((v = xs_dict_get(q_vars, "skip")) != NULL)
@@ -4298,6 +4527,8 @@ int html_get_handler(const xs_dict *req, const char *q_path,
4298 /* a show of 0 has no sense */ 4527 /* a show of 0 has no sense */
4299 if (show == 0) 4528 if (show == 0)
4300 show = def_show; 4529 show = def_show;
4530 if (show > max_show)
4531 show = max_show;
4301 4532
4302 if (p_path == NULL) { /** public timeline **/ 4533 if (p_path == NULL) { /** public timeline **/
4303 xs *h = xs_str_localtime(0, "%Y-%m.html"); 4534 xs *h = xs_str_localtime(0, "%Y-%m.html");
@@ -4343,12 +4574,14 @@ int html_get_handler(const xs_dict *req, const char *q_path,
4343 status = HTTP_STATUS_UNAUTHORIZED; 4574 status = HTTP_STATUS_UNAUTHORIZED;
4344 } 4575 }
4345 else { 4576 else {
4346 xs *q = NULL; 4577 const char *q = NULL;
4347 const char *q1 = xs_dict_get(q_vars, "q"); 4578 xs *cq = xs_dup(xs_dict_get(q_vars, "q"));
4348 xs *url_acct = NULL; 4579 xs *url_acct = NULL;
4349 4580
4350 if (xs_is_string(q1)) 4581 if (xs_is_string(cq)) {
4351 q = xs_strip_i(xs_dup(q1)); 4582 cq = xs_strip_i(cq);
4583 q = cq;
4584 }
4352 4585
4353 /* searching for an URL? */ 4586 /* searching for an URL? */
4354 if (q && xs_match(q, "https://*|http://*")) { 4587 if (q && xs_match(q, "https://*|http://*")) {
@@ -4413,11 +4646,11 @@ int html_get_handler(const xs_dict *req, const char *q_path,
4413 actor_add(actor, actor_obj); 4646 actor_add(actor, actor_obj);
4414 4647
4415 /* create a people list with only one element */ 4648 /* create a people list with only one element */
4416 l = xs_list_append(xs_list_new(), actor); 4649 l = xs_list_append(l, actor);
4417 4650
4418 xs *title = xs_fmt(L("Search results for account %s"), q); 4651 xs *title = xs_fmt(L("Search results for account %s"), q);
4419 4652
4420 page = html_people_list(&snac, l, title, "wf", NULL); 4653 page = html_people_list(&snac, l, title, "wf", NULL, 1);
4421 } 4654 }
4422 } 4655 }
4423 4656
@@ -4553,6 +4786,37 @@ int html_get_handler(const xs_dict *req, const char *q_path,
4553 } 4786 }
4554 } 4787 }
4555 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
4556 if (strcmp(p_path, "notifications") == 0) { /** the list of notifications **/ 4820 if (strcmp(p_path, "notifications") == 0) { /** the list of notifications **/
4557 if (!login(&snac, req)) { 4821 if (!login(&snac, req)) {
4558 *body = xs_dup(uid); 4822 *body = xs_dup(uid);
@@ -5665,6 +5929,33 @@ int html_post_handler(const xs_dict *req, const char *q_path,
5665 5929
5666 status = HTTP_STATUS_SEE_OTHER; 5930 status = HTTP_STATUS_SEE_OTHER;
5667 } 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 }
5668 5959
5669 if (status == HTTP_STATUS_SEE_OTHER) { 5960 if (status == HTTP_STATUS_SEE_OTHER) {
5670 const char *hard_redir = xs_dict_get(p_vars, "hard-redir"); 5961 const char *hard_redir = xs_dict_get(p_vars, "hard-redir");