summaryrefslogtreecommitdiff
path: root/html.c
diff options
context:
space:
mode:
authorGravatar shtrophic2025-02-15 14:37:36 +0100
committerGravatar shtrophic2025-02-15 14:37:36 +0100
commit7611a6bee4bcbad2f1710aafa99aba730e5cf995 (patch)
tree33ab7bee30379e16f6869b2efda5494be8aeb858 /html.c
parentenforce tls when supported && add tests (diff)
parentVersion 2.72 RELEASED. (diff)
downloadsnac2-7611a6bee4bcbad2f1710aafa99aba730e5cf995.tar.gz
snac2-7611a6bee4bcbad2f1710aafa99aba730e5cf995.tar.xz
snac2-7611a6bee4bcbad2f1710aafa99aba730e5cf995.zip
Merge tag '2.72' into curl-smtp
Version 2.72 RELEASED.
Diffstat (limited to 'html.c')
-rw-r--r--html.c393
1 files changed, 282 insertions, 111 deletions
diff --git a/html.c b/html.c
index 3fd1e4b..e742e94 100644
--- a/html.c
+++ b/html.c
@@ -13,6 +13,7 @@
13#include "xs_html.h" 13#include "xs_html.h"
14#include "xs_curl.h" 14#include "xs_curl.h"
15#include "xs_unicode.h" 15#include "xs_unicode.h"
16#include "xs_url.h"
16 17
17#include "snac.h" 18#include "snac.h"
18 19
@@ -115,7 +116,8 @@ xs_str *actor_name(xs_dict *actor, const char *proxy)
115 116
116xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, 117xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
117 const char *udate, const char *url, int priv, 118 const char *udate, const char *url, int priv,
118 int in_people, const char *proxy, const char *lang) 119 int in_people, const char *proxy, const char *lang,
120 const char *md5)
119{ 121{
120 xs_html *actor_icon = xs_html_tag("p", NULL); 122 xs_html *actor_icon = xs_html_tag("p", NULL);
121 123
@@ -224,12 +226,31 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
224 if (xs_is_string(lang)) 226 if (xs_is_string(lang))
225 date_title = xs_str_cat(date_title, " (", lang, ")"); 227 date_title = xs_str_cat(date_title, " (", lang, ")");
226 228
229 xs_html *date_text = xs_html_text(date_label);
230
231 if (user && md5) {
232 xs *lpost_url = xs_fmt("%s/admin/p/%s#%s_entry",
233 user->actor, md5, md5);
234 date_text = xs_html_tag("a",
235 xs_html_attr("href", lpost_url),
236 xs_html_attr("class", "snac-pubdate"),
237 date_text);
238 }
239 else if (user && url) {
240 xs *lpost_url = xs_fmt("%s/admin?q=%s",
241 user->actor, xs_url_enc(url));
242 date_text = xs_html_tag("a",
243 xs_html_attr("href", lpost_url),
244 xs_html_attr("class", "snac-pubdate"),
245 date_text);
246 }
247
227 xs_html_add(actor_icon, 248 xs_html_add(actor_icon,
228 xs_html_text(" "), 249 xs_html_text(" "),
229 xs_html_tag("time", 250 xs_html_tag("time",
230 xs_html_attr("class", "dt-published snac-pubdate"), 251 xs_html_attr("class", "dt-published snac-pubdate"),
231 xs_html_attr("title", date_title), 252 xs_html_attr("title", date_title),
232 xs_html_text(date_label))); 253 date_text));
233 } 254 }
234 255
235 { 256 {
@@ -261,7 +282,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
261} 282}
262 283
263 284
264xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, const char *proxy) 285xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, const char *proxy, const char *md5)
265{ 286{
266 xs *actor = NULL; 287 xs *actor = NULL;
267 xs_html *actor_icon = NULL; 288 xs_html *actor_icon = NULL;
@@ -292,7 +313,7 @@ xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, con
292 else 313 else
293 lang = NULL; 314 lang = NULL;
294 315
295 actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy, lang); 316 actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy, lang, md5);
296 } 317 }
297 318
298 return actor_icon; 319 return actor_icon;
@@ -306,7 +327,7 @@ xs_html *html_note(snac *user, const char *summary,
306 const xs_val *cw_yn, const char *cw_text, 327 const xs_val *cw_yn, const char *cw_text,
307 const xs_val *mnt_only, const char *redir, 328 const xs_val *mnt_only, const char *redir,
308 const char *in_reply_to, int poll, 329 const char *in_reply_to, int poll,
309 const char *att_file, const char *att_alt_text, 330 const xs_list *att_files, const xs_list *att_alt_texts,
310 int is_draft) 331 int is_draft)
311/* Yes, this is a FUCKTON of arguments and I'm a bit embarrased */ 332/* Yes, this is a FUCKTON of arguments and I'm a bit embarrased */
312{ 333{
@@ -411,30 +432,71 @@ xs_html *html_note(snac *user, const char *summary,
411 xs_html_tag("p", NULL), 432 xs_html_tag("p", NULL),
412 att = xs_html_tag("details", 433 att = xs_html_tag("details",
413 xs_html_tag("summary", 434 xs_html_tag("summary",
414 xs_html_text(L("Attachment..."))), 435 xs_html_text(L("Attachments..."))),
415 xs_html_tag("p", NULL))); 436 xs_html_tag("p", NULL)));
416 437
417 if (att_file && *att_file) 438 int max_attachments = xs_number_get(xs_dict_get_def(srv_config, "max_attachments", "4"));
439 int att_n = 0;
440
441 /* fields for the currently existing attachments */
442 if (xs_is_list(att_files) && xs_is_list(att_alt_texts)) {
443 while (att_n < max_attachments) {
444 const char *att_file = xs_list_get(att_files, att_n);
445 const char *att_alt_text = xs_list_get(att_alt_texts, att_n);
446
447 if (!xs_is_string(att_file) || !xs_is_string(att_alt_text))
448 break;
449
450 xs *att_lbl = xs_fmt("attach_url_%d", att_n);
451 xs *alt_lbl = xs_fmt("alt_text_%d", att_n);
452
453 if (att_n)
454 xs_html_add(att,
455 xs_html_sctag("br", NULL));
456
457 xs_html_add(att,
458 xs_html_text(L("File:")),
459 xs_html_sctag("input",
460 xs_html_attr("type", "text"),
461 xs_html_attr("name", att_lbl),
462 xs_html_attr("title", L("Clear this field to delete the attachment")),
463 xs_html_attr("value", att_file)));
464
465 xs_html_add(att,
466 xs_html_text(" "),
467 xs_html_sctag("input",
468 xs_html_attr("type", "text"),
469 xs_html_attr("name", alt_lbl),
470 xs_html_attr("value", att_alt_text),
471 xs_html_attr("placeholder", L("Attachment description"))));
472
473 att_n++;
474 }
475 }
476
477 /* the rest of possible attachments */
478 while (att_n < max_attachments) {
479 xs *att_lbl = xs_fmt("attach_%d", att_n);
480 xs *alt_lbl = xs_fmt("alt_text_%d", att_n);
481
482 if (att_n)
483 xs_html_add(att,
484 xs_html_sctag("br", NULL));
485
418 xs_html_add(att, 486 xs_html_add(att,
419 xs_html_text(L("File:")),
420 xs_html_sctag("input", 487 xs_html_sctag("input",
421 xs_html_attr("type", "text"), 488 xs_html_attr("type", "file"),
422 xs_html_attr("name", "attach_url"), 489 xs_html_attr("name", att_lbl)));
423 xs_html_attr("title", L("Clear this field to delete the attachment")), 490
424 xs_html_attr("value", att_file)));
425 else
426 xs_html_add(att, 491 xs_html_add(att,
492 xs_html_text(" "),
427 xs_html_sctag("input", 493 xs_html_sctag("input",
428 xs_html_attr("type", "file"), 494 xs_html_attr("type", "text"),
429 xs_html_attr("name", "attach"))); 495 xs_html_attr("name", alt_lbl),
496 xs_html_attr("placeholder", L("Attachment description"))));
430 497
431 xs_html_add(att, 498 att_n++;
432 xs_html_text(" "), 499 }
433 xs_html_sctag("input",
434 xs_html_attr("type", "text"),
435 xs_html_attr("name", "alt_text"),
436 xs_html_attr("value", att_alt_text),
437 xs_html_attr("placeholder", L("Attachment description"))));
438 500
439 /* add poll controls */ 501 /* add poll controls */
440 if (poll) { 502 if (poll) {
@@ -553,10 +615,11 @@ xs_html *html_instance_head(void)
553 615
554static xs_html *html_instance_body(void) 616static xs_html *html_instance_body(void)
555{ 617{
556 const char *host = xs_dict_get(srv_config, "host"); 618 const char *host = xs_dict_get(srv_config, "host");
557 const char *sdesc = xs_dict_get(srv_config, "short_description"); 619 const char *sdesc = xs_dict_get(srv_config, "short_description");
558 const char *email = xs_dict_get(srv_config, "admin_email"); 620 const char *sdescraw = xs_dict_get(srv_config, "short_description_raw");
559 const char *acct = xs_dict_get(srv_config, "admin_account"); 621 const char *email = xs_dict_get(srv_config, "admin_email");
622 const char *acct = xs_dict_get(srv_config, "admin_account");
560 623
561 xs *blurb = xs_replace(snac_blurb, "%host%", host); 624 xs *blurb = xs_replace(snac_blurb, "%host%", host);
562 625
@@ -569,12 +632,21 @@ static xs_html *html_instance_body(void)
569 dl = xs_html_tag("dl", NULL))); 632 dl = xs_html_tag("dl", NULL)));
570 633
571 if (sdesc && *sdesc) { 634 if (sdesc && *sdesc) {
572 xs_html_add(dl, 635 if (!xs_is_null(sdescraw) && xs_type(sdescraw) == XSTYPE_TRUE) {
573 xs_html_tag("di", 636 xs_html_add(dl,
574 xs_html_tag("dt", 637 xs_html_tag("di",
575 xs_html_text(L("Site description"))), 638 xs_html_tag("dt",
576 xs_html_tag("dd", 639 xs_html_text(L("Site description"))),
577 xs_html_text(sdesc)))); 640 xs_html_tag("dd",
641 xs_html_raw(sdesc))));
642 } else {
643 xs_html_add(dl,
644 xs_html_tag("di",
645 xs_html_tag("dt",
646 xs_html_text(L("Site description"))),
647 xs_html_tag("dd",
648 xs_html_text(sdesc))));
649 }
578 } 650 }
579 if (email && *email) { 651 if (email && *email) {
580 xs *mailto = xs_fmt("mailto:%s", email); 652 xs *mailto = xs_fmt("mailto:%s", email);
@@ -1028,7 +1100,7 @@ xs_html *html_top_controls(snac *snac)
1028 NULL, NULL, 1100 NULL, NULL,
1029 xs_stock(XSTYPE_FALSE), "", 1101 xs_stock(XSTYPE_FALSE), "",
1030 xs_stock(XSTYPE_FALSE), NULL, 1102 xs_stock(XSTYPE_FALSE), NULL,
1031 NULL, 1, "", "", 0), 1103 NULL, 1, NULL, NULL, 0),
1032 1104
1033 /** operations **/ 1105 /** operations **/
1034 xs_html_tag("details", 1106 xs_html_tag("details",
@@ -1600,17 +1672,22 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1600 xs *form_id = xs_fmt("%s_edit_form", md5); 1672 xs *form_id = xs_fmt("%s_edit_form", md5);
1601 xs *redir = xs_fmt("%s_entry", md5); 1673 xs *redir = xs_fmt("%s_entry", md5);
1602 1674
1603 const char *att_file = ""; 1675 xs *att_files = xs_list_new();
1604 const char *att_alt_text = ""; 1676 xs *att_alt_texts = xs_list_new();
1677
1605 const xs_list *att_list = xs_dict_get(msg, "attachment"); 1678 const xs_list *att_list = xs_dict_get(msg, "attachment");
1606 1679
1607 /* does it have an attachment? */ 1680 if (xs_is_list(att_list)) {
1608 if (xs_type(att_list) == XSTYPE_LIST && xs_list_len(att_list)) { 1681 const xs_dict *d;
1609 const xs_dict *d = xs_list_get(att_list, 0); 1682
1683 xs_list_foreach(att_list, d) {
1684 const char *att_file = xs_dict_get(d, "url");
1685 const char *att_alt_text = xs_dict_get(d, "name");
1610 1686
1611 if (xs_type(d) == XSTYPE_DICT) { 1687 if (xs_is_string(att_file) && xs_is_string(att_alt_text)) {
1612 att_file = xs_dict_get_def(d, "url", ""); 1688 att_files = xs_list_append(att_files, att_file);
1613 att_alt_text = xs_dict_get_def(d, "name", ""); 1689 att_alt_texts = xs_list_append(att_alt_texts, att_alt_text);
1690 }
1614 } 1691 }
1615 } 1692 }
1616 1693
@@ -1622,7 +1699,7 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1622 id, NULL, 1699 id, NULL,
1623 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), 1700 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"),
1624 xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, 1701 xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir,
1625 NULL, 0, att_file, att_alt_text, is_draft(snac, id))), 1702 NULL, 0, att_files, att_alt_texts, is_draft(snac, id))),
1626 xs_html_tag("p", NULL)); 1703 xs_html_tag("p", NULL));
1627 } 1704 }
1628 1705
@@ -1641,7 +1718,7 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1641 NULL, NULL, 1718 NULL, NULL,
1642 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), 1719 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"),
1643 xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, 1720 xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir,
1644 id, 0, "", "", 0)), 1721 id, 0, NULL, NULL, 0)),
1645 xs_html_tag("p", NULL)); 1722 xs_html_tag("p", NULL));
1646 } 1723 }
1647 1724
@@ -1696,7 +1773,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1696 xs_html_tag("div", 1773 xs_html_tag("div",
1697 xs_html_attr("class", "snac-origin"), 1774 xs_html_attr("class", "snac-origin"),
1698 xs_html_text(L("follows you"))), 1775 xs_html_text(L("follows you"))),
1699 html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg, proxy))); 1776 html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg, proxy, NULL)));
1700 } 1777 }
1701 else 1778 else
1702 if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) { 1779 if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) {
@@ -1877,7 +1954,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1877 } 1954 }
1878 1955
1879 xs_html_add(post_header, 1956 xs_html_add(post_header,
1880 html_msg_icon(read_only ? NULL : user, actor, msg, proxy)); 1957 html_msg_icon(read_only ? NULL : user, actor, msg, proxy, md5));
1881 1958
1882 /** post content **/ 1959 /** post content **/
1883 1960
@@ -2022,16 +2099,17 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2022 const char *name = xs_dict_get(v, "name"); 2099 const char *name = xs_dict_get(v, "name");
2023 const xs_dict *replies = xs_dict_get(v, "replies"); 2100 const xs_dict *replies = xs_dict_get(v, "replies");
2024 2101
2025 if (name && replies) { 2102 if (xs_is_string(name) && xs_is_dict(replies)) {
2026 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); 2103 const char *ti = xs_number_str(xs_dict_get(replies, "totalItems"));
2027 2104
2028 xs_html_add(poll_result, 2105 if (xs_is_string(ti))
2029 xs_html_tag("tr", 2106 xs_html_add(poll_result,
2030 xs_html_tag("td", 2107 xs_html_tag("tr",
2031 xs_html_text(name), 2108 xs_html_tag("td",
2032 xs_html_text(":")), 2109 xs_html_text(name),
2033 xs_html_tag("td", 2110 xs_html_text(":")),
2034 xs_html_text(ti)))); 2111 xs_html_tag("td",
2112 xs_html_text(ti))));
2035 } 2113 }
2036 } 2114 }
2037 2115
@@ -2629,6 +2707,29 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2629 xs_html_attr("title", L("Post drafts")), 2707 xs_html_attr("title", L("Post drafts")),
2630 xs_html_text("drafts")))); 2708 xs_html_text("drafts"))));
2631 } 2709 }
2710
2711 /* the list of followed hashtags */
2712 const char *followed_hashtags = xs_dict_get(user->config, "followed_hashtags");
2713
2714 if (xs_is_list(followed_hashtags) && xs_list_len(followed_hashtags)) {
2715 xs_html *loht = xs_html_tag("ul",
2716 xs_html_attr("class", "snac-list-of-lists"));
2717 xs_html_add(body, loht);
2718
2719 const char *ht;
2720
2721 xs_list_foreach(followed_hashtags, ht) {
2722 xs *url = xs_fmt("%s/admin?q=%s", user->actor, ht);
2723 url = xs_replace_i(url, "#", "%23");
2724
2725 xs_html_add(loht,
2726 xs_html_tag("li",
2727 xs_html_tag("a",
2728 xs_html_attr("href", url),
2729 xs_html_attr("class", "snac-list-link"),
2730 xs_html_text(ht))));
2731 }
2732 }
2632 } 2733 }
2633 2734
2634 xs_html_add(body, 2735 xs_html_add(body,
@@ -2648,10 +2749,32 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2648 xs_html_add(body, 2749 xs_html_add(body,
2649 posts); 2750 posts);
2650 2751
2752 int mark_shown = 0;
2753
2651 while (xs_list_iter(&p, &v)) { 2754 while (xs_list_iter(&p, &v)) {
2652 xs *msg = NULL; 2755 xs *msg = NULL;
2653 int status; 2756 int status;
2654 2757
2758 /* "already seen" mark? */
2759 if (strcmp(v, MD5_ALREADY_SEEN_MARK) == 0) {
2760 if (skip == 0 && !mark_shown) {
2761 xs *s = xs_fmt("%s/admin", user->actor);
2762
2763 xs_html_add(posts,
2764 xs_html_tag("div",
2765 xs_html_attr("class", "snac-no-more-unseen-posts"),
2766 xs_html_text(L("No more unseen posts")),
2767 xs_html_text(" - "),
2768 xs_html_tag("a",
2769 xs_html_attr("href", s),
2770 xs_html_text(L("Back to top")))));
2771 }
2772
2773 mark_shown = 1;
2774
2775 continue;
2776 }
2777
2655 if (utl && user && !is_pinned_by_md5(user, v)) 2778 if (utl && user && !is_pinned_by_md5(user, v))
2656 status = timeline_get_by_md5(user, v, &msg); 2779 status = timeline_get_by_md5(user, v, &msg);
2657 else 2780 else
@@ -2788,7 +2911,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons
2788 xs_html_tag("div", 2911 xs_html_tag("div",
2789 xs_html_attr("class", "snac-post-header"), 2912 xs_html_attr("class", "snac-post-header"),
2790 html_actor_icon(snac, actor, xs_dict_get(actor, "published"), 2913 html_actor_icon(snac, actor, xs_dict_get(actor, "published"),
2791 NULL, NULL, 0, 1, proxy, NULL))); 2914 NULL, NULL, 0, 1, proxy, NULL, NULL)));
2792 2915
2793 /* content (user bio) */ 2916 /* content (user bio) */
2794 const char *c = xs_dict_get(actor, "summary"); 2917 const char *c = xs_dict_get(actor, "summary");
@@ -2885,7 +3008,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons
2885 NULL, actor_id, 3008 NULL, actor_id,
2886 xs_stock(XSTYPE_FALSE), "", 3009 xs_stock(XSTYPE_FALSE), "",
2887 xs_stock(XSTYPE_FALSE), NULL, 3010 xs_stock(XSTYPE_FALSE), NULL,
2888 NULL, 0, "", "", 0), 3011 NULL, 0, NULL, NULL, 0),
2889 xs_html_tag("p", NULL)); 3012 xs_html_tag("p", NULL));
2890 3013
2891 xs_html_add(snac_post, snac_controls); 3014 xs_html_add(snac_post, snac_controls);
@@ -2970,6 +3093,9 @@ xs_str *html_notifications(snac *user, int skip, int show)
2970 xs_set rep; 3093 xs_set rep;
2971 xs_set_init(&rep); 3094 xs_set_init(&rep);
2972 3095
3096 /* dict to store previous notification labels */
3097 xs *admiration_labels = xs_dict_new();
3098
2973 const xs_str *v; 3099 const xs_str *v;
2974 3100
2975 xs_list_foreach(n_list, v) { 3101 xs_list_foreach(n_list, v) {
@@ -2983,6 +3109,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
2983 const char *utype = xs_dict_get(noti, "utype"); 3109 const char *utype = xs_dict_get(noti, "utype");
2984 const char *id = xs_dict_get(noti, "objid"); 3110 const char *id = xs_dict_get(noti, "objid");
2985 const char *date = xs_dict_get(noti, "date"); 3111 const char *date = xs_dict_get(noti, "date");
3112 const char *id2 = xs_dict_get_path(noti, "msg.id");
2986 xs *wrk = NULL; 3113 xs *wrk = NULL;
2987 3114
2988 if (xs_is_null(id)) 3115 if (xs_is_null(id))
@@ -2991,12 +3118,15 @@ xs_str *html_notifications(snac *user, int skip, int show)
2991 if (is_hidden(user, id)) 3118 if (is_hidden(user, id))
2992 continue; 3119 continue;
2993 3120
3121 if (xs_is_string(id2) && xs_set_add(&rep, id2) != 1)
3122 continue;
3123
2994 object_get(id, &obj); 3124 object_get(id, &obj);
2995 3125
2996 const char *msg_id = NULL; 3126 const char *msg_id = NULL;
2997 3127
2998 if (xs_is_dict(obj) && (msg_id = xs_dict_get(obj, "id")) && xs_set_add(&rep, msg_id) != 1) 3128 if (xs_is_dict(obj))
2999 continue; 3129 msg_id = xs_dict_get(obj, "id");
3000 3130
3001 const char *actor_id = xs_dict_get(noti, "actor"); 3131 const char *actor_id = xs_dict_get(noti, "actor");
3002 xs *actor = NULL; 3132 xs *actor = NULL;
@@ -3030,9 +3160,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
3030 3160
3031 xs *s_date = xs_crop_i(xs_dup(date), 0, 10); 3161 xs *s_date = xs_crop_i(xs_dup(date), 0, 10);
3032 3162
3033 xs_html *entry = xs_html_tag("div", 3163 xs_html *this_html_label = xs_html_container(
3034 xs_html_attr("class", "snac-post-with-desc"),
3035 xs_html_tag("p",
3036 xs_html_tag("b", 3164 xs_html_tag("b",
3037 xs_html_text(label), 3165 xs_html_text(label),
3038 xs_html_text(" by "), 3166 xs_html_text(" by "),
@@ -3043,13 +3171,45 @@ xs_str *html_notifications(snac *user, int skip, int show)
3043 xs_html_tag("time", 3171 xs_html_tag("time",
3044 xs_html_attr("class", "dt-published snac-pubdate"), 3172 xs_html_attr("class", "dt-published snac-pubdate"),
3045 xs_html_attr("title", date), 3173 xs_html_attr("title", date),
3046 xs_html_text(s_date)))); 3174 xs_html_text(s_date)));
3175
3176 xs_html *html_label = NULL;
3177
3178 if (xs_is_string(msg_id)) {
3179 const xs_val *prev_label = xs_dict_get(admiration_labels, msg_id);
3180
3181 if (xs_type(prev_label) == XSTYPE_DATA) {
3182 /* there is a previous list of admiration labels! */
3183 xs_data_get(&html_label, prev_label);
3184
3185 xs_html_add(html_label,
3186 xs_html_sctag("br", NULL),
3187 this_html_label);
3188
3189 continue;
3190 }
3191 }
3192
3193 xs_html *entry = NULL;
3194
3195 html_label = xs_html_tag("p",
3196 this_html_label);
3197
3198 /* store in the admiration labels dict */
3199 xs *pl = xs_data_new(&html_label, sizeof(html_label));
3200
3201 if (xs_is_string(msg_id))
3202 admiration_labels = xs_dict_set(admiration_labels, msg_id, pl);
3203
3204 entry = xs_html_tag("div",
3205 xs_html_attr("class", "snac-post-with-desc"),
3206 html_label);
3047 3207
3048 if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0 || strcmp(type, "Block") == 0) { 3208 if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0 || strcmp(type, "Block") == 0) {
3049 xs_html_add(entry, 3209 xs_html_add(entry,
3050 xs_html_tag("div", 3210 xs_html_tag("div",
3051 xs_html_attr("class", "snac-post"), 3211 xs_html_attr("class", "snac-post"),
3052 html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL))); 3212 html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL)));
3053 } 3213 }
3054 else 3214 else
3055 if (strcmp(type, "Move") == 0) { 3215 if (strcmp(type, "Move") == 0) {
@@ -3063,18 +3223,23 @@ xs_str *html_notifications(snac *user, int skip, int show)
3063 xs_html_add(entry, 3223 xs_html_add(entry,
3064 xs_html_tag("div", 3224 xs_html_tag("div",
3065 xs_html_attr("class", "snac-post"), 3225 xs_html_attr("class", "snac-post"),
3066 html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy, NULL))); 3226 html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL)));
3067 } 3227 }
3068 } 3228 }
3069 } 3229 }
3070 else 3230 else
3071 if (obj != NULL) { 3231 if (obj != NULL) {
3072 xs *md5 = xs_md5_hex(id, strlen(id)); 3232 xs *md5 = xs_md5_hex(id, strlen(id));
3233 xs *ctxt = xs_fmt("%s/admin/p/%s#%s_entry", user->actor, md5, md5);
3073 3234
3074 xs_html *h = html_entry(user, obj, 0, 0, md5, 1); 3235 xs_html *h = html_entry(user, obj, 0, 0, md5, 1);
3075 3236
3076 if (h != NULL) { 3237 if (h != NULL) {
3077 xs_html_add(entry, 3238 xs_html_add(entry,
3239 xs_html_tag("p",
3240 xs_html_tag("a",
3241 xs_html_attr("href", ctxt),
3242 xs_html_text(L("Context")))),
3078 h); 3243 h);
3079 } 3244 }
3080 } 3245 }
@@ -3111,8 +3276,6 @@ xs_str *html_notifications(snac *user, int skip, int show)
3111 } 3276 }
3112 } 3277 }
3113 3278
3114 xs_set_free(&rep);
3115
3116 if (noti_new == NULL && noti_seen == NULL) 3279 if (noti_new == NULL && noti_seen == NULL)
3117 xs_html_add(body, 3280 xs_html_add(body,
3118 xs_html_tag("h2", 3281 xs_html_tag("h2",
@@ -3132,6 +3295,8 @@ xs_str *html_notifications(snac *user, int skip, int show)
3132 xs_html_text(L("More..."))))); 3295 xs_html_text(L("More...")))));
3133 } 3296 }
3134 3297
3298 xs_set_free(&rep);
3299
3135 xs_html_add(body, 3300 xs_html_add(body,
3136 html_footer()); 3301 html_footer());
3137 3302
@@ -3232,7 +3397,8 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3232 cache = 0; 3397 cache = 0;
3233 3398
3234 int skip = 0; 3399 int skip = 0;
3235 int def_show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); 3400 int def_show = xs_number_get(xs_dict_get_def(srv_config, "def_timeline_entries",
3401 xs_dict_get_def(srv_config, "max_timeline_entries", "50")));
3236 int show = def_show; 3402 int show = def_show;
3237 3403
3238 if ((v = xs_dict_get(q_vars, "skip")) != NULL) 3404 if ((v = xs_dict_get(q_vars, "skip")) != NULL)
@@ -3277,21 +3443,17 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3277 } 3443 }
3278 else { 3444 else {
3279 xs *list = NULL; 3445 xs *list = NULL;
3280 xs *next = NULL; 3446 int more = 0;
3281 3447
3282 if (xs_is_true(xs_dict_get(srv_config, "strict_public_timelines"))) { 3448 if (xs_is_true(xs_dict_get(srv_config, "strict_public_timelines")))
3283 list = timeline_simple_list(&snac, "public", skip, show); 3449 list = timeline_simple_list(&snac, "public", skip, show, &more);
3284 next = timeline_simple_list(&snac, "public", skip + show, 1); 3450 else
3285 } 3451 list = timeline_list(&snac, "public", skip, show, &more);
3286 else {
3287 list = timeline_list(&snac, "public", skip, show);
3288 next = timeline_list(&snac, "public", skip + show, 1);
3289 }
3290 3452
3291 xs *pins = pinned_list(&snac); 3453 xs *pins = pinned_list(&snac);
3292 pins = xs_list_cat(pins, list); 3454 pins = xs_list_cat(pins, list);
3293 3455
3294 *body = html_timeline(&snac, pins, 1, skip, show, xs_list_len(next), NULL, "", 1, error); 3456 *body = html_timeline(&snac, pins, 1, skip, show, more, NULL, "", 1, error);
3295 3457
3296 *b_size = strlen(*body); 3458 *b_size = strlen(*body);
3297 status = HTTP_STATUS_OK; 3459 status = HTTP_STATUS_OK;
@@ -3440,6 +3602,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3440 } 3602 }
3441 } 3603 }
3442 else { 3604 else {
3605 /** the private timeline **/
3443 double t = history_mtime(&snac, "timeline.html_"); 3606 double t = history_mtime(&snac, "timeline.html_");
3444 3607
3445 /* if enabled by admin, return a cached page if its timestamp is: 3608 /* if enabled by admin, return a cached page if its timestamp is:
@@ -3453,19 +3616,22 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3453 xs_dict_get(req, "if-none-match"), etag); 3616 xs_dict_get(req, "if-none-match"), etag);
3454 } 3617 }
3455 else { 3618 else {
3619 int more = 0;
3620
3456 snac_debug(&snac, 1, xs_fmt("building timeline")); 3621 snac_debug(&snac, 1, xs_fmt("building timeline"));
3457 3622
3458 xs *list = timeline_list(&snac, "private", skip, show); 3623 xs *list = timeline_list(&snac, "private", skip, show, &more);
3459 xs *next = timeline_list(&snac, "private", skip + show, 1);
3460 3624
3461 *body = html_timeline(&snac, list, 0, skip, show, 3625 *body = html_timeline(&snac, list, 0, skip, show,
3462 xs_list_len(next), NULL, "/admin", 1, error); 3626 more, NULL, "/admin", 1, error);
3463 3627
3464 *b_size = strlen(*body); 3628 *b_size = strlen(*body);
3465 status = HTTP_STATUS_OK; 3629 status = HTTP_STATUS_OK;
3466 3630
3467 if (save) 3631 if (save)
3468 history_add(&snac, "timeline.html_", *body, *b_size, etag); 3632 history_add(&snac, "timeline.html_", *body, *b_size, etag);
3633
3634 timeline_add_mark(&snac);
3469 } 3635 }
3470 } 3636 }
3471 } 3637 }
@@ -3481,7 +3647,8 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3481 const char *md5 = xs_list_get(l, -1); 3647 const char *md5 = xs_list_get(l, -1);
3482 3648
3483 if (md5 && *md5 && timeline_here(&snac, md5)) { 3649 if (md5 && *md5 && timeline_here(&snac, md5)) {
3484 xs *list = xs_list_append(xs_list_new(), md5); 3650 xs *list0 = xs_list_append(xs_list_new(), md5);
3651 xs *list = timeline_top_level(&snac, list0);
3485 3652
3486 *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1, error); 3653 *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1, error);
3487 *b_size = strlen(*body); 3654 *b_size = strlen(*body);
@@ -3665,7 +3832,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3665 3832
3666 int cnt = xs_number_get(xs_dict_get_def(srv_config, "max_public_entries", "20")); 3833 int cnt = xs_number_get(xs_dict_get_def(srv_config, "max_public_entries", "20"));
3667 3834
3668 xs *elems = timeline_simple_list(&snac, "public", 0, cnt); 3835 xs *elems = timeline_simple_list(&snac, "public", 0, cnt, NULL);
3669 xs *bio = xs_dup(xs_dict_get(snac.config, "bio")); 3836 xs *bio = xs_dup(xs_dict_get(snac.config, "bio"));
3670 3837
3671 xs *rss_title = xs_fmt("%s (@%s@%s)", 3838 xs *rss_title = xs_fmt("%s (@%s@%s)",
@@ -3869,52 +4036,56 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3869 /* post note */ 4036 /* post note */
3870 const xs_str *content = xs_dict_get(p_vars, "content"); 4037 const xs_str *content = xs_dict_get(p_vars, "content");
3871 const xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to"); 4038 const xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to");
3872 const xs_str *attach_url = xs_dict_get(p_vars, "attach_url");
3873 const xs_list *attach_file = xs_dict_get(p_vars, "attach");
3874 const xs_str *to = xs_dict_get(p_vars, "to"); 4039 const xs_str *to = xs_dict_get(p_vars, "to");
3875 const xs_str *sensitive = xs_dict_get(p_vars, "sensitive"); 4040 const xs_str *sensitive = xs_dict_get(p_vars, "sensitive");
3876 const xs_str *summary = xs_dict_get(p_vars, "summary"); 4041 const xs_str *summary = xs_dict_get(p_vars, "summary");
3877 const xs_str *edit_id = xs_dict_get(p_vars, "edit_id"); 4042 const xs_str *edit_id = xs_dict_get(p_vars, "edit_id");
3878 const xs_str *alt_text = xs_dict_get(p_vars, "alt_text");
3879 int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only")); 4043 int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only"));
3880 int store_as_draft = !xs_is_null(xs_dict_get(p_vars, "is_draft")); 4044 int store_as_draft = !xs_is_null(xs_dict_get(p_vars, "is_draft"));
3881 xs *attach_list = xs_list_new(); 4045 xs *attach_list = xs_list_new();
3882 4046
3883 /* default alt text */ 4047 /* iterate the attachments */
3884 if (xs_is_null(alt_text)) 4048 int max_attachments = xs_number_get(xs_dict_get_def(srv_config, "max_attachments", "4"));
3885 alt_text = "";
3886 4049
3887 /* is attach_url set? */ 4050 for (int att_n = 0; att_n < max_attachments; att_n++) {
3888 if (!xs_is_null(attach_url) && *attach_url != '\0') { 4051 xs *url_lbl = xs_fmt("attach_url_%d", att_n);
3889 xs *l = xs_list_new(); 4052 xs *att_lbl = xs_fmt("attach_%d", att_n);
4053 xs *alt_lbl = xs_fmt("alt_text_%d", att_n);
3890 4054
3891 l = xs_list_append(l, attach_url); 4055 const char *attach_url = xs_dict_get(p_vars, url_lbl);
3892 l = xs_list_append(l, alt_text); 4056 const xs_list *attach_file = xs_dict_get(p_vars, att_lbl);
4057 const char *alt_text = xs_dict_get_def(p_vars, alt_lbl, "");
3893 4058
3894 attach_list = xs_list_append(attach_list, l); 4059 if (xs_is_string(attach_url) && *attach_url != '\0') {
3895 } 4060 xs *l = xs_list_new();
3896 4061
3897 /* is attach_file set? */ 4062 l = xs_list_append(l, attach_url);
3898 if (!xs_is_null(attach_file) && xs_type(attach_file) == XSTYPE_LIST) { 4063 l = xs_list_append(l, alt_text);
3899 const char *fn = xs_list_get(attach_file, 0);
3900 4064
3901 if (*fn != '\0') { 4065 attach_list = xs_list_append(attach_list, l);
3902 char *ext = strrchr(fn, '.'); 4066 }
3903 xs *hash = xs_md5_hex(fn, strlen(fn)); 4067 else
3904 xs *id = xs_fmt("%s%s", hash, ext); 4068 if (xs_is_list(attach_file)) {
3905 xs *url = xs_fmt("%s/s/%s", snac.actor, id); 4069 const char *fn = xs_list_get(attach_file, 0);
3906 int fo = xs_number_get(xs_list_get(attach_file, 1));
3907 int fs = xs_number_get(xs_list_get(attach_file, 2));
3908 4070
3909 /* store */ 4071 if (xs_is_string(fn) && *fn != '\0') {
3910 static_put(&snac, id, payload + fo, fs); 4072 char *ext = strrchr(fn, '.');
4073 xs *hash = xs_md5_hex(fn, strlen(fn));
4074 xs *id = xs_fmt("%s%s", hash, ext);
4075 xs *url = xs_fmt("%s/s/%s", snac.actor, id);
4076 int fo = xs_number_get(xs_list_get(attach_file, 1));
4077 int fs = xs_number_get(xs_list_get(attach_file, 2));
3911 4078
3912 xs *l = xs_list_new(); 4079 /* store */
4080 static_put(&snac, id, payload + fo, fs);
3913 4081
3914 l = xs_list_append(l, url); 4082 xs *l = xs_list_new();
3915 l = xs_list_append(l, alt_text);
3916 4083
3917 attach_list = xs_list_append(attach_list, l); 4084 l = xs_list_append(l, url);
4085 l = xs_list_append(l, alt_text);
4086
4087 attach_list = xs_list_append(attach_list, l);
4088 }
3918 } 4089 }
3919 } 4090 }
3920 4091