about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2020-07-17 13:23:21 -0400
committerJune McEnroe <june@causal.agency>2020-07-17 13:23:21 -0400
commit0a054aeb9ce8430a62be05283300ec34e9c2b0af (patch)
tree344ec24121d800a28df101defd9d01140dda4db1
parentAdd margin to inputs (diff)
downloadscooper-0a054aeb9ce8430a62be05283300ec34e9c2b0af.tar.gz
scooper-0a054aeb9ce8430a62be05283300ec34e9c2b0af.zip
Add export option
Adding the export query parameter to any page downloads a .html with the
default stylesheet embedded, and all navigation, forms and internal
links removed, for being able to share some part of a private instance
of litterbox.
-rw-r--r--.gitignore2
-rw-r--r--Makefile7
-rw-r--r--contexts.c4
-rw-r--r--css.sh2
-rw-r--r--default.css5
-rw-r--r--events.c10
-rw-r--r--html.c49
-rw-r--r--networks.c4
-rw-r--r--search.c10
-rw-r--r--server.c3
-rw-r--r--server.h21
11 files changed, 80 insertions, 37 deletions
diff --git a/.gitignore b/.gitignore
index 791d6d0..94ee777 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 *.o
 .test
 config.mk
-css.h
+css.c
 scooper
 tags
diff --git a/Makefile b/Makefile
index 1cb3bf4..b6387ef 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@ TEST_DB = ~/.local/share/litterbox/litterbox.sqlite
 -include config.mk
 
 OBJS += contexts.o
+OBJS += css.o
 OBJS += events.o
 OBJS += html.o
 OBJS += networks.o
@@ -24,9 +25,7 @@ scooper: ${OBJS}
 
 ${OBJS}: server.h
 
-server.o: css.h
-
-css.h: css.sh default.css color.css
+css.c: css.sh default.css color.css
 	sh css.sh default.css color.css > $@
 
 test: .test
@@ -39,7 +38,7 @@ tags: *.c *.h
 	ctags -w *.c *.h
 
 clean:
-	rm -f scooper ${OBJS} css.h .test tags
+	rm -f scooper ${OBJS} css.c .test tags
 
 install: scooper scooper.1
 	install -d ${PREFIX}/bin ${MANDIR}/man1
diff --git a/contexts.c b/contexts.c
index 5c5e5cc..d55181b 100644
--- a/contexts.c
+++ b/contexts.c
@@ -69,7 +69,7 @@ enum kcgi_err contextsPage(struct kreq *req) {
 	struct khtmlreq html;
 	error = error
 		|| khtml_open(&html, req, 0)
-		|| htmlHead(&html, network)
+		|| htmlHead(&html, req, network)
 		|| htmlNav(&html, req);
 	if (error) return error;
 
@@ -142,5 +142,5 @@ enum kcgi_err contextsPage(struct kreq *req) {
 	}
 	if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
 
-	return htmlFooter(&html) || khtml_close(&html);
+	return htmlFooter(&html, req) || khtml_close(&html);
 }
diff --git a/css.sh b/css.sh
index 80f28d1..be3e44b 100644
--- a/css.sh
+++ b/css.sh
@@ -3,7 +3,7 @@ set -eu
 
 cat <<EOF
 #define CSS(...) #__VA_ARGS__
-static const char CSS[] = CSS(
+const char *CSS = CSS(
 EOF
 cat "$@"
 echo ');'
diff --git a/default.css b/default.css
index 7b68600..552b7b9 100644
--- a/default.css
+++ b/default.css
@@ -115,10 +115,13 @@ tr.gap {
 td.time, td.network, td.context, td.nick {
 	white-space: nowrap;
 }
-td.time a, td.network a, td.context a {
+td.time, td.network, td.context {
 	font-size: x-small;
 	color: var(--dark);
 }
+td.time a, td.network a, td.context a {
+	color: inherit;
+}
 tr:target td.time a {
 	color: var(--accent);
 	text-decoration: underline;
diff --git a/events.c b/events.c
index 932f535..7f56081 100644
--- a/events.c
+++ b/events.c
@@ -36,6 +36,7 @@ static const char *timestamp(time_t time) {
 
 static enum kcgi_err
 dateForm(struct khtmlreq *html, struct kreq *req, const char *current) {
+	if (req->fieldmap[Export]) return KCGI_OK;
 	struct tm tm = {0};
 	if (!strptime(current, "%F", &tm)) {
 		tm = *gmtime(&(time_t) { time(NULL) });
@@ -66,6 +67,7 @@ dateForm(struct khtmlreq *html, struct kreq *req, const char *current) {
 
 static enum kcgi_err
 displayForm(struct khtmlreq *html, struct kreq *req, bool tidy) {
+	if (req->fieldmap[Export]) return KCGI_OK;
 	return 0
 		|| khtml_attr(
 			html, KELEM_FORM,
@@ -203,7 +205,7 @@ enum kcgi_err eventsPage(struct kreq *req) {
 	struct khtmlreq html;
 	error = error
 		|| khtml_open(&html, req, 0)
-		|| htmlHead(&html, context)
+		|| htmlHead(&html, req, context)
 		|| htmlNav(&html, req)
 		|| khtml_elem(&html, KELEM_DIV)
 		|| dateForm(&html, req, time)
@@ -257,7 +259,7 @@ enum kcgi_err eventsPage(struct kreq *req) {
 		event.target = sqlite3_column_text(events, i++);
 		event.message = sqlite3_column_text(events, i++);
 
-		if (!rows) {
+		if (!rows && !req->fieldmap[Export]) {
 			char *href = NULL;
 			char *page = pageURL(
 				network, context, Before, event.time + eventsOverlap, tidy
@@ -291,7 +293,7 @@ enum kcgi_err eventsPage(struct kreq *req) {
 	}
 	if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
 
-	if (rows && (rows == eventsLimit || req->fieldmap[Before])) {
+	if ((rows == eventsLimit || req->fieldmap[Before]) && !req->fieldmap[Export]) {
 		char *href = pageURL(
 			network, context, After, prevTime - eventsOverlap, tidy
 		);
@@ -313,5 +315,5 @@ enum kcgi_err eventsPage(struct kreq *req) {
 		if (error) return error;
 	}
 
-	return htmlFooter(&html) || khtml_close(&html);
+	return htmlFooter(&html, req) || khtml_close(&html);
 }
diff --git a/html.c b/html.c
index a28d84b..2f6be5f 100644
--- a/html.c
+++ b/html.c
@@ -30,18 +30,30 @@
 
 const char *htmlStylesheet = "stylesheet.css";
 
-enum kcgi_err htmlHead(struct khtmlreq *html, const char *title) {
+static enum kcgi_err htmlCSS(struct khtmlreq *html, struct kreq *req) {
+	if (req->fieldmap[Export]) {
+		return 0
+			|| khtml_elem(html, KELEM_STYLE)
+			|| khttp_puts(req, CSS)
+			|| khtml_closeelem(html, 1);
+	} else {
+		return khtml_attr(
+			html, KELEM_LINK,
+			KATTR_REL, "stylesheet",
+			KATTR_HREF, htmlStylesheet,
+			KATTR__MAX
+		);
+	}
+}
+
+enum kcgi_err
+htmlHead(struct khtmlreq *html, struct kreq *req, const char *title) {
 	return khtml_elem(html, KELEM_DOCTYPE)
 		|| khtml_attr(html, KELEM_META, KATTR_CHARSET, "utf-8", KATTR__MAX)
 		|| khtml_elem(html, KELEM_TITLE)
 		|| khtml_puts(html, title)
 		|| khtml_closeelem(html, 1)
-		|| khtml_attr(
-			html, KELEM_LINK,
-			KATTR_REL, "stylesheet",
-			KATTR_HREF, htmlStylesheet,
-			KATTR__MAX
-		)
+		|| htmlCSS(html, req)
 		|| khtml_elem(html, KELEM_H1)
 		|| khtml_puts(html, title)
 		|| khtml_closeelem(html, 1);
@@ -60,6 +72,8 @@ htmlHidden(struct khtmlreq *html, struct kreq *req, enum Key key) {
 }
 
 enum kcgi_err htmlNav(struct khtmlreq *html, struct kreq *req) {
+	if (req->fieldmap[Export]) return KCGI_OK;
+
 	enum kcgi_err error = 0
 		|| khtml_elem(html, KELEM_NAV)
 		|| khtml_elem(html, KELEM_OL)
@@ -255,10 +269,10 @@ static const char *timestamp(time_t time) {
 }
 
 static enum kcgi_err
-eventTime(struct khtmlreq *html, const struct Event *event) {
+eventTime(struct khtmlreq *html, struct kreq *req, struct Event *event) {
 	char *page = NULL;
 	char *href = NULL;
-	if (event->network && event->context) {
+	if (event->network && event->context && !req->fieldmap[Export]) {
 		page = khttp_urlpart(
 			NULL, NULL, Pages[Events],
 			Keys[Network].name, event->network,
@@ -290,6 +304,12 @@ eventTime(struct khtmlreq *html, const struct Event *event) {
 static enum kcgi_err
 eventNetwork(struct khtmlreq *html, struct kreq *req, struct Event *event) {
 	if (!req->fieldmap[Query] || req->fieldmap[Network]) return KCGI_OK;
+	if (req->fieldmap[Export]) {
+		return 0
+			|| khtml_attr(html, KELEM_TD, KATTR_CLASS, "network", KATTR__MAX)
+			|| khtml_puts(html, event->network)
+			|| khtml_closeelem(html, 1);
+	}
 	char *href = khttp_urlpart(
 		NULL, NULL, Pages[Search],
 		Keys[Query].name, req->fieldmap[Query]->parsed.s,
@@ -309,6 +329,12 @@ eventNetwork(struct khtmlreq *html, struct kreq *req, struct Event *event) {
 static enum kcgi_err
 eventContext(struct khtmlreq *html, struct kreq *req, struct Event *event) {
 	if (!req->fieldmap[Query] || req->fieldmap[Context]) return KCGI_OK;
+	if (req->fieldmap[Export]) {
+		return 0
+			|| khtml_attr(html, KELEM_TD, KATTR_CLASS, "context", KATTR__MAX)
+			|| khtml_puts(html, event->context)
+			|| khtml_closeelem(html, 1);
+	}
 	char *href = khttp_urlpart(
 		NULL, NULL, Pages[Search],
 		Keys[Query].name, req->fieldmap[Query]->parsed.s,
@@ -445,7 +471,7 @@ htmlEvent(struct khtmlreq *html, struct kreq *req, struct Event *event) {
 			KATTR_CLASS, KATTRX_STRING, type,
 			KATTR__MAX
 		)
-		|| eventTime(html, event)
+		|| eventTime(html, req, event)
 		|| eventNetwork(html, req, event)
 		|| eventContext(html, req, event)
 		|| eventNick(html, event)
@@ -461,7 +487,8 @@ static const char *Columns = {
 	"network, channel, query, nick, user, target, message"
 };
 
-enum kcgi_err htmlFooter(struct khtmlreq *html) {
+enum kcgi_err htmlFooter(struct khtmlreq *html, struct kreq *req) {
+	if (req->fieldmap[Export]) return KCGI_OK;
 	return 0
 		|| khtml_closeto(html, 0)
 		|| khtml_elem(html, KELEM_FOOTER)
diff --git a/networks.c b/networks.c
index af3ad80..91f5131 100644
--- a/networks.c
+++ b/networks.c
@@ -53,7 +53,7 @@ enum kcgi_err networksPage(struct kreq *req) {
 	struct khtmlreq html;
 	error = error
 		|| khtml_open(&html, req, 0)
-		|| htmlHead(&html, "Litterbox")
+		|| htmlHead(&html, req, "Litterbox")
 		|| htmlNav(&html, req);
 	if (error) return error;
 
@@ -100,5 +100,5 @@ enum kcgi_err networksPage(struct kreq *req) {
 		if (error) return error;
 	}
 	if (result != SQLITE_DONE) errx(EX_SOFTWARE, "%s", sqlite3_errmsg(db));
-	return htmlFooter(&html) || khtml_close(&html);
+	return htmlFooter(&html, req) || khtml_close(&html);
 }
diff --git a/search.c b/search.c
index 67ce79c..d2bd4ca 100644
--- a/search.c
+++ b/search.c
@@ -78,12 +78,12 @@ enum kcgi_err searchPage(struct kreq *req) {
 	struct khtmlreq html;
 	error = error
 		|| khtml_open(&html, req, 0)
-		|| htmlHead(&html, query)
+		|| htmlHead(&html, req, query)
 		|| htmlNav(&html, req)
 		|| khtml_elem(&html, KELEM_TABLE);
 	if (error) return error;
 
-	if (offset) {
+	if (offset && !req->fieldmap[Export]) {
 		int64_t prev = offset - eventsLimit;
 		char *href = offsetURL(network, context, query, (prev > 0 ? prev : 0));
 		error = 0
@@ -128,11 +128,11 @@ enum kcgi_err searchPage(struct kreq *req) {
 			|| khtml_attr(&html, KELEM_TR, KATTR_CLASS, "error", KATTR__MAX)
 			|| khtml_elem(&html, KELEM_TH)
 			|| khtml_puts(&html, sqlite3_errmsg(db))
-			|| htmlFooter(&html)
+			|| htmlFooter(&html, req)
 			|| khtml_close(&html);
 	}
 
-	if (rows == eventsLimit) {
+	if (rows == eventsLimit && !req->fieldmap[Export]) {
 		char *href = offsetURL(network, context, query, offset + eventsLimit);
 		error = 0
 			|| khtml_attr(&html, KELEM_TR, KATTR_CLASS, "page", KATTR__MAX)
@@ -152,5 +152,5 @@ enum kcgi_err searchPage(struct kreq *req) {
 		if (error) return error;
 	}
 
-	return htmlFooter(&html) || khtml_close(&html);
+	return htmlFooter(&html, req) || khtml_close(&html);
 }
diff --git a/server.c b/server.c
index 514dad3..8aea1e5 100644
--- a/server.c
+++ b/server.c
@@ -21,7 +21,6 @@
 #include <sysexits.h>
 #include <unistd.h>
 
-#include "css.h"
 #include "server.h"
 
 sqlite3 *db;
@@ -62,7 +61,7 @@ static enum kcgi_err stylesheet(struct kreq *req) {
 		)
 		|| khttp_body(req);
 	if (req->method == KMETHOD_HEAD) return error;
-	return error || khttp_write(req, CSS, sizeof(CSS) - 1);
+	return error || khttp_puts(req, CSS);
 }
 
 static enum kcgi_err dispatch(struct kreq *req) {
diff --git a/server.h b/server.h
index 20d1803..4607d76 100644
--- a/server.h
+++ b/server.h
@@ -42,6 +42,8 @@
 	X(Search, "search") \
 	X(Stylesheet, "stylesheet")
 
+extern const char *CSS;
+
 enum {
 #define X(page, path) page,
 	ENUM_PAGES
@@ -57,7 +59,8 @@ extern const char *Pages[PagesLen];
 	X(Before, "before", kvalid_stringne) \
 	X(Tidy, "tidy", kvalid_int) \
 	X(Query, "query", kvalid_stringne) \
-	X(Offset, "offset", kvalid_int)
+	X(Offset, "offset", kvalid_int) \
+	X(Export, "export", NULL)
 
 enum Key {
 #define X(key, name, valid) key,
@@ -166,19 +169,29 @@ struct Event {
 };
 
 extern const char *htmlStylesheet;
-enum kcgi_err htmlHead(struct khtmlreq *html, const char *title);
+enum kcgi_err htmlHead(
+	struct khtmlreq *html, struct kreq *req, const char *title
+);
 enum kcgi_err htmlHidden(struct khtmlreq *html, struct kreq *req, enum Key key);
 enum kcgi_err htmlNav(struct khtmlreq *html, struct kreq *req);
 enum kcgi_err htmlIRC(struct khtmlreq *html, const char *str);
 enum kcgi_err htmlEvent(
 	struct khtmlreq *html, struct kreq *req, struct Event *event
 );
-enum kcgi_err htmlFooter(struct khtmlreq *html);
+enum kcgi_err htmlFooter(struct khtmlreq *html, struct kreq *req);
 
 static inline enum kcgi_err
 httpHead(struct kreq *req, enum khttp http, enum kmime mime) {
-	return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http])
+	enum kcgi_err error = 0
+		|| khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http])
 		|| khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]);
+	if (req->fieldmap[Export]) {
+		error = error || khttp_head(
+			req, kresps[KRESP_CONTENT_DISPOSITION],
+			"attachment; filename=\"%s.%s\"", req->pagename, ksuffixes[mime]
+		);
+	}
+	return error;
 }
 
 static inline enum kcgi_err