Add the option to automatically open content warnings
This commit is contained in:
parent
e231afb49c
commit
402e74dd6e
|
@ -29,8 +29,8 @@ list(APPEND FLAGS -Werror -Wall -Wextra -Wshadow -Wpedantic -Wno-gnu-anonymous-s
|
|||
add_link_options(${FLAGS})
|
||||
|
||||
|
||||
add_executable(${PROJECT_NAME} main.cpp numberhelper.cpp config.cpp models.cpp client.cpp servehelper.cpp timeutils.cpp hiredis_wrapper.cpp
|
||||
routes/home.cpp routes/css.cpp routes/user.cpp routes/status.cpp routes/tags.cpp routes/about.cpp
|
||||
add_executable(${PROJECT_NAME} main.cpp numberhelper.cpp config.cpp settings.cpp models.cpp client.cpp servehelper.cpp timeutils.cpp hiredis_wrapper.cpp
|
||||
routes/home.cpp routes/css.cpp routes/user.cpp routes/status.cpp routes/tags.cpp routes/about.cpp routes/user_settings.cpp
|
||||
blankie/serializer.cpp blankie/escape.cpp)
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
|
|
4
main.cpp
4
main.cpp
|
@ -40,9 +40,13 @@ int main(int argc, char** argv) {
|
|||
atexit(MastodonClient::cleanup);
|
||||
|
||||
httplib::Server server;
|
||||
server.set_payload_max_length(8192);
|
||||
|
||||
server.Get("/", home_route);
|
||||
server.Get("/style.css", css_route);
|
||||
server.Get("/settings", user_settings_route);
|
||||
server.Post("/settings", user_settings_route);
|
||||
|
||||
server.Get("/(" DOMAIN_RE ")/@(" ACCT_RE ")(|/with_replies|/media)", user_route);
|
||||
server.Get("/(" DOMAIN_RE ")/users/(" ACCT_RE ")(|/with_replies|/media)", [](const httplib::Request& req, httplib::Response& res) {
|
||||
serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/@" + req.matches.str(2) + req.matches.str(3), true);
|
||||
|
|
|
@ -14,6 +14,10 @@ static const constexpr char css[] = R"EOF(
|
|||
--error-border-color: red;
|
||||
--error-text-color: white;
|
||||
|
||||
--success-background-color: green;
|
||||
--success-border-color: lightgreen;
|
||||
--success-text-color: white;
|
||||
|
||||
--accent-color: #962AC3;
|
||||
--bright-accent-color: #DE6DE6;
|
||||
}
|
||||
|
@ -163,6 +167,20 @@ svg {
|
|||
padding: revert;
|
||||
}
|
||||
|
||||
/* SUCCESS BOX */
|
||||
.success {
|
||||
text-align: center;
|
||||
background-color: var(--success-background-color);
|
||||
color: var(--success-text-color);
|
||||
border-style: solid;
|
||||
border-color: var(--success-border-color);
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.success * {
|
||||
margin: revert;
|
||||
padding: revert;
|
||||
}
|
||||
|
||||
/* USER PAGE */
|
||||
.user_page-header {
|
||||
width: 100%;
|
||||
|
@ -252,6 +270,18 @@ svg {
|
|||
margin: revert;
|
||||
padding: revert;
|
||||
}
|
||||
|
||||
/* USER SETTINGS PAGE */
|
||||
.user_settings_page-form {
|
||||
display: inline;
|
||||
}
|
||||
.user_settings_page-form input[type=submit] {
|
||||
margin-top: 0.5em;
|
||||
padding: revert;
|
||||
}
|
||||
.user_settings_page-form .cancel {
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
)EOF";
|
||||
// one for \0, one for trailing newline
|
||||
#define CSS_LEN sizeof(css) / sizeof(css[0]) - 2
|
||||
|
|
|
@ -8,6 +8,7 @@ void home_route(const httplib::Request& req, httplib::Response& res) {
|
|||
"their posts, posts themselves (and their context), posts with a certain hashtag, "
|
||||
"and instance details.",
|
||||
}),
|
||||
Element("p", {Element("a", {{"href", get_origin(req) + "/settings"}}, {"Configure settings"})}),
|
||||
Element("h2", {"Example links"}),
|
||||
Element("ul", {
|
||||
Element("li", {Element("a", {{"href", get_origin(req) + "/vt.social/@lina"}}, {"Asahi Lina (朝日リナ) // nullptr::live"})}),
|
||||
|
|
|
@ -10,3 +10,4 @@ void user_route(const httplib::Request& req, httplib::Response& res);
|
|||
void status_route(const httplib::Request& req, httplib::Response& res);
|
||||
void tags_route(const httplib::Request& req, httplib::Response& res);
|
||||
void about_route(const httplib::Request& req, httplib::Response& res);
|
||||
void user_settings_route(const httplib::Request& req, httplib::Response& res);
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
#include "routes.h"
|
||||
#include "../servehelper.h"
|
||||
#include "../settings.h"
|
||||
#include "../timeutils.h"
|
||||
#include "../curlu_wrapper.h"
|
||||
|
||||
static void set_cookie(const httplib::Request& req, httplib::Response& res, const char* key, std::string_view value);
|
||||
|
||||
|
||||
void user_settings_route(const httplib::Request& req, httplib::Response& res) {
|
||||
UserSettings settings;
|
||||
|
||||
if (req.method == "POST") {
|
||||
for (const auto& i : req.params) {
|
||||
settings.set(i.first, i.second);
|
||||
}
|
||||
|
||||
set_cookie(req, res, "auto-open-cw", settings.auto_open_cw ? "true" : "false");
|
||||
} else {
|
||||
settings.load_from_cookies(req);
|
||||
}
|
||||
|
||||
Element auto_open_cw_checkbox("input", {{"type", "checkbox"}, {"name", "auto-open-cw"}, {"value", "true"}}, {});
|
||||
if (settings.auto_open_cw) {
|
||||
auto_open_cw_checkbox.attributes.push_back({"checked", ""});
|
||||
}
|
||||
|
||||
Element body("body", {
|
||||
Element("form", {{"class", "user_settings_page-form"}, {"method", "post"}}, {
|
||||
Element("label", {
|
||||
std::move(auto_open_cw_checkbox),
|
||||
" Automatically open Content Warnings",
|
||||
}),
|
||||
|
||||
Element("br"),
|
||||
Element("input", {{"type", "submit"}, {"value", "Save"}}, {}),
|
||||
}),
|
||||
Element("form", {{"class", "user_settings_page-form"}, {"method", "get"}, {"action", get_origin(req)}}, {
|
||||
Element("input", {{"class", "cancel"}, {"type", "submit"}, {"value", "Cancel"}}, {}),
|
||||
}),
|
||||
});
|
||||
if (req.method == "POST") {
|
||||
body.nodes.insert(body.nodes.begin(), Element("div", {{"class", "success"}}, {
|
||||
Element("h3", {"Settings saved!"}),
|
||||
}));
|
||||
}
|
||||
|
||||
serve(req, res, "User settings", std::move(body));
|
||||
}
|
||||
|
||||
|
||||
static void set_cookie(const httplib::Request& req, httplib::Response& res, const char* key, std::string_view value) {
|
||||
CurlUrl origin;
|
||||
origin.set(CURLUPART_URL, get_origin(req));
|
||||
|
||||
std::string header = std::string(key) + '=' + std::string(value)
|
||||
+ "; HttpOnly; SameSite=Strict; Domain=" + origin.get(CURLUPART_HOST).get() + "; Path=" + origin.get(CURLUPART_PATH).get()
|
||||
+ "; Expires=" + to_web_date(current_time() + 365 * 24 * 60 * 60);
|
||||
if (strcmp(origin.get(CURLUPART_SCHEME).get(), "https") == 0) {
|
||||
header += "; Secure";
|
||||
}
|
||||
|
||||
res.set_header("Set-Cookie", header);
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "font_awesome.h"
|
||||
#include "config.h"
|
||||
#include "settings.h"
|
||||
#include "models.h"
|
||||
#include "timeutils.h"
|
||||
#include "servehelper.h"
|
||||
|
@ -394,6 +395,9 @@ static Element serialize_post(const httplib::Request& req, const std::string& se
|
|||
Element("summary", {preprocess_html(req, post.emojis, std::move(spoiler_text))}),
|
||||
std::move(contents),
|
||||
});
|
||||
if (UserSettings(req).auto_open_cw) {
|
||||
contents.attributes.push_back({"open", ""});
|
||||
}
|
||||
}
|
||||
|
||||
Element div("div", {{"class", "post"}}, {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include "settings.h"
|
||||
|
||||
static void set_settings(std::string_view str, const char* delimiter, UserSettings& settings);
|
||||
static inline bool lowercase_compare(std::string_view lhs, std::string_view rhs);
|
||||
static bool parse_bool(std::string_view value);
|
||||
|
||||
|
||||
void UserSettings::set(std::string_view key, std::string_view value) {
|
||||
if (key == "auto-open-cw") {
|
||||
this->auto_open_cw = parse_bool(value);
|
||||
}
|
||||
}
|
||||
|
||||
void UserSettings::load_from_cookies(const httplib::Request& req) {
|
||||
for (const auto& i : req.headers) {
|
||||
if (lowercase_compare(i.first, "cookie")) {
|
||||
set_settings(i.second, "; ", *this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void set_settings(std::string_view str, const char* delimiter, UserSettings& settings) {
|
||||
using namespace std::string_literals;
|
||||
size_t offset = 0;
|
||||
size_t new_offset = 0;
|
||||
size_t delimiter_len = strlen(delimiter);
|
||||
|
||||
while (offset < str.size()) {
|
||||
new_offset = str.find(delimiter, offset);
|
||||
|
||||
std::string_view item = str.substr(offset, new_offset != std::string_view::npos ? new_offset - offset : std::string_view::npos);
|
||||
size_t equal_offset = item.find('=');
|
||||
if (equal_offset == std::string_view::npos) {
|
||||
throw std::invalid_argument("invalid user setting item: "s + std::string(item));
|
||||
}
|
||||
settings.set(item.substr(0, equal_offset), item.substr(equal_offset + 1));
|
||||
|
||||
if (new_offset == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
offset = new_offset + delimiter_len;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool lowercase_compare(std::string_view lhs, std::string_view rhs) {
|
||||
if (lhs.size() != rhs.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lower = [](char c) {
|
||||
return c >= 'A' && c <= 'Z' ? c - 'A' + 'a' : c;
|
||||
};
|
||||
for (size_t i = 0; i < lhs.size(); i++) {
|
||||
if (lower(lhs[i]) != lower(rhs[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_bool(std::string_view value) {
|
||||
using namespace std::string_literals;
|
||||
|
||||
if (value == "true") {
|
||||
return true;
|
||||
} else if (value == "false") {
|
||||
return false;
|
||||
} else {
|
||||
throw std::invalid_argument("unknown boolean value: "s + std::string(value));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <httplib/httplib.h>
|
||||
|
||||
struct UserSettings {
|
||||
bool auto_open_cw = false;
|
||||
|
||||
UserSettings() = default;
|
||||
UserSettings(const httplib::Request& req) {
|
||||
this->load_from_cookies(req);
|
||||
}
|
||||
|
||||
void set(std::string_view key, std::string_view value);
|
||||
void load_from_cookies(const httplib::Request& req);
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
#include <cstring>
|
||||
#include <system_error>
|
||||
|
||||
#include "timeutils.h"
|
||||
|
@ -39,6 +40,19 @@ std::string to_rfc3339(time_t time) {
|
|||
return std::string(buf, len);
|
||||
}
|
||||
|
||||
static const char* day_names[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
||||
static const char* month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
std::string to_web_date(time_t time) {
|
||||
struct tm tm;
|
||||
gmtime_r(&time, &tm);
|
||||
|
||||
char buf[32];
|
||||
size_t len = strftime(buf, 32, "XXX, %d XXX %Y %H:%M:%S GMT", &tm);
|
||||
memcpy(buf, day_names[tm.tm_wday], 3);
|
||||
memcpy(buf + 3 + 2 + 2 + 1, month_names[tm.tm_mon], 3);
|
||||
return std::string(buf, len);
|
||||
}
|
||||
|
||||
std::string relative_time(time_t from, time_t to) {
|
||||
time_t diff = to - from;
|
||||
|
||||
|
|
|
@ -8,4 +8,5 @@ time_t current_time();
|
|||
std::string short_time(time_t time);
|
||||
std::string full_time(time_t time);
|
||||
std::string to_rfc3339(time_t time);
|
||||
std::string to_web_date(time_t time);
|
||||
std::string relative_time(time_t from, time_t to);
|
||||
|
|
Loading…
Reference in New Issue