Merge
FossilOrigin-Name: aede803dd7e9334e756798bf609e43bdeb391418a7e4af79ffe23c005a1d7dcf
This commit is contained in:
commit
4733000997
209 changed files with 6313 additions and 6149 deletions
|
@ -1,4 +1,5 @@
|
|||
template
|
||||
ctemplate
|
||||
filec
|
||||
emojitoc
|
||||
**/*.cgi
|
||||
|
@ -9,4 +10,6 @@ mastodont-c
|
|||
config.h
|
||||
treebird
|
||||
test/tests
|
||||
scripts/*.o
|
||||
scripts/*.o
|
||||
templates/*.ctt
|
||||
test/unit/*.bin
|
74
Makefile
74
Makefile
|
@ -2,42 +2,59 @@ CC ?= cc
|
|||
GIT ?= git
|
||||
MASTODONT_DIR = mastodont-c/
|
||||
MASTODONT = $(MASTODONT_DIR)libmastodont.a
|
||||
CFLAGS += -Wall -I $(MASTODONT_DIR)include/ -Wno-unused-variable -Wno-ignored-qualifiers -I/usr/include/ -I $(MASTODONT_DIR)/libs $(shell pkg-config --cflags libcurl libpcre2-8)
|
||||
LDFLAGS = -L$(MASTODONT_DIR) -lmastodont $(shell pkg-config --libs libcurl libpcre2-8) -lfcgi -lpthread
|
||||
CFLAGS += -Wall -I $(MASTODONT_DIR)include/ -Wno-unused-variable -Wno-ignored-qualifiers -I/usr/include/ -I $(MASTODONT_DIR)/libs $(shell pkg-config --cflags libcurl libpcre2-8) `perl -MExtUtils::Embed -e ccopts` -DDEBUGGING_MSTATS
|
||||
LDFLAGS += -L$(MASTODONT_DIR) -lmastodont $(shell pkg-config --libs libcurl libpcre2-8) -lfcgi -lpthread `perl -MExtUtils::Embed -e ldopts` -DDEBUGGING_MSTATS
|
||||
SRC = $(wildcard src/*.c)
|
||||
OBJ = $(patsubst %.c,%.o,$(SRC))
|
||||
HEADERS = $(wildcard src/*.h) config.h
|
||||
PAGES_DIR = static
|
||||
PAGES = $(wildcard $(PAGES_DIR)/*.tmpl)
|
||||
PAGES_CMP = $(patsubst %.tmpl,%.ctmpl,$(PAGES))
|
||||
PAGES_C = $(patsubst %.tmpl, %.c,$(PAGES))
|
||||
PAGES_C_OBJ = $(patsubst %.c,%.o,$(PAGES_C))
|
||||
TMPL_DIR = templates
|
||||
TMPLS = $(wildcard $(TMPL_DIR)/*.tt)
|
||||
TMPLS_C = $(patsubst %.tt,%.ctt,$(TMPLS))
|
||||
TEST_DIR = test/unit
|
||||
TESTS = $(wildcard $(TEST_DIR)/t*.c)
|
||||
UNIT_TESTS = $(patsubst %.c,%.bin,$(TESTS))
|
||||
DIST = dist/
|
||||
PREFIX ?= /usr/local
|
||||
TARGET = treebird
|
||||
# For tests
|
||||
OBJ_NO_MAIN = $(filter-out src/main.o,$(OBJ))
|
||||
|
||||
MASTODONT_URL = https://fossil.nekobit.net/mastodont-c
|
||||
|
||||
all: $(MASTODONT_DIR) dep_build $(TARGET)
|
||||
apache: all apache_start
|
||||
# Not parallel friendly
|
||||
#all: $(MASTODONT_DIR) dep_build $(TARGET)
|
||||
|
||||
$(TARGET): filec template $(PAGES_CMP) $(PAGES_C) $(PAGES_C_OBJ) $(OBJ) $(HEADERS)
|
||||
ifneq ($(strip $(SINGLE_THREADED)),)
|
||||
CFLAGS += -DSINGLE_THREADED
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(SINGLE_THREADED)),)
|
||||
CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
all:
|
||||
$(MAKE) dep_build
|
||||
$(MAKE) filec
|
||||
$(MAKE) make_tmpls
|
||||
$(MAKE) $(TARGET)
|
||||
|
||||
install_deps:
|
||||
cpan Template::Toolkit
|
||||
|
||||
$(TARGET): $(HEADERS) $(OBJ)
|
||||
$(CC) -o $(TARGET) $(OBJ) $(PAGES_C_OBJ) $(LDFLAGS)
|
||||
|
||||
template: src/template/main.o
|
||||
$(CC) $(LDFLAGS) -o template $<
|
||||
|
||||
filec: src/file-to-c/main.o
|
||||
$(CC) -o filec $<
|
||||
$(CC) $(LDFLAGS) -o filec $<
|
||||
|
||||
emojitoc: scripts/emoji-to.o
|
||||
$(CC) -o emojitoc $< $(LDFLAGS)
|
||||
./emojitoc meta/emoji.json > src/emoji_codes.h
|
||||
|
||||
# Redirect stdout and stderr into separate contents as a hack
|
||||
# Let bash do the work :)
|
||||
$(PAGES_DIR)/%.ctmpl: $(PAGES_DIR)/%.tmpl
|
||||
./template $< $(notdir $*) 2> $(PAGES_DIR)/$(notdir $*).c 1> $@
|
||||
$(TMPL_DIR)/%.ctt: $(TMPL_DIR)/%.tt
|
||||
./filec $< data_$(notdir $*)_tt > $@
|
||||
|
||||
make_tmpls: $(TMPLS_C)
|
||||
|
||||
$(MASTODONT_DIR):
|
||||
cd ..; fossil clone $(MASTODONT_URL) || true
|
||||
|
@ -48,11 +65,9 @@ install: $(TARGET)
|
|||
install -d $(PREFIX)/share/treebird/
|
||||
cp -r dist/ $(PREFIX)/share/treebird/
|
||||
|
||||
test:
|
||||
make -C test
|
||||
|
||||
apache_start:
|
||||
./scripts/fcgistarter.sh
|
||||
test: all $(UNIT_TESTS)
|
||||
@echo " ... Tests ready"
|
||||
@./test/test.pl
|
||||
|
||||
dep_build:
|
||||
make -C $(MASTODONT_DIR)
|
||||
|
@ -60,10 +75,17 @@ dep_build:
|
|||
%.o: %.c %.h $(PAGES)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# For tests
|
||||
%.bin: %.c
|
||||
@$(CC) $(CFLAGS) $< -o $@ $(OBJ_NO_MAIN) $(PAGES_C_OBJ) $(LDFLAGS)
|
||||
@echo -n " $@"
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ) src/file-to-c/main.o
|
||||
rm -f $(PAGES_CMP)
|
||||
rm -f filec
|
||||
rm -f $(TMPLS_C)
|
||||
rm -f test/unit/*.bin
|
||||
rm -f filec ctemplate
|
||||
rm $(TARGET) || true
|
||||
make -C $(MASTODONT_DIR) clean
|
||||
|
||||
clean_deps:
|
||||
|
@ -71,4 +93,4 @@ clean_deps:
|
|||
|
||||
clean_all: clean clean_deps
|
||||
|
||||
.PHONY: all filec clean update clean clean_deps clean_all test
|
||||
.PHONY: all filec clean update clean clean_deps clean_all test install_deps
|
||||
|
|
|
@ -10,7 +10,9 @@ The goal is to create a frontend that's lightweight enough to be viewed without
|
|||
usable enough to improve the experience with JS.
|
||||
|
||||
Treebird uses C with FCGI, mastodont-c (library designed for Treebird, but can be used
|
||||
for other applications as well), and plain JavaScript for the frontend (100% optional).
|
||||
for other applications as well), Perl, and **optional** JavaScript for the frontend (100% functional without
|
||||
javascript, it only helps). Uses [RE:DOM](https://redom.js.org/) (2kb JS library) to assist with DOM
|
||||
creation and native JS apis. (Already bundled)
|
||||
|
||||
## Why?
|
||||
|
||||
|
@ -24,7 +26,7 @@ This led me to one choice, to develop my own frontend.
|
|||
|
||||
Treebird respects compatibility with old browsers, and thus uses HTML table layouts, which are
|
||||
supported even by most modern terminal web browsers. The core browser we aim to at least maintain compatibility
|
||||
with is Netsurf, but most other browsers like GNU Emacs EWW, elinks, render Treebird wonderfully.
|
||||
with is Netsurf, but most other browsers like GNU Emacs EWW, elinks, render Treebird just alright.
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
#include <mastodont.h>
|
||||
#if !(defined(FALSE) && defined(TRUE))
|
||||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
#endif
|
||||
#define UNSET NULL
|
||||
|
||||
/*
|
||||
|
|
539
dist/js/main.js
vendored
539
dist/js/main.js
vendored
|
@ -1,145 +1,159 @@
|
|||
(function(){
|
||||
Element.prototype.insertAfter = function(element) {
|
||||
element.parentNode.insertBefore(this, element.nextSibling);
|
||||
const { el, mount } = redom;
|
||||
|
||||
'use strict';
|
||||
|
||||
function get_cookie(cookiestr)
|
||||
{
|
||||
return document.cookie
|
||||
.split(';')
|
||||
.find(row => row.startsWith(cookiestr+'='))
|
||||
.split('=')[1];
|
||||
}
|
||||
|
||||
// TODO Check if logged in .acct value is the same
|
||||
function reply_get_mentions(reply, content)
|
||||
{
|
||||
const regexpr = /<a target="_parent" class="mention" href="https?:\/\/.*?\/@(.*?)@(.*?)">.*?<\/a>/g;
|
||||
const arr = [...content.matchAll(regexpr)];
|
||||
let res = reply ? `@${reply} ` : "";
|
||||
const matches = content.matchAll(regexpr);
|
||||
|
||||
for (let x of matches)
|
||||
{
|
||||
res += `@${x[1]}@${x[2]} `;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
function form_enter_submit(e, that)
|
||||
{
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 13)
|
||||
that.closest('form').submit();
|
||||
}
|
||||
|
||||
// Submit form entry on enter when in textbox/textarea
|
||||
document.querySelectorAll("input[type=text], input[type=url], input[type=email], input[type=password], textarea").forEach((i) => {
|
||||
i.addEventListener("keydown", e => form_enter_submit(e, i));
|
||||
});
|
||||
|
||||
function construct_query(query)
|
||||
{
|
||||
let query_string = "";
|
||||
let keys = Object.keys(query);
|
||||
let vals = Object.values(query);
|
||||
const len = keys.length;
|
||||
|
||||
for (let i = 0; i < keys.length; ++i)
|
||||
{
|
||||
query_string += keys[i] + "=" + vals[i];
|
||||
if (i !== keys.length-1)
|
||||
query_string += "&";
|
||||
}
|
||||
return query_string;
|
||||
}
|
||||
|
||||
function send_request(url, query, type, cb, cb_args)
|
||||
{
|
||||
let query_str = construct_query(query);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open(type, url);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.send(query_str);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function get_cookie(cookiestr)
|
||||
{
|
||||
return document.cookie
|
||||
.split(';')
|
||||
.find(row => row.startsWith(cookiestr+'='))
|
||||
.split('=')[1];
|
||||
}
|
||||
function upload_file(url, file_name, file, cb, cb_args, onprogress, onload)
|
||||
{
|
||||
let xhr = new XMLHttpRequest();
|
||||
let form_data = new FormData();
|
||||
xhr.open("post", url);
|
||||
form_data.append(file_name, file);
|
||||
// xhr.setRequestHeader("Content-Type", "multipart/form-data");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.upload.onprogress = onprogress;
|
||||
xhr.upload.onload = onload;
|
||||
xhr.send(form_data);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
// TODO Check if logged in .acct value is the same
|
||||
function reply_get_mentions(reply, content)
|
||||
{
|
||||
const regexpr = /<a target="_parent" class="mention" href="https?:\/\/.*?\/@(.*?)@(.*?)">.*?<\/a>/g;
|
||||
const arr = [...content.matchAll(regexpr)];
|
||||
let res = reply ? `@${reply} ` : "";
|
||||
const matches = content.matchAll(regexpr);
|
||||
function change_count_text(val, sum)
|
||||
{
|
||||
if (val === "")
|
||||
val = 0
|
||||
else
|
||||
val = parseInt(val);
|
||||
val += sum;
|
||||
return val > 0 ? val.toString() : "";
|
||||
}
|
||||
|
||||
for (let x of matches)
|
||||
function interact_action(status, type)
|
||||
{
|
||||
let like = status.querySelector(".statbtn .like");
|
||||
let repeat = status.querySelector(".statbtn .repeat");
|
||||
|
||||
let svg;
|
||||
if (type.value === "like" || type.value === "unlike")
|
||||
svg = [ like ];
|
||||
else if (type.value === "repeat" || type.value === "unrepeat")
|
||||
svg = [ repeat ];
|
||||
else if (type.value === "likeboost")
|
||||
svg = [ like, repeat ];
|
||||
|
||||
if (svg)
|
||||
svg.forEach(that => {
|
||||
let label = that.parentNode;
|
||||
let counter = label.querySelector(".count");
|
||||
let is_interacted = label.classList.contains("interacted");
|
||||
|
||||
if (counter)
|
||||
{
|
||||
res += `@${x[1]}@${x[2]} `;
|
||||
counter.innerHTML = change_count_text(counter.innerHTML, is_interacted ? -1 : 1);
|
||||
}
|
||||
else {
|
||||
// Nobody interacted with this yet, create counter
|
||||
const counter = el("span.count", 1)
|
||||
mount(label, counter);
|
||||
is_interacted = false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function form_enter_submit(e, that)
|
||||
{
|
||||
if ((e.ctrlKey || e.metaKey) && e.keyCode === 13)
|
||||
that.closest('form').submit();
|
||||
}
|
||||
|
||||
// Submit form entry on enter when in textbox/textarea
|
||||
document.querySelectorAll("input[type=text], input[type=url], input[type=email], input[type=password], textarea").forEach((i) => {
|
||||
i.addEventListener("keydown", e => form_enter_submit(e, i));
|
||||
if (is_interacted)
|
||||
{
|
||||
// Animation
|
||||
that.classList.remove("interacted-anim");
|
||||
label.classList.remove("interacted");
|
||||
|
||||
// Flip itype value
|
||||
type.value = type.value.replace("un", "");
|
||||
}
|
||||
else {
|
||||
that.classList.add("interacted-anim");
|
||||
label.classList.add("interacted");
|
||||
type.value = "un" + type.value;
|
||||
}
|
||||
});
|
||||
|
||||
function construct_query(query)
|
||||
}
|
||||
|
||||
function status_event(e)
|
||||
{
|
||||
let target = e.target.closest(".statbtn");
|
||||
if (target)
|
||||
{
|
||||
query_string = "";
|
||||
let keys = Object.keys(query);
|
||||
let vals = Object.values(query);
|
||||
const len = keys.length;
|
||||
|
||||
for (let i = 0; i < keys.length; ++i)
|
||||
{
|
||||
query_string += keys[i] + "=" + vals[i];
|
||||
if (i !== keys.length-1)
|
||||
query_string += "&";
|
||||
}
|
||||
return query_string;
|
||||
}
|
||||
|
||||
function send_request(url, query, type, cb, cb_args)
|
||||
{
|
||||
let query_str = construct_query(query);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open(type, url);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.send(query_str);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function upload_file(url, file_name, file, cb, cb_args, onprogress, onload)
|
||||
{
|
||||
let xhr = new XMLHttpRequest();
|
||||
let form_data = new FormData();
|
||||
xhr.open("post", url);
|
||||
form_data.append(file_name, file);
|
||||
// xhr.setRequestHeader("Content-Type", "multipart/form-data");
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === XMLHttpRequest.DONE)
|
||||
cb(this, cb_args);
|
||||
};
|
||||
xhr.upload.onprogress = onprogress;
|
||||
xhr.upload.onload = onload;
|
||||
xhr.send(form_data);
|
||||
return xhr;
|
||||
}
|
||||
|
||||
function change_count_text(val, sum)
|
||||
{
|
||||
if (val === "")
|
||||
val = 0
|
||||
else
|
||||
val = parseInt(val);
|
||||
val += sum;
|
||||
return val > 0 ? val.toString() : "";
|
||||
}
|
||||
|
||||
function interact_action(status, type)
|
||||
{
|
||||
let like = status.querySelector(".statbtn .like");
|
||||
let repeat = status.querySelector(".statbtn .repeat");
|
||||
|
||||
let svg;
|
||||
if (type.value === "like" || type.value === "unlike")
|
||||
svg = [ like ];
|
||||
else if (type.value === "repeat" || type.value === "unrepeat")
|
||||
svg = [ repeat ];
|
||||
else if (type.value === "likeboost")
|
||||
svg = [ like, repeat ];
|
||||
|
||||
svg.forEach(that => {
|
||||
let label = that.parentNode;
|
||||
let counter = label.querySelector(".count");
|
||||
|
||||
let is_active = that.classList.contains("active");
|
||||
|
||||
that.classList.toggle("active");
|
||||
|
||||
|
||||
if (is_active)
|
||||
{
|
||||
// Animation
|
||||
that.classList.remove("active-anim");
|
||||
|
||||
// Flip itype value
|
||||
type.value = type.value.replace("un", "");
|
||||
}
|
||||
else {
|
||||
that.classList.add("active-anim");
|
||||
type.value = "un" + type.value;
|
||||
}
|
||||
|
||||
counter.innerHTML = change_count_text(counter.innerHTML, is_active ? -1 : 1);
|
||||
});
|
||||
}
|
||||
|
||||
function status_interact_props(e)
|
||||
{
|
||||
let interact = e.target.closest(".statbtn");
|
||||
let type = interact.parentNode.querySelector(".itype");
|
||||
if (type === null)
|
||||
console.log('huh');
|
||||
// Don't JS these
|
||||
if (target.classList.contains("reply-btn") ||
|
||||
target.classList.contains("view-btn") ||
|
||||
target.classList.contains("statbtn-last"))
|
||||
return true;
|
||||
let status = interact.closest(".status");
|
||||
let type = target.parentNode.querySelector(".itype");
|
||||
let status = e.target.closest(".status");
|
||||
|
||||
send_request("/treebird_api/v1/interact",
|
||||
{
|
||||
|
@ -160,146 +174,145 @@
|
|||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function frame_resize()
|
||||
function frame_resize()
|
||||
{
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
let rbar_frame_win = rightbar_frame.contentWindow;
|
||||
rightbar_frame.height = rbar_frame_win.document.body.scrollHeight;
|
||||
}
|
||||
|
||||
function filesize_to_str(bs)
|
||||
{
|
||||
const val = bs === 0 ? 0 : Math.floor(Math.log(bs) / Math.log(1024));
|
||||
return (bs / 1024**val).toFixed(1) * 1 + ['B', 'kB', 'MB', 'GB', 'TB'][val];
|
||||
}
|
||||
|
||||
function html_encode(str)
|
||||
{
|
||||
let en = document.createElement("span");
|
||||
en.textContent = str;
|
||||
return en.innerHTML;
|
||||
}
|
||||
|
||||
function construct_file_upload(file, file_content)
|
||||
{
|
||||
let container = document.createElement("div");
|
||||
let content = document.createElement("img");
|
||||
let info = document.createElement("span");
|
||||
container.className = "file-upload";
|
||||
|
||||
info.className = "upload-info";
|
||||
info.innerHTML = `<span class="filesize">${filesize_to_str(file.size)}</span> • <span class="filename">${html_encode(file.name)}</span>`;
|
||||
let progress_div = document.createElement("div");
|
||||
progress_div.className = "file-progress";
|
||||
info.appendChild(progress_div);
|
||||
|
||||
content.src = file_content;
|
||||
content.className = "upload-content";
|
||||
|
||||
container.appendChild(content);
|
||||
container.appendChild(info);
|
||||
return container;
|
||||
}
|
||||
|
||||
function update_uploads_json(dom)
|
||||
{
|
||||
let root = dom.parentNode;
|
||||
let items = root.getElementsByClassName("file-upload");
|
||||
let ids = [];
|
||||
|
||||
for (let i of items)
|
||||
{
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
let rbar_frame_win = rightbar_frame.contentWindow;
|
||||
rightbar_frame.height = rbar_frame_win.document.body.scrollHeight;
|
||||
if (i.dataset.id)
|
||||
ids.push(i.dataset.id);
|
||||
}
|
||||
|
||||
function filesize_to_str(bs)
|
||||
// Goto statusbox
|
||||
root = root.parentNode;
|
||||
let file_ids = root.querySelector(".file-ids-json");
|
||||
if (!file_ids)
|
||||
{
|
||||
const val = bs === 0 ? 0 : Math.floor(Math.log(bs) / Math.log(1024));
|
||||
return (bs / 1024**val).toFixed(1) * 1 + ['B', 'kB', 'MB', 'GB', 'TB'][val];
|
||||
// Create if doesn't exist
|
||||
file_ids = document.createElement("input");
|
||||
file_ids.type = "hidden";
|
||||
file_ids.className = "file-ids-json";
|
||||
file_ids.name = "fileids";
|
||||
root.appendChild(file_ids);
|
||||
}
|
||||
|
||||
function html_encode(str)
|
||||
file_ids.value = JSON.stringify(ids);
|
||||
}
|
||||
|
||||
function evt_file_upload(e)
|
||||
{
|
||||
let target = e.target;
|
||||
let file_upload_dom = this.closest("form").querySelector(".file-uploads-container");
|
||||
file_upload_dom.className = "file-uploads-container";
|
||||
const files = [...this.files];
|
||||
|
||||
let reader;
|
||||
|
||||
// Clear file input
|
||||
this.value = '';
|
||||
|
||||
// Create file upload
|
||||
for (let file of files)
|
||||
{
|
||||
let en = document.createElement("span");
|
||||
en.textContent = str;
|
||||
return en.innerHTML;
|
||||
reader = new FileReader();
|
||||
reader.onload = (() => {
|
||||
return (e) => {
|
||||
let file_dom = construct_file_upload(file, e.target.result);
|
||||
|
||||
file_upload_dom.appendChild(file_dom);
|
||||
|
||||
let xhr = upload_file("/treebird_api/v1/attachment",
|
||||
"file",
|
||||
file,
|
||||
(xhr, args) => {
|
||||
// TODO errors
|
||||
file_dom.dataset.id = JSON.parse(xhr.response).id;
|
||||
update_uploads_json(file_dom);
|
||||
}, null,
|
||||
(e) => {
|
||||
let upload_file_progress = file_dom
|
||||
.querySelector(".file-progress");
|
||||
// Add offset of 3
|
||||
upload_file_progress.style.width = 3+((e.loaded/e.total)*97);
|
||||
},
|
||||
(e) => {
|
||||
file_dom.querySelector(".upload-content").style.opacity = "1.0";
|
||||
file_dom.querySelector(".file-progress").remove();
|
||||
});
|
||||
}
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Main (when loaded)
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let interact_btn = document.getElementsByClassName("status");
|
||||
|
||||
// Add event listener to add specificied buttons
|
||||
for (let i = 0; i < interact_btn.length; ++i)
|
||||
{
|
||||
interact_btn[i].addEventListener('click', status_event);
|
||||
}
|
||||
|
||||
// Resize notifications iFrame to full height
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
if (rightbar_frame)
|
||||
{
|
||||
rightbar_frame.contentWindow.addEventListener('DOMContentLoaded', frame_resize);
|
||||
}
|
||||
|
||||
function construct_file_upload(file, file_content)
|
||||
// File upload
|
||||
let file_inputs = document.querySelectorAll(".statusbox input[type=file]");
|
||||
for (let file_input of file_inputs)
|
||||
{
|
||||
let container = document.createElement("div");
|
||||
let content = document.createElement("img");
|
||||
let info = document.createElement("span");
|
||||
container.className = "file-upload";
|
||||
|
||||
info.className = "upload-info";
|
||||
info.innerHTML = `<span class="filesize">${filesize_to_str(file.size)}</span> • <span class="filename">${html_encode(file.name)}</span>`;
|
||||
let progress_div = document.createElement("div");
|
||||
progress_div.className = "file-progress";
|
||||
info.appendChild(progress_div);
|
||||
|
||||
content.src = file_content;
|
||||
content.className = "upload-content";
|
||||
|
||||
container.appendChild(content);
|
||||
container.appendChild(info);
|
||||
return container;
|
||||
file_input.addEventListener('change', evt_file_upload);
|
||||
}
|
||||
|
||||
function update_uploads_json(dom)
|
||||
{
|
||||
let root = dom.parentNode;
|
||||
let items = root.getElementsByClassName("file-upload");
|
||||
let ids = [];
|
||||
|
||||
for (let i of items)
|
||||
{
|
||||
if (i.dataset.id)
|
||||
ids.push(i.dataset.id);
|
||||
}
|
||||
|
||||
// Goto statusbox
|
||||
root = root.parentNode;
|
||||
let file_ids = root.querySelector(".file-ids-json");
|
||||
if (!file_ids)
|
||||
{
|
||||
// Create if doesn't exist
|
||||
file_ids = document.createElement("input");
|
||||
file_ids.type = "hidden";
|
||||
file_ids.className = "file-ids-json";
|
||||
file_ids.name = "fileids";
|
||||
root.appendChild(file_ids);
|
||||
}
|
||||
|
||||
file_ids.value = JSON.stringify(ids);
|
||||
}
|
||||
|
||||
function evt_file_upload(e)
|
||||
{
|
||||
let target = e.target;
|
||||
let file_upload_dom = this.closest("form").querySelector(".file-uploads-container");
|
||||
file_upload_dom.className = "file-uploads-container";
|
||||
const files = [...this.files];
|
||||
|
||||
let reader;
|
||||
|
||||
// Clear file input
|
||||
this.value = '';
|
||||
|
||||
// Create file upload
|
||||
for (let file of files)
|
||||
{
|
||||
reader = new FileReader();
|
||||
reader.onload = (() => {
|
||||
return (e) => {
|
||||
let file_dom = construct_file_upload(file, e.target.result);
|
||||
|
||||
file_upload_dom.appendChild(file_dom);
|
||||
|
||||
let xhr = upload_file("/treebird_api/v1/attachment",
|
||||
"file",
|
||||
file,
|
||||
(xhr, args) => {
|
||||
// TODO errors
|
||||
file_dom.dataset.id = JSON.parse(xhr.response).id;
|
||||
update_uploads_json(file_dom);
|
||||
}, null,
|
||||
(e) => {
|
||||
let upload_file_progress = file_dom
|
||||
.querySelector(".file-progress");
|
||||
// Add offset of 3
|
||||
upload_file_progress.style.width = 3+((e.loaded/e.total)*97);
|
||||
},
|
||||
(e) => {
|
||||
file_dom.querySelector(".upload-content").style.opacity = "1.0";
|
||||
file_dom.querySelector(".file-progress").remove();
|
||||
});
|
||||
}
|
||||
})(file);
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Main (when loaded)
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let reply_btn = document.getElementsByClassName("reply-btn");
|
||||
let interact_btn = document.getElementsByClassName("statbtn");
|
||||
|
||||
// Add event listener to add specificied buttons
|
||||
for (let i = 0; i < interact_btn.length; ++i)
|
||||
{
|
||||
interact_btn[i].addEventListener('click', status_interact_props);
|
||||
}
|
||||
|
||||
// Resize notifications iFrame to full height
|
||||
let rightbar_frame = document.querySelector("#rightbar .sidebar-frame");
|
||||
if (rightbar_frame)
|
||||
{
|
||||
rightbar_frame.contentWindow.addEventListener('DOMContentLoaded', frame_resize);
|
||||
}
|
||||
|
||||
// File upload
|
||||
let file_inputs = document.querySelectorAll(".statusbox input[type=file]");
|
||||
for (let file_input of file_inputs)
|
||||
{
|
||||
file_input.addEventListener('change', evt_file_upload);
|
||||
}
|
||||
});
|
||||
})();
|
||||
});
|
||||
|
|
755
dist/js/redom.js
vendored
Normal file
755
dist/js/redom.js
vendored
Normal file
|
@ -0,0 +1,755 @@
|
|||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.redom = {}));
|
||||
})(this, (function (exports) { 'use strict';
|
||||
|
||||
function createElement (query, ns) {
|
||||
var ref = parse(query);
|
||||
var tag = ref.tag;
|
||||
var id = ref.id;
|
||||
var className = ref.className;
|
||||
var element = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
|
||||
|
||||
if (id) {
|
||||
element.id = id;
|
||||
}
|
||||
|
||||
if (className) {
|
||||
if (ns) {
|
||||
element.setAttribute('class', className);
|
||||
} else {
|
||||
element.className = className;
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function parse (query) {
|
||||
var chunks = query.split(/([.#])/);
|
||||
var className = '';
|
||||
var id = '';
|
||||
|
||||
for (var i = 1; i < chunks.length; i += 2) {
|
||||
switch (chunks[i]) {
|
||||
case '.':
|
||||
className += " " + (chunks[i + 1]);
|
||||
break;
|
||||
|
||||
case '#':
|
||||
id = chunks[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
className: className.trim(),
|
||||
tag: chunks[0] || 'div',
|
||||
id: id
|
||||
};
|
||||
}
|
||||
|
||||
function unmount (parent, child) {
|
||||
var parentEl = getEl(parent);
|
||||
var childEl = getEl(child);
|
||||
|
||||
if (child === childEl && childEl.__redom_view) {
|
||||
// try to look up the view if not provided
|
||||
child = childEl.__redom_view;
|
||||
}
|
||||
|
||||
if (childEl.parentNode) {
|
||||
doUnmount(child, childEl, parentEl);
|
||||
|
||||
parentEl.removeChild(childEl);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
function doUnmount (child, childEl, parentEl) {
|
||||
var hooks = childEl.__redom_lifecycle;
|
||||
|
||||
if (hooksAreEmpty(hooks)) {
|
||||
childEl.__redom_lifecycle = {};
|
||||
return;
|
||||
}
|
||||
|
||||
var traverse = parentEl;
|
||||
|
||||
if (childEl.__redom_mounted) {
|
||||
trigger(childEl, 'onunmount');
|
||||
}
|
||||
|
||||
while (traverse) {
|
||||
var parentHooks = traverse.__redom_lifecycle || {};
|
||||
|
||||
for (var hook in hooks) {
|
||||
if (parentHooks[hook]) {
|
||||
parentHooks[hook] -= hooks[hook];
|
||||
}
|
||||
}
|
||||
|
||||
if (hooksAreEmpty(parentHooks)) {
|
||||
traverse.__redom_lifecycle = null;
|
||||
}
|
||||
|
||||
traverse = traverse.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
function hooksAreEmpty (hooks) {
|
||||
if (hooks == null) {
|
||||
return true;
|
||||
}
|
||||
for (var key in hooks) {
|
||||
if (hooks[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* global Node, ShadowRoot */
|
||||
|
||||
var hookNames = ['onmount', 'onremount', 'onunmount'];
|
||||
var shadowRootAvailable = typeof window !== 'undefined' && 'ShadowRoot' in window;
|
||||
|
||||
function mount (parent, child, before, replace) {
|
||||
var parentEl = getEl(parent);
|
||||
var childEl = getEl(child);
|
||||
|
||||
if (child === childEl && childEl.__redom_view) {
|
||||
// try to look up the view if not provided
|
||||
child = childEl.__redom_view;
|
||||
}
|
||||
|
||||
if (child !== childEl) {
|
||||
childEl.__redom_view = child;
|
||||
}
|
||||
|
||||
var wasMounted = childEl.__redom_mounted;
|
||||
var oldParent = childEl.parentNode;
|
||||
|
||||
if (wasMounted && (oldParent !== parentEl)) {
|
||||
doUnmount(child, childEl, oldParent);
|
||||
}
|
||||
|
||||
if (before != null) {
|
||||
if (replace) {
|
||||
var beforeEl = getEl(before);
|
||||
|
||||
if (beforeEl.__redom_mounted) {
|
||||
trigger(beforeEl, 'onunmount');
|
||||
}
|
||||
|
||||
parentEl.replaceChild(childEl, beforeEl);
|
||||
} else {
|
||||
parentEl.insertBefore(childEl, getEl(before));
|
||||
}
|
||||
} else {
|
||||
parentEl.appendChild(childEl);
|
||||
}
|
||||
|
||||
doMount(child, childEl, parentEl, oldParent);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
function trigger (el, eventName) {
|
||||
if (eventName === 'onmount' || eventName === 'onremount') {
|
||||
el.__redom_mounted = true;
|
||||
} else if (eventName === 'onunmount') {
|
||||
el.__redom_mounted = false;
|
||||
}
|
||||
|
||||
var hooks = el.__redom_lifecycle;
|
||||
|
||||
if (!hooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
var view = el.__redom_view;
|
||||
var hookCount = 0;
|
||||
|
||||
view && view[eventName] && view[eventName]();
|
||||
|
||||
for (var hook in hooks) {
|
||||
if (hook) {
|
||||
hookCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (hookCount) {
|
||||
var traverse = el.firstChild;
|
||||
|
||||
while (traverse) {
|
||||
var next = traverse.nextSibling;
|
||||
|
||||
trigger(traverse, eventName);
|
||||
|
||||
traverse = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doMount (child, childEl, parentEl, oldParent) {
|
||||
var hooks = childEl.__redom_lifecycle || (childEl.__redom_lifecycle = {});
|
||||
var remount = (parentEl === oldParent);
|
||||
var hooksFound = false;
|
||||
|
||||
for (var i = 0, list = hookNames; i < list.length; i += 1) {
|
||||
var hookName = list[i];
|
||||
|
||||
if (!remount) { // if already mounted, skip this phase
|
||||
if (child !== childEl) { // only Views can have lifecycle events
|
||||
if (hookName in child) {
|
||||
hooks[hookName] = (hooks[hookName] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hooks[hookName]) {
|
||||
hooksFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hooksFound) {
|
||||
childEl.__redom_lifecycle = {};
|
||||
return;
|
||||
}
|
||||
|
||||
var traverse = parentEl;
|
||||
var triggered = false;
|
||||
|
||||
if (remount || (traverse && traverse.__redom_mounted)) {
|
||||
trigger(childEl, remount ? 'onremount' : 'onmount');
|
||||
triggered = true;
|
||||
}
|
||||
|
||||
while (traverse) {
|
||||
var parent = traverse.parentNode;
|
||||
var parentHooks = traverse.__redom_lifecycle || (traverse.__redom_lifecycle = {});
|
||||
|
||||
for (var hook in hooks) {
|
||||
parentHooks[hook] = (parentHooks[hook] || 0) + hooks[hook];
|
||||
}
|
||||
|
||||
if (triggered) {
|
||||
break;
|
||||
} else {
|
||||
if (traverse.nodeType === Node.DOCUMENT_NODE ||
|
||||
(shadowRootAvailable && (traverse instanceof ShadowRoot)) ||
|
||||
(parent && parent.__redom_mounted)
|
||||
) {
|
||||
trigger(traverse, remount ? 'onremount' : 'onmount');
|
||||
triggered = true;
|
||||
}
|
||||
traverse = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setStyle (view, arg1, arg2) {
|
||||
var el = getEl(view);
|
||||
|
||||
if (typeof arg1 === 'object') {
|
||||
for (var key in arg1) {
|
||||
setStyleValue(el, key, arg1[key]);
|
||||
}
|
||||
} else {
|
||||
setStyleValue(el, arg1, arg2);
|
||||
}
|
||||
}
|
||||
|
||||
function setStyleValue (el, key, value) {
|
||||
el.style[key] = value == null ? '' : value;
|
||||
}
|
||||
|
||||
/* global SVGElement */
|
||||
|
||||
var xlinkns = 'http://www.w3.org/1999/xlink';
|
||||
|
||||
function setAttr (view, arg1, arg2) {
|
||||
setAttrInternal(view, arg1, arg2);
|
||||
}
|
||||
|
||||
function setAttrInternal (view, arg1, arg2, initial) {
|
||||
var el = getEl(view);
|
||||
|
||||
var isObj = typeof arg1 === 'object';
|
||||
|
||||
if (isObj) {
|
||||
for (var key in arg1) {
|
||||
setAttrInternal(el, key, arg1[key], initial);
|
||||
}
|
||||
} else {
|
||||
var isSVG = el instanceof SVGElement;
|
||||
var isFunc = typeof arg2 === 'function';
|
||||
|
||||
if (arg1 === 'style' && typeof arg2 === 'object') {
|
||||
setStyle(el, arg2);
|
||||
} else if (isSVG && isFunc) {
|
||||
el[arg1] = arg2;
|
||||
} else if (arg1 === 'dataset') {
|
||||
setData(el, arg2);
|
||||
} else if (!isSVG && (arg1 in el || isFunc) && (arg1 !== 'list')) {
|
||||
el[arg1] = arg2;
|
||||
} else {
|
||||
if (isSVG && (arg1 === 'xlink')) {
|
||||
setXlink(el, arg2);
|
||||
return;
|
||||
}
|
||||
if (initial && arg1 === 'class') {
|
||||
arg2 = el.className + ' ' + arg2;
|
||||
}
|
||||
if (arg2 == null) {
|
||||
el.removeAttribute(arg1);
|
||||
} else {
|
||||
el.setAttribute(arg1, arg2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setXlink (el, arg1, arg2) {
|
||||
if (typeof arg1 === 'object') {
|
||||
for (var key in arg1) {
|
||||
setXlink(el, key, arg1[key]);
|
||||
}
|
||||
} else {
|
||||
if (arg2 != null) {
|
||||
el.setAttributeNS(xlinkns, arg1, arg2);
|
||||
} else {
|
||||
el.removeAttributeNS(xlinkns, arg1, arg2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setData (el, arg1, arg2) {
|
||||
if (typeof arg1 === 'object') {
|
||||
for (var key in arg1) {
|
||||
setData(el, key, arg1[key]);
|
||||
}
|
||||
} else {
|
||||
if (arg2 != null) {
|
||||
el.dataset[arg1] = arg2;
|
||||
} else {
|
||||
delete el.dataset[arg1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function text (str) {
|
||||
return document.createTextNode((str != null) ? str : '');
|
||||
}
|
||||
|
||||
function parseArgumentsInternal (element, args, initial) {
|
||||
for (var i = 0, list = args; i < list.length; i += 1) {
|
||||
var arg = list[i];
|
||||
|
||||
if (arg !== 0 && !arg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = typeof arg;
|
||||
|
||||
if (type === 'function') {
|
||||
arg(element);
|
||||
} else if (type === 'string' || type === 'number') {
|
||||
element.appendChild(text(arg));
|
||||
} else if (isNode(getEl(arg))) {
|
||||
mount(element, arg);
|
||||
} else if (arg.length) {
|
||||
parseArgumentsInternal(element, arg, initial);
|
||||
} else if (type === 'object') {
|
||||
setAttrInternal(element, arg, null, initial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureEl (parent) {
|
||||
return typeof parent === 'string' ? html(parent) : getEl(parent);
|
||||
}
|
||||
|
||||
function getEl (parent) {
|
||||
return (parent.nodeType && parent) || (!parent.el && parent) || getEl(parent.el);
|
||||
}
|
||||
|
||||
function isNode (arg) {
|
||||
return arg && arg.nodeType;
|
||||
}
|
||||
|
||||
function html (query) {
|
||||
var args = [], len = arguments.length - 1;
|
||||
while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
|
||||
|
||||
var element;
|
||||
|
||||
var type = typeof query;
|
||||
|
||||
if (type === 'string') {
|
||||
element = createElement(query);
|
||||
} else if (type === 'function') {
|
||||
var Query = query;
|
||||
element = new (Function.prototype.bind.apply( Query, [ null ].concat( args) ));
|
||||
} else {
|
||||
throw new Error('At least one argument required');
|
||||
}
|
||||
|
||||
parseArgumentsInternal(getEl(element), args, true);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
var el = html;
|
||||
var h = html;
|
||||
|
||||
html.extend = function extendHtml () {
|
||||
var args = [], len = arguments.length;
|
||||
while ( len-- ) args[ len ] = arguments[ len ];
|
||||
|
||||
return html.bind.apply(html, [ this ].concat( args ));
|
||||
};
|
||||
|
||||
function setChildren (parent) {
|
||||
var children = [], len = arguments.length - 1;
|
||||
while ( len-- > 0 ) children[ len ] = arguments[ len + 1 ];
|
||||
|
||||
var parentEl = getEl(parent);
|
||||
var current = traverse(parent, children, parentEl.firstChild);
|
||||
|
||||
while (current) {
|
||||
var next = current.nextSibling;
|
||||
|
||||
unmount(parent, current);
|
||||
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
function traverse (parent, children, _current) {
|
||||
var current = _current;
|
||||
|
||||
var childEls = Array(children.length);
|
||||
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
childEls[i] = children[i] && getEl(children[i]);
|
||||
}
|
||||
|
||||
for (var i$1 = 0; i$1 < children.length; i$1++) {
|
||||
var child = children[i$1];
|
||||
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var childEl = childEls[i$1];
|
||||
|
||||
if (childEl === current) {
|
||||
current = current.nextSibling;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isNode(childEl)) {
|
||||
var next = current && current.nextSibling;
|
||||
var exists = child.__redom_index != null;
|
||||
var replace = exists && next === childEls[i$1 + 1];
|
||||
|
||||
mount(parent, child, current, replace);
|
||||
|
||||
if (replace) {
|
||||
current = next;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.length != null) {
|
||||
current = traverse(parent, child, current);
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
function listPool (View, key, initData) {
|
||||
return new ListPool(View, key, initData);
|
||||
}
|
||||
|
||||
var ListPool = function ListPool (View, key, initData) {
|
||||
this.View = View;
|
||||
this.initData = initData;
|
||||
this.oldLookup = {};
|
||||
this.lookup = {};
|
||||
this.oldViews = [];
|
||||
this.views = [];
|
||||
|
||||
if (key != null) {
|
||||
this.key = typeof key === 'function' ? key : propKey(key);
|
||||
}
|
||||
};
|
||||
|
||||
ListPool.prototype.update = function update (data, context) {
|
||||
var ref = this;
|
||||
var View = ref.View;
|
||||
var key = ref.key;
|
||||
var initData = ref.initData;
|
||||
var keySet = key != null;
|
||||
|
||||
var oldLookup = this.lookup;
|
||||
var newLookup = {};
|
||||
|
||||
var newViews = Array(data.length);
|
||||
var oldViews = this.views;
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var item = data[i];
|
||||
var view = (void 0);
|
||||
|
||||
if (keySet) {
|
||||
var id = key(item);
|
||||
|
||||
view = oldLookup[id] || new View(initData, item, i, data);
|
||||
newLookup[id] = view;
|
||||
view.__redom_id = id;
|
||||
} else {
|
||||
view = oldViews[i] || new View(initData, item, i, data);
|
||||
}
|
||||
view.update && view.update(item, i, data, context);
|
||||
|
||||
var el = getEl(view.el);
|
||||
|
||||
el.__redom_view = view;
|
||||
newViews[i] = view;
|
||||
}
|
||||
|
||||
this.oldViews = oldViews;
|
||||
this.views = newViews;
|
||||
|
||||
this.oldLookup = oldLookup;
|
||||
this.lookup = newLookup;
|
||||
};
|
||||
|
||||
function propKey (key) {
|
||||
return function (item) {
|
||||
return item[key];
|
||||
};
|
||||
}
|
||||
|
||||
function list (parent, View, key, initData) {
|
||||
return new List(parent, View, key, initData);
|
||||
}
|
||||
|
||||
var List = function List (parent, View, key, initData) {
|
||||
this.View = View;
|
||||
this.initData = initData;
|
||||
this.views = [];
|
||||
this.pool = new ListPool(View, key, initData);
|
||||
this.el = ensureEl(parent);
|
||||
this.keySet = key != null;
|
||||
};
|
||||
|
||||
List.prototype.update = function update (data, context) {
|
||||
if ( data === void 0 ) data = [];
|
||||
|
||||
var ref = this;
|
||||
var keySet = ref.keySet;
|
||||
var oldViews = this.views;
|
||||
|
||||
this.pool.update(data, context);
|
||||
|
||||
var ref$1 = this.pool;
|
||||
var views = ref$1.views;
|
||||
var lookup = ref$1.lookup;
|
||||
|
||||
if (keySet) {
|
||||
for (var i = 0; i < oldViews.length; i++) {
|
||||
var oldView = oldViews[i];
|
||||
var id = oldView.__redom_id;
|
||||
|
||||
if (lookup[id] == null) {
|
||||
oldView.__redom_index = null;
|
||||
unmount(this, oldView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i$1 = 0; i$1 < views.length; i$1++) {
|
||||
var view = views[i$1];
|
||||
|
||||
view.__redom_index = i$1;
|
||||
}
|
||||
|
||||
setChildren(this, views);
|
||||
|
||||
if (keySet) {
|
||||
this.lookup = lookup;
|
||||
}
|
||||
this.views = views;
|
||||
};
|
||||
|
||||
List.extend = function extendList (parent, View, key, initData) {
|
||||
return List.bind(List, parent, View, key, initData);
|
||||
};
|
||||
|
||||
list.extend = List.extend;
|
||||
|
||||
/* global Node */
|
||||
|
||||
function place (View, initData) {
|
||||
return new Place(View, initData);
|
||||
}
|
||||
|
||||
var Place = function Place (View, initData) {
|
||||
this.el = text('');
|
||||
this.visible = false;
|
||||
this.view = null;
|
||||
this._placeholder = this.el;
|
||||
|
||||
if (View instanceof Node) {
|
||||
this._el = View;
|
||||
} else if (View.el instanceof Node) {
|
||||
this._el = View;
|
||||
this.view = View;
|
||||
} else {
|
||||
this._View = View;
|
||||
}
|
||||
|
||||
this._initData = initData;
|
||||
};
|
||||
|
||||
Place.prototype.update = function update (visible, data) {
|
||||
var placeholder = this._placeholder;
|
||||
var parentNode = this.el.parentNode;
|
||||
|
||||
if (visible) {
|
||||
if (!this.visible) {
|
||||
if (this._el) {
|
||||
mount(parentNode, this._el, placeholder);
|
||||
unmount(parentNode, placeholder);
|
||||
|
||||
this.el = getEl(this._el);
|
||||
this.visible = visible;
|
||||
} else {
|
||||
var View = this._View;
|
||||
var view = new View(this._initData);
|
||||
|
||||
this.el = getEl(view);
|
||||
this.view = view;
|
||||
|
||||
mount(parentNode, view, placeholder);
|
||||
unmount(parentNode, placeholder);
|
||||
}
|
||||
}
|
||||
this.view && this.view.update && this.view.update(data);
|
||||
} else {
|
||||
if (this.visible) {
|
||||
if (this._el) {
|
||||
mount(parentNode, placeholder, this._el);
|
||||
unmount(parentNode, this._el);
|
||||
|
||||
this.el = placeholder;
|
||||
this.visible = visible;
|
||||
|
||||
return;
|
||||
}
|
||||
mount(parentNode, placeholder, this.view);
|
||||
unmount(parentNode, this.view);
|
||||
|
||||
this.el = placeholder;
|
||||
this.view = null;
|
||||
}
|
||||
}
|
||||
this.visible = visible;
|
||||
};
|
||||
|
||||
/* global Node */
|
||||
|
||||
function router (parent, Views, initData) {
|
||||
return new Router(parent, Views, initData);
|
||||
}
|
||||
|
||||
var Router = function Router (parent, Views, initData) {
|
||||
this.el = ensureEl(parent);
|
||||
this.Views = Views;
|
||||
this.initData = initData;
|
||||
};
|
||||
|
||||
Router.prototype.update = function update (route, data) {
|
||||
if (route !== this.route) {
|
||||
var Views = this.Views;
|
||||
var View = Views[route];
|
||||
|
||||
this.route = route;
|
||||
|
||||
if (View && (View instanceof Node || View.el instanceof Node)) {
|
||||
this.view = View;
|
||||
} else {
|
||||
this.view = View && new View(this.initData, data);
|
||||
}
|
||||
|
||||
setChildren(this.el, [this.view]);
|
||||
}
|
||||
this.view && this.view.update && this.view.update(data, route);
|
||||
};
|
||||
|
||||
var ns = 'http://www.w3.org/2000/svg';
|
||||
|
||||
function svg (query) {
|
||||
var args = [], len = arguments.length - 1;
|
||||
while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
|
||||
|
||||
var element;
|
||||
|
||||
var type = typeof query;
|
||||
|
||||
if (type === 'string') {
|
||||
element = createElement(query, ns);
|
||||
} else if (type === 'function') {
|
||||
var Query = query;
|
||||
element = new (Function.prototype.bind.apply( Query, [ null ].concat( args) ));
|
||||
} else {
|
||||
throw new Error('At least one argument required');
|
||||
}
|
||||
|
||||
parseArgumentsInternal(getEl(element), args, true);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
var s = svg;
|
||||
|
||||
svg.extend = function extendSvg () {
|
||||
var args = [], len = arguments.length;
|
||||
while ( len-- ) args[ len ] = arguments[ len ];
|
||||
|
||||
return svg.bind.apply(svg, [ this ].concat( args ));
|
||||
};
|
||||
|
||||
svg.ns = ns;
|
||||
|
||||
exports.List = List;
|
||||
exports.ListPool = ListPool;
|
||||
exports.Place = Place;
|
||||
exports.Router = Router;
|
||||
exports.el = el;
|
||||
exports.h = h;
|
||||
exports.html = html;
|
||||
exports.list = list;
|
||||
exports.listPool = listPool;
|
||||
exports.mount = mount;
|
||||
exports.place = place;
|
||||
exports.router = router;
|
||||
exports.s = s;
|
||||
exports.setAttr = setAttr;
|
||||
exports.setChildren = setChildren;
|
||||
exports.setData = setData;
|
||||
exports.setStyle = setStyle;
|
||||
exports.setXlink = setXlink;
|
||||
exports.svg = svg;
|
||||
exports.text = text;
|
||||
exports.unmount = unmount;
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
}));
|
1
dist/js/redom.min.js
vendored
Normal file
1
dist/js/redom.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/svg/searchmenu.svg
vendored
Normal file
1
dist/svg/searchmenu.svg
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="10.107" cy="8.7642" r="6.7391" stroke-width="1.6848"/><line x1="18.531" x2="14.866" y1="17.188" y2="13.524" stroke-width="1.6848"/><path d="m14.756 20.738 1.9607 1.9607 1.9091-1.9297" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6554"/></svg>
|
After Width: | Height: | Size: 446 B |
759
dist/treebird.css
vendored
759
dist/treebird.css
vendored
File diff suppressed because it is too large
Load diff
11
docs/DEVELOP.md
Normal file
11
docs/DEVELOP.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Developing Treebird
|
||||
|
||||
Treebird development is a bit hacky. There are better ways to work with development
|
||||
|
||||
### Compiler flags
|
||||
|
||||
You can compile Treebird with some helpful flags, such as single_threaded to improve debugging for Treebird.
|
||||
|
||||
```
|
||||
make SINGLE_THREADED=1 all
|
||||
```
|
|
@ -8,22 +8,45 @@ For the following GNU/Linux distributions, you will need the following libraries
|
|||
|
||||
###### Debian
|
||||
|
||||
`# apt install libcurl4-gnutls-dev libpcre2-dev libfcgi-dev base-devel`
|
||||
`# apt install libcurl4-gnutls-dev libpcre2-dev libfcgi-dev build-essential perl libperl-dev libtemplate-perl`
|
||||
|
||||
###### Void GNU/Linux
|
||||
|
||||
`# xbps-install libcurl libcurl-devel base-devel pcre2 pcre2-devel fcgi fcgi-devel`
|
||||
`# xbps-install libcurl libcurl-devel base-devel pcre2 pcre2-devel fcgi fcgi-devel perl-Template-Toolkit`
|
||||
|
||||
###### Arch
|
||||
|
||||
`# pacman -S curl base-devel`
|
||||
`# pacman -S curl base-devel perl perl-template-toolkit`
|
||||
|
||||
###### Gentoo
|
||||
|
||||
TODO
|
||||
|
||||
Create a copy of `config.def.h` at `config.h`, edit the file with your information
|
||||
|
||||
Run `make`. This will also clone mastodont-c, and compile both it and Treebird.
|
||||
Run `make`. (**hint:** Pass -j3 to speed up compilation). This will also clone mastodont-c, and compile both it and Treebird.
|
||||
|
||||
If you `fossil update` any changes, `make update` should be run after updating
|
||||
|
||||
## Perl dependencies manual install
|
||||
|
||||
**Note:** You **WONT** need to do this if your distribution above included all the deps (Template Toolkit)
|
||||
|
||||
At the moment, all of them listed above do, but if your distro is nonstandard, keep reading:
|
||||
|
||||
---
|
||||
|
||||
Treebird renders most of the content that you see in Perl using the Template Toolkit.
|
||||
|
||||
You can install it by running `make install_deps`
|
||||
|
||||
If that doesn't work, you can open a CPAN shell
|
||||
|
||||
```
|
||||
perl -MCPAN -e shell
|
||||
install Template::Toolkit
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Run `# make install`
|
||||
|
@ -33,15 +56,14 @@ If this succeeds (assuming you used default variables), you can now find Treebir
|
|||
- `/usr/local/share/treebird/` - Contains CSS, images, and other meta files
|
||||
- `/usr/local/bin/treebird` - Regular executable CGI file, test it by running it as is, it shouldn't spit anything out
|
||||
|
||||
### Using NGINX
|
||||
## Development
|
||||
|
||||
For developing Treebird, see `DEVELOP.md`.
|
||||
|
||||
## Nginx
|
||||
|
||||
Treebird can be served over nginx by using a FastCGI daemon such as spawn-fcgi.
|
||||
|
||||
The example static files will be in `/usr/local/share/treebird/`, with `treebird.cgi` at `/usr/local/bin/treebird`.
|
||||
|
||||
After running `make`, Treebird's files will be in the `dist/` directory. _Copy_, ***DO NOT MOVE***, **everything but treebird.cgi** of this folder to your web server. Copy `treebird.cgi` to another directory of your choosing.
|
||||
|
||||
## Nginx
|
||||
An example Nginx configuration is available in [treebird.nginx.conf](./sample/treebird.nginx.conf).
|
||||
* Make sure to change `example.com` to your instance's domain.
|
||||
* Make sure to change the `root` to wherever the static files are being stored
|
||||
|
@ -51,7 +73,7 @@ An example Nginx configuration is available in [treebird.nginx.conf](./sample/tr
|
|||
Apache hasn't caused many troubles, and is in fact, what I use for development. You can see how to start
|
||||
spawn-fcgi in `scripts/fcgistarter.sh`.
|
||||
|
||||
Example Apache configuration is available in [treebird.apache.conf](./sample/treebird.apache.conf).
|
||||
An example Apache configuration is available in [treebird.apache.conf](./sample/treebird.apache.conf).
|
||||
|
||||
#### spawn-fcgi
|
||||
|
||||
|
|
118
perl/account.pm
Normal file
118
perl/account.pm
Normal file
|
@ -0,0 +1,118 @@
|
|||
package account;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( account content_statuses generate_account_list generate_account_item status_interactions );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use l10n 'lang';
|
||||
use status 'generate_status';
|
||||
use string_helpers qw( simple_escape emojify random_error_kaomoji format_username );
|
||||
use navigation 'generate_navigation';
|
||||
|
||||
sub generate_account
|
||||
{
|
||||
my ($ssn, $data, $acct, $relationship, $content) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
lang => \&lang,
|
||||
relationship => $relationship,
|
||||
content => $content,
|
||||
acct => $acct,
|
||||
escape => \&simple_escape,
|
||||
emojify => \&emojify,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'account.tt'});
|
||||
}
|
||||
|
||||
sub content_statuses
|
||||
{
|
||||
my ($ssn, $data, $acct, $relationship, $statuses) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
acct => $acct,
|
||||
statuses => $statuses,
|
||||
create_status => sub { generate_status($ssn, $data, shift); },
|
||||
# Make subroutine so Perl doesn't autovivify
|
||||
nav => sub { generate_navigation($ssn, $data, $statuses->[0]->{id}, $statuses->[-1]->{id}) },
|
||||
random_error_kaomoji => \&random_error_kaomoji,
|
||||
);
|
||||
|
||||
generate_account($ssn, $data, $acct, $relationship, to_template(\%vars, \$data->{'account_statuses.tt'}));
|
||||
}
|
||||
|
||||
sub generate_account_item
|
||||
{
|
||||
my ($ssn, $data, $account) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
account => $account,
|
||||
# Functions
|
||||
icon => \&get_icon,
|
||||
lang => \&lang,
|
||||
format_username => \&format_username,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'account_item.tt'});
|
||||
}
|
||||
|
||||
sub generate_account_list
|
||||
{
|
||||
my ($ssn, $data, $accounts, $title) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
accounts => $accounts,
|
||||
title => $title,
|
||||
create_account => sub { generate_account_item($ssn, $data, shift); },
|
||||
nav => sub { generate_navigation($ssn, $data, $accounts->[0]->{id}, $accounts->[-1]->{id}) },
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'accounts.tt'});
|
||||
}
|
||||
|
||||
sub status_interactions
|
||||
{
|
||||
my ($ssn, $data, $accounts, $label) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
data => $data,
|
||||
accounts => $accounts,
|
||||
label => $label,
|
||||
# Functions
|
||||
create_account => sub { generate_account_item($ssn, $data, shift); },
|
||||
);
|
||||
|
||||
|
||||
to_template(\%vars, \$data->{'status_interactions.tt'});
|
||||
}
|
||||
|
||||
sub content_accounts
|
||||
{
|
||||
my ($ssn, $data, $acct, $relationship, $accounts, $title) = @_;
|
||||
|
||||
my $acct_list_page = generate_account_list($ssn, $data, $accounts, $title);
|
||||
|
||||
# Should we create a full accounts view?
|
||||
if ($acct)
|
||||
{
|
||||
generate_account($ssn, $data, $acct, $relationship, $acct_list_page);
|
||||
}
|
||||
else {
|
||||
return $acct_list_page;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
26
perl/attachments.pm
Normal file
26
perl/attachments.pm
Normal file
|
@ -0,0 +1,26 @@
|
|||
package attachments;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_attachment );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use icons 'get_icon';
|
||||
|
||||
sub generate_attachment
|
||||
{
|
||||
my ($ssn, $data, $att, $sensitive) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
attachment => $att,
|
||||
sensitive => $sensitive,
|
||||
icon => \&get_icon,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'attachment.tt'});
|
||||
}
|
||||
|
||||
1;
|
42
perl/chat.pm
Normal file
42
perl/chat.pm
Normal file
|
@ -0,0 +1,42 @@
|
|||
package chat;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORTS = qw( content_chats construct_chat );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use string_helpers qw( format_username emojify reltime_to_str );
|
||||
|
||||
sub construct_chat
|
||||
{
|
||||
my ($ssn, $data, $chat, $messages) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
chat => $chat,
|
||||
messages => $messages,
|
||||
format_username => \&format_username,
|
||||
emojify => \&emojify,
|
||||
reltime => \&reltime_to_str,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'chat.tt'});
|
||||
}
|
||||
|
||||
sub content_chats
|
||||
{
|
||||
my ($ssn, $data, $chats) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
chats => $chats,
|
||||
format_username => \&format_username,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'content_chats.tt'});
|
||||
}
|
||||
|
||||
1;
|
20
perl/config.pm
Normal file
20
perl/config.pm
Normal file
|
@ -0,0 +1,20 @@
|
|||
package config;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our @EXPORT = qw( general appearance );
|
||||
use Exporter 'import';
|
||||
|
||||
use template_helpers 'simple_page';
|
||||
|
||||
sub general
|
||||
{
|
||||
simple_page @_, 'config_general.tt';
|
||||
}
|
||||
|
||||
sub appearance
|
||||
{
|
||||
simple_page @_, 'config_appearance.tt';
|
||||
}
|
||||
|
||||
1;
|
25
perl/embed.pm
Normal file
25
perl/embed.pm
Normal file
|
@ -0,0 +1,25 @@
|
|||
package embed;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_embedded_page );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
|
||||
sub generate_embedded_page
|
||||
{
|
||||
my ($ssn, $data, $content) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
content => $content,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'embed.tt'});
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
|
35
perl/emojis.pm
Normal file
35
perl/emojis.pm
Normal file
|
@ -0,0 +1,35 @@
|
|||
package emojis;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_emoji );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
|
||||
sub generate_emoji
|
||||
{
|
||||
my ($ssn, $data, $status_id, $emoji) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
status_id => $status_id,
|
||||
emoji => $emoji
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'emoji.tt'});
|
||||
}
|
||||
|
||||
sub emoji_picker
|
||||
{
|
||||
my ($data, $emojis) = @_;
|
||||
|
||||
my %vars = (
|
||||
e => $emojis
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'emoji_picker.tt'});
|
||||
}
|
||||
|
||||
1;
|
75
perl/icons.pm
Normal file
75
perl/icons.pm
Normal file
|
@ -0,0 +1,75 @@
|
|||
package icons;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Scalar::Util 'looks_like_number';
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( &get_icon &get_icon_svg &get_icon_png &visibility_to_icon );
|
||||
|
||||
sub get_icon
|
||||
{
|
||||
my ($ico, $is_png) = @_;
|
||||
$is_png ||= 0;
|
||||
|
||||
$is_png ? get_icon_png($ico) : get_icon_svg($ico);
|
||||
}
|
||||
|
||||
sub get_icon_svg
|
||||
{
|
||||
my %res = (
|
||||
repeat => '<svg class="repeat" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 2.1l4 4-4 4"/><path d="M3 12.2v-2a4 4 0 0 1 4-4h12.8M7 21.9l-4-4 4-4"/><path d="M21 11.8v2a4 4 0 0 1-4 4H4.2"/></svg>',
|
||||
|
||||
like => '<svg class="like" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>',
|
||||
|
||||
expand => '<svg class="expand" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M14 10l6.1-6.1M9 21H3v-6M10 14l-6.1 6.1"/></svg>',
|
||||
|
||||
reply => '<svg class="reply" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 9l6 6-6 6"/><path d="M4 4v7a4 4 0 0 0 4 4h11"/></svg>',
|
||||
|
||||
emoji => '<svg class="emoji-btn" width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><g><line x1="9" x2="9" y1="6.9367" y2="11.755" stroke-width="1.7916"/><line x1="15" x2="15" y1="6.9367" y2="11.755" stroke-width="1.7916"/><path d="m7.0891 15.099s4.7206 4.7543 9.7109 0" stroke-linecap="round" stroke-linejoin="miter" stroke-width="1.9764"/></g></svg>',
|
||||
|
||||
likeboost => '<svg class="one-click-software" width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><g stroke-width="1.98"><path d="m19.15 8.5061 2.7598 2.7598-2.7598 2.7598"/><path d="m14.756 11.325s2.5484-0.05032 6.3258 0.01026m-15.639 10.807-2.7598-2.7598 2.7598-2.7598"/><path d="m22.4 15.327v1.2259c0 1.156-1.2356 2.7598-2.7598 2.7598h-16.664"/></g><polygon transform="matrix(.60736 0 0 .60736 .60106 .63577)" points="18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2 15.09 8.26 22 9.27 17 14.14" stroke-width="2.9656"/></g></svg>',
|
||||
|
||||
fileclip => '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>',
|
||||
|
||||
'local' => '<svg class="visibility vis-local" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 9v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9"/><path d="M9 22V12h6v10M2 10.6L12 2l10 8.6"/></svg>',
|
||||
|
||||
direct => '<svg class="visibility vis-direct xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>',
|
||||
|
||||
private => '<svg class="visibility vis-private" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>',
|
||||
|
||||
list => '<svg class="visibility vis-list" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>',
|
||||
|
||||
unlisted => '<svg class="visibility vis-unlisted" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>',
|
||||
|
||||
public => '<svg class="visibility vis-public" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>',
|
||||
|
||||
follow => '<svg class="follow" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>',
|
||||
|
||||
search => '<svg class="search" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>',
|
||||
|
||||
'search-menu' => '<svg class="search-menu" width="20" height="20" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="10.107" cy="8.7642" r="6.7391" stroke-width="1.6848"/><line x1="18.531" x2="14.866" y1="17.188" y2="13.524" stroke-width="1.6848"/><path d="m14.756 20.738 1.9607 1.9607 1.9091-1.9297" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6554"/></svg>',
|
||||
);
|
||||
|
||||
$res{$_[0]};
|
||||
}
|
||||
|
||||
sub visibility_to_icon
|
||||
{
|
||||
# I thought of an array, but I don't want to call get_icon UNLESS
|
||||
# we know the visibility
|
||||
return unless looks_like_number($_[0]);
|
||||
my $vis = $_[0];
|
||||
|
||||
return get_icon('public') if $vis == 1;
|
||||
return get_icon('unlisted') if $vis == 2;
|
||||
return get_icon('private') if $vis == 3;
|
||||
return get_icon('list') if $vis == 4;
|
||||
return get_icon('direct') if $vis == 5;
|
||||
return get_icon('local') if $vis == 6;
|
||||
|
||||
# Assume local for anything else, because well... I'm not sure
|
||||
get_icon('local');
|
||||
}
|
||||
|
||||
1;
|
96
perl/l10n.pm
Normal file
96
perl/l10n.pm
Normal file
|
@ -0,0 +1,96 @@
|
|||
package l10n;
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( &lang %L10N );
|
||||
|
||||
our %L10N = (
|
||||
EN_US => {
|
||||
APP_NAME => 'Treebird',
|
||||
HOME => 'Home',
|
||||
LOCAL => 'Local',
|
||||
FEDERATED => 'Federated',
|
||||
NOTIFICATIONS => 'Notifications',
|
||||
LISTS => 'Lists',
|
||||
FAVOURITES => 'Favorites',
|
||||
BOOKMARKS => 'Bookmarks',
|
||||
DIRECT => 'Direct',
|
||||
CONFIG => 'Config',
|
||||
SEARCH_PLACEHOLDER => 'Search',
|
||||
SEARCH_BUTTON => 'Search',
|
||||
GENERAL => 'General',
|
||||
ACCOUNT => 'Account',
|
||||
JAVASCRIPT => 'JavaScript',
|
||||
CFG_QUICK_ACTIONS => 'Quick actions - Likes, Boosts, etc done in the background',
|
||||
CFG_QUICK_REPLY => 'Quick reply - Replies don\'t require redirects',
|
||||
LIVE_STATUSES => 'Live statuses - Statuses fetch on the fly',
|
||||
APPEARANCE => 'Appearance',
|
||||
VARIANT => 'Variant',
|
||||
THEME_TREEBIRD20 => 'Treebird - Default, simple theme',
|
||||
THEME_TREEBIRD30 => 'Treebird 3.0 - Flat, modern theme',
|
||||
COLOR_SCHEME => 'Color Scheme',
|
||||
LIGHT => 'Light',
|
||||
DARK => 'Dark',
|
||||
SAVE => 'Save',
|
||||
ACCT_MENU => 'Menu',
|
||||
SUBSCRIBE => 'Subscribe',
|
||||
UNSUBSCRIBE => 'Unsubscribe',
|
||||
BLOCK => 'Block',
|
||||
UNBLOCK => 'Unblock',
|
||||
MUTE => 'Mute',
|
||||
UNMUTE => 'Unmute',
|
||||
TAB_STATUSES => 'Statuses',
|
||||
TAB_FOLLOWING => 'Following',
|
||||
TAB_FOLLOWERS => 'Followers',
|
||||
TAB_SCROBBLES => 'Scrobbles',
|
||||
TAB_MEDIA => 'Media',
|
||||
TAB_PINNED => 'Pinned',
|
||||
FOLLOWS_YOU => 'Follows you!',
|
||||
FOLLOW => 'Follow',
|
||||
FOLLOW_PENDING => 'Follow pending',
|
||||
FOLLOWING => 'Following!',
|
||||
BLOCKED => 'You are blocked by this user.',
|
||||
REPLY => 'Reply',
|
||||
REPEAT => 'Repeat',
|
||||
LIKE => 'Like',
|
||||
QUICK => 'Quick',
|
||||
VIEW => 'View',
|
||||
IN_REPLY_TO => 'In reply to',
|
||||
PAGE_NOT_FOUND => 'Content not found',
|
||||
STATUS_NOT_FOUND => 'Status not found',
|
||||
ACCOUNT_NOT_FOUND => 'Account not found',
|
||||
VIS_PUBLIC => 'Public',
|
||||
VIS_UNLISTED => 'Unlisted',
|
||||
VIS_PRIVATE => 'Private',
|
||||
VIS_DIRECT => 'Direct',
|
||||
VIS_LOCAL => 'Local',
|
||||
VIS_LIST => 'List',
|
||||
LOGIN => 'Login',
|
||||
REGISTER => 'Register',
|
||||
USERNAME => 'Username',
|
||||
PASSWORD => 'Password',
|
||||
LOGIN_BTN => 'Login',
|
||||
LOGIN_HEADER => 'Login / Register',
|
||||
LOGIN_FAIL => 'Couldn\'t login',
|
||||
NOTIF_LIKED => 'liked your status',
|
||||
NOTIF_REACTED_WITH => 'reacted with',
|
||||
NOTIF_REPEATED => 'repeated your status',
|
||||
NOTIF_FOLLOW => 'followed you',
|
||||
NOTIF_FOLLOW_REQUEST => 'wants to follow you',
|
||||
NOTIF_POLL => 'poll results',
|
||||
NOTIF_COMPACT_LIKED => 'liked',
|
||||
NOTIF_COMPACT_REACTED_WITH => 'reacted',
|
||||
NOTIF_COMPACT_REPEATED => 'repeated',
|
||||
NOTIF_COMPACT_FOLLOW => 'followed',
|
||||
COMPACT_FOLLOW_REQUEST => 'wants to follow',
|
||||
NOTIF_COMPACT_POLL => 'poll',
|
||||
},
|
||||
# TODO bring over Spanish and Chinese
|
||||
);
|
||||
|
||||
sub lang
|
||||
{
|
||||
$L10N{'EN_US'}->{shift(@_)}
|
||||
}
|
||||
|
||||
return 1;
|
19
perl/lists.pm
Normal file
19
perl/lists.pm
Normal file
|
@ -0,0 +1,19 @@
|
|||
package lists;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORTS = qw( content_lists );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
|
||||
sub content_lists
|
||||
{
|
||||
my ($ssn, $data, $lists) = @_;
|
||||
|
||||
my %vars = (
|
||||
lists => $lists
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'content_lists.tt'});
|
||||
}
|
24
perl/login.pm
Normal file
24
perl/login.pm
Normal file
|
@ -0,0 +1,24 @@
|
|||
package login;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( content_login );
|
||||
|
||||
use l10n 'lang';
|
||||
use template_helpers 'to_template';
|
||||
|
||||
sub content_login
|
||||
{
|
||||
my ($ssn, $data, $error) = @_;
|
||||
|
||||
my %vars = (
|
||||
error => $error,
|
||||
lang => \&lang,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'login.tt'});
|
||||
}
|
||||
|
||||
1;
|
59
perl/main.pl
Normal file
59
perl/main.pl
Normal file
|
@ -0,0 +1,59 @@
|
|||
use strict;
|
||||
use warnings;
|
||||
# Modules
|
||||
use Template;
|
||||
use l10n qw( &lang %L10N );
|
||||
use notifications qw( generate_notification content_notifications );
|
||||
use template_helpers qw( &to_template );
|
||||
use timeline;
|
||||
use icons 'get_icon';
|
||||
use status;
|
||||
use account;
|
||||
use lists;
|
||||
use search;
|
||||
use chat;
|
||||
use config;
|
||||
use embed;
|
||||
use meta;
|
||||
use login;
|
||||
|
||||
# use Devel::Leak;
|
||||
|
||||
# my $handle;
|
||||
# Devel::Leak::NoteSV($handle);
|
||||
|
||||
# sub leaky_friend
|
||||
# {
|
||||
# $count = Devel::Leak::CheckSV($handle);
|
||||
# my $leakstr = "Memory: $count SVs\n";
|
||||
# print STDERR $leakstr;
|
||||
# }
|
||||
|
||||
sub base_page
|
||||
{
|
||||
my ($ssn,
|
||||
$data,
|
||||
$main,
|
||||
$notifs) = @_;
|
||||
my $result;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
title => $L10N{'EN_US'}->{'APP_NAME'},
|
||||
lang => \&lang,
|
||||
main => $main,
|
||||
icon => \&get_icon,
|
||||
sidebar_opacity => $ssn->{config}->{sidebar_opacity} / 255,
|
||||
acct => $ssn->{account},
|
||||
data => $data,
|
||||
notifs => $notifs,
|
||||
notification => \&generate_notification,
|
||||
);
|
||||
|
||||
my $ret = to_template(\%vars, \$data->{'main.tt'});
|
||||
|
||||
# leaky_friend();
|
||||
|
||||
return $ret;
|
||||
}
|
21
perl/meta.pm
Normal file
21
perl/meta.pm
Normal file
|
@ -0,0 +1,21 @@
|
|||
package meta;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our @EXPORT = qw( );
|
||||
|
||||
use Exporter 'import';
|
||||
|
||||
use template_helpers 'simple_page';
|
||||
|
||||
sub about
|
||||
{
|
||||
simple_page @_, 'about.tt';
|
||||
}
|
||||
|
||||
sub license
|
||||
{
|
||||
simple_page @_, 'license.tt';
|
||||
}
|
||||
|
||||
1;
|
25
perl/navigation.pm
Normal file
25
perl/navigation.pm
Normal file
|
@ -0,0 +1,25 @@
|
|||
package navigation;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_navigation );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use l10n 'lang';
|
||||
|
||||
sub generate_navigation
|
||||
{
|
||||
my ($ssn, $data, $first_id, $last_id) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
lang => \&lang,
|
||||
start_id => $ssn->{post}->{start_id} || $first_id,
|
||||
prev_id => $first_id,
|
||||
next_id => $last_id,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'navigation.tt'});
|
||||
}
|
63
perl/notifications.pm
Normal file
63
perl/notifications.pm
Normal file
|
@ -0,0 +1,63 @@
|
|||
package notifications;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_notification content_notifications embed_notifications );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use status 'generate_status';
|
||||
use string_helpers qw( random_error_kaomoji );
|
||||
use icons 'get_icon';
|
||||
use embed 'generate_embedded_page';
|
||||
use navigation 'generate_navigation';
|
||||
|
||||
sub generate_notification
|
||||
{
|
||||
my ($ssn, $data, $notif, $is_compact) = @_;
|
||||
|
||||
$is_compact ||= 0;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
notif => $notif,
|
||||
compact => $is_compact,
|
||||
create_status => sub { generate_status($ssn, $data, shift, shift, $is_compact); },
|
||||
icon => \&get_icon,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'notification.tt'});
|
||||
}
|
||||
|
||||
sub content_notifications
|
||||
{
|
||||
my ($ssn, $data, $notifs) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
notifs => $notifs,
|
||||
notification => sub { generate_notification($ssn, $data, shift); },
|
||||
random_error_kaomoji => \&random_error_kaomoji,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'content_notifs.tt'});
|
||||
}
|
||||
|
||||
sub embed_notifications
|
||||
{
|
||||
my ($ssn, $data, $notifs) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
notifs => $notifs,
|
||||
notification => sub { generate_notification($ssn, $data, shift, 1); },
|
||||
nav => sub { generate_navigation($ssn, $data, $notifs->[0]->{id}, $notifs->[-1]->{id}) },
|
||||
);
|
||||
|
||||
generate_embedded_page($ssn, $data, to_template(\%vars, \$data->{'notifs_embed.tt'}));
|
||||
}
|
||||
|
||||
1;
|
26
perl/postbox.pm
Normal file
26
perl/postbox.pm
Normal file
|
@ -0,0 +1,26 @@
|
|||
package postbox;
|
||||
use strict;
|
||||
use warnings;
|
||||
use template_helpers 'to_template';
|
||||
use string_helpers qw( get_mentions_from_content );
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( generate_postbox );
|
||||
|
||||
use icons 'get_icon';
|
||||
|
||||
sub generate_postbox
|
||||
{
|
||||
my ($ssn, $data, $status) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
data => $data,
|
||||
status => $status,
|
||||
icon => \&get_icon,
|
||||
mentionify => \&get_mentions_from_content,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'postbox.tt'});
|
||||
}
|
106
perl/search.pm
Normal file
106
perl/search.pm
Normal file
|
@ -0,0 +1,106 @@
|
|||
package search;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORTS = qw( content_search content_search_tags content_search_accounts content_search_statuses search_tags search_accounts search_statuses );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use status 'generate_status';
|
||||
use account 'generate_account_item';
|
||||
|
||||
use constant
|
||||
{
|
||||
SEARCH_CAT_STATUSES => 0,
|
||||
SEARCH_CAT_ACCOUNTS => 1,
|
||||
SEARCH_CAT_TAGS => 2
|
||||
};
|
||||
|
||||
sub search_page
|
||||
{
|
||||
my ($ssn, $data, $tab, $content) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
tab => $tab,
|
||||
content => $content,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'search.tt'});
|
||||
}
|
||||
|
||||
# CONTENT
|
||||
sub search_accounts
|
||||
{
|
||||
my ($ssn, $data, $search) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
search => $search,
|
||||
create_account => sub { generate_account_item($ssn, $data, shift); },
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'search_accounts.tt'})
|
||||
}
|
||||
|
||||
sub search_statuses
|
||||
{
|
||||
my ($ssn, $data, $search) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
search => $search,
|
||||
create_status => sub { generate_status($ssn, $data, shift); },
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'search_statuses.tt'})
|
||||
}
|
||||
|
||||
sub search_tags
|
||||
{
|
||||
my ($ssn, $data, $search) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
search => $search,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'search_tags.tt'})
|
||||
}
|
||||
|
||||
sub content_search_accounts
|
||||
{
|
||||
search_page($_[0], $_[1], SEARCH_CAT_ACCOUNTS, search_accounts(@_));
|
||||
}
|
||||
|
||||
sub content_search_statuses
|
||||
{
|
||||
search_page($_[0], $_[1], SEARCH_CAT_STATUSES, search_statuses(@_));
|
||||
}
|
||||
|
||||
sub content_search_tags
|
||||
{
|
||||
search_page($_[0], $_[1], SEARCH_CAT_TAGS, search_tags(@_));
|
||||
}
|
||||
|
||||
sub content_search
|
||||
{
|
||||
my ($ssn, $data, $search) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
search => $search,
|
||||
|
||||
statuses => search_statuses(@_),
|
||||
accounts => search_accounts(@_),
|
||||
hashtags => search_tags(@_),
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'content_search.tt'});
|
||||
}
|
||||
|
99
perl/status.pm
Normal file
99
perl/status.pm
Normal file
|
@ -0,0 +1,99 @@
|
|||
package status;
|
||||
use strict;
|
||||
use warnings;
|
||||
use string_helpers qw( reltime_to_str greentextify emojify format_username localize_mentions simple_escape );
|
||||
use icons qw( get_icon visibility_to_icon );
|
||||
use attachments 'generate_attachment';
|
||||
use postbox 'generate_postbox';
|
||||
use emojis 'generate_emoji';
|
||||
use Exporter 'import';
|
||||
use l10n 'lang';
|
||||
|
||||
our @EXPORT = qw( content_status generate_status );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
|
||||
# Useful variable to prevent collisions
|
||||
my $rel_context = 0;
|
||||
|
||||
sub generate_status
|
||||
{
|
||||
my ($ssn, $data, $status, $notif, $is_compact, $picker) = @_;
|
||||
my $boost_acct;
|
||||
|
||||
# Move status reference for boosts and keep account
|
||||
# I hate this design but blame MastoAPI, not me.
|
||||
if ($status->{reblog})
|
||||
{
|
||||
$boost_acct = $status->{account};
|
||||
$status = $status->{reblog};
|
||||
}
|
||||
|
||||
my $is_statusey_notif = ($notif && ($notif->{type} eq 'mention' || $notif->{type} eq 'status'));
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
status => $status,
|
||||
boost => $boost_acct, # May be undef
|
||||
data => $data,
|
||||
emoji_picker => $picker,
|
||||
notif => $notif, # May be undef
|
||||
compact => $is_compact, # May be undef
|
||||
is_statusey_notif => $is_statusey_notif,
|
||||
unique_toggle_id => $rel_context++,
|
||||
interacted_with => $boost_acct || ($notif && !$is_statusey_notif),
|
||||
|
||||
# Functions
|
||||
|
||||
action_to_string => sub {
|
||||
return lang('NOTIF_LIKED') if $notif && $notif->{type} eq 'favourite';
|
||||
return lang('NOTIF_REPEATED') if $boost_acct || $notif->{type} eq 'reblog';
|
||||
return lang('NOTIF_REACTED_WITH') .' '. $notif->{emoji} if $notif->{type} eq 'emoji reaction';
|
||||
},
|
||||
|
||||
action_to_icon => sub {
|
||||
return get_icon('like') if $notif && $notif->{type} eq 'favourite';
|
||||
return get_icon('repeat') if $boost_acct || $notif->{type} eq 'reblog';
|
||||
return $notif->{emoji} if $notif && $notif->{type} eq 'emoji reaction';
|
||||
},
|
||||
|
||||
icon => \&get_icon,
|
||||
lang => \&lang,
|
||||
rel_to_str => \&reltime_to_str,
|
||||
vis_to_icon => \&visibility_to_icon,
|
||||
make_att => \&generate_attachment,
|
||||
make_emoji => \&generate_emoji,
|
||||
greentextify => \&greentextify,
|
||||
emojify => \&emojify,
|
||||
escape => \&simple_escape,
|
||||
fix_mentions => \&localize_mentions,
|
||||
format_username => \&format_username,
|
||||
make_postbox => \&generate_postbox,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'status.tt'});
|
||||
}
|
||||
|
||||
sub content_status
|
||||
{
|
||||
my ($ssn, $data, $status, $statuses_before, $statuses_after, $picker) = @_;
|
||||
|
||||
$rel_context = 0;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
status => $status,
|
||||
picker => $picker,
|
||||
statuses_before => $statuses_before,
|
||||
statuses_after => $statuses_after,
|
||||
# Functions
|
||||
create_status => sub { generate_status($ssn, $data, shift, 0, 0, shift) },
|
||||
);
|
||||
|
||||
|
||||
to_template(\%vars, \$data->{'content_status.tt'});
|
||||
}
|
||||
|
||||
1;
|
103
perl/string_helpers.pm
Normal file
103
perl/string_helpers.pm
Normal file
|
@ -0,0 +1,103 @@
|
|||
package string_helpers;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
use Scalar::Util 'looks_like_number';
|
||||
|
||||
our @EXPORT = qw( reltime_to_str greentextify emojify format_username get_mentions_from_content localize_mentions simple_escape random_error_kaomoji );
|
||||
|
||||
my $re_mentions = '(?=<a .*?mention.*?)<a .*?href="https?:\/\/(.*?)\/(?:@|users\/|\/u)?(.*?)?".*?>';
|
||||
|
||||
sub reltime_to_str
|
||||
{
|
||||
return unless looks_like_number($_[0]);
|
||||
my $since = time() - $_[0];
|
||||
|
||||
return $since . 's' if $since < 60;
|
||||
return int($since / 60) . 'm' if $since < 60 * 60;
|
||||
return int($since / (60 * 60)) . 'h' if $since < 60 * 60 * 24;
|
||||
return int($since / (60 * 60 * 24)) . 'd' if $since < 60 * 60 * 24 * 31;
|
||||
return int($since / (60 * 60 * 24 * 31)) . 'mon' if $since < 60 * 60 * 24 * 365;
|
||||
return int($since / (60 * 60 * 24 * 365)) . 'yr';
|
||||
}
|
||||
|
||||
sub simple_escape
|
||||
{
|
||||
my $text = shift;
|
||||
$text =~ s/&/&/gs;
|
||||
$text =~ s/</</gs;
|
||||
$text =~ s/>/>/gs;
|
||||
$text =~ s/"/"/gs;
|
||||
$text;
|
||||
}
|
||||
|
||||
sub greentextify
|
||||
{
|
||||
my $text = shift;
|
||||
$text =~ s/(>.*?)(?=<|$)/<span class="greentext">$1<\/span>/gs;
|
||||
$text =~ s/(<.*?)(?=<|$)/<span class="bluetext">$1<\/span>/gs;
|
||||
$text =~ s/(?:^|>| )(\^.*?)(?=<|$)/<span class="yellowtext">$1<\/span>/gs;
|
||||
$text;
|
||||
}
|
||||
|
||||
sub emojify
|
||||
{
|
||||
my ($text, $emojis) = @_;
|
||||
if ($emojis)
|
||||
{
|
||||
foreach my $emoji (@{$emojis})
|
||||
{
|
||||
my $emo = $emoji->{shortcode};
|
||||
my $url = $emoji->{url};
|
||||
$text =~ s/:$emo:/<img class="emoji" src="$url" loading="lazy">/gsi;
|
||||
}
|
||||
}
|
||||
$text;
|
||||
}
|
||||
|
||||
sub format_username
|
||||
{
|
||||
my $account = shift;
|
||||
return unless $account;
|
||||
|
||||
#TODO ESCAPE DISPLAY NAME
|
||||
emojify(simple_escape($account->{display_name}), $account->{emojis});
|
||||
}
|
||||
|
||||
sub localize_mentions
|
||||
{
|
||||
my $text = shift;
|
||||
# idk how to work around this
|
||||
my $at = '@';
|
||||
|
||||
$text =~ s/$re_mentions/<a target="_parent" class="mention" href="\/$at$2$at$1">/gs;
|
||||
$text;
|
||||
}
|
||||
|
||||
sub get_mentions_from_content
|
||||
{
|
||||
my ($ssn, $status) = @_;
|
||||
my $result = '';
|
||||
my $acct;
|
||||
while ($status->{'content'} =~
|
||||
/<a .*?href=\"https?:\/\/(.*?)\/(?:@|users\/|u\/)?(.*?)?\".*?>@(?:<span>)?.*?(?:<\/span>)?/gs)
|
||||
{
|
||||
$acct = $2 . '@' . $1;
|
||||
# TODO this does not account for the domain (alt interference)
|
||||
$result .= '@' . $acct unless $ssn->{account}->{acct} eq $2;
|
||||
}
|
||||
($status->{account}->{acct} eq $ssn->{account}->{acct})
|
||||
? $result : '@' . $status->{account}->{acct} . ' ' . $result;
|
||||
}
|
||||
|
||||
sub random_error_kaomoji
|
||||
{
|
||||
my @messages = (
|
||||
"(; ̄Д ̄)",
|
||||
"(`Δ´)!",
|
||||
"¯\\_(ツ)_/¯",
|
||||
"(ノ´・ω・)ノ ミ ┸━┸",
|
||||
"(╯°□°)╯︵ ┻━┻",
|
||||
);
|
||||
@messages[rand(scalar @messages)];
|
||||
}
|
55
perl/template_helpers.pm
Normal file
55
perl/template_helpers.pm
Normal file
|
@ -0,0 +1,55 @@
|
|||
package template_helpers;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( to_template simple_page );
|
||||
|
||||
use string_helpers 'simple_escape';
|
||||
|
||||
my $template = Template->new(
|
||||
{
|
||||
INTERPOLATE => 1,
|
||||
POST_CHOMP => 1,
|
||||
EVAL_PERL => 1,
|
||||
TRIM => 1
|
||||
});
|
||||
|
||||
sub pretty_error($)
|
||||
{
|
||||
my $error = simple_escape(shift);
|
||||
<< "END_ERROR";
|
||||
<span class="e-error error-pad">
|
||||
$error
|
||||
</span>
|
||||
END_ERROR
|
||||
}
|
||||
|
||||
sub to_template
|
||||
{
|
||||
my ($vars, $data) = @_;
|
||||
my $result;
|
||||
|
||||
return 0 unless ref $data;
|
||||
return 0 unless ref $vars;
|
||||
|
||||
# TODO HTML error formatting
|
||||
$template->process($data, $vars, \$result) ||
|
||||
return pretty_error($template->error());
|
||||
|
||||
$result;
|
||||
}
|
||||
|
||||
# Generic simple page with only session data and pages.
|
||||
# Pretty commonly done, so useful function.
|
||||
sub simple_page
|
||||
{
|
||||
my ($ssn, $data, $page) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{$page});
|
||||
}
|
33
perl/timeline.pm
Normal file
33
perl/timeline.pm
Normal file
|
@ -0,0 +1,33 @@
|
|||
package timeline;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT = qw( content_timeline );
|
||||
|
||||
use template_helpers 'to_template';
|
||||
use icons 'get_icon';
|
||||
use postbox 'generate_postbox';
|
||||
use status 'generate_status';
|
||||
use navigation 'generate_navigation';
|
||||
|
||||
sub content_timeline
|
||||
{
|
||||
my ($ssn, $data, $statuses, $title, $show_post_box, $fake_timeline) = @_;
|
||||
|
||||
my %vars = (
|
||||
prefix => '',
|
||||
ssn => $ssn,
|
||||
data => $data,
|
||||
statuses => $statuses,
|
||||
title => $title,
|
||||
fake_timeline => $fake_timeline,
|
||||
show_post_box => $show_post_box,
|
||||
postbox => \&generate_postbox,
|
||||
create_status => sub { generate_status($ssn, $data, shift); },
|
||||
# Don't autovivify statuses
|
||||
nav => sub { generate_navigation($ssn, $data, $statuses->[0]->{id}, $statuses->[-1]->{id}) },
|
||||
);
|
||||
|
||||
to_template(\%vars, \$data->{'timeline.tt'});
|
||||
}
|
37
src/about.c
37
src/about.c
|
@ -20,40 +20,47 @@
|
|||
#include "base_page.h"
|
||||
#include "about.h"
|
||||
|
||||
#include "../static/about.ctmpl"
|
||||
#include "../static/license.ctmpl"
|
||||
|
||||
void content_about(PATH_ARGS)
|
||||
{
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
mXPUSHs(newRV_inc((SV*)session_hv));
|
||||
mXPUSHs(newRV_inc((SV*)template_files));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("meta::about");
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = (char*)data_about,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
|
||||
void content_about_license(PATH_ARGS)
|
||||
{
|
||||
char* page;
|
||||
char* referer = GET_ENV("HTTP_REFERER", req);
|
||||
struct license_template tdata = {
|
||||
.back_ref = referer,
|
||||
.license_str = "License"
|
||||
};
|
||||
page = tmpl_gen_license(&tdata, NULL);
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("meta::license");
|
||||
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = page,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
free(page);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
|
|
787
src/account.c
787
src/account.c
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "global_perl.h"
|
||||
#include "helpers.h"
|
||||
#include "base_page.h"
|
||||
#include "error.h"
|
||||
|
@ -30,19 +31,8 @@
|
|||
#include "base_page.h"
|
||||
#include "scrobble.h"
|
||||
#include "string_helpers.h"
|
||||
#include "navigation.h"
|
||||
#include "emoji.h"
|
||||
|
||||
// Files
|
||||
#include "../static/account.ctmpl"
|
||||
#include "../static/account_info.ctmpl"
|
||||
#include "../static/account_follow_btn.ctmpl"
|
||||
#include "../static/favourites_page.ctmpl"
|
||||
#include "../static/bookmarks_page.ctmpl"
|
||||
#include "../static/account_stub.ctmpl"
|
||||
#include "../static/account_sidebar.ctmpl"
|
||||
#include "../static/account_current_menubar.ctmpl"
|
||||
#include "../static/basic_page.ctmpl"
|
||||
#include "timeline.h"
|
||||
|
||||
#define FOLLOWS_YOU_HTML "<span class=\"acct-badge\">%s</span>"
|
||||
|
||||
|
@ -53,116 +43,58 @@ struct account_args
|
|||
uint8_t flags;
|
||||
};
|
||||
|
||||
char* load_account_info(struct mstdnt_account* acct,
|
||||
size_t* size)
|
||||
static char* accounts_page(HV* session_hv,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
char* header,
|
||||
struct mstdnt_storage* storage,
|
||||
struct mstdnt_account* accts,
|
||||
size_t accts_len)
|
||||
{
|
||||
char* info_html;
|
||||
char* note = emojify(acct->note,
|
||||
acct->emojis,
|
||||
acct->emojis_len);
|
||||
struct account_info_template data = {
|
||||
.acct_note = note
|
||||
};
|
||||
info_html = tmpl_gen_account_info(&data, size);
|
||||
if (note != acct->note)
|
||||
free(note);
|
||||
return info_html;
|
||||
}
|
||||
|
||||
char* construct_account_sidebar(struct mstdnt_account* acct, size_t* size)
|
||||
{
|
||||
char* result = NULL;
|
||||
char* sanitized_display_name = NULL;
|
||||
char* display_name = NULL;
|
||||
char* header_css = NULL;
|
||||
if (acct->display_name)
|
||||
{
|
||||
sanitized_display_name = sanitize_html(acct->display_name);
|
||||
display_name = emojify(sanitized_display_name,
|
||||
acct->emojis,
|
||||
acct->emojis_len);
|
||||
}
|
||||
easprintf(&header_css, "style=\"background: linear-gradient(var(--account-overlay-gradient-top), var(--account-overlay-gradient-bottom)), url(%s);\"", acct->header);
|
||||
struct account_sidebar_template data = {
|
||||
.prefix = config_url_prefix,
|
||||
.avatar = acct->avatar,
|
||||
.username = display_name,
|
||||
.header = acct->header ? header_css : "",
|
||||
.statuses_text = L10N[L10N_EN_US][L10N_TAB_STATUSES],
|
||||
.following_text = L10N[L10N_EN_US][L10N_TAB_FOLLOWING],
|
||||
.followers_text = L10N[L10N_EN_US][L10N_TAB_FOLLOWERS],
|
||||
.statuses_count = acct->statuses_count,
|
||||
.following_count = acct->following_count,
|
||||
.followers_count = acct->followers_count,
|
||||
.acct = acct->acct,
|
||||
};
|
||||
result = tmpl_gen_account_sidebar(&data, size);
|
||||
if (sanitized_display_name != acct->display_name) free(sanitized_display_name);
|
||||
if (display_name != sanitized_display_name &&
|
||||
display_name != acct->display_name)
|
||||
free(display_name);
|
||||
free(header_css);
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO put account stuff into one function to cleanup a bit
|
||||
static char* account_followers_cb(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
void* _args)
|
||||
{
|
||||
struct mstdnt_account_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.offset = 0,
|
||||
.limit = 20,
|
||||
.with_relationships = 0,
|
||||
};
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
char* accounts_html = NULL, *navigation_box = NULL;
|
||||
char* output;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_account* accounts = NULL;
|
||||
size_t accts_len = 0;
|
||||
char* start_id;
|
||||
|
||||
PERL_STACK_INIT;
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
if (acct)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_account(acct)));
|
||||
else ARG_UNDEFINED();
|
||||
if (rel)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_relationship(rel)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (mastodont_get_followers(api, &m_args, acct->id, &args, &storage, &accounts, &accts_len))
|
||||
{
|
||||
accounts_html = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
accounts_html = construct_accounts(api, accounts, accts_len, 0, NULL);
|
||||
if (!accounts_html)
|
||||
accounts_html = construct_error("No followers...", E_NOTICE, 1, NULL);
|
||||
}
|
||||
if (accts && accts_len)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_accounts(accts, accts_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (accounts)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : accounts[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
accounts[0].id,
|
||||
accounts[accts_len-1].id,
|
||||
NULL);
|
||||
}
|
||||
easprintf(&output, "%s%s",
|
||||
STR_NULL_EMPTY(accounts_html),
|
||||
STR_NULL_EMPTY(navigation_box));
|
||||
// perlapi doesn't specify if a string length of 0 calls strlen so calling just to be safe...
|
||||
if (header)
|
||||
mXPUSHp(header, strlen(header));
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_accounts(accounts, accts_len);
|
||||
if (accounts_html) free(accounts_html);
|
||||
if (navigation_box) free(navigation_box);
|
||||
PERL_STACK_SCALAR_CALL("account::content_accounts");
|
||||
|
||||
output = PERL_GET_STACK_EXIT;
|
||||
|
||||
mastodont_storage_cleanup(storage);
|
||||
mstdnt_cleanup_accounts(accts, accts_len);
|
||||
return output;
|
||||
}
|
||||
|
||||
static char* account_following_cb(struct session* ssn,
|
||||
static char* account_followers_cb(HV* session_hv,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
void* _args)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_account* accounts = NULL;
|
||||
size_t accts_len = 0;
|
||||
char* result;
|
||||
|
||||
struct mstdnt_account_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
|
@ -171,99 +103,94 @@ static char* account_following_cb(struct session* ssn,
|
|||
.limit = 20,
|
||||
.with_relationships = 0,
|
||||
};
|
||||
|
||||
mastodont_get_followers(api, &m_args, acct->id, &args, &storage, &accounts, &accts_len);
|
||||
|
||||
return accounts_page(session_hv, api, acct, rel, NULL, &storage, accounts, accts_len);
|
||||
}
|
||||
|
||||
static char* account_following_cb(HV* session_hv,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
void* _args)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
char* accounts_html = NULL, *navigation_box = NULL;
|
||||
char* output;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_account* accounts = NULL;
|
||||
size_t accts_len = 0;
|
||||
char* start_id;
|
||||
char* result;
|
||||
|
||||
if (mastodont_get_following(api, &m_args, acct->id, &args, &storage, &accounts, &accts_len))
|
||||
{
|
||||
accounts_html = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
accounts_html = construct_accounts(api, accounts, accts_len, 0, NULL);
|
||||
if (!accounts_html)
|
||||
accounts_html = construct_error("Not following anyone", E_NOTICE, 1, NULL);
|
||||
}
|
||||
struct mstdnt_account_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.offset = 0,
|
||||
.limit = 20,
|
||||
.with_relationships = 0,
|
||||
};
|
||||
|
||||
mastodont_get_following(api, &m_args, acct->id, &args, &storage, &accounts, &accts_len);
|
||||
|
||||
if (accounts)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : accounts[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
accounts[0].id,
|
||||
accounts[accts_len-1].id,
|
||||
NULL);
|
||||
}
|
||||
easprintf(&output, "%s%s",
|
||||
STR_NULL_EMPTY(accounts_html),
|
||||
STR_NULL_EMPTY(navigation_box));
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_accounts(accounts, accts_len);
|
||||
if (accounts_html) free(accounts_html);
|
||||
if (navigation_box) free(navigation_box);
|
||||
return output;
|
||||
return accounts_page(session_hv, api, acct, rel, NULL, &storage, accounts, accts_len);
|
||||
}
|
||||
|
||||
static char* account_statuses_cb(struct session* ssn,
|
||||
static char* account_statuses_cb(HV* session_hv,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
void* _args)
|
||||
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_account_statuses_args* args = _args;
|
||||
char* statuses_html = NULL, *navigation_box = NULL;
|
||||
char* output;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_status* statuses = NULL;
|
||||
size_t statuses_len = 0;
|
||||
char* start_id;
|
||||
char* result;
|
||||
|
||||
if (mastodont_get_account_statuses(api, &m_args, acct->id, args, &storage, &statuses, &statuses_len))
|
||||
{
|
||||
statuses_html = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
statuses_html = construct_statuses(ssn, api, statuses, statuses_len, NULL, NULL);
|
||||
if (!statuses_html)
|
||||
statuses_html = construct_error("No statuses", E_NOTICE, 1, NULL);
|
||||
}
|
||||
mastodont_get_account_statuses(api, &m_args, acct->id, args, &storage, &statuses, &statuses_len);
|
||||
|
||||
if (statuses)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : statuses[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
statuses[0].id,
|
||||
statuses[statuses_len-1].id,
|
||||
NULL);
|
||||
}
|
||||
easprintf(&output, "%s%s",
|
||||
STR_NULL_EMPTY(statuses_html),
|
||||
STR_NULL_EMPTY(navigation_box));
|
||||
PERL_STACK_INIT;
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_account(acct)));
|
||||
if (rel)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_relationship(rel)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (statuses && statuses_len)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_statuses(statuses, statuses_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
PERL_STACK_SCALAR_CALL("account::content_statuses");
|
||||
|
||||
result = PERL_GET_STACK_EXIT;
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_statuses(statuses, statuses_len);
|
||||
if (statuses_html) free(statuses_html);
|
||||
if (navigation_box) free(navigation_box);
|
||||
return output;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static char* account_scrobbles_cb(struct session* ssn, mastodont_t* api, struct mstdnt_account* acct, void* _args)
|
||||
static char* account_scrobbles_cb(HV* session_hv,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* rel,
|
||||
void* _args)
|
||||
{
|
||||
(void)_args;
|
||||
char* scrobbles_html = NULL;
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_scrobble* scrobbles = NULL;
|
||||
size_t scrobbles_len = 0;
|
||||
char* result;
|
||||
|
||||
struct mstdnt_get_scrobbles_args args = {
|
||||
.max_id = NULL,
|
||||
.min_id = NULL,
|
||||
|
@ -271,22 +198,26 @@ static char* account_scrobbles_cb(struct session* ssn, mastodont_t* api, struct
|
|||
.offset = 0,
|
||||
.limit = 20
|
||||
};
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
mastodont_get_scrobbles(api, &m_args, acct->id, &args, &storage, &scrobbles, &scrobbles_len);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_account(acct)));
|
||||
if (rel)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_relationship(rel)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (mastodont_get_scrobbles(api, &m_args, acct->id, &args, &storage, &scrobbles, &scrobbles_len))
|
||||
{
|
||||
scrobbles_html = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
scrobbles_html = construct_scrobbles(scrobbles, scrobbles_len, NULL);
|
||||
if (!scrobbles_html)
|
||||
scrobbles_html = construct_error("No scrobbles", E_NOTICE, 1, NULL);
|
||||
}
|
||||
if (scrobbles && scrobbles_len)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_scrobbles(scrobbles, scrobbles_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
PERL_STACK_SCALAR_CALL("account::content_scrobbles");
|
||||
|
||||
result = PERL_GET_STACK_EXIT;
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
free(scrobbles);
|
||||
return scrobbles_html;
|
||||
return result;
|
||||
}
|
||||
|
||||
void get_account_info(mastodont_t* api, struct session* ssn)
|
||||
|
@ -299,16 +230,27 @@ void get_account_info(mastodont_t* api, struct session* ssn)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the account information, and then calls a callback on the information received which
|
||||
* passes the account information
|
||||
*
|
||||
* @param req The request context
|
||||
* @param ssn The session, which will get transcribed into Perl
|
||||
* @param api Initiated mstdnt API
|
||||
* @param id User's ID to fetch
|
||||
* @param args The arguments to pass into the callback
|
||||
* @param tab Current tab to focus
|
||||
* @param callback Calls back with a perlified session, session and api as you passed in, the account,
|
||||
* the relationship, and additional arguments passed
|
||||
*/
|
||||
static void fetch_account_page(FCGX_Request* req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
char* id,
|
||||
void* args,
|
||||
enum account_tab tab,
|
||||
char* (*callback)(struct session* ssn, mastodont_t* api, struct mstdnt_account* acct, void* args))
|
||||
char* (*callback)(HV* ssn_hv, struct session* ssn, mastodont_t* api, struct mstdnt_account* acct, struct mstdnt_relationship* rel, void* args))
|
||||
{
|
||||
char* account_page;
|
||||
char* data;
|
||||
struct mstdnt_storage storage = { 0 },
|
||||
relations_storage = { 0 };
|
||||
struct mstdnt_account acct = { 0 };
|
||||
|
@ -319,32 +261,18 @@ static void fetch_account_page(FCGX_Request* req,
|
|||
|
||||
int lookup_type = config_experimental_lookup ? MSTDNT_LOOKUP_ACCT : MSTDNT_LOOKUP_ID;
|
||||
|
||||
if (mastodont_get_account(api, &m_args, lookup_type, id, &acct, &storage))
|
||||
{
|
||||
account_page = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
// Relationships may fail
|
||||
mastodont_get_relationships(api, &m_args, &(acct.id), 1, &relations_storage, &relationships, &relationships_len);
|
||||
|
||||
data = callback(ssn, api,
|
||||
&acct, args);
|
||||
account_page = load_account_page(ssn,
|
||||
api,
|
||||
&acct,
|
||||
relationships,
|
||||
tab,
|
||||
data,
|
||||
NULL);
|
||||
if (!account_page)
|
||||
account_page = construct_error("Couldn't load page", E_ERROR, 1, NULL);
|
||||
|
||||
free(data);
|
||||
}
|
||||
mastodont_get_account(api, &m_args, lookup_type, id, &acct, &storage);
|
||||
// Relationships may fail
|
||||
mastodont_get_relationships(api, &m_args, &(acct.id), 1, &relations_storage, &relationships, &relationships_len);
|
||||
|
||||
struct base_page b = {
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
|
||||
char* data = callback(session_hv, ssn, api, &acct, relationships, args);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = account_page,
|
||||
.content = data,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
|
@ -355,216 +283,7 @@ static void fetch_account_page(FCGX_Request* req,
|
|||
mstdnt_cleanup_relationships(relationships);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mastodont_storage_cleanup(&relations_storage);
|
||||
free(account_page);
|
||||
}
|
||||
|
||||
size_t construct_account_page(struct session* ssn,
|
||||
char** result,
|
||||
struct account_page* page,
|
||||
char* content)
|
||||
{
|
||||
if (!page->account)
|
||||
{
|
||||
*result = NULL;
|
||||
return 0;
|
||||
}
|
||||
size_t size;
|
||||
struct mstdnt_relationship* rel = page->relationship;
|
||||
int is_same_user = ssn->logged_in && strcmp(ssn->acct.acct, page->acct) == 0;
|
||||
char* follow_btn = NULL,
|
||||
* follow_btn_text = NULL,
|
||||
* follows_you = NULL,
|
||||
* info_html = NULL,
|
||||
* is_blocked = NULL,
|
||||
* menubar = NULL,
|
||||
* display_name = NULL,
|
||||
* sanitized_display_name = NULL;
|
||||
|
||||
sanitized_display_name = sanitize_html(page->display_name);
|
||||
display_name = emojify(sanitized_display_name,
|
||||
page->account->emojis,
|
||||
page->account->emojis_len);
|
||||
|
||||
// Check if note is not empty
|
||||
if (page->note && strcmp(page->note, "") != 0)
|
||||
{
|
||||
info_html = load_account_info(page->account, NULL);
|
||||
}
|
||||
|
||||
// Display follow button only if not the same user
|
||||
if (rel && !is_same_user)
|
||||
{
|
||||
if (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWED_BY))
|
||||
easprintf(&follows_you, FOLLOWS_YOU_HTML, L10N[page->locale][L10N_FOLLOWS_YOU]);
|
||||
|
||||
if (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_BLOCKED_BY))
|
||||
is_blocked = construct_error(L10N[page->locale][L10N_BLOCKED], E_NOTICE, 0, NULL);
|
||||
|
||||
if (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_REQUESTED))
|
||||
follow_btn_text = L10N[page->locale][L10N_FOLLOW_PENDING];
|
||||
else if (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWING))
|
||||
follow_btn_text = L10N[page->locale][L10N_FOLLOWING];
|
||||
else
|
||||
follow_btn_text = L10N[page->locale][L10N_FOLLOW];
|
||||
|
||||
struct account_follow_btn_template data = {
|
||||
.prefix = config_url_prefix,
|
||||
.active = (rel && MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWING)
|
||||
? "active" : ""),
|
||||
.follow_text = follow_btn_text,
|
||||
.unfollow = (rel && (MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_FOLLOWING) ||
|
||||
MSTDNT_FLAG_ISSET(rel->flags, MSTDNT_RELATIONSHIP_REQUESTED))
|
||||
? "un" : ""),
|
||||
.userid = page->id,
|
||||
};
|
||||
|
||||
follow_btn = tmpl_gen_account_follow_btn(&data, NULL);
|
||||
}
|
||||
|
||||
// Display menubar with extra options for access if same user
|
||||
if (is_same_user)
|
||||
{
|
||||
struct account_current_menubar_template acmdata = {
|
||||
.prefix = config_url_prefix,
|
||||
.blocked_str = "Blocks",
|
||||
.muted_str = "Mutes",
|
||||
.favourited_str = "Favorites",
|
||||
};
|
||||
|
||||
menubar = tmpl_gen_account_current_menubar(&acmdata, NULL);
|
||||
}
|
||||
|
||||
struct account_template acct_data = {
|
||||
.is_blocked = STR_NULL_EMPTY(is_blocked),
|
||||
.header = page->header_image,
|
||||
.menubar = menubar,
|
||||
.display_name = display_name,
|
||||
.acct = page->acct,
|
||||
.prefix = config_url_prefix,
|
||||
.userid = page->id,
|
||||
.follows_you = follows_you,
|
||||
.unsubscribe = (rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_NOTIFYING)
|
||||
? "un" : ""),
|
||||
.subscribe_text = (rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_NOTIFYING)
|
||||
? L10N[page->locale][L10N_UNSUBSCRIBE] : L10N[page->locale][L10N_SUBSCRIBE]),
|
||||
.unblock = (rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_BLOCKING)
|
||||
? "un" : ""),
|
||||
.block_text = (rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_BLOCKING)
|
||||
? L10N[page->locale][L10N_UNBLOCK] : L10N[page->locale][L10N_BLOCK]),
|
||||
.unmute = (rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_MUTING)
|
||||
? "un" : ""),
|
||||
.mute_text = (rel && MSTDNT_FLAG_ISSET(rel->flags,
|
||||
MSTDNT_RELATIONSHIP_MUTING)
|
||||
? L10N[page->locale][L10N_UNMUTE] : L10N[page->locale][L10N_MUTE]),
|
||||
.tab_statuses_text = L10N[page->locale][L10N_TAB_STATUSES],
|
||||
.statuses_count = page->statuses_count,
|
||||
.tab_following_text = L10N[page->locale][L10N_TAB_FOLLOWING],
|
||||
.following_count = page->following_count,
|
||||
.tab_followers_text = L10N[page->locale][L10N_TAB_FOLLOWERS],
|
||||
.followers_count = page->followers_count,
|
||||
.follow_btn = follow_btn,
|
||||
.avatar = page->profile_image,
|
||||
.info = info_html,
|
||||
.tab_statuses_focused = MAKE_FOCUSED_IF(page->tab, ACCT_TAB_STATUSES),
|
||||
.tab_statuses_text = L10N[page->locale][L10N_TAB_STATUSES],
|
||||
.tab_scrobbles_focused = MAKE_FOCUSED_IF(page->tab, ACCT_TAB_SCROBBLES),
|
||||
.tab_scrobbles_text = L10N[page->locale][L10N_TAB_SCROBBLES],
|
||||
.tab_media_focused = MAKE_FOCUSED_IF(page->tab, ACCT_TAB_MEDIA),
|
||||
.tab_media_text = L10N[page->locale][L10N_TAB_MEDIA],
|
||||
.tab_pinned_focused = MAKE_FOCUSED_IF(page->tab, ACCT_TAB_PINNED),
|
||||
.tab_pinned_text = L10N[page->locale][L10N_TAB_PINNED],
|
||||
.acct_content = content
|
||||
};
|
||||
|
||||
*result = tmpl_gen_account(&acct_data, &size);
|
||||
|
||||
free(info_html);
|
||||
free(follows_you);
|
||||
free(follow_btn);
|
||||
free(is_blocked);
|
||||
free(menubar);
|
||||
if (sanitized_display_name != page->display_name) free(sanitized_display_name);
|
||||
if (display_name != page->display_name &&
|
||||
display_name != sanitized_display_name)
|
||||
free(display_name);
|
||||
return size;
|
||||
}
|
||||
|
||||
char* construct_account(mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
uint8_t flags,
|
||||
size_t* size)
|
||||
{
|
||||
char* result;
|
||||
char* sanitized_display_name = sanitize_html(acct->display_name);
|
||||
struct account_stub_template data = {
|
||||
.prefix = config_url_prefix,
|
||||
.acct = acct->acct,
|
||||
.avatar = acct->avatar,
|
||||
.display_name = sanitized_display_name,
|
||||
};
|
||||
|
||||
result = tmpl_gen_account_stub(&data, size);
|
||||
if (sanitized_display_name != acct->display_name) free(sanitized_display_name);
|
||||
return result;
|
||||
}
|
||||
|
||||
static char* construct_account_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
struct account_args* args = passed;
|
||||
return construct_account(args->api, args->accts + index, args->flags, res);
|
||||
}
|
||||
|
||||
char* construct_accounts(mastodont_t* api,
|
||||
struct mstdnt_account* accounts,
|
||||
size_t size,
|
||||
uint8_t flags,
|
||||
size_t* ret_size)
|
||||
{
|
||||
if (!(accounts && size)) return NULL;
|
||||
struct account_args acct_args = {
|
||||
.api = api,
|
||||
.accts = accounts,
|
||||
.flags = flags,
|
||||
};
|
||||
return construct_func_strings(construct_account_voidwrap, &acct_args, size, ret_size);
|
||||
}
|
||||
|
||||
char* load_account_page(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* relationship,
|
||||
enum account_tab tab,
|
||||
char* content,
|
||||
size_t* res_size)
|
||||
{
|
||||
size_t size;
|
||||
char* result;
|
||||
struct account_page page = {
|
||||
.locale = l10n_normalize(ssn->config.lang),
|
||||
.account = acct,
|
||||
.header_image = acct->header,
|
||||
.profile_image = acct->avatar,
|
||||
.acct = acct->acct,
|
||||
.display_name = acct->display_name,
|
||||
.statuses_count = acct->statuses_count,
|
||||
.following_count = acct->following_count,
|
||||
.followers_count = acct->followers_count,
|
||||
.note = acct->note,
|
||||
.id = acct->id,
|
||||
.tab = tab,
|
||||
.relationship = relationship,
|
||||
};
|
||||
|
||||
size = construct_account_page(ssn, &result, &page, content);
|
||||
|
||||
if (res_size) *res_size = size;
|
||||
return result;
|
||||
Safefree(data);
|
||||
}
|
||||
|
||||
void content_account_statuses(PATH_ARGS)
|
||||
|
@ -671,14 +390,9 @@ void content_account_action(PATH_ARGS)
|
|||
|
||||
void content_account_bookmarks(PATH_ARGS)
|
||||
{
|
||||
size_t status_count = 0, statuses_html_count = 0;
|
||||
size_t statuses_len = 0;
|
||||
struct mstdnt_status* statuses = NULL;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
char* status_format = NULL,
|
||||
*navigation_box = NULL,
|
||||
*output = NULL;
|
||||
char* start_id;
|
||||
|
||||
struct mstdnt_bookmarks_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.since_id = NULL,
|
||||
|
@ -688,86 +402,11 @@ void content_account_bookmarks(PATH_ARGS)
|
|||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
||||
if (mastodont_get_bookmarks(api, &m_args, &args, &storage, &statuses, &status_count))
|
||||
{
|
||||
status_format = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
// Construct statuses into HTML
|
||||
status_format = construct_statuses(ssn, api, statuses, status_count, NULL, &statuses_html_count);
|
||||
if (!status_format)
|
||||
status_format = construct_error("Couldn't load posts", E_ERROR, 1, NULL);
|
||||
}
|
||||
mastodont_get_bookmarks(api, &m_args, &args, &storage, &statuses, &statuses_len);
|
||||
|
||||
// Create post box
|
||||
if (statuses)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : statuses[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
statuses[0].id,
|
||||
statuses[status_count-1].id,
|
||||
NULL);
|
||||
}
|
||||
|
||||
struct bookmarks_page_template tdata = {
|
||||
.statuses = status_format,
|
||||
.navigation = navigation_box
|
||||
};
|
||||
output = tmpl_gen_bookmarks_page(&tdata, NULL);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_BOOKMARKS,
|
||||
.content = output,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
// Cleanup
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_statuses(statuses, status_count);
|
||||
free(status_format);
|
||||
free(navigation_box);
|
||||
free(output);
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_BOOKMARKS, "Bookmarks", 0, 1);
|
||||
}
|
||||
|
||||
static void accounts_page(FCGX_Request* req,
|
||||
mastodont_t* api,
|
||||
struct session* ssn,
|
||||
struct mstdnt_storage* storage,
|
||||
char* header,
|
||||
struct mstdnt_account* accts,
|
||||
size_t accts_len)
|
||||
{
|
||||
char* output;
|
||||
char* content = construct_accounts(api, accts, accts_len, 0, NULL);
|
||||
if (!content)
|
||||
content = construct_error("No accounts here!", E_NOTICE, 1, NULL);
|
||||
|
||||
struct basic_page_template tdata = {
|
||||
.back_ref = getenv("HTTP_REFERER"),
|
||||
.page_title = header,
|
||||
.page_content = content,
|
||||
};
|
||||
output = tmpl_gen_basic_page(&tdata, NULL);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = output,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
mastodont_storage_cleanup(storage);
|
||||
free(output);
|
||||
free(content);
|
||||
}
|
||||
|
||||
|
||||
void content_account_blocked(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_account_args args = {
|
||||
|
@ -786,8 +425,18 @@ void content_account_blocked(PATH_ARGS)
|
|||
|
||||
mastodont_get_blocks(api, &m_args, &args, &storage, &accts, &accts_len);
|
||||
|
||||
accounts_page(req, api, ssn, &storage, "Blocked users", accts, accts_len);
|
||||
mstdnt_cleanup_accounts(accts, accts_len);
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
char* result = accounts_page(session_hv, api, NULL, NULL, "Blocked users", &storage, accts, accts_len);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = result,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, req, ssn, api);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void content_account_muted(PATH_ARGS)
|
||||
|
@ -808,70 +457,86 @@ void content_account_muted(PATH_ARGS)
|
|||
|
||||
mastodont_get_mutes(api, &m_args, &args, &storage, &accts, &accts_len);
|
||||
|
||||
accounts_page(req, api, ssn, &storage, "Muted users", accts, accts_len);
|
||||
mstdnt_cleanup_accounts(accts, accts_len);
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
char* result = accounts_page(session_hv, api, NULL, NULL, "Muted users", &storage, accts, accts_len);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = result,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, req, ssn, api);
|
||||
free(result);
|
||||
}
|
||||
|
||||
void content_account_favourites(PATH_ARGS)
|
||||
{
|
||||
size_t status_count = 0, statuses_html_count = 0;
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
size_t statuses_len = 0;
|
||||
struct mstdnt_status* statuses = NULL;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
char* status_format = NULL,
|
||||
*navigation_box = NULL,
|
||||
*output = NULL,
|
||||
*page = NULL;
|
||||
char* start_id;
|
||||
|
||||
struct mstdnt_favourites_args args = {
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.limit = 20,
|
||||
};
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
||||
if (mastodont_get_favourites(api, &m_args, &args, &storage, &statuses, &status_count))
|
||||
{
|
||||
status_format = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
// Construct statuses into HTML
|
||||
status_format = construct_statuses(ssn, api, statuses, status_count, NULL, &statuses_html_count);
|
||||
if (!status_format)
|
||||
status_format = construct_error("Couldn't load posts", E_ERROR, 1, NULL);
|
||||
}
|
||||
|
||||
// Create post box
|
||||
if (statuses)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : statuses[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
statuses[0].id,
|
||||
statuses[status_count-1].id,
|
||||
NULL);
|
||||
}
|
||||
|
||||
struct favourites_page_template tdata = {
|
||||
.statuses = status_format,
|
||||
.navigation = navigation_box
|
||||
};
|
||||
output = tmpl_gen_favourites_page(&tdata, NULL);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_FAVOURITES,
|
||||
.content = output,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
// Cleanup
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mstdnt_cleanup_statuses(statuses, status_count);
|
||||
if (status_format) free(status_format);
|
||||
if (navigation_box) free(navigation_box);
|
||||
if (output) free(output);
|
||||
mastodont_get_favourites(api, &m_args, &args, &storage, &statuses, &statuses_len);
|
||||
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_BOOKMARKS, "Favorites", 0, 1);
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(account, accounts, mstdnt_account)
|
||||
|
||||
HV* perlify_account(const struct mstdnt_account* acct)
|
||||
{
|
||||
if (!acct) return NULL;
|
||||
HV* acct_hv = newHV();
|
||||
|
||||
hvstores_str(acct_hv, "id", acct->id);
|
||||
hvstores_str(acct_hv, "username", acct->username);
|
||||
hvstores_str(acct_hv, "acct", acct->acct);
|
||||
hvstores_str(acct_hv, "display_name", acct->display_name);
|
||||
hvstores_str(acct_hv, "note", acct->note);
|
||||
hvstores_str(acct_hv, "avatar", acct->avatar);
|
||||
hvstores_str(acct_hv, "avatar_static", acct->avatar_static);
|
||||
hvstores_str(acct_hv, "header", acct->header);
|
||||
hvstores_str(acct_hv, "header_static", acct->header_static);
|
||||
hvstores_int(acct_hv, "created_at", acct->created_at);
|
||||
hvstores_str(acct_hv, "last_status_at", acct->last_status_at);
|
||||
hvstores_str(acct_hv, "mute_expires_at", acct->mute_expires_at);
|
||||
hvstores_int(acct_hv, "statuses_count", acct->statuses_count);
|
||||
hvstores_int(acct_hv, "followers_count", acct->followers_count);
|
||||
hvstores_int(acct_hv, "following_count", acct->following_count);
|
||||
hvstores_int(acct_hv, "bot", acct->bot);
|
||||
hvstores_int(acct_hv, "suspended", acct->suspended);
|
||||
hvstores_int(acct_hv, "locked", acct->locked);
|
||||
hvstores_int(acct_hv, "discoverable", acct->discoverable);
|
||||
hvstores_ref(acct_hv, "emojis", perlify_emojis(acct->emojis, acct->emojis_len));
|
||||
|
||||
return acct_hv;
|
||||
}
|
||||
|
||||
HV* perlify_relationship(const struct mstdnt_relationship* rel)
|
||||
{
|
||||
if (!rel) return NULL;
|
||||
HV* rel_hv = newHV();
|
||||
|
||||
hvstores_str(rel_hv, "id", rel->id);
|
||||
hvstores_int(rel_hv, "following", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_FOLLOWING));
|
||||
hvstores_int(rel_hv, "requested", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_REQUESTED));
|
||||
hvstores_int(rel_hv, "endoresed", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_ENDORSED));
|
||||
hvstores_int(rel_hv, "followed_by", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_FOLLOWED_BY));
|
||||
hvstores_int(rel_hv, "muting", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_MUTING));
|
||||
hvstores_int(rel_hv, "muting_notifs", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_MUTING_NOTIFS));
|
||||
hvstores_int(rel_hv, "showing_reblogs", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_SHOWING_REBLOGS));
|
||||
hvstores_int(rel_hv, "notifying", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_NOTIFYING));
|
||||
hvstores_int(rel_hv, "blocking", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_BLOCKING));
|
||||
hvstores_int(rel_hv, "domain_blocking", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_DOMAIN_BLOCKING));
|
||||
hvstores_int(rel_hv, "blocked_by", MSTDNT_T_FLAG_ISSET(rel, MSTDNT_RELATIONSHIP_BLOCKED_BY));
|
||||
|
||||
return rel_hv;
|
||||
}
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
|
||||
#ifndef ACCOUNT_H
|
||||
#define ACCOUNT_H
|
||||
#include "global_perl.h"
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
#include "path.h"
|
||||
#include "l10n.h"
|
||||
|
||||
#define ACCOUNT_NOP 0
|
||||
|
@ -55,33 +57,6 @@ struct account_page
|
|||
};
|
||||
|
||||
void get_account_info(mastodont_t* api, struct session* ssn);
|
||||
char* construct_account_sidebar(struct mstdnt_account* acct, size_t* size);
|
||||
|
||||
char* construct_account(mastodont_t* api,
|
||||
struct mstdnt_account* account,
|
||||
uint8_t flags,
|
||||
size_t* size);
|
||||
char* construct_accounts(mastodont_t* api,
|
||||
struct mstdnt_account* accounts,
|
||||
size_t size,
|
||||
uint8_t flags,
|
||||
size_t* ret_size);
|
||||
|
||||
size_t construct_account_page(struct session *ssn,
|
||||
char** result,
|
||||
struct account_page* page,
|
||||
char* content);
|
||||
|
||||
char* load_account_page(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_account* acct,
|
||||
struct mstdnt_relationship* relationship,
|
||||
enum account_tab tab,
|
||||
char* content,
|
||||
size_t* res_size);
|
||||
|
||||
char* load_account_info(struct mstdnt_account* acct,
|
||||
size_t* size);
|
||||
|
||||
void content_account_followers(PATH_ARGS);
|
||||
void content_account_following(PATH_ARGS);
|
||||
|
@ -95,4 +70,8 @@ void content_account_action(PATH_ARGS);
|
|||
void content_account_favourites(PATH_ARGS);
|
||||
void content_account_bookmarks(PATH_ARGS);
|
||||
|
||||
HV* perlify_account(const struct mstdnt_account* acct);
|
||||
AV* perlify_accounts(const struct mstdnt_account* accounts, size_t len);
|
||||
HV* perlify_relationship(const struct mstdnt_relationship* rel);
|
||||
|
||||
#endif // ACCOUNT_H
|
||||
|
|
|
@ -16,19 +16,21 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REPLY_H
|
||||
#define REPLY_H
|
||||
#include "session.h"
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include "applications.h"
|
||||
|
||||
char* construct_post_box(struct mstdnt_status* reply_id,
|
||||
char* default_content,
|
||||
size_t* size);
|
||||
HV* perlify_application(const struct mstdnt_app* app)
|
||||
{
|
||||
if (!app) return NULL;
|
||||
|
||||
HV* app_hv = newHV();
|
||||
hvstores_str(app_hv, "id", app->id);
|
||||
hvstores_str(app_hv, "name", app->name);
|
||||
hvstores_str(app_hv, "website", app->website);
|
||||
hvstores_str(app_hv, "redirect_uri", app->redirect_uri);
|
||||
hvstores_str(app_hv, "client_id", app->client_id);
|
||||
hvstores_str(app_hv, "client_secret", app->client_secret);
|
||||
hvstores_str(app_hv, "vapid_key", app->vapid_key);
|
||||
|
||||
char* reply_status(struct session* ssn,
|
||||
char* id,
|
||||
struct mstdnt_status* status);
|
||||
return app_hv;
|
||||
}
|
||||
|
||||
|
||||
#endif // REPLY_H
|
|
@ -16,13 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TEST_H
|
||||
#define TEST_H
|
||||
#include <stddef.h>
|
||||
#ifndef APPLICATIONS_H
|
||||
#define APPLICATIONS_H
|
||||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
#include "path.h"
|
||||
#include "global_perl.h"
|
||||
|
||||
void content_test(PATH_ARGS);
|
||||
HV* perlify_application(const struct mstdnt_app* app);
|
||||
|
||||
#endif /* TEST_H */
|
||||
#endif /* APPLICATIONS_H */
|
|
@ -24,13 +24,6 @@
|
|||
#include "attachments.h"
|
||||
#include "string_helpers.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/attachments.ctmpl"
|
||||
#include "../static/attachment_image.ctmpl"
|
||||
#include "../static/attachment_gifv.ctmpl"
|
||||
#include "../static/attachment_video.ctmpl"
|
||||
#include "../static/attachment_link.ctmpl"
|
||||
#include "../static/attachment_audio.ctmpl"
|
||||
|
||||
struct attachments_args
|
||||
{
|
||||
|
@ -123,79 +116,22 @@ void cleanup_media_ids(struct session* ssn, char** media_ids)
|
|||
free(media_ids);
|
||||
}
|
||||
|
||||
char* construct_attachment(struct session* ssn,
|
||||
mstdnt_bool sensitive,
|
||||
struct mstdnt_attachment* att,
|
||||
size_t* str_size)
|
||||
HV* perlify_attachment(const struct mstdnt_attachment* const attachment)
|
||||
{
|
||||
// Due to how similar the attachment templates are, we're just going to use their data files
|
||||
// and not generate any templates, saves some LOC!
|
||||
char* att_html;
|
||||
size_t s;
|
||||
const char* attachment_str;
|
||||
if (!att) return NULL;
|
||||
|
||||
if (ssn->config.stat_attachments)
|
||||
switch (att->type)
|
||||
{
|
||||
case MSTDNT_ATTACHMENT_IMAGE:
|
||||
attachment_str = data_attachment_image; break;
|
||||
case MSTDNT_ATTACHMENT_GIFV:
|
||||
attachment_str = data_attachment_gifv; break;
|
||||
case MSTDNT_ATTACHMENT_VIDEO:
|
||||
attachment_str = data_attachment_video; break;
|
||||
case MSTDNT_ATTACHMENT_AUDIO:
|
||||
attachment_str = data_attachment_audio; break;
|
||||
case MSTDNT_ATTACHMENT_UNKNOWN: // Fall through
|
||||
default:
|
||||
attachment_str = data_attachment_link; break;
|
||||
}
|
||||
else
|
||||
attachment_str = data_attachment_link;
|
||||
|
||||
// Images/visible content displays sensitive placeholder after
|
||||
if ((att->type == MSTDNT_ATTACHMENT_IMAGE ||
|
||||
att->type == MSTDNT_ATTACHMENT_GIFV ||
|
||||
att->type == MSTDNT_ATTACHMENT_VIDEO) &&
|
||||
ssn->config.stat_attachments)
|
||||
{
|
||||
s = easprintf(&att_html, attachment_str,
|
||||
att->url,
|
||||
sensitive ? "<div class=\"sensitive-contain sensitive\"></div>" : "");
|
||||
}
|
||||
else {
|
||||
s = easprintf(&att_html, attachment_str,
|
||||
sensitive ? "sensitive" : "",
|
||||
att->url);
|
||||
}
|
||||
|
||||
if (str_size) *str_size = s;
|
||||
return att_html;
|
||||
if (!attachment) return NULL;
|
||||
HV* attach_hv = newHV();
|
||||
hvstores_str(attach_hv, "id", attachment->id);
|
||||
hvstores_int(attach_hv, "type", attachment->type);
|
||||
hvstores_str(attach_hv, "url", attachment->url);
|
||||
hvstores_str(attach_hv, "preview_url", attachment->preview_url);
|
||||
hvstores_str(attach_hv, "remote_url", attachment->remote_url);
|
||||
hvstores_str(attach_hv, "hash", attachment->hash);
|
||||
hvstores_str(attach_hv, "description", attachment->description);
|
||||
hvstores_str(attach_hv, "blurhash", attachment->blurhash);
|
||||
return attach_hv;
|
||||
}
|
||||
|
||||
static char* construct_attachments_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
struct attachments_args* args = passed;
|
||||
return construct_attachment(args->ssn, args->sensitive, args->atts + index, res);
|
||||
}
|
||||
|
||||
char* construct_attachments(struct session* ssn,
|
||||
mstdnt_bool sensitive,
|
||||
struct mstdnt_attachment* atts,
|
||||
size_t atts_len,
|
||||
size_t* str_size)
|
||||
{
|
||||
size_t elements_size;
|
||||
struct attachments_args args = { ssn, atts, sensitive };
|
||||
char* elements = construct_func_strings(construct_attachments_voidwrap, &args, atts_len, &elements_size);
|
||||
char* att_view;
|
||||
|
||||
size_t s = easprintf(&att_view, data_attachments, elements);
|
||||
if (str_size) *str_size = s;
|
||||
// Cleanup
|
||||
free(elements);
|
||||
return att_view;
|
||||
}
|
||||
PERLIFY_MULTI(attachment, attachments, mstdnt_attachment)
|
||||
|
||||
void api_attachment_create(PATH_ARGS)
|
||||
{
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <mastodont.h>
|
||||
#include "path.h"
|
||||
#include "session.h"
|
||||
#include "global_perl.h"
|
||||
|
||||
#define FILES_READY(ssn) (ssn->post.files.type.f.array_size && \
|
||||
ssn->post.files.type.f.content && \
|
||||
|
@ -33,8 +34,10 @@ int try_upload_media(struct mstdnt_storage** storage,
|
|||
char*** media_ids);
|
||||
void cleanup_media_storages(struct session* ssn, struct mstdnt_storage* storage);
|
||||
void cleanup_media_ids(struct session* ssn, char** media_ids);
|
||||
char* construct_attachment(struct session* ssn, mstdnt_bool sensitive, struct mstdnt_attachment* att, size_t* str_size);
|
||||
char* construct_attachments(struct session* ssn, mstdnt_bool sensitive, struct mstdnt_attachment* atts, size_t atts_len, size_t* str_size);
|
||||
void api_attachment_create(PATH_ARGS);
|
||||
|
||||
// Perl
|
||||
HV* perlify_attachment(const struct mstdnt_attachment* const attachment);
|
||||
AV* perlify_attachments(const struct mstdnt_attachment* const attachments, size_t len);
|
||||
|
||||
#endif // ATTACHMENTS_H
|
||||
|
|
191
src/base_page.c
191
src/base_page.c
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fcgi_stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "helpers.h"
|
||||
|
@ -28,179 +27,69 @@
|
|||
#include "../config.h"
|
||||
#include "local_config_set.h"
|
||||
#include "account.h"
|
||||
#include "cgi.h"
|
||||
#include "global_cache.h"
|
||||
|
||||
// Files
|
||||
#include "../static/index.ctmpl"
|
||||
#include "../static/quick_login.ctmpl"
|
||||
|
||||
#define BODY_STYLE "style=\"background:url('%s');\""
|
||||
|
||||
void render_base_page(struct base_page* page, FCGX_Request* req, struct session* ssn, mastodont_t* api)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
char* cookie = GET_ENV("HTTP_COOKIE", req);
|
||||
enum l10n_locale locale = l10n_normalize(ssn->config.lang);
|
||||
char* theme_str = NULL;
|
||||
const char* login_string = "<a href=\"login\" id=\"login-header\">Login / Register</a>";
|
||||
const char* sidebar_embed = "<iframe class=\"sidebar-frame\" loading=\"lazy\" src=\"/notifications_compact\"></iframe>";
|
||||
char* background_url_css = NULL;
|
||||
// Sidebar
|
||||
char* sidebar_str,
|
||||
* main_sidebar_str = NULL,
|
||||
* account_sidebar_str = NULL,
|
||||
* instance_str = NULL;
|
||||
// Mastodont, used for notifications sidebar
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_notification* notifs = NULL;
|
||||
size_t notifs_len = 0;
|
||||
#define SIDEBAR_CSS_LEN 128
|
||||
char sidebar_css[SIDEBAR_CSS_LEN];
|
||||
|
||||
if (keyint(ssn->cookies.logged_in))
|
||||
login_string = "";
|
||||
|
||||
if (ssn->config.background_url)
|
||||
// Fetch notification (if not iFrame)
|
||||
if (keystr(ssn->cookies.logged_in) && keystr(ssn->cookies.access_token) &&
|
||||
!ssn->config.notif_embed)
|
||||
{
|
||||
easprintf(&background_url_css, BODY_STYLE, ssn->config.background_url);
|
||||
}
|
||||
|
||||
// If user is logged in
|
||||
if (keystr(ssn->cookies.logged_in) && keystr(ssn->cookies.access_token))
|
||||
{
|
||||
account_sidebar_str = construct_account_sidebar(&(ssn->acct), NULL);
|
||||
|
||||
// Get / Show notifications on sidebar
|
||||
if (ssn->config.notif_embed)
|
||||
{
|
||||
main_sidebar_str = (char*)sidebar_embed;
|
||||
}
|
||||
else {
|
||||
struct mstdnt_get_notifications_args args = {
|
||||
.exclude_types = 0,
|
||||
.account_id = NULL,
|
||||
.exclude_visibilities = 0,
|
||||
.include_types = 0,
|
||||
.with_muted = 1,
|
||||
.max_id = NULL,
|
||||
.min_id = NULL,
|
||||
.since_id = NULL,
|
||||
.offset = 0,
|
||||
.limit = 8,
|
||||
};
|
||||
|
||||
if (mastodont_get_notifications(api,
|
||||
&m_args,
|
||||
&args,
|
||||
&storage,
|
||||
¬ifs,
|
||||
¬ifs_len) == 0)
|
||||
{
|
||||
main_sidebar_str = construct_notifications_compact(ssn, api, notifs, notifs_len, NULL);
|
||||
}
|
||||
|
||||
mstdnt_cleanup_notifications(notifs, notifs_len);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Construct small login page
|
||||
struct quick_login_template tdata = {
|
||||
.prefix = config_url_prefix,
|
||||
.username = L10N[locale][L10N_USERNAME],
|
||||
.password = L10N[locale][L10N_PASSWORD],
|
||||
.login = L10N[locale][L10N_LOGIN_BTN],
|
||||
struct mstdnt_notifications_args args = {
|
||||
.exclude_types = 0,
|
||||
.account_id = NULL,
|
||||
.exclude_visibilities = 0,
|
||||
.include_types = 0,
|
||||
.with_muted = 1,
|
||||
.max_id = NULL,
|
||||
.min_id = NULL,
|
||||
.since_id = NULL,
|
||||
.offset = 0,
|
||||
.limit = 8,
|
||||
};
|
||||
main_sidebar_str = tmpl_gen_quick_login(&tdata, NULL);
|
||||
|
||||
mastodont_get_notifications(
|
||||
api,
|
||||
&m_args,
|
||||
&args,
|
||||
&storage,
|
||||
¬ifs,
|
||||
¬ifs_len
|
||||
);
|
||||
}
|
||||
|
||||
// Combine into sidebar
|
||||
easprintf(&sidebar_str, "%s%s",
|
||||
account_sidebar_str ? account_sidebar_str : "",
|
||||
main_sidebar_str ? main_sidebar_str : "");
|
||||
PERL_STACK_INIT;
|
||||
|
||||
// Create instance panel
|
||||
if (g_cache.panel_html.response)
|
||||
easprintf(&instance_str, "<div class=\"static-html\" id=\"instance-panel\">%s</div>",
|
||||
(g_cache.panel_html.response ?
|
||||
g_cache.panel_html.response : ""));
|
||||
HV* real_ssn = page->session ? page->session : perlify_session(ssn);
|
||||
mXPUSHs(newRV_noinc((SV*)real_ssn));
|
||||
mXPUSHs(newRV_inc((SV*)template_files));
|
||||
mXPUSHs(newSVpv(page->content, 0));
|
||||
|
||||
if (ssn->config.theme && !(strcmp(ssn->config.theme, "treebird") == 0 &&
|
||||
ssn->config.themeclr == 0))
|
||||
if (notifs && notifs_len)
|
||||
{
|
||||
easprintf(&theme_str, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/%s%s.css\">",
|
||||
ssn->config.theme,
|
||||
ssn->config.themeclr ? "-dark" : "");
|
||||
mXPUSHs(newRV_noinc(perlify_notifications(notifs, notifs_len)));
|
||||
}
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (ssn->config.sidebar_opacity)
|
||||
{
|
||||
float sidebar_opacity = (float)ssn->config.sidebar_opacity / 255.0f;
|
||||
snprintf(sidebar_css, SIDEBAR_CSS_LEN, ":root { --sidebar-opacity: %.2f; }",
|
||||
sidebar_opacity);
|
||||
}
|
||||
// Run function
|
||||
PERL_STACK_SCALAR_CALL("base_page");
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct index_template index_tmpl = {
|
||||
.title = L10N[locale][L10N_APP_NAME],
|
||||
.sidebar_css = sidebar_css,
|
||||
.theme_str = theme_str,
|
||||
.prefix = config_url_prefix,
|
||||
.background_url = background_url_css,
|
||||
.name = L10N[locale][L10N_APP_NAME],
|
||||
.sidebar_cnt = login_string,
|
||||
.placeholder = L10N[locale][L10N_SEARCH_PLACEHOLDER],
|
||||
.search_btn = L10N[locale][L10N_SEARCH_BUTTON],
|
||||
.active_home = CAT_TEXT(page->category, BASE_CAT_HOME),
|
||||
.home = L10N[locale][L10N_HOME],
|
||||
.active_local = CAT_TEXT(page->category, BASE_CAT_LOCAL),
|
||||
.local = L10N[locale][L10N_LOCAL],
|
||||
.active_federated = CAT_TEXT(page->category, BASE_CAT_FEDERATED),
|
||||
.federated = L10N[locale][L10N_FEDERATED],
|
||||
.active_notifications = CAT_TEXT(page->category, BASE_CAT_NOTIFICATIONS),
|
||||
.notifications = L10N[locale][L10N_NOTIFICATIONS],
|
||||
.active_lists = CAT_TEXT(page->category, BASE_CAT_LISTS),
|
||||
.lists = L10N[locale][L10N_LISTS],
|
||||
.active_favourites = CAT_TEXT(page->category, BASE_CAT_FAVOURITES),
|
||||
.favourites = L10N[locale][L10N_FAVOURITES],
|
||||
.active_bookmarks = CAT_TEXT(page->category, BASE_CAT_BOOKMARKS),
|
||||
.bookmarks = L10N[locale][L10N_BOOKMARKS],
|
||||
.active_direct = CAT_TEXT(page->category, BASE_CAT_DIRECT),
|
||||
.direct = L10N[locale][L10N_DIRECT],
|
||||
.active_chats = CAT_TEXT(page->category, BASE_CAT_CHATS),
|
||||
.chats = "Chats",
|
||||
.active_config = CAT_TEXT(page->category, BASE_CAT_CONFIG),
|
||||
.config = L10N[locale][L10N_CONFIG],
|
||||
.sidebar_leftbar = page->sidebar_left,
|
||||
.instance_panel = ssn->config.instance_panel ? instance_str : "",
|
||||
.main = page->content,
|
||||
.sidebar_rightbar = sidebar_str,
|
||||
.about_link_str = "About",
|
||||
.license_link_str = "License",
|
||||
.source_link_str = "Source code",
|
||||
};
|
||||
|
||||
size_t len;
|
||||
char* data = tmpl_gen_index(&index_tmpl, &len);
|
||||
send_result(req, NULL, "text/html", dup, 0);
|
||||
|
||||
if (!data)
|
||||
{
|
||||
perror("malloc");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
send_result(req, NULL, "text/html", data, len);
|
||||
|
||||
// Cleanup
|
||||
/* cleanup_all: */
|
||||
free(data);
|
||||
cleanup:
|
||||
free(sidebar_str);
|
||||
if (main_sidebar_str != sidebar_embed) free(main_sidebar_str);
|
||||
free(account_sidebar_str);
|
||||
free(background_url_css);
|
||||
free(instance_str);
|
||||
free(theme_str);
|
||||
mstdnt_cleanup_notifications(notifs, notifs_len);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
void send_result(FCGX_Request* req, char* status, char* content_type, char* data, size_t data_len)
|
||||
|
@ -216,7 +105,7 @@ void send_result(FCGX_Request* req, char* status, char* content_type, char* data
|
|||
"Content-Length: %d\r\n\r\n",
|
||||
status ? status : "200 OK",
|
||||
content_type ? content_type : "text/html",
|
||||
data_len + 1);
|
||||
data_len);
|
||||
#ifdef SINGLE_THREADED
|
||||
puts(data);
|
||||
#else
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
|
||||
#ifndef BASE_PAGE_H
|
||||
#define BASE_PAGE_H
|
||||
#include "global_perl.h"
|
||||
#include "session.h"
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include <mastodont.h>
|
||||
#include "l10n.h"
|
||||
#include "local_config.h"
|
||||
#include "path.h"
|
||||
#include "session.h"
|
||||
|
||||
enum base_category
|
||||
{
|
||||
BASE_CAT_NONE,
|
||||
|
@ -46,6 +46,7 @@ struct base_page
|
|||
enum base_category category;
|
||||
char* content;
|
||||
char* sidebar_left;
|
||||
HV* session;
|
||||
};
|
||||
|
||||
void render_base_page(struct base_page* page, FCGX_Request* req, struct session* ssn, mastodont_t* api);
|
||||
|
|
|
@ -16,14 +16,15 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NAVIGATION_H
|
||||
#define NAVIGATION_H
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
/* #ifndef IMPORT_CGI_H */
|
||||
/* #define IMPORT_CGI_H */
|
||||
|
||||
char* construct_navigation_box(char* start_id,
|
||||
char* prev_id,
|
||||
char* next_id,
|
||||
size_t* size);
|
||||
#ifndef NO_FCGI
|
||||
#include <fcgi_stdio.h>
|
||||
#endif // NO_FCGI
|
||||
|
||||
#endif // NAVIGATION_H
|
||||
#ifndef SINGLE_THREADED
|
||||
#include <fcgiapp.h>
|
||||
#endif // SINGLE_THREADED
|
||||
|
||||
// #endif
|
|
@ -17,6 +17,8 @@
|
|||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "account.h"
|
||||
#include "emoji.h"
|
||||
#include "../config.h"
|
||||
#include "conversations.h"
|
||||
#include "helpers.h"
|
||||
|
@ -24,138 +26,6 @@
|
|||
#include "error.h"
|
||||
#include "base_page.h"
|
||||
|
||||
// Files
|
||||
#include "../static/chat.ctmpl"
|
||||
#include "../static/chats_page.ctmpl"
|
||||
#include "../static/message.ctmpl"
|
||||
#include "../static/chat_view.ctmpl"
|
||||
#include "../static/embed.ctmpl"
|
||||
|
||||
struct construct_message_args
|
||||
{
|
||||
struct mstdnt_message* msg;
|
||||
struct mstdnt_account* you;
|
||||
struct mstdnt_account* them;
|
||||
size_t msg_size; // Read messages backwards
|
||||
};
|
||||
|
||||
struct construct_chats_args
|
||||
{
|
||||
mastodont_t* api;
|
||||
struct mstdnt_args* args;
|
||||
struct mstdnt_chat* chats;
|
||||
};
|
||||
|
||||
char* construct_chat(mastodont_t* api,
|
||||
struct mstdnt_args* m_args,
|
||||
struct mstdnt_chat* chat,
|
||||
size_t* size)
|
||||
{
|
||||
char* result;
|
||||
char* msg_id = NULL;
|
||||
char* last_message = "<span class=\"empty-chat-text\">Chat created</span>";
|
||||
|
||||
// Get latest message
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_message* messages = NULL;
|
||||
size_t messages_len = 0;
|
||||
|
||||
struct mstdnt_chats_args args = {
|
||||
.with_muted = MSTDNT_TRUE,
|
||||
.offset = 0,
|
||||
.limit = 1,
|
||||
};
|
||||
|
||||
if (mastodont_get_chat_messages(api, m_args, chat->id, &args, &storage,
|
||||
&messages, &messages_len) == 0 && messages_len == 1)
|
||||
{
|
||||
last_message = messages[0].content;
|
||||
msg_id = messages[0].id;
|
||||
}
|
||||
|
||||
struct chat_template data = {
|
||||
.id = chat->id,
|
||||
.prefix = config_url_prefix,
|
||||
.acct = chat->account.acct,
|
||||
.avatar = chat->account.avatar,
|
||||
.display_name = chat->account.display_name,
|
||||
.message_id = msg_id,
|
||||
.last_message = last_message,
|
||||
};
|
||||
result = tmpl_gen_chat(&data, size);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
// TODO cleanup messages
|
||||
return result;
|
||||
}
|
||||
|
||||
static char* construct_chat_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
struct construct_chats_args* args = passed;
|
||||
return construct_chat(args->api, args->args, args->chats + index, res);
|
||||
}
|
||||
|
||||
char* construct_chats(mastodont_t* api,
|
||||
struct mstdnt_args* m_args,
|
||||
struct mstdnt_chat* chats,
|
||||
size_t size,
|
||||
size_t* ret_size)
|
||||
{
|
||||
struct construct_chats_args args = {
|
||||
.api = api,
|
||||
.args = m_args,
|
||||
.chats = chats,
|
||||
};
|
||||
|
||||
return construct_func_strings(construct_chat_voidwrap, &args, size, ret_size);
|
||||
}
|
||||
|
||||
char* construct_message(struct mstdnt_message* msg,
|
||||
struct mstdnt_account* you,
|
||||
struct mstdnt_account* them,
|
||||
size_t* size)
|
||||
{
|
||||
char* result;
|
||||
if (!(you && them)) return NULL;
|
||||
int is_you = strcmp(you->id, msg->account_id) == 0;
|
||||
struct message_template data = {
|
||||
.id = msg->id,
|
||||
.content = msg->content,
|
||||
.is_you = is_you ? "message-you" : NULL,
|
||||
.avatar = is_you ? you->avatar : them->avatar
|
||||
};
|
||||
result = tmpl_gen_message(&data, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static char* construct_message_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
struct construct_message_args* args = passed;
|
||||
return construct_message(args->msg + (args->msg_size - index - 1), args->you, args->them, res);
|
||||
}
|
||||
|
||||
char* construct_messages(struct mstdnt_message* messages,
|
||||
struct mstdnt_account* you,
|
||||
struct mstdnt_account* them,
|
||||
size_t size,
|
||||
size_t* ret_size)
|
||||
{
|
||||
struct construct_message_args args = {
|
||||
.msg = messages,
|
||||
.you = you,
|
||||
.them = them,
|
||||
.msg_size = size
|
||||
};
|
||||
return construct_func_strings(construct_message_voidwrap, &args, size, ret_size);
|
||||
}
|
||||
|
||||
char* construct_chats_view(char* lists_string, size_t* size)
|
||||
{
|
||||
struct chats_page_template data = {
|
||||
.content = lists_string,
|
||||
};
|
||||
return tmpl_gen_chats_page(&data, size);
|
||||
}
|
||||
|
||||
void content_chats(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
|
@ -163,8 +33,6 @@ void content_chats(PATH_ARGS)
|
|||
struct mstdnt_chat* chats = NULL;
|
||||
size_t chats_len = 0;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
char* chats_page = NULL;
|
||||
char* chats_html = NULL;
|
||||
|
||||
struct mstdnt_chats_args args = {
|
||||
.with_muted = MSTDNT_TRUE,
|
||||
|
@ -175,20 +43,25 @@ void content_chats(PATH_ARGS)
|
|||
.limit = 20,
|
||||
};
|
||||
|
||||
if (mastodont_get_chats_v2(api, &m_args, &args, &storage, &chats, &chats_len))
|
||||
{
|
||||
chats_page = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
chats_html = construct_chats(api, &m_args, chats, chats_len, NULL);
|
||||
if (!chats_html)
|
||||
chats_html = construct_error("No chats", E_NOTICE, 1, NULL);
|
||||
chats_page = construct_chats_view(chats_html, NULL);
|
||||
}
|
||||
mastodont_get_chats_v2(api, &m_args, &args, &storage, &chats, &chats_len);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
if (chats)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_chats(chats, chats_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
PERL_STACK_SCALAR_CALL("chat::content_chats");
|
||||
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_CHATS,
|
||||
.content = chats_page,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
|
@ -197,12 +70,11 @@ void content_chats(PATH_ARGS)
|
|||
|
||||
// Cleanup
|
||||
mastodont_storage_cleanup(&storage);
|
||||
free(chats_page);
|
||||
free(chats_html);
|
||||
// TOOD cleanup chats
|
||||
mstdnt_cleanup_chats(chats, chats_len);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
char* construct_chat_view(struct session* ssn, mastodont_t* api, char* id, size_t* len)
|
||||
void content_chat_view(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
@ -211,9 +83,6 @@ char* construct_chat_view(struct session* ssn, mastodont_t* api, char* id, size_
|
|||
size_t messages_len = 0;
|
||||
struct mstdnt_storage storage = { 0 }, storage_chat = { 0 };
|
||||
struct mstdnt_chat chat;
|
||||
struct mstdnt_storage acct_storage = { 0 };
|
||||
char* chats_page = NULL;
|
||||
char* messages_html = NULL;
|
||||
|
||||
struct mstdnt_chats_args args = {
|
||||
.with_muted = MSTDNT_TRUE,
|
||||
|
@ -224,72 +93,71 @@ char* construct_chat_view(struct session* ssn, mastodont_t* api, char* id, size_
|
|||
.limit = 20,
|
||||
};
|
||||
|
||||
if (len) *len = 0;
|
||||
mastodont_get_chat_messages(api, &m_args, data[0], &args, &storage, &messages, &messages_len);
|
||||
int chat_code = mastodont_get_chat(api, &m_args, data[0],
|
||||
&storage_chat, &chat);
|
||||
|
||||
if (mastodont_get_chat_messages(api, &m_args, id,
|
||||
&args, &storage, &messages, &messages_len) ||
|
||||
mastodont_get_chat(api, &m_args, id,
|
||||
&storage_chat, &chat))
|
||||
{
|
||||
chats_page = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
messages_html = construct_messages(messages, &(ssn->acct), &(chat.account), messages_len, NULL);
|
||||
if (!messages_html)
|
||||
messages_html = construct_error("This is the start of something new...", E_NOTICE, 1, NULL);
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
if (chat_code == 0)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_chat(&chat)));
|
||||
else ARG_UNDEFINED();
|
||||
if (messages)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_messages(messages, messages_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
PERL_STACK_SCALAR_CALL("chat::construct_chat");
|
||||
|
||||
struct chat_view_template tmpl = {
|
||||
.back_link = "/chats",
|
||||
.prefix = config_url_prefix,
|
||||
.avatar = chat.account.avatar,
|
||||
.acct = chat.account.acct,
|
||||
.messages = messages_html
|
||||
};
|
||||
|
||||
chats_page = tmpl_gen_chat_view(&tmpl, len);
|
||||
}
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mastodont_storage_cleanup(&acct_storage);
|
||||
free(messages_html);
|
||||
// TODO cleanup messages
|
||||
return chats_page;
|
||||
}
|
||||
|
||||
void content_chat_view(PATH_ARGS)
|
||||
{
|
||||
char* chat_view = construct_chat_view(ssn, api, data[0], NULL);
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_CHATS,
|
||||
.content = chat_view,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
free(chat_view);
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mastodont_storage_cleanup(&storage_chat);
|
||||
mstdnt_cleanup_chat(&chat);
|
||||
mstdnt_cleanup_messages(messages);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
|
||||
void content_chat_embed(PATH_ARGS)
|
||||
HV* perlify_chat(const struct mstdnt_chat* chat)
|
||||
{
|
||||
size_t result_len;
|
||||
char* result;
|
||||
char* chat_view = construct_chat_view(ssn, api, data[0], NULL);
|
||||
if (!chat) return NULL;
|
||||
|
||||
struct embed_template tmpl = {
|
||||
.stylesheet = "treebird20",
|
||||
.embed = chat_view,
|
||||
};
|
||||
HV* chat_hv = newHV();
|
||||
hvstores_ref(chat_hv, "account", perlify_account(&(chat->account)));
|
||||
hvstores_str(chat_hv, "id", chat->id);
|
||||
hvstores_int(chat_hv, "unread", chat->unread);
|
||||
|
||||
result = tmpl_gen_embed(&tmpl, &result_len);
|
||||
|
||||
// Output
|
||||
send_result(req, NULL, NULL, result, result_len);
|
||||
|
||||
free(chat_view);
|
||||
free(result);
|
||||
return chat_hv;
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(chat, chats, mstdnt_chat)
|
||||
|
||||
HV* perlify_message(const struct mstdnt_message* message)
|
||||
{
|
||||
if (!message) return NULL;
|
||||
|
||||
HV* message_hv = newHV();
|
||||
hvstores_str(message_hv, "account_id", message->account_id);
|
||||
hvstores_str(message_hv, "chat_id", message->chat_id);
|
||||
hvstores_str(message_hv, "id", message->id);
|
||||
hvstores_str(message_hv, "content", message->content);
|
||||
hvstores_int(message_hv, "created_at", message->created_at);
|
||||
hvstores_ref(message_hv, "emojis", perlify_emojis(message->emojis, message->emojis_len));
|
||||
hvstores_int(message_hv, "unread", message->unread);
|
||||
|
||||
return message_hv;
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(message, messages, mstdnt_message)
|
||||
|
|
|
@ -23,30 +23,13 @@
|
|||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
|
||||
char* construct_chat(mastodont_t* api,
|
||||
struct mstdnt_args* m_args,
|
||||
struct mstdnt_chat* chat,
|
||||
size_t* size);
|
||||
char* construct_chats(mastodont_t* api,
|
||||
struct mstdnt_args* m_args,
|
||||
struct mstdnt_chat* chats,
|
||||
size_t size,
|
||||
size_t* ret_size);
|
||||
char* construct_chats_view(char* lists_string, size_t* size);
|
||||
// Message
|
||||
char* construct_message(struct mstdnt_message* message,
|
||||
struct mstdnt_account* your_profile,
|
||||
struct mstdnt_account* their_profile,
|
||||
size_t* size);
|
||||
char* construct_messages(struct mstdnt_message* message,
|
||||
struct mstdnt_account* your_profile,
|
||||
struct mstdnt_account* their_profile,
|
||||
size_t size,
|
||||
size_t* ret_size);
|
||||
|
||||
void content_chats(PATH_ARGS);
|
||||
char* construct_chat_view(struct session* ssn, mastodont_t* api, char* id, size_t* len);
|
||||
void content_chat_embed(PATH_ARGS);
|
||||
void content_chat_view(PATH_ARGS);
|
||||
|
||||
AV* perlify_chats(const struct mstdnt_chat* chats, size_t chats_len);
|
||||
HV* perlify_chat(const struct mstdnt_chat* chat);
|
||||
|
||||
AV* perlify_messages(const struct mstdnt_message* messages, size_t messages_len);
|
||||
HV* perlify_message(const struct mstdnt_message* message);
|
||||
|
||||
#endif // LISTS_H
|
||||
|
|
38
src/cookie.c
38
src/cookie.c
|
@ -16,11 +16,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include "cookie.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "cookie.h"
|
||||
#include "env.h"
|
||||
|
||||
enum cookie_state
|
||||
|
@ -31,7 +29,7 @@ enum cookie_state
|
|||
STATE_V_START,
|
||||
};
|
||||
|
||||
char* read_cookies_env(FCGX_Request* req, struct cookie_values* cookies)
|
||||
char* read_cookies_env(REQUEST_T req, struct cookie_values* cookies)
|
||||
{
|
||||
struct http_cookie_info info;
|
||||
char* cookies_env = GET_ENV("HTTP_COOKIE", req);
|
||||
|
@ -164,3 +162,35 @@ int cookie_get_val(char* src, char* key, struct http_cookie_info* info)
|
|||
|
||||
return 1;
|
||||
}
|
||||
|
||||
HV* perlify_cookies(struct cookie_values* cookies)
|
||||
{
|
||||
HV* ssn_cookies_hv = newHV();
|
||||
|
||||
hv_stores(ssn_cookies_hv, "lang", newSViv(keyint(cookies->lang)));
|
||||
hv_stores(ssn_cookies_hv, "interact_img", newSViv(keyint(cookies->interact_img)));
|
||||
hv_stores(ssn_cookies_hv, "themeclr", newSViv(keyint(cookies->themeclr)));
|
||||
hv_stores(ssn_cookies_hv, "jsactions", newSViv(keyint(cookies->jsactions)));
|
||||
hv_stores(ssn_cookies_hv, "jsreply", newSViv(keyint(cookies->jsreply)));
|
||||
hv_stores(ssn_cookies_hv, "jslive", newSViv(keyint(cookies->jslive)));
|
||||
hv_stores(ssn_cookies_hv, "js", newSViv(keyint(cookies->js)));
|
||||
hv_stores(ssn_cookies_hv, "interact_img", newSViv(keyint(cookies->interact_img)));
|
||||
hv_stores(ssn_cookies_hv, "statattachments", newSViv(keyint(cookies->stat_attachments)));
|
||||
hv_stores(ssn_cookies_hv, "statgreentexts", newSViv(keyint(cookies->stat_greentexts)));
|
||||
hv_stores(ssn_cookies_hv, "statdope", newSViv(keyint(cookies->stat_dope)));
|
||||
hv_stores(ssn_cookies_hv, "statoneclicksoftware", newSViv(keyint(cookies->stat_oneclicksoftware)));
|
||||
hv_stores(ssn_cookies_hv, "statemojolikes", newSViv(keyint(cookies->stat_emojo_likes)));
|
||||
hv_stores(ssn_cookies_hv, "stathidemuted", newSViv(keyint(cookies->stat_hide_muted)));
|
||||
hv_stores(ssn_cookies_hv, "instanceshowshoutbox", newSViv(keyint(cookies->instance_show_shoutbox)));
|
||||
hv_stores(ssn_cookies_hv, "instancepanel", newSViv(keyint(cookies->instance_panel)));
|
||||
hv_stores(ssn_cookies_hv, "notifembed", newSViv(keyint(cookies->notif_embed)));
|
||||
hv_stores(ssn_cookies_hv, "access_token", newSVpv(keystr(cookies->access_token), 0));
|
||||
hv_stores(ssn_cookies_hv, "logged_in", newSVpv(keystr(cookies->logged_in), 0));
|
||||
hv_stores(ssn_cookies_hv, "theme", newSVpv(keystr(cookies->theme), 0));
|
||||
hv_stores(ssn_cookies_hv, "instance_url", newSVpv(keystr(cookies->instance_url), 0));
|
||||
hv_stores(ssn_cookies_hv, "background_url", newSVpv(keystr(cookies->background_url), 0));
|
||||
hv_stores(ssn_cookies_hv, "client_id", newSVpv(keystr(cookies->client_id), 0));
|
||||
hv_stores(ssn_cookies_hv, "client_secret", newSVpv(keystr(cookies->client_secret), 0));
|
||||
|
||||
return ssn_cookies_hv;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
#ifndef COOKIE_H
|
||||
#define COOKIE_H
|
||||
#include <stddef.h>
|
||||
#include "global_perl.h"
|
||||
#include "key.h"
|
||||
#include "cgi.h"
|
||||
#include "request.h"
|
||||
|
||||
struct cookie_values
|
||||
{
|
||||
|
@ -58,7 +61,9 @@ struct http_cookie_info
|
|||
|
||||
// Stupidly fast simple cookie parser
|
||||
char* parse_cookies(char* begin, struct http_cookie_info* info);
|
||||
char* read_cookies_env(FCGX_Request* req, struct cookie_values* cookies);
|
||||
char* read_cookies_env(REQUEST_T req, struct cookie_values* cookies);
|
||||
int cookie_get_val(char* src, char* key, struct http_cookie_info* info);
|
||||
|
||||
HV* perlify_cookies(struct cookie_values* cookies);
|
||||
|
||||
#endif // COOKIE_H
|
||||
|
|
118
src/emoji.c
118
src/emoji.c
|
@ -16,19 +16,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "emoji.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "base_page.h"
|
||||
#include "string.h"
|
||||
#include "emoji.h"
|
||||
#include "easprintf.h"
|
||||
#include "string_helpers.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/emoji.ctmpl"
|
||||
#include "../static/emoji_plain.ctmpl"
|
||||
#include "../static/emoji_picker.ctmpl"
|
||||
|
||||
enum emoji_categories
|
||||
{
|
||||
EMO_CAT_SMILEYS,
|
||||
|
@ -42,38 +37,6 @@ enum emoji_categories
|
|||
EMO_CAT_LEN
|
||||
};
|
||||
|
||||
char* emojify(char* content, struct mstdnt_emoji* emos, size_t emos_len)
|
||||
{
|
||||
if (!content) return NULL;
|
||||
size_t sc_len;
|
||||
char* oldres = NULL;
|
||||
char* res = content;
|
||||
char* emoji_url_str;
|
||||
char* coloned;
|
||||
for (size_t i = 0; i < emos_len; ++i)
|
||||
{
|
||||
oldres = res;
|
||||
|
||||
// Add colons around string
|
||||
sc_len = strlen(emos[i].shortcode);
|
||||
// 3 = \0 and two :
|
||||
coloned = malloc(sc_len+3);
|
||||
coloned[0] = ':';
|
||||
strncpy(coloned + 1, emos[i].shortcode, sc_len);
|
||||
coloned[sc_len+1] = ':';
|
||||
coloned[sc_len+2] = '\0';
|
||||
|
||||
easprintf(&emoji_url_str, "<img class=\"emoji\" src=\"%s\" loading=\"lazy\">", emos[i].url);
|
||||
|
||||
res = strrepl(res, coloned, emoji_url_str, STRREPL_ALL);
|
||||
if (oldres != content && res != oldres) free(oldres);
|
||||
// Cleanup
|
||||
free(emoji_url_str);
|
||||
free(coloned);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
struct construct_emoji_picker_args
|
||||
{
|
||||
char* status_id;
|
||||
|
@ -84,21 +47,14 @@ char* construct_emoji(struct emoji_info* emoji, char* status_id, size_t* size)
|
|||
{
|
||||
if (!emoji)
|
||||
return NULL;
|
||||
char* emoji_str;
|
||||
|
||||
if (status_id)
|
||||
{
|
||||
struct emoji_template data = {
|
||||
.status_id = status_id,
|
||||
.emoji = emoji->codes
|
||||
};
|
||||
return tmpl_gen_emoji(&data, size);
|
||||
}
|
||||
else {
|
||||
struct emoji_plain_template data = {
|
||||
.emoji = emoji->codes
|
||||
};
|
||||
return tmpl_gen_emoji_plain(&data, size);
|
||||
}
|
||||
*size = easprintf(&emoji_str, "<a href=\"/status/%s/react/%s\" class=\"emoji\">%s</a>",
|
||||
status_id, emoji->codes, emoji->codes);
|
||||
else
|
||||
*size = easprintf(&emoji_str, "<span class=\"emoji\">%s</span>", emoji->codes);
|
||||
return emoji_str;
|
||||
}
|
||||
|
||||
static char* construct_emoji_voidwrap(void* passed, size_t index, size_t* res)
|
||||
|
@ -136,30 +92,48 @@ char* construct_emoji_picker(char* status_id, size_t* size)
|
|||
};
|
||||
|
||||
char* emojis[EMO_CAT_LEN];
|
||||
size_t len[EMO_CAT_LEN];
|
||||
|
||||
// TODO refactor to use #define lol
|
||||
emojis[EMO_CAT_SMILEYS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_SMILEYS, EMOJO_CAT_ANIMALS - EMOJO_CAT_SMILEY, NULL);
|
||||
emojis[EMO_CAT_ANIMALS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_ANIMALS, EMOJO_CAT_FOOD - EMOJO_CAT_ANIMALS, NULL);
|
||||
emojis[EMO_CAT_FOOD] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_FOOD, EMOJO_CAT_TRAVEL - EMOJO_CAT_FOOD, NULL);
|
||||
emojis[EMO_CAT_TRAVEL] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_TRAVEL, EMOJO_CAT_ACTIVITIES - EMOJO_CAT_TRAVEL, NULL);
|
||||
emojis[EMO_CAT_ACTIVITIES] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_ACTIVITIES, EMOJO_CAT_OBJECTS - EMOJO_CAT_ACTIVITIES, NULL);
|
||||
emojis[EMO_CAT_OBJECTS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_OBJECTS, EMOJO_CAT_SYMBOLS - EMOJO_CAT_OBJECTS, NULL);
|
||||
emojis[EMO_CAT_SYMBOLS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_SYMBOLS, EMOJO_CAT_FLAGS - EMOJO_CAT_SYMBOLS, NULL);
|
||||
emojis[EMO_CAT_FLAGS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_FLAGS, EMOJO_CAT_MAX - EMOJO_CAT_FLAGS, NULL);
|
||||
|
||||
struct emoji_picker_template data = {
|
||||
.emojis_smileys = emojis[EMO_CAT_SMILEYS],
|
||||
.emojis_animals = emojis[EMO_CAT_ANIMALS],
|
||||
.emojis_food = emojis[EMO_CAT_FOOD],
|
||||
.emojis_travel = emojis[EMO_CAT_TRAVEL],
|
||||
.emojis_activities = emojis[EMO_CAT_ACTIVITIES],
|
||||
.emojis_objects = emojis[EMO_CAT_OBJECTS],
|
||||
.emojis_symbols = emojis[EMO_CAT_SYMBOLS],
|
||||
.emojis_flags = emojis[EMO_CAT_FLAGS],
|
||||
};
|
||||
emojis[EMO_CAT_SMILEYS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_SMILEYS, EMOJO_CAT_ANIMALS - EMOJO_CAT_SMILEY, len);
|
||||
emojis[EMO_CAT_ANIMALS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_ANIMALS, EMOJO_CAT_FOOD - EMOJO_CAT_ANIMALS, len + 1);
|
||||
emojis[EMO_CAT_FOOD] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_FOOD, EMOJO_CAT_TRAVEL - EMOJO_CAT_FOOD, len + 2);
|
||||
emojis[EMO_CAT_TRAVEL] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_TRAVEL, EMOJO_CAT_ACTIVITIES - EMOJO_CAT_TRAVEL, len + 3);
|
||||
emojis[EMO_CAT_ACTIVITIES] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_ACTIVITIES, EMOJO_CAT_OBJECTS - EMOJO_CAT_ACTIVITIES, len + 4);
|
||||
emojis[EMO_CAT_OBJECTS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_OBJECTS, EMOJO_CAT_SYMBOLS - EMOJO_CAT_OBJECTS, len + 5);
|
||||
emojis[EMO_CAT_SYMBOLS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_SYMBOLS, EMOJO_CAT_FLAGS - EMOJO_CAT_SYMBOLS, len + 6);
|
||||
emojis[EMO_CAT_FLAGS] = construct_func_strings(construct_emoji_voidwrap, args + EMO_CAT_FLAGS, EMOJO_CAT_MAX - EMOJO_CAT_FLAGS, len + 7);
|
||||
|
||||
emoji_picker_html = tmpl_gen_emoji_picker(&data, size);
|
||||
PERL_STACK_INIT;
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
AV* av = newAV();
|
||||
av_extend(av, EMO_CAT_LEN);
|
||||
for (int i = 0; i < EMO_CAT_LEN; ++i)
|
||||
{
|
||||
av_store(av, i, newSVpv(emojis[i], 0));
|
||||
}
|
||||
mXPUSHs(newRV_noinc((SV*)av));
|
||||
PERL_STACK_SCALAR_CALL("emojis::emoji_picker");
|
||||
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
// Cleanup
|
||||
for (size_t i = 0; i < EMO_CAT_LEN; ++i)
|
||||
free(emojis[i]);
|
||||
return emoji_picker_html;
|
||||
return dup;
|
||||
}
|
||||
|
||||
HV* perlify_emoji(const struct mstdnt_emoji* const emoji)
|
||||
{
|
||||
if (!emoji) return NULL;
|
||||
HV* emoji_hv = newHV();
|
||||
hvstores_str(emoji_hv, "shortcode", emoji->shortcode);
|
||||
hvstores_str(emoji_hv, "url", emoji->url);
|
||||
hvstores_str(emoji_hv, "static_url", emoji->static_url);
|
||||
hvstores_int(emoji_hv, "visible_in_picker", emoji->visible_in_picker);
|
||||
hvstores_str(emoji_hv, "category", emoji->category);
|
||||
return emoji_hv;
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(emoji, emojis, mstdnt_emoji)
|
||||
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
#define EMOJI_H
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include "global_perl.h"
|
||||
#include "emoji_codes.h"
|
||||
#include "path.h"
|
||||
|
||||
#define EMOJI_FACTOR_NUM 32
|
||||
|
||||
|
@ -31,9 +33,12 @@ enum emoji_picker_cat
|
|||
EMOJI_CAT_FACES,
|
||||
};
|
||||
|
||||
char* emojify(char* content, struct mstdnt_emoji* emos, size_t emos_len);
|
||||
char* construct_emoji(struct emoji_info* emoji, char* status_id, size_t* size);
|
||||
void content_emoji_picker(PATH_ARGS);
|
||||
char* construct_emoji_picker(char* status_id, size_t* size);
|
||||
|
||||
// Perl
|
||||
HV* perlify_emoji(const struct mstdnt_emoji* const emoji);
|
||||
AV* perlify_emojis(const struct mstdnt_emoji* const emos, size_t len);
|
||||
|
||||
#endif // EMOJI_H
|
||||
|
|
|
@ -22,67 +22,17 @@
|
|||
#include <stdlib.h>
|
||||
#include "easprintf.h"
|
||||
|
||||
// Templates
|
||||
#include "../static/custom_emoji_reaction.ctmpl"
|
||||
#include "../static/emoji_reaction.ctmpl"
|
||||
#include "../static/emoji_reactions.ctmpl"
|
||||
|
||||
struct construct_emoji_reactions_args
|
||||
HV* perlify_emoji_reaction(const struct mstdnt_emoji_reaction* const emoji)
|
||||
{
|
||||
struct mstdnt_emoji_reaction* emojis;
|
||||
char* id;
|
||||
};
|
||||
|
||||
char* construct_emoji_reaction(char* id, struct mstdnt_emoji_reaction* emo, size_t* str_size)
|
||||
{
|
||||
char* ret;
|
||||
char* emoji = emo->name;
|
||||
if (emo->url)
|
||||
{
|
||||
struct custom_emoji_reaction_template c_data = {
|
||||
.url = emo->url,
|
||||
};
|
||||
emoji = tmpl_gen_custom_emoji_reaction(&c_data, NULL);
|
||||
}
|
||||
|
||||
struct emoji_reaction_template data = {
|
||||
.prefix = config_url_prefix,
|
||||
.status_id = id,
|
||||
.custom_emoji = emo->url ? "custom-emoji-container" : NULL,
|
||||
.emoji = emo->name,
|
||||
.emoji_display = emoji,
|
||||
.emoji_active = emo->me ? "active" : NULL,
|
||||
.emoji_count = emo->count
|
||||
};
|
||||
|
||||
ret = tmpl_gen_emoji_reaction(&data, str_size);
|
||||
if (emoji != emo->name)
|
||||
free(emoji);
|
||||
return ret;
|
||||
if (!emoji) return NULL;
|
||||
HV* emoji_hv = newHV();
|
||||
hvstores_str(emoji_hv, "name", emoji->name);
|
||||
hvstores_str(emoji_hv, "url", emoji->url);
|
||||
hvstores_str(emoji_hv, "static_url", emoji->static_url);
|
||||
hvstores_int(emoji_hv, "count", emoji->count);
|
||||
hvstores_int(emoji_hv, "me", emoji->me);
|
||||
return emoji_hv;
|
||||
}
|
||||
|
||||
static char* construct_emoji_reactions_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
struct construct_emoji_reactions_args* args = passed;
|
||||
return construct_emoji_reaction(args->id, args->emojis + index, res);
|
||||
}
|
||||
|
||||
char* construct_emoji_reactions(char* id, struct mstdnt_emoji_reaction* emos, size_t emos_len, size_t* str_size)
|
||||
{
|
||||
size_t elements_size;
|
||||
struct construct_emoji_reactions_args args = {
|
||||
.emojis = emos,
|
||||
.id = id
|
||||
};
|
||||
char* elements = construct_func_strings(construct_emoji_reactions_voidwrap, &args, emos_len, &elements_size);
|
||||
char* emos_view;
|
||||
|
||||
struct emoji_reactions_template data = {
|
||||
.emojis = elements
|
||||
};
|
||||
emos_view = tmpl_gen_emoji_reactions(&data, str_size);
|
||||
// Cleanup
|
||||
free(elements);
|
||||
return emos_view;
|
||||
}
|
||||
PERLIFY_MULTI(emoji_reaction, emoji_reactions, mstdnt_emoji_reaction)
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@
|
|||
#ifndef EMOJI_REACTION_H
|
||||
#define EMOJI_REACTION_H
|
||||
#include <mastodont.h>
|
||||
#include "global_perl.h"
|
||||
|
||||
char* construct_emoji_reaction(char* id, struct mstdnt_emoji_reaction* emo, size_t* str_len);
|
||||
char* construct_emoji_reactions(char* id, struct mstdnt_emoji_reaction* emos, size_t emos_len, size_t* str_len);
|
||||
// Perl
|
||||
HV* perlify_emoji_reaction(const struct mstdnt_emoji_reaction* const emoji);
|
||||
AV* perlify_emoji_reactions(const struct mstdnt_emoji_reaction* const emos, size_t len);
|
||||
|
||||
#endif // EMOJI_REACTION_H
|
||||
|
|
36
src/error.c
36
src/error.c
|
@ -21,46 +21,12 @@
|
|||
#include "easprintf.h"
|
||||
#include "l10n.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/error_404.ctmpl"
|
||||
#include "../static/error.ctmpl"
|
||||
|
||||
char* construct_error(const char* error, enum error_type type, unsigned pad, size_t* size)
|
||||
{
|
||||
char* class;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case E_ERROR:
|
||||
class = "error"; break;
|
||||
case E_WARNING:
|
||||
class = "warning"; break;
|
||||
case E_NOTICE:
|
||||
class = "notice"; break;
|
||||
}
|
||||
|
||||
struct error_template data = {
|
||||
.err_type = class,
|
||||
.is_padded = pad ? "error-pad" : NULL,
|
||||
.error = error ? error : "An error occured",
|
||||
};
|
||||
|
||||
return tmpl_gen_error(&data, size);
|
||||
}
|
||||
|
||||
void content_not_found(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* path)
|
||||
{
|
||||
char* page;
|
||||
struct error_404_template data = {
|
||||
.error = L10N[L10N_EN_US][L10N_PAGE_NOT_FOUND]
|
||||
};
|
||||
page = tmpl_gen_error_404(&data, NULL);
|
||||
|
||||
struct base_page b = {
|
||||
.content = page,
|
||||
.content = "Content not found",
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, req, ssn, api);
|
||||
free(page);
|
||||
}
|
||||
|
|
|
@ -23,14 +23,6 @@
|
|||
#include "session.h"
|
||||
#include "path.h"
|
||||
|
||||
enum error_type
|
||||
{
|
||||
E_ERROR,
|
||||
E_WARNING,
|
||||
E_NOTICE
|
||||
};
|
||||
|
||||
char* construct_error(const char* error, enum error_type type, unsigned pad, size_t* size);
|
||||
void content_not_found(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* path);
|
||||
|
||||
#endif // ERROR_H
|
||||
|
|
100
src/global_perl.c
Normal file
100
src/global_perl.c
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "global_perl.h"
|
||||
|
||||
/* TODO let's generate this file dynamically with a Perl script? */
|
||||
#include "../templates/main.ctt"
|
||||
#include "../templates/notification.ctt"
|
||||
#include "../templates/status.ctt"
|
||||
#include "../templates/content_status.ctt"
|
||||
#include "../templates/emoji_picker.ctt"
|
||||
#include "../templates/attachment.ctt"
|
||||
#include "../templates/emoji.ctt"
|
||||
#include "../templates/postbox.ctt"
|
||||
#include "../templates/timeline.ctt"
|
||||
#include "../templates/account.ctt"
|
||||
#include "../templates/account_statuses.ctt"
|
||||
#include "../templates/account_scrobbles.ctt"
|
||||
#include "../templates/content_notifs.ctt"
|
||||
#include "../templates/content_lists.ctt"
|
||||
#include "../templates/navigation.ctt"
|
||||
#include "../templates/accounts.ctt"
|
||||
#include "../templates/account_item.ctt"
|
||||
#include "../templates/content_search.ctt"
|
||||
#include "../templates/search_accounts.ctt"
|
||||
#include "../templates/search_statuses.ctt"
|
||||
#include "../templates/search_tags.ctt"
|
||||
#include "../templates/search.ctt"
|
||||
#include "../templates/content_chats.ctt"
|
||||
#include "../templates/chat.ctt"
|
||||
#include "../templates/config_general.ctt"
|
||||
#include "../templates/config_appearance.ctt"
|
||||
#include "../templates/embed.ctt"
|
||||
#include "../templates/notifs_embed.ctt"
|
||||
#include "../templates/about.ctt"
|
||||
#include "../templates/license.ctt"
|
||||
#include "../templates/login.ctt"
|
||||
#include "../templates/status_interactions.ctt"
|
||||
|
||||
PerlInterpreter* my_perl;
|
||||
HV* template_files;
|
||||
pthread_mutex_t perllock_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void init_template_files(pTHX)
|
||||
{
|
||||
template_files = newHV();
|
||||
|
||||
hv_stores(template_files, "main.tt", newSVpv(data_main_tt, data_main_tt_size));
|
||||
hv_stores(template_files, "notification.tt", newSVpv(data_notification_tt, data_notification_tt_size));
|
||||
hv_stores(template_files, "status.tt", newSVpv(data_status_tt, data_status_tt_size));
|
||||
hv_stores(template_files, "content_status.tt", newSVpv(data_content_status_tt, data_content_status_tt_size));
|
||||
hv_stores(template_files, "emoji_picker.tt", newSVpv(data_emoji_picker_tt, data_emoji_picker_tt_size));
|
||||
hv_stores(template_files, "attachment.tt", newSVpv(data_attachment_tt, data_attachment_tt_size));
|
||||
hv_stores(template_files, "emoji.tt", newSVpv(data_emoji_tt, data_emoji_tt_size));
|
||||
hv_stores(template_files, "postbox.tt", newSVpv(data_postbox_tt, data_postbox_tt_size));
|
||||
hv_stores(template_files, "timeline.tt", newSVpv(data_timeline_tt, data_timeline_tt_size));
|
||||
hv_stores(template_files, "account.tt", newSVpv(data_account_tt, data_account_tt_size));
|
||||
hv_stores(template_files, "account_statuses.tt", newSVpv(data_account_statuses_tt, data_account_statuses_tt_size));
|
||||
hv_stores(template_files, "account_scrobbles.tt", newSVpv(data_account_scrobbles_tt, data_account_scrobbles_tt_size));
|
||||
hv_stores(template_files, "content_notifs.tt", newSVpv(data_content_notifs_tt, data_content_notifs_tt_size));
|
||||
hv_stores(template_files, "content_lists.tt", newSVpv(data_content_lists_tt, data_content_lists_tt_size));
|
||||
hv_stores(template_files, "navigation.tt", newSVpv(data_navigation_tt, data_navigation_tt_size));
|
||||
hv_stores(template_files, "accounts.tt", newSVpv(data_accounts_tt, data_accounts_tt_size));
|
||||
hv_stores(template_files, "account_item.tt", newSVpv(data_account_item_tt, data_account_item_tt_size));
|
||||
hv_stores(template_files, "content_search.tt", newSVpv(data_content_search_tt, data_content_search_tt_size));
|
||||
hv_stores(template_files, "search.tt", newSVpv(data_search_tt, data_search_tt_size));
|
||||
hv_stores(template_files, "search_accounts.tt", newSVpv(data_search_accounts_tt, data_search_accounts_tt_size));
|
||||
hv_stores(template_files, "search_statuses.tt", newSVpv(data_search_statuses_tt, data_search_statuses_tt_size));
|
||||
hv_stores(template_files, "search_tags.tt", newSVpv(data_search_tags_tt, data_search_tags_tt_size));
|
||||
hv_stores(template_files, "content_chats.tt", newSVpv(data_content_chats_tt, data_content_chats_tt_size));
|
||||
hv_stores(template_files, "chat.tt", newSVpv(data_chat_tt, data_chat_tt_size));
|
||||
hv_stores(template_files, "config_general.tt", newSVpv(data_config_general_tt, data_config_general_tt_size));
|
||||
hv_stores(template_files, "config_appearance.tt", newSVpv(data_config_appearance_tt, data_config_appearance_tt_size));
|
||||
hv_stores(template_files, "embed.tt", newSVpv(data_embed_tt, data_embed_tt_size));
|
||||
hv_stores(template_files, "notifs_embed.tt", newSVpv(data_notifs_embed_tt, data_notifs_embed_tt_size));
|
||||
hv_stores(template_files, "about.tt", newSVpv(data_about_tt, data_about_tt_size));
|
||||
hv_stores(template_files, "license.tt", newSVpv(data_license_tt, data_license_tt_size));
|
||||
hv_stores(template_files, "login.tt", newSVpv(data_login_tt, data_login_tt_size));
|
||||
hv_stores(template_files, "status_interactions.tt", newSVpv(data_status_interactions_tt, data_status_interactions_tt_size));
|
||||
}
|
||||
|
||||
void cleanup_template_files()
|
||||
{
|
||||
hv_undef(template_files);
|
||||
}
|
82
src/global_perl.h
Normal file
82
src/global_perl.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GLOBAL_PERL_H
|
||||
#define GLOBAL_PERL_H
|
||||
#include <EXTERN.h>
|
||||
#include <perl.h>
|
||||
#include <pthread.h>
|
||||
|
||||
// hv_stores(ssn_hv, "id", newSVpv(acct->id, 0));
|
||||
// TODO use sv_usepvn_flags soon
|
||||
#define hvstores_str(hv, key, val) if ((val)) { hv_stores((hv), key, newSVpvn((val), strlen(val))); }
|
||||
#define hvstores_int(hv, key, val) hv_stores((hv), key, newSViv((val)))
|
||||
#define hvstores_ref(hv, key, val) if (1) { \
|
||||
SV* tmp = (SV*)(val); \
|
||||
if (tmp) \
|
||||
hv_stores((hv), key, newRV_noinc(tmp)); \
|
||||
}
|
||||
|
||||
/* Seeing all this shit littered in Treebird's code made me decide to write some macros */
|
||||
#define PERL_STACK_INIT perl_lock(); \
|
||||
dSP; \
|
||||
ENTER; \
|
||||
SAVETMPS; \
|
||||
PUSHMARK(SP)
|
||||
|
||||
#define PERL_STACK_SCALAR_CALL(name) PUTBACK; \
|
||||
call_pv((name), G_SCALAR); \
|
||||
SPAGAIN
|
||||
|
||||
/* you MUST assign scalar from savesharedsvpv, then free when done */
|
||||
#define PERL_GET_STACK_EXIT savesvpv(POPs); \
|
||||
PUTBACK; \
|
||||
FREETMPS; \
|
||||
LEAVE; \
|
||||
perl_unlock()
|
||||
|
||||
#define PERLIFY_MULTI(type, types, mstype) AV* perlify_##types(const struct mstype* const types, size_t len) { \
|
||||
if (!(types && len)) return NULL; \
|
||||
AV* av = newAV(); \
|
||||
av_extend(av, len-1); \
|
||||
for (size_t i = 0; i < len; ++i) \
|
||||
av_store(av, i, newRV_noinc((SV*)perlify_##type(types + i))); \
|
||||
return av; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern PerlInterpreter* my_perl;
|
||||
extern HV* template_files;
|
||||
extern pthread_mutex_t perllock_mutex;
|
||||
|
||||
#ifndef SINGLE_THREADED
|
||||
#define perl_lock() do { pthread_mutex_lock(&perllock_mutex); } while (0)
|
||||
#define perl_unlock() do { pthread_mutex_unlock(&perllock_mutex); } while (0)
|
||||
#else
|
||||
// NOOP
|
||||
#define perl_lock() ;;
|
||||
#define perl_unlock() ;;
|
||||
#endif
|
||||
|
||||
#define ARG_UNDEFINED() do { mXPUSHs(&PL_sv_undef); } while (0)
|
||||
|
||||
void init_template_files(pTHX);
|
||||
void cleanup_template_files();
|
||||
|
||||
#endif /* GLOBAL_PERL_H */
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "graphsnbars.h"
|
||||
#include "easprintf.h"
|
||||
#include "string_helpers.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/bar.ctmpl"
|
||||
#include "../static/bar_graph.ctmpl"
|
||||
|
||||
struct hashtags_graph_args
|
||||
{
|
||||
struct mstdnt_tag* tags;
|
||||
size_t tags_len;
|
||||
unsigned max;
|
||||
time_t rel_day;
|
||||
size_t days;
|
||||
};
|
||||
|
||||
char* construct_bar_graph_container(char* bars, size_t* size)
|
||||
{
|
||||
struct bar_graph_template data = {
|
||||
.graph = bars,
|
||||
};
|
||||
return tmpl_gen_bar_graph(&data, size);
|
||||
}
|
||||
|
||||
char* construct_bar(float value, size_t* size)
|
||||
{
|
||||
struct bar_template data = {
|
||||
.value = value * 100
|
||||
};
|
||||
return tmpl_gen_bar(&data, size);
|
||||
}
|
||||
|
||||
static char* construct_hashgraph_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
unsigned curr_sum = 0;
|
||||
struct hashtags_graph_args* args = passed;
|
||||
struct mstdnt_tag* tags = args->tags;
|
||||
size_t tags_len = args->tags_len;
|
||||
unsigned max = args->max;
|
||||
time_t rel_day = args->rel_day;
|
||||
size_t days = args->days;
|
||||
|
||||
for (int i = 0; i < tags_len; ++i)
|
||||
{
|
||||
for (int j = 0; j < tags[i].history_len; ++j)
|
||||
{
|
||||
if (tags[i].history[j].day == rel_day-((days-index-1)*86400))
|
||||
curr_sum += tags[i].history[j].uses;
|
||||
}
|
||||
}
|
||||
|
||||
return construct_bar((float)curr_sum / max, res);
|
||||
}
|
||||
|
||||
char* construct_hashtags_graph(struct mstdnt_tag* tags,
|
||||
size_t tags_len,
|
||||
size_t days,
|
||||
size_t* ret_size)
|
||||
{
|
||||
unsigned max_sum = 0;
|
||||
unsigned curr_sum = 0;
|
||||
size_t max_history_len = 0;
|
||||
|
||||
// Get current time at midnight for basis, copy over
|
||||
time_t t = time(NULL);
|
||||
struct tm* mn_ptr = gmtime(&t);
|
||||
struct tm mn;
|
||||
memcpy(&mn, mn_ptr, sizeof(mn));
|
||||
mn.tm_hour = 0;
|
||||
mn.tm_min = 0;
|
||||
mn.tm_sec = 0;
|
||||
time_t rel_day = timegm(&mn);
|
||||
|
||||
// Run a loop through all the hashtags, sum each set up,
|
||||
// then get the largest sum
|
||||
for (size_t i = 0; i < days; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < tags_len && i < tags[j].history_len; ++j)
|
||||
{
|
||||
if (tags[j].history_len > max_history_len)
|
||||
max_history_len = tags[j].history_len;
|
||||
if (tags[j].history[i].day >= rel_day-(i*86400))
|
||||
curr_sum += tags[j].history[i].uses;
|
||||
}
|
||||
|
||||
if (curr_sum > max_sum)
|
||||
max_sum = curr_sum;
|
||||
curr_sum = 0;
|
||||
}
|
||||
|
||||
struct hashtags_graph_args args = {
|
||||
.tags = tags,
|
||||
.tags_len = tags_len,
|
||||
.max = max_sum,
|
||||
.rel_day = rel_day,
|
||||
.days = max_history_len,
|
||||
};
|
||||
|
||||
return construct_func_strings(construct_hashgraph_voidwrap, &args, max_history_len, ret_size);
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef GRAPHS_N_BARS_H
|
||||
#define GRAPHS_N_BARS_H
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
|
||||
char* construct_bar_graph_container(char* bars, size_t* size);
|
||||
char* construct_bar(float value, size_t* size);
|
||||
char* construct_hashtags_graph(struct mstdnt_tag* tags,
|
||||
size_t tags_len,
|
||||
size_t days,
|
||||
size_t* ret_size);
|
||||
|
||||
#endif /* GRAPHS_N_BARS_H */
|
|
@ -22,44 +22,7 @@
|
|||
#include "easprintf.h"
|
||||
#include "../config.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/hashtag.ctmpl"
|
||||
#include "../static/hashtag_page.ctmpl"
|
||||
|
||||
#define TAG_SIZE_INITIAL 12
|
||||
|
||||
static unsigned hashtag_history_daily_uses(size_t max, struct mstdnt_history* history, size_t history_len)
|
||||
{
|
||||
unsigned total = 0;
|
||||
|
||||
for (int i = 0; i < history_len && i < max; ++i)
|
||||
total += history[i].uses;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
char* construct_hashtag(struct mstdnt_tag* hashtag, size_t* size)
|
||||
{
|
||||
// Lol!
|
||||
unsigned hash_size = TAG_SIZE_INITIAL +
|
||||
CLAMP(hashtag_history_daily_uses(7, hashtag->history, hashtag->history_len)*2, 0, 42);
|
||||
|
||||
struct hashtag_template data = {
|
||||
.prefix = config_url_prefix,
|
||||
.tag = hashtag->name,
|
||||
.tag_size = hash_size,
|
||||
};
|
||||
return tmpl_gen_hashtag(&data, size);
|
||||
}
|
||||
|
||||
static char* construct_hashtag_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
return construct_hashtag((struct mstdnt_tag*)passed + index, res);
|
||||
}
|
||||
|
||||
char* construct_hashtags(struct mstdnt_tag* hashtags, size_t size, size_t* ret_size)
|
||||
{
|
||||
if (!(hashtags && size)) return NULL;
|
||||
return construct_func_strings(construct_hashtag_voidwrap, hashtags, size, ret_size);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
|
||||
char* construct_hashtag(struct mstdnt_tag* hashtag, size_t* size);
|
||||
char* construct_hashtags(struct mstdnt_tag* hashtags, size_t size, size_t* ret_size);
|
||||
// TODO?
|
||||
|
||||
#endif /* HASHTAG_H */
|
||||
|
|
15
src/http.c
15
src/http.c
|
@ -27,15 +27,14 @@
|
|||
#define REDIR_HTML_END "</body>" \
|
||||
"</html>"
|
||||
|
||||
void redirect(FCGX_Request* req, char* status, char* location)
|
||||
void redirect(REQUEST_T req, char* status, char* location)
|
||||
{
|
||||
char* loc_str = location ? location : "/";
|
||||
|
||||
FCGX_FPrintF(req->out,
|
||||
"Status: %s\r\n"
|
||||
"Location: %s\r\n\r\n"
|
||||
REDIR_HTML_BEGIN "Redirecting to <a href=\"\">%s</a>..." REDIR_HTML_END,
|
||||
status,
|
||||
loc_str,
|
||||
loc_str);
|
||||
PRINTF("Status: %s\r\n"
|
||||
"Location: %s\r\n\r\n"
|
||||
REDIR_HTML_BEGIN "Redirecting to <a href=\"\">%s</a>..." REDIR_HTML_END,
|
||||
status,
|
||||
loc_str,
|
||||
loc_str);
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
|
||||
#ifndef HTTP_H
|
||||
#define HTTP_H
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include "request.h"
|
||||
#include "cgi.h"
|
||||
|
||||
#define REDIRECT_303 "303 See Other"
|
||||
|
||||
void redirect(FCGX_Request* req, char* status, char* location);
|
||||
void redirect(REQUEST_T req, char* status, char* location);
|
||||
|
||||
#endif // HTTP_H
|
||||
|
|
88
src/lists.c
88
src/lists.c
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "global_perl.h"
|
||||
#include "helpers.h"
|
||||
#include "base_page.h"
|
||||
#include "../config.h"
|
||||
|
@ -28,55 +29,14 @@
|
|||
#include "lists.h"
|
||||
#include "string_helpers.h"
|
||||
#include "http.h"
|
||||
// Files
|
||||
#include "../static/account.ctmpl"
|
||||
#include "../static/list.ctmpl"
|
||||
#include "../static/lists.ctmpl"
|
||||
|
||||
char* construct_list(struct mstdnt_list* list, size_t* size)
|
||||
{
|
||||
char* result;
|
||||
char* title = list->title;
|
||||
char* list_name = sanitize_html(title);
|
||||
struct list_template data = {
|
||||
.list = list_name,
|
||||
.prefix = config_url_prefix,
|
||||
.list_id = list->id
|
||||
};
|
||||
result = tmpl_gen_list(&data, size);
|
||||
if (list_name != title)
|
||||
free(list_name);
|
||||
return result;
|
||||
}
|
||||
|
||||
static char* construct_list_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
return construct_list((struct mstdnt_list*)passed + index, res);
|
||||
}
|
||||
|
||||
char* construct_lists(struct mstdnt_list* lists, size_t size, size_t* ret_size)
|
||||
{
|
||||
return construct_func_strings(construct_list_voidwrap, lists, size, ret_size);
|
||||
}
|
||||
|
||||
char* construct_lists_view(char* lists_string, size_t* size)
|
||||
{
|
||||
struct lists_template data = {
|
||||
.lists = lists_string,
|
||||
.prefix = config_url_prefix
|
||||
};
|
||||
return tmpl_gen_lists(&data, size);
|
||||
}
|
||||
|
||||
void content_lists(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_list* lists = NULL;
|
||||
size_t size_list = 0;
|
||||
size_t lists_len = 0;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
char* lists_format = NULL;
|
||||
char* lists_page = NULL;
|
||||
|
||||
if (ssn->post.title.is_set)
|
||||
{
|
||||
|
@ -89,20 +49,24 @@ void content_lists(PATH_ARGS)
|
|||
mastodont_storage_cleanup(&create_storage);
|
||||
}
|
||||
|
||||
if (mastodont_get_lists(api, &m_args, &storage, &lists, &size_list))
|
||||
{
|
||||
lists_page = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
lists_format = construct_lists(lists, size_list, NULL);
|
||||
if (!lists_format)
|
||||
lists_format = construct_error("No lists", E_ERROR, 1, NULL);
|
||||
lists_page = construct_lists_view(lists_format, NULL);
|
||||
}
|
||||
mastodont_get_lists(api, &m_args, &storage, &lists, &lists_len);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
if (lists)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_lists(lists, lists_len)));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("lists::content_lists");
|
||||
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_LISTS,
|
||||
.content = lists_page,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
|
@ -111,9 +75,8 @@ void content_lists(PATH_ARGS)
|
|||
|
||||
// Cleanup
|
||||
mastodont_storage_cleanup(&storage);
|
||||
if (lists_format) free(lists_format);
|
||||
if (lists_page) free(lists_page);
|
||||
mstdnt_cleanup_lists(lists);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
void list_edit(PATH_ARGS)
|
||||
|
@ -139,3 +102,18 @@ void list_edit(PATH_ARGS)
|
|||
redirect(req, REDIRECT_303, referer);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
}
|
||||
|
||||
HV* perlify_list(const struct mstdnt_list* list)
|
||||
{
|
||||
if (!list) return NULL;
|
||||
|
||||
HV* list_hv = newHV();
|
||||
hvstores_str(list_hv, "id", list->id);
|
||||
hvstores_str(list_hv, "title", list->title);
|
||||
// hvstores_int(list_hv, "replies_policy", list->replies_policy);
|
||||
|
||||
return list_hv;
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(list, lists, mstdnt_list)
|
||||
|
||||
|
|
11
src/lists.h
11
src/lists.h
|
@ -22,10 +22,15 @@
|
|||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
|
||||
char* construct_list(struct mstdnt_list* list, size_t* size);
|
||||
char* construct_lists(struct mstdnt_list* lists, size_t size, size_t* ret_size);
|
||||
char* construct_lists_view(char* lists_string, size_t* size);
|
||||
/** Creates the main lists view */
|
||||
void content_lists(PATH_ARGS);
|
||||
/** Creates a list and then redirects */
|
||||
void list_edit(PATH_ARGS);
|
||||
|
||||
/** Converts list to perl hash */
|
||||
HV* perlify_list(const struct mstdnt_list* list);
|
||||
|
||||
/** Converts lists to perl array */
|
||||
AV* perlify_lists(const struct mstdnt_list* lists, size_t len);
|
||||
|
||||
#endif // LISTS_H
|
||||
|
|
47
src/local_config.c
Normal file
47
src/local_config.c
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "global_perl.h"
|
||||
#include "local_config.h"
|
||||
|
||||
HV* perlify_config(struct local_config* config)
|
||||
{
|
||||
HV* ssn_config_hv = newHV();
|
||||
|
||||
hv_stores(ssn_config_hv, "logged_in", newSVpv(config->logged_in, 0));
|
||||
hv_stores(ssn_config_hv, "theme", newSVpv(config->theme, 0));
|
||||
hv_stores(ssn_config_hv, "background_url", newSVpv(config->background_url, 0));
|
||||
hv_stores(ssn_config_hv, "lang", newSViv(config->lang));
|
||||
hv_stores(ssn_config_hv, "jsactions", newSViv(config->jsactions));
|
||||
hv_stores(ssn_config_hv, "jslive", newSViv(config->jslive));
|
||||
hv_stores(ssn_config_hv, "js", newSViv(config->js));
|
||||
hv_stores(ssn_config_hv, "interact_img", newSViv(config->interact_img));
|
||||
hv_stores(ssn_config_hv, "stat_attachments", newSViv(config->stat_attachments));
|
||||
hv_stores(ssn_config_hv, "stat_greentexts", newSViv(config->stat_greentexts));
|
||||
hv_stores(ssn_config_hv, "stat_dope", newSViv(config->stat_dope));
|
||||
hv_stores(ssn_config_hv, "stat_oneclicksoftware", newSViv(config->stat_oneclicksoftware));
|
||||
hv_stores(ssn_config_hv, "stat_emojo_likes", newSViv(config->stat_emojo_likes));
|
||||
hv_stores(ssn_config_hv, "stat_hide_muted", newSViv(config->stat_hide_muted));
|
||||
hv_stores(ssn_config_hv, "instance_show_shoutbox", newSViv(config->instance_show_shoutbox));
|
||||
hv_stores(ssn_config_hv, "instance_panel", newSViv(config->instance_panel));
|
||||
hv_stores(ssn_config_hv, "notif_embed", newSViv(config->notif_embed));
|
||||
hv_stores(ssn_config_hv, "sidebar_opacity", newSViv(config->sidebar_opacity));
|
||||
|
||||
return ssn_config_hv;
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#ifndef LOCAL_CONFIG_H
|
||||
#define LOCAL_CONFIG_H
|
||||
#include "global_perl.h"
|
||||
#include "query.h"
|
||||
|
||||
struct local_config
|
||||
|
@ -26,7 +27,6 @@ struct local_config
|
|||
char* theme;
|
||||
char* background_url;
|
||||
int lang;
|
||||
int themeclr;
|
||||
int jsactions;
|
||||
int jsreply;
|
||||
int jslive;
|
||||
|
@ -44,4 +44,6 @@ struct local_config
|
|||
int sidebar_opacity;
|
||||
};
|
||||
|
||||
HV* perlify_config(struct local_config* config);
|
||||
|
||||
#endif // LOCAL_CONFIG_H
|
||||
|
|
|
@ -34,9 +34,8 @@ void set_config_str(FCGX_Request* req,
|
|||
{
|
||||
if (ssn->post.set.is_set && post->is_set && page == curr_page)
|
||||
{
|
||||
FCGX_FPrintF(req->out,
|
||||
"Set-Cookie: %s=%s; HttpOnly; Path=/; Max-Age=31536000; SameSite=Strict;\r\n",
|
||||
cookie_name, keypstr(post));
|
||||
PRINTF("Set-Cookie: %s=%s; HttpOnly; Path=/; Max-Age=31536000; SameSite=Strict;\r\n",
|
||||
cookie_name, keypstr(post));
|
||||
}
|
||||
|
||||
if ((ssn->post.set.is_set && post->is_set) || cookie->is_set)
|
||||
|
@ -60,9 +59,8 @@ void set_config_int(FCGX_Request* req,
|
|||
{
|
||||
if (ssn->post.set.is_set && page == curr_page)
|
||||
{
|
||||
FCGX_FPrintF(req->out,
|
||||
"Set-Cookie: %s=%d; HttpOnly; Path=/; Max-Age=31536000; SameSite=Strict;\r\n",
|
||||
cookie_name, post_bool_intp(post));
|
||||
PRINTF("Set-Cookie: %s=%d; HttpOnly; Path=/; Max-Age=31536000; SameSite=Strict;\r\n",
|
||||
cookie_name, post_bool_intp(post));
|
||||
}
|
||||
|
||||
// Checks if boolean option
|
||||
|
@ -93,7 +91,6 @@ struct mstdnt_storage* load_config(FCGX_Request* req,
|
|||
set_config_str(req, ssn, &(ssn->config.background_url), "background_url", &(atm), &(ssn->cookies.background_url), page, CONFIG_APPEARANCE);
|
||||
set_config_int(LOAD_CFG_SIM("sidebaropacity", sidebar_opacity), CONFIG_APPEARANCE);
|
||||
set_config_str(LOAD_CFG_SIM("theme", theme), CONFIG_APPEARANCE);
|
||||
set_config_int(LOAD_CFG_SIM("themeclr", themeclr), CONFIG_APPEARANCE);
|
||||
set_config_int(LOAD_CFG_SIM("jsactions", jsactions), CONFIG_GENERAL);
|
||||
set_config_int(LOAD_CFG_SIM("jsreply", jsreply), CONFIG_GENERAL);
|
||||
set_config_int(LOAD_CFG_SIM("jslive", jslive), CONFIG_GENERAL);
|
||||
|
|
|
@ -19,12 +19,11 @@
|
|||
#ifndef LOCAL_CONFIG_SET_H
|
||||
#define LOCAL_CONFIG_SET_H
|
||||
#include <mastodont.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include "local_config.h"
|
||||
#include "session.h"
|
||||
#include "attachments.h"
|
||||
#include "local_config.h"
|
||||
#include "key.h"
|
||||
#include "cgi.h"
|
||||
|
||||
enum config_page
|
||||
{
|
||||
|
|
57
src/login.c
57
src/login.c
|
@ -16,28 +16,26 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include "login.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "helpers.h"
|
||||
#include "query.h"
|
||||
#include "base_page.h"
|
||||
#include "login.h"
|
||||
#include "error.h"
|
||||
#include "easprintf.h"
|
||||
#include "../config.h"
|
||||
#include "http.h"
|
||||
|
||||
// Files
|
||||
#include "../static/login.ctmpl"
|
||||
#include <curl/curl.h>
|
||||
#include "cgi.h"
|
||||
#include "request.h"
|
||||
|
||||
#define LOGIN_SCOPE "read+write+follow+push"
|
||||
|
||||
void apply_access_token(FCGX_Request* req, char* token)
|
||||
static void apply_access_token(REQUEST_T req, char* token)
|
||||
{
|
||||
FCGX_FPrintF(req->out, "Set-Cookie: access_token=%s; Path=/; Max-Age=31536000\r\n", token);
|
||||
FCGX_FPrintF(req->out, "Set-Cookie: logged_in=t; Path=/; Max-Age=31536000\r\n");
|
||||
PRINTF("Set-Cookie: access_token=%s; Path=/; Max-Age=31536000\r\n", token);
|
||||
PUT("Set-Cookie: logged_in=t; Path=/; Max-Age=31536000\r\n");
|
||||
// if config_url_prefix is empty, make it root
|
||||
redirect(req, REDIRECT_303, config_url_prefix &&
|
||||
config_url_prefix[0] != '\0' ? config_url_prefix : "/");
|
||||
|
@ -102,9 +100,9 @@ void content_login_oauth(PATH_ARGS)
|
|||
decode_url, encode_id, urlify_redirect_url);
|
||||
|
||||
// Set cookie and redirect
|
||||
FCGX_FPrintF(req->out, "Set-Cookie: instance_url=%s; Path=/; Max-Age=3153600\r\n", decode_url);
|
||||
FCGX_FPrintF(req->out, "Set-Cookie: client_id=%s; Path=/; Max-Age=3153600\r\n", app.client_id);
|
||||
FCGX_FPrintF(req->out, "Set-Cookie: client_secret=%s; Path=/; Max-Age=3153600\r\n", app.client_secret);
|
||||
PRINTF("Set-Cookie: instance_url=%s; Path=/; Max-Age=3153600\r\n", decode_url);
|
||||
PRINTF("Set-Cookie: client_id=%s; Path=/; Max-Age=3153600\r\n", app.client_id);
|
||||
PRINTF("Set-Cookie: client_secret=%s; Path=/; Max-Age=3153600\r\n", app.client_secret);
|
||||
|
||||
redirect(req, REDIRECT_303, url);
|
||||
free(url);
|
||||
|
@ -163,7 +161,7 @@ void content_login(PATH_ARGS)
|
|||
|
||||
if (mastodont_register_app(api, &m_args, &args_app, &storage, &app) != 0)
|
||||
{
|
||||
error = construct_error(oauth_store.error, E_ERROR, 1, NULL);
|
||||
// error = construct_error(oauth_store.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
struct mstdnt_application_args args_token = {
|
||||
|
@ -183,14 +181,14 @@ void content_login(PATH_ARGS)
|
|||
&oauth_store,
|
||||
&token) != 0 && oauth_store.error)
|
||||
{
|
||||
error = construct_error(oauth_store.error, E_ERROR, 1, NULL);
|
||||
//error = construct_error(oauth_store.error, E_ERROR, 1, NULL);
|
||||
}
|
||||
else {
|
||||
if (url_link)
|
||||
FCGX_FPrintF(req->out, "Set-Cookie: instance_url=%s; Path=/; Max-Age=31536000\r\n", url_link);
|
||||
PRINTF("Set-Cookie: instance_url=%s; Path=/; Max-Age=31536000\r\n", url_link);
|
||||
else
|
||||
// Clear
|
||||
FCGX_FPrintF(req->out, "Set-Cookie: instance_url=; Path=/; Max-Age=-1\r\n");
|
||||
PUT("Set-Cookie: instance_url=; Path=/; Max-Age=-1\r\n");
|
||||
|
||||
apply_access_token(req, token.access_token);
|
||||
free(url_link);
|
||||
|
@ -206,23 +204,21 @@ void content_login(PATH_ARGS)
|
|||
}
|
||||
}
|
||||
|
||||
// Concat
|
||||
struct login_template tdata = {
|
||||
.login_header = L10N[L10N_EN_US][L10N_LOGIN],
|
||||
.error = error,
|
||||
.prefix = config_url_prefix,
|
||||
.username = L10N[L10N_EN_US][L10N_USERNAME],
|
||||
.password = L10N[L10N_EN_US][L10N_PASSWORD],
|
||||
.login_submit = L10N[L10N_EN_US][L10N_LOGIN_BTN],
|
||||
.instance_text = "Or",
|
||||
.instance_url = "Instance url",
|
||||
.instance_submit = "Authorize"
|
||||
};
|
||||
page = tmpl_gen_login(&tdata, NULL);
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
if (storage.error || oauth_store.error)
|
||||
mXPUSHs(newSVpv(storage.error ? storage.error : oauth_store.error, 0));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("login::content_login");
|
||||
|
||||
page = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = page,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
|
@ -232,6 +228,5 @@ void content_login(PATH_ARGS)
|
|||
// Cleanup
|
||||
mastodont_storage_cleanup(&storage);
|
||||
mastodont_storage_cleanup(&oauth_store);
|
||||
if (error) free(error);
|
||||
if (page) free(page);
|
||||
Safefree(page);
|
||||
}
|
||||
|
|
94
src/main.c
94
src/main.c
|
@ -16,9 +16,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <EXTERN.h>
|
||||
#include <perl.h>
|
||||
#include "global_perl.h"
|
||||
#include <pthread.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include <string.h>
|
||||
#include <mastodont.h>
|
||||
#include <stdlib.h>
|
||||
|
@ -37,16 +38,30 @@
|
|||
#include "timeline.h"
|
||||
#include "session.h"
|
||||
#include "notifications.h"
|
||||
#include "test.h"
|
||||
#include "env.h"
|
||||
#include "search.h"
|
||||
#include "about.h"
|
||||
#include "local_config_set.h"
|
||||
#include "global_cache.h"
|
||||
#include "conversations.h"
|
||||
#include "request.h"
|
||||
#include "cgi.h"
|
||||
|
||||
#define THREAD_COUNT 20
|
||||
|
||||
// Allow dynamic loading for Perl
|
||||
static void xs_init (pTHX);
|
||||
void boot_DynaLoader (pTHX_ CV* cv);
|
||||
|
||||
#ifdef DEBUG
|
||||
static int quit = 0;
|
||||
static void exit_treebird(PATH_ARGS)
|
||||
{
|
||||
quit = 1;
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*******************
|
||||
* Path handling *
|
||||
******************/
|
||||
|
@ -57,7 +72,6 @@ static struct path_info paths[] = {
|
|||
{ "/config", content_config },
|
||||
{ "/login/oauth", content_login_oauth },
|
||||
{ "/login", content_login },
|
||||
{ "/test", content_test },
|
||||
{ "/user/:/action/:", content_account_action },
|
||||
{ "/user/:", content_account_statuses },
|
||||
{ "/@:/scrobbles", content_account_scrobbles },
|
||||
|
@ -95,24 +109,30 @@ static struct path_info paths[] = {
|
|||
{ "/blocked", content_account_blocked },
|
||||
{ "/muted", content_account_muted },
|
||||
{ "/notifications_compact", content_notifications_compact },
|
||||
{ "/notification/:/read", content_notifications_read },
|
||||
{ "/notification/:/delete", content_notifications_clear },
|
||||
{ "/notifications/read", content_notifications_read },
|
||||
{ "/notifications/clear", content_notifications_clear },
|
||||
{ "/notifications", content_notifications },
|
||||
{ "/tag/:", content_tl_tag },
|
||||
{ "/chats/:", content_chat_view },
|
||||
{ "/chats", content_chats },
|
||||
{ "/chats_embed/:", content_chat_embed },
|
||||
#ifdef DEBUG
|
||||
{ "/quit", exit_treebird },
|
||||
{ "/exit", exit_treebird },
|
||||
#endif
|
||||
// API
|
||||
{ "/treebird_api/v1/notifications", api_notifications },
|
||||
{ "/treebird_api/v1/interact", api_status_interact },
|
||||
{ "/treebird_api/v1/attachment", api_attachment_create },
|
||||
};
|
||||
|
||||
static void application(mastodont_t* api, FCGX_Request* req)
|
||||
static void application(mastodont_t* api, REQUEST_T req)
|
||||
{
|
||||
// Default config
|
||||
struct session ssn = {
|
||||
.config = {
|
||||
.theme = "treebird20",
|
||||
.themeclr = 0,
|
||||
.lang = L10N_EN_US,
|
||||
.jsactions = 1,
|
||||
.jsreply = 1,
|
||||
|
@ -166,7 +186,8 @@ static void application(mastodont_t* api, FCGX_Request* req)
|
|||
cleanup_media_storages(&ssn, attachments);
|
||||
}
|
||||
|
||||
static void* cgi_start(void* arg)
|
||||
#ifndef SINGLE_THREADED
|
||||
static void* threaded_fcgi_start(void* arg)
|
||||
{
|
||||
mastodont_t* api = arg;
|
||||
int rc;
|
||||
|
@ -189,12 +210,46 @@ static void* cgi_start(void* arg)
|
|||
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
void cgi_start(mastodont_t* api)
|
||||
{
|
||||
while (FCGI_Accept() >= 0 && quit == 0)
|
||||
{
|
||||
application(api, NULL);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(void)
|
||||
void xs_init(pTHX)
|
||||
{
|
||||
static const char file[] = __FILE__;
|
||||
dXSUB_SYS;
|
||||
PERL_UNUSED_CONTEXT;
|
||||
|
||||
newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv, char **env)
|
||||
{
|
||||
// Global init
|
||||
mastodont_global_curl_init();
|
||||
#ifndef SINGLE_THREADED
|
||||
FCGX_Init();
|
||||
#endif
|
||||
|
||||
// Initialize Perl
|
||||
PERL_SYS_INIT3(&argc, &argv, &env);
|
||||
my_perl = perl_alloc();
|
||||
perl_construct(my_perl);
|
||||
//char* perl_argv[] = { "", "-e", data_main_pl, NULL };
|
||||
char* perl_argv[] = { "", "-I", "perl/", "perl/main.pl", NULL };
|
||||
|
||||
perl_parse(my_perl, xs_init, (sizeof(perl_argv) / sizeof(perl_argv[0])) - 1, perl_argv, (char**)NULL);
|
||||
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
|
||||
PL_perl_destruct_level = 1;
|
||||
perl_run(my_perl);
|
||||
|
||||
init_template_files(aTHX);
|
||||
|
||||
// Initiate mastodont library
|
||||
mastodont_t api;
|
||||
|
@ -202,17 +257,32 @@ int main(void)
|
|||
// Fetch information about the current instance
|
||||
load_instance_info_cache(&api);
|
||||
|
||||
#ifndef SINGLE_THREADED
|
||||
// Start thread pool
|
||||
pthread_t id[THREAD_COUNT];
|
||||
|
||||
for (unsigned i = 0; i < THREAD_COUNT; ++i)
|
||||
pthread_create(&id[i], NULL, cgi_start, &api);
|
||||
pthread_create(&id[i], NULL, threaded_fcgi_start, &api);
|
||||
|
||||
// Hell, let's not sit around here either
|
||||
threaded_fcgi_start(&api);
|
||||
|
||||
FCGX_ShutdownPending();
|
||||
|
||||
for (unsigned i = 0; i < THREAD_COUNT; ++i)
|
||||
pthread_join(id[i], NULL);
|
||||
#else
|
||||
cgi_start(&api);
|
||||
|
||||
#endif
|
||||
|
||||
free_instance_info_cache();
|
||||
mastodont_cleanup(&api);
|
||||
mastodont_global_curl_cleanup();
|
||||
mastodont_cleanup(&api);
|
||||
|
||||
cleanup_template_files();
|
||||
|
||||
perl_destruct(my_perl);
|
||||
perl_free(my_perl);
|
||||
PERL_SYS_TERM();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "navigation.h"
|
||||
#include "easprintf.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/navigation.ctmpl"
|
||||
|
||||
#define SUBMIT_HTML "<input type=\"submit\" class=\"hidden\">"
|
||||
|
||||
char* construct_navigation_box(char* start_id,
|
||||
char* prev_id,
|
||||
char* next_id,
|
||||
size_t* size)
|
||||
{
|
||||
int is_start = start_id && prev_id ? strcmp(start_id, prev_id) == 0 : 0;
|
||||
|
||||
struct navigation_template tdata = {
|
||||
.start_id = start_id,
|
||||
.min_id = prev_id,
|
||||
.prev_active = is_start ? "btn-disabled" : NULL,
|
||||
.prev_submit = is_start ? "" : SUBMIT_HTML,
|
||||
.max_id = next_id
|
||||
};
|
||||
return tmpl_gen_navigation(&tdata, size);
|
||||
}
|
|
@ -25,253 +25,76 @@
|
|||
#include "base_page.h"
|
||||
#include "string_helpers.h"
|
||||
#include "easprintf.h"
|
||||
#include "navigation.h"
|
||||
#include "http.h"
|
||||
#include "status.h"
|
||||
#include "error.h"
|
||||
#include "emoji.h"
|
||||
#include "account.h"
|
||||
#include "../config.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/notifications_page.ctmpl"
|
||||
#include "../static/notifications.ctmpl"
|
||||
#include "../static/notification_action.ctmpl"
|
||||
#include "../static/notification.ctmpl"
|
||||
#include "../static/notification_compact.ctmpl"
|
||||
#include "../static/like_svg.ctmpl"
|
||||
#include "../static/repeat_svg.ctmpl"
|
||||
#include "../static/notifications_embed.ctmpl"
|
||||
|
||||
struct notification_args
|
||||
{
|
||||
struct session* ssn;
|
||||
mastodont_t* api;
|
||||
struct mstdnt_notification* notifs;
|
||||
};
|
||||
|
||||
char* construct_notification(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_notification* notif,
|
||||
size_t* size)
|
||||
{
|
||||
char* notif_html;
|
||||
|
||||
if (notif->status)
|
||||
{
|
||||
// Construct status with notification_info
|
||||
notif_html = construct_status(ssn, api, notif->status, size, notif, NULL, 0);
|
||||
}
|
||||
else {
|
||||
notif_html = construct_notification_action(notif, size);
|
||||
}
|
||||
|
||||
return notif_html;
|
||||
}
|
||||
|
||||
char* construct_notification_action(struct mstdnt_notification* notif, size_t* size)
|
||||
{
|
||||
char* res;
|
||||
char* serialized_display_name = sanitize_html(notif->account->display_name);
|
||||
char* display_name = emojify(serialized_display_name,
|
||||
notif->account->emojis,
|
||||
notif->account->emojis_len);
|
||||
struct notification_action_template tdata = {
|
||||
.avatar = notif->account->avatar,
|
||||
.acct = notif->account->acct,
|
||||
.display_name = display_name,
|
||||
.prefix = config_url_prefix,
|
||||
.action = notification_type_compact_str(notif->type),
|
||||
.notif_svg = notification_type_svg(notif->type)
|
||||
};
|
||||
res = tmpl_gen_notification_action(&tdata, size);
|
||||
/* // Cleanup */
|
||||
if (display_name != notif->account->display_name &&
|
||||
display_name != serialized_display_name)
|
||||
free(display_name);
|
||||
if (serialized_display_name != notif->account->display_name)
|
||||
free(serialized_display_name);
|
||||
return res;
|
||||
}
|
||||
|
||||
char* construct_notification_compact(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_notification* notif,
|
||||
size_t* size)
|
||||
{
|
||||
char* notif_html;
|
||||
char* status_format = NULL;
|
||||
char* notif_stats = NULL;
|
||||
|
||||
const char* type_str = notification_type_compact_str(notif->type);
|
||||
const char* type_svg = notification_type_svg(notif->type);
|
||||
|
||||
if (notif->status)
|
||||
{
|
||||
if (notif->type == MSTDNT_NOTIFICATION_MENTION)
|
||||
notif_stats = construct_interaction_buttons(ssn, notif->status, NULL,
|
||||
STATUS_NO_LIKEBOOST | STATUS_NO_DOPAMEME);
|
||||
status_format = reformat_status(ssn,
|
||||
notif->status->content,
|
||||
notif->status->emojis,
|
||||
notif->status->emojis_len);
|
||||
}
|
||||
|
||||
char* serialized_display_name = sanitize_html(notif->account->display_name);
|
||||
char* display_name = emojify(serialized_display_name,
|
||||
notif->account->emojis,
|
||||
notif->account->emojis_len);
|
||||
struct notification_compact_template tdata = {
|
||||
.avatar = notif->account->avatar,
|
||||
.has_icon = strlen(type_svg) == 0 ? "" : "-with-icon",
|
||||
.acct = notif->account->acct,
|
||||
.display_name = display_name,
|
||||
.action = type_str,
|
||||
.notif_svg = type_svg,
|
||||
.is_status = (notif->type == MSTDNT_NOTIFICATION_STATUS ||
|
||||
notif->type == MSTDNT_NOTIFICATION_MENTION ? "is-mention" : NULL),
|
||||
/* Might show follower address */
|
||||
.content = (notif->type == MSTDNT_NOTIFICATION_FOLLOW ?
|
||||
notif->account->acct : status_format),
|
||||
.stats = notif_stats
|
||||
};
|
||||
|
||||
notif_html = tmpl_gen_notification_compact(&tdata, size);
|
||||
|
||||
if (status_format &&
|
||||
status_format != notif->status->content) free(status_format);
|
||||
if (notif_stats) free(notif_stats);
|
||||
if (serialized_display_name != notif->account->display_name)
|
||||
free(serialized_display_name);
|
||||
if (display_name != notif->account->display_name &&
|
||||
display_name != serialized_display_name)
|
||||
free(display_name);
|
||||
return notif_html;
|
||||
}
|
||||
|
||||
static char* construct_notification_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
struct notification_args* args = passed;
|
||||
return construct_notification(args->ssn, args->api, args->notifs + index, res);
|
||||
}
|
||||
|
||||
static char* construct_notification_compact_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
struct notification_args* args = passed;
|
||||
return construct_notification_compact(args->ssn, args->api, args->notifs + index, res);
|
||||
}
|
||||
|
||||
char* construct_notifications(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_notification* notifs,
|
||||
size_t size,
|
||||
size_t* ret_size)
|
||||
{
|
||||
struct notification_args args = {
|
||||
.ssn = ssn,
|
||||
.api = api,
|
||||
.notifs = notifs
|
||||
};
|
||||
return construct_func_strings(construct_notification_voidwrap, &args, size, ret_size);
|
||||
}
|
||||
|
||||
char* construct_notifications_compact(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_notification* notifs,
|
||||
size_t size,
|
||||
size_t* ret_size)
|
||||
{
|
||||
struct notification_args args = {
|
||||
.ssn = ssn,
|
||||
.api = api,
|
||||
.notifs = notifs
|
||||
};
|
||||
return construct_func_strings(construct_notification_compact_voidwrap,
|
||||
&args,
|
||||
size,
|
||||
ret_size);
|
||||
}
|
||||
|
||||
void content_notifications(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
char* page, *notif_html = NULL;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_notification* notifs = NULL;
|
||||
size_t notifs_len = 0;
|
||||
char* start_id;
|
||||
char* navigation_box = NULL;
|
||||
|
||||
if (keystr(ssn->cookies.logged_in))
|
||||
{
|
||||
struct mstdnt_get_notifications_args args = {
|
||||
.exclude_types = 0,
|
||||
.account_id = NULL,
|
||||
.exclude_visibilities = 0,
|
||||
.include_types = 0,
|
||||
.with_muted = 1,
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.since_id = NULL,
|
||||
.offset = 0,
|
||||
.limit = 20,
|
||||
};
|
||||
|
||||
if (mastodont_get_notifications(api, &m_args, &args, &storage, ¬ifs, ¬ifs_len) == 0)
|
||||
{
|
||||
if (notifs && notifs_len)
|
||||
{
|
||||
notif_html = construct_notifications(ssn, api, notifs, notifs_len, NULL);
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : notifs[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
notifs[0].id,
|
||||
notifs[notifs_len-1].id,
|
||||
NULL);
|
||||
mstdnt_cleanup_notifications(notifs, notifs_len);
|
||||
}
|
||||
else
|
||||
notif_html = construct_error("No notifications", E_NOTICE, 1, NULL);
|
||||
}
|
||||
else
|
||||
notif_html = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
|
||||
}
|
||||
|
||||
struct notifications_page_template tdata = {
|
||||
.notifications = notif_html,
|
||||
.navigation = navigation_box
|
||||
|
||||
struct mstdnt_notifications_args args = {
|
||||
.exclude_types = 0,
|
||||
.account_id = NULL,
|
||||
.exclude_visibilities = 0,
|
||||
.include_types = 0,
|
||||
.with_muted = 1,
|
||||
.max_id = keystr(ssn->post.max_id),
|
||||
.min_id = keystr(ssn->post.min_id),
|
||||
.since_id = NULL,
|
||||
.offset = 0,
|
||||
.limit = 20,
|
||||
};
|
||||
|
||||
page = tmpl_gen_notifications_page(&tdata, NULL);
|
||||
if (keystr(ssn->cookies.logged_in))
|
||||
mastodont_get_notifications(api, &m_args, &args, &storage, ¬ifs, ¬ifs_len);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
mXPUSHs(newRV_inc((SV*)session_hv));
|
||||
mXPUSHs(newRV_inc((SV*)template_files));
|
||||
if (notifs)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_notifications(notifs, notifs_len)));
|
||||
|
||||
// ARGS
|
||||
PERL_STACK_SCALAR_CALL("notifications::content_notifications");
|
||||
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NOTIFICATIONS,
|
||||
.content = page,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
if (notif_html) free(notif_html);
|
||||
if (navigation_box) free(navigation_box);
|
||||
if (page) free(page);
|
||||
mstdnt_cleanup_notifications(notifs, notifs_len);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
void content_notifications_compact(PATH_ARGS)
|
||||
{
|
||||
char* theme_str = NULL;
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
char* page, *notif_html = NULL;
|
||||
char* page;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_notification* notifs = NULL;
|
||||
size_t notifs_len = 0;
|
||||
char* start_id = NULL;
|
||||
char* navigation_box = NULL;
|
||||
|
||||
if (keystr(ssn->cookies.logged_in))
|
||||
{
|
||||
struct mstdnt_get_notifications_args args = {
|
||||
struct mstdnt_notifications_args args = {
|
||||
.exclude_types = 0,
|
||||
.account_id = NULL,
|
||||
.exclude_visibilities = 0,
|
||||
|
@ -284,58 +107,99 @@ void content_notifications_compact(PATH_ARGS)
|
|||
.limit = 20,
|
||||
};
|
||||
|
||||
if (mastodont_get_notifications(api,
|
||||
&m_args,
|
||||
&args,
|
||||
&storage,
|
||||
¬ifs,
|
||||
¬ifs_len) == 0)
|
||||
{
|
||||
if (notifs && notifs_len)
|
||||
{
|
||||
notif_html = construct_notifications_compact(ssn, api, notifs, notifs_len, NULL);
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : notifs[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
notifs[0].id,
|
||||
notifs[notifs_len-1].id,
|
||||
NULL);
|
||||
mstdnt_cleanup_notifications(notifs, notifs_len);
|
||||
}
|
||||
else
|
||||
notif_html = construct_error("No notifications", E_NOTICE, 1, NULL);
|
||||
}
|
||||
else
|
||||
notif_html = construct_error(storage.error, E_ERROR, 1, NULL);
|
||||
|
||||
mastodont_get_notifications(api, &m_args, &args, &storage, ¬ifs, ¬ifs_len);
|
||||
}
|
||||
|
||||
// Set theme
|
||||
if (ssn->config.theme && !(strcmp(ssn->config.theme, "treebird") == 0 &&
|
||||
ssn->config.themeclr == 0))
|
||||
{
|
||||
easprintf(&theme_str, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/%s%s.css\">",
|
||||
ssn->config.theme,
|
||||
ssn->config.themeclr ? "-dark" : "");
|
||||
}
|
||||
|
||||
size_t len;
|
||||
struct notifications_embed_template tdata = {
|
||||
.theme_str = theme_str,
|
||||
.notifications = notif_html,
|
||||
.navigation_box = navigation_box
|
||||
};
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
mXPUSHs(newRV_noinc((SV*)session_hv));
|
||||
mXPUSHs(newRV_inc((SV*)template_files));
|
||||
if (notifs)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_notifications(notifs, notifs_len)));
|
||||
|
||||
page = tmpl_gen_notifications_embed(&tdata, &len);
|
||||
PERL_STACK_SCALAR_CALL("notifications::embed_notifications");
|
||||
|
||||
send_result(req, NULL, NULL, page, len);
|
||||
page = PERL_GET_STACK_EXIT;
|
||||
|
||||
send_result(req, NULL, NULL, page, 0);
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
free(notif_html);
|
||||
free(navigation_box);
|
||||
free(page);
|
||||
free(theme_str);
|
||||
mstdnt_cleanup_notifications(notifs, notifs_len);
|
||||
Safefree(page);
|
||||
}
|
||||
|
||||
void content_notifications_clear(PATH_ARGS)
|
||||
{
|
||||
char* referer = GET_ENV("HTTP_REFERER", req);
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
|
||||
if (data)
|
||||
{
|
||||
mastodont_notification_dismiss(api, &m_args, &storage, data[0]);
|
||||
}
|
||||
else {
|
||||
mastodont_notifications_clear(api, &m_args, &storage);
|
||||
}
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
redirect(req, REDIRECT_303, referer);
|
||||
}
|
||||
|
||||
void content_notifications_read(PATH_ARGS)
|
||||
{
|
||||
char* referer = GET_ENV("HTTP_REFERER", req);
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
|
||||
if (data)
|
||||
{
|
||||
struct mstdnt_notifications_args args = { .id = data[0] };
|
||||
mastodont_notifications_read(api, &m_args, &args, &storage, NULL);
|
||||
}
|
||||
else {
|
||||
struct mstdnt_notifications_args args = { .max_id = keystr(ssn->post.max_id) };
|
||||
mastodont_notifications_read(api, &m_args, &args, &storage, NULL);
|
||||
}
|
||||
|
||||
mastodont_storage_cleanup(&storage);
|
||||
redirect(req, REDIRECT_303, referer);
|
||||
}
|
||||
|
||||
// Converts it into a perl struct
|
||||
static HV* perlify_notification_pleroma(struct mstdnt_notification_pleroma* notif)
|
||||
{
|
||||
if (!notif) return NULL;
|
||||
HV* notif_pl_hv = newHV();
|
||||
|
||||
hvstores_int(notif_pl_hv, "is_muted", notif->is_muted);
|
||||
hvstores_int(notif_pl_hv, "is_seen", notif->is_seen);
|
||||
|
||||
return notif_pl_hv;
|
||||
}
|
||||
|
||||
|
||||
// Converts it into a perl struct
|
||||
HV* perlify_notification(const struct mstdnt_notification* const notif)
|
||||
{
|
||||
if (!notif) return NULL;
|
||||
HV* notif_hv = newHV();
|
||||
|
||||
hvstores_str(notif_hv, "id", notif->id);
|
||||
hvstores_int(notif_hv, "created_at", notif->created_at);
|
||||
hvstores_str(notif_hv, "emoji", notif->emoji);
|
||||
hvstores_str(notif_hv, "type", mstdnt_notification_t_to_str(notif->type));
|
||||
hvstores_ref(notif_hv, "account", perlify_account(notif->account));
|
||||
hvstores_ref(notif_hv, "pleroma", perlify_notification_pleroma(notif->pleroma));
|
||||
hvstores_ref(notif_hv, "status", perlify_status(notif->status));
|
||||
|
||||
return notif_hv;
|
||||
}
|
||||
|
||||
PERLIFY_MULTI(notification, notifications, mstdnt_notification)
|
||||
|
||||
void api_notifications(PATH_ARGS)
|
||||
{
|
||||
send_result(req, NULL, "application/json", "{\"status\":0}", 0);
|
||||
|
|
|
@ -19,36 +19,20 @@
|
|||
#ifndef NOTIFICATIONS_H
|
||||
#define NOTIFICATIONS_H
|
||||
#include <mastodont.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include "session.h"
|
||||
#include "path.h"
|
||||
#include "type_string.h"
|
||||
|
||||
char* construct_notification(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_notification* notif,
|
||||
size_t* size);
|
||||
char* construct_notification_action(struct mstdnt_notification* notif, size_t* size);
|
||||
char* construct_notification_compact(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_notification* notif,
|
||||
size_t* size);
|
||||
char* construct_notifications(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_notification* notifs,
|
||||
size_t size,
|
||||
size_t* ret_size);
|
||||
char* construct_notifications_compact(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_notification* notifs,
|
||||
size_t size,
|
||||
size_t* ret_size);
|
||||
#include "global_perl.h"
|
||||
#include "cgi.h"
|
||||
|
||||
// Page contents
|
||||
void content_notifications(PATH_ARGS);
|
||||
void content_notifications_compact(PATH_ARGS);
|
||||
void content_notifications_clear(PATH_ARGS);
|
||||
void content_notifications_read(PATH_ARGS);
|
||||
|
||||
void api_notifications(PATH_ARGS);
|
||||
|
||||
HV* perlify_notification(const struct mstdnt_notification* const notif);
|
||||
AV* perlify_notifications(const struct mstdnt_notification* const notif, size_t len);
|
||||
|
||||
#endif // NOTIFICATION_H
|
||||
|
|
|
@ -16,24 +16,20 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fcgi_stdio.h>
|
||||
#include "page_config.h"
|
||||
#include "global_perl.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "http.h"
|
||||
#include "base_page.h"
|
||||
#include "../config.h"
|
||||
#include "easprintf.h"
|
||||
#include "page_config.h"
|
||||
#include "query.h"
|
||||
#include "cookie.h"
|
||||
#include "local_config_set.h"
|
||||
#include "string_helpers.h"
|
||||
#include "l10n.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/config_general.ctmpl"
|
||||
#include "../static/config_appearance.ctmpl"
|
||||
#include "../static/config_sidebar.ctmpl"
|
||||
#include <fcgi_stdio.h>
|
||||
|
||||
#define bool_checked(key) (ssn->config.key ? "checked" : "")
|
||||
|
||||
|
@ -44,69 +40,51 @@ enum config_category
|
|||
CONFIG_CAT_ACCOUNT
|
||||
};
|
||||
|
||||
static char* construct_config_sidebar(enum config_category cat, size_t* size)
|
||||
{
|
||||
struct config_sidebar_template tdata = {
|
||||
.prefix = config_url_prefix,
|
||||
.general_active = CAT_TEXT(cat, CONFIG_CAT_GENERAL),
|
||||
.general = L10N[L10N_EN_US][L10N_GENERAL],
|
||||
.appearance_active = CAT_TEXT(cat, CONFIG_CAT_APPEARANCE),
|
||||
.appearance = L10N[L10N_EN_US][L10N_APPEARANCE],
|
||||
.account_active = CAT_TEXT(cat, CONFIG_CAT_ACCOUNT),
|
||||
.account = L10N[L10N_EN_US][L10N_ACCOUNT],
|
||||
};
|
||||
|
||||
return tmpl_gen_config_sidebar(&tdata, size);
|
||||
}
|
||||
|
||||
void content_config_general(PATH_ARGS)
|
||||
{
|
||||
char* sidebar_html = construct_config_sidebar(CONFIG_CAT_GENERAL, NULL);
|
||||
|
||||
struct config_general_template tdata = {
|
||||
.js_on = bool_checked(js),
|
||||
.jsactions_on = bool_checked(jsactions),
|
||||
.jsreply_on = bool_checked(jsreply),
|
||||
.jslive_on = bool_checked(jslive),
|
||||
.status_attachments_on = bool_checked(stat_attachments),
|
||||
.status_greentexts_on = bool_checked(stat_greentexts),
|
||||
.status_dopameme_on = bool_checked(stat_dope),
|
||||
.status_oneclicksoftware_on = bool_checked(stat_oneclicksoftware),
|
||||
.status_emojo_likes_on = bool_checked(stat_emojo_likes),
|
||||
.status_hide_muted_on = bool_checked(stat_hide_muted),
|
||||
.instance_show_shoutbox_on = bool_checked(instance_show_shoutbox),
|
||||
.instance_panel_on = bool_checked(instance_panel),
|
||||
.notifications_embed_on = bool_checked(notif_embed)
|
||||
};
|
||||
|
||||
char* general_page = tmpl_gen_config_general(&tdata, NULL);
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
mXPUSHs(newRV_inc((SV*)template_files));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("config::general");
|
||||
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_CONFIG,
|
||||
.content = general_page,
|
||||
.sidebar_left = sidebar_html
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, req, ssn, api);
|
||||
// Cleanup
|
||||
free(sidebar_html);
|
||||
free(general_page);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
|
||||
void content_config_appearance(PATH_ARGS)
|
||||
{
|
||||
char* sidebar_html = construct_config_sidebar(CONFIG_CAT_APPEARANCE, NULL);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("config::appearance");
|
||||
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_CONFIG,
|
||||
.content = data_config_appearance,
|
||||
.sidebar_left = sidebar_html
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, req, ssn, api);
|
||||
// Cleanup
|
||||
free(sidebar_html);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
void content_config(PATH_ARGS)
|
||||
|
|
|
@ -20,14 +20,13 @@
|
|||
#define PAGE_CONFIG_H
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include "path.h"
|
||||
#include "session.h"
|
||||
#include "cgi.h"
|
||||
|
||||
void content_config_appearance(PATH_ARGS);
|
||||
void content_config_general(PATH_ARGS);
|
||||
void content_config_account(PATH_ARGS);
|
||||
//void content_config_account(PATH_ARGS);
|
||||
void content_config(PATH_ARGS);
|
||||
|
||||
#endif // PAGE_CONFIG_H
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include "account.h"
|
||||
#include "error.h"
|
||||
|
||||
int parse_path(FCGX_Request* req,
|
||||
int parse_path(REQUEST_T req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct path_info* path_info)
|
||||
|
@ -101,7 +101,7 @@ breakpt:
|
|||
return res;
|
||||
}
|
||||
|
||||
void handle_paths(FCGX_Request* req,
|
||||
void handle_paths(REQUEST_T req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct path_info* paths,
|
||||
|
|
21
src/path.h
21
src/path.h
|
@ -18,14 +18,14 @@
|
|||
|
||||
#ifndef PATH_H
|
||||
#define PATH_H
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include "session.h"
|
||||
#include <mastodont.h>
|
||||
#include <stddef.h>
|
||||
#include "env.h"
|
||||
#include "session.h"
|
||||
#include "cgi.h"
|
||||
#include "request.h"
|
||||
|
||||
#define PATH_ARGS FCGX_Request* req, struct session* ssn, mastodont_t* api, char** data
|
||||
#define PATH_ARGS REQUEST_T req, struct session* ssn, mastodont_t* api, char** data
|
||||
|
||||
struct path_info
|
||||
{
|
||||
|
@ -33,13 +33,14 @@ struct path_info
|
|||
void (*callback)(PATH_ARGS);
|
||||
};
|
||||
|
||||
void handle_paths(FCGX_Request* req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct path_info* paths,
|
||||
size_t paths_len);
|
||||
void handle_paths(
|
||||
REQUEST_T req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct path_info* paths,
|
||||
size_t paths_len);
|
||||
|
||||
int parse_path(FCGX_Request* req,
|
||||
int parse_path(REQUEST_T req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct path_info* path_info);
|
||||
|
|
81
src/query.c
81
src/query.c
|
@ -16,17 +16,17 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include "query.h"
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "query.h"
|
||||
#include "env.h"
|
||||
#include "mime.h"
|
||||
#include "cgi.h"
|
||||
|
||||
char* read_get_data(FCGX_Request* req, struct get_values* query)
|
||||
|
||||
char* read_get_data(REQUEST_T req, struct get_values* query)
|
||||
{
|
||||
struct http_query_info info = { 0 };
|
||||
char* query_string = GET_ENV("QUERY_STRING", req);
|
||||
|
@ -37,6 +37,7 @@ char* read_get_data(FCGX_Request* req, struct get_values* query)
|
|||
{ "offset", &(query->offset), key_string },
|
||||
{ "q", &(query->query), key_string },
|
||||
{ "code", &(query->code), key_string },
|
||||
{ "type", &(query->type), key_int },
|
||||
};
|
||||
// END Query references
|
||||
|
||||
|
@ -75,7 +76,7 @@ char* read_get_data(FCGX_Request* req, struct get_values* query)
|
|||
|
||||
|
||||
|
||||
char* read_post_data(FCGX_Request* req, struct post_values* post)
|
||||
char* read_post_data(REQUEST_T req, struct post_values* post)
|
||||
{
|
||||
ptrdiff_t begin_curr_size;
|
||||
struct http_query_info query_info;
|
||||
|
@ -142,7 +143,11 @@ char* read_post_data(FCGX_Request* req, struct post_values* post)
|
|||
}
|
||||
|
||||
// fread should be a macro to FCGI_fread, which is set by FCGI_Accept in previous definitions
|
||||
#ifndef SINGLE_THREADED
|
||||
size_t len = FCGX_GetStr(post_query, content_length, req->in);
|
||||
#else
|
||||
size_t len = fread(post_query, 1, content_length, stdin);
|
||||
#endif
|
||||
post_query[content_length] = '\0';
|
||||
|
||||
// For shifting through
|
||||
|
@ -211,7 +216,7 @@ char* parse_query(char* begin, struct http_query_info* info)
|
|||
return end ? NULL : begin+1;
|
||||
}
|
||||
|
||||
char* try_handle_post(FCGX_Request* req, void (*call)(struct http_query_info*, void*), void* arg)
|
||||
char* try_handle_post(REQUEST_T req, void (*call)(struct http_query_info*, void*), void* arg)
|
||||
{
|
||||
char* request_method = GET_ENV("REQUEST_METHOD", req);
|
||||
char* post_query = NULL, * p_query_read;
|
||||
|
@ -228,10 +233,12 @@ char* try_handle_post(FCGX_Request* req, void (*call)(struct http_query_info*, v
|
|||
return NULL;
|
||||
}
|
||||
#ifdef SINGLE_THREADED
|
||||
read(STDIN_FILENO, post_query, content_length);
|
||||
int size = read(STDIN_FILENO, post_query, content_length);
|
||||
#else
|
||||
FCGX_GetStr(post_query, content_length, req->in);
|
||||
int size = FCGX_GetStr(post_query, content_length, req->in);
|
||||
#endif
|
||||
if (size != content_length)
|
||||
return NULL;
|
||||
post_query[content_length] = '\0';
|
||||
|
||||
|
||||
|
@ -262,3 +269,61 @@ void free_files(struct file_array* files)
|
|||
}
|
||||
free(content);
|
||||
}
|
||||
|
||||
// TODO use hvstores_XXX macros
|
||||
HV* perlify_post_values(struct post_values* post)
|
||||
{
|
||||
HV* ssn_post_hv = newHV();
|
||||
|
||||
// This ugly...
|
||||
hv_stores(ssn_post_hv, "theme", newSVpv(keystr(post->theme), 0));
|
||||
hv_stores(ssn_post_hv, "themeclr", newSViv(keyint(post->themeclr)));
|
||||
hv_stores(ssn_post_hv, "lang", newSViv(keyint(post->lang)));
|
||||
hv_stores(ssn_post_hv, "title", newSViv(keyint(post->title)));
|
||||
hv_stores(ssn_post_hv, "jsactions", newSViv(keyint(post->jsactions)));
|
||||
hv_stores(ssn_post_hv, "jsreply", newSViv(keyint(post->jsreply)));
|
||||
hv_stores(ssn_post_hv, "jslive", newSViv(keyint(post->jslive)));
|
||||
hv_stores(ssn_post_hv, "js", newSViv(keyint(post->js)));
|
||||
hv_stores(ssn_post_hv, "interact_img", newSViv(keyint(post->interact_img)));
|
||||
hv_stores(ssn_post_hv, "stat_attachments", newSViv(keyint(post->stat_attachments)));
|
||||
hv_stores(ssn_post_hv, "stat_greentexts", newSViv(keyint(post->stat_greentexts)));
|
||||
hv_stores(ssn_post_hv, "stat_dope", newSViv(keyint(post->stat_dope)));
|
||||
hv_stores(ssn_post_hv, "stat_oneclicksoftware", newSViv(keyint(post->stat_oneclicksoftware)));
|
||||
hv_stores(ssn_post_hv, "stat_emojo_likes", newSViv(keyint(post->stat_emojo_likes)));
|
||||
hv_stores(ssn_post_hv, "stat_hide_muted", newSViv(keyint(post->stat_hide_muted)));
|
||||
hv_stores(ssn_post_hv, "instance_show_shoutbox", newSViv(keyint(post->instance_show_shoutbox)));
|
||||
hv_stores(ssn_post_hv, "instance_panel", newSViv(keyint(post->instance_panel)));
|
||||
hv_stores(ssn_post_hv, "notif_embed", newSViv(keyint(post->notif_embed)));
|
||||
hv_stores(ssn_post_hv, "set", newSViv(keyint(post->set)));
|
||||
hv_stores(ssn_post_hv, "only_media", newSViv(keyint(post->only_media)));
|
||||
hv_stores(ssn_post_hv, "replies_only", newSViv(keyint(post->replies_only)));
|
||||
hv_stores(ssn_post_hv, "replies_policy", newSViv(keyint(post->replies_policy)));
|
||||
hv_stores(ssn_post_hv, "emojoindex", newSViv(keyint(post->emojoindex)));
|
||||
hv_stores(ssn_post_hv, "sidebar_opacity", newSViv(keyint(post->sidebar_opacity)));
|
||||
hv_stores(ssn_post_hv, "file_ids", newSVpv(keystr(post->file_ids), 0));
|
||||
hv_stores(ssn_post_hv, "content", newSVpv(keystr(post->content), 0));
|
||||
hv_stores(ssn_post_hv, "itype", newSVpv(keystr(post->itype), 0));
|
||||
hv_stores(ssn_post_hv, "id", newSVpv(keystr(post->id), 0));
|
||||
hv_stores(ssn_post_hv, "username", newSVpv(keystr(post->username), 0));
|
||||
hv_stores(ssn_post_hv, "password", newSVpv(keystr(post->password), 0));
|
||||
hv_stores(ssn_post_hv, "replyid", newSVpv(keystr(post->replyid), 0));
|
||||
hv_stores(ssn_post_hv, "visibility", newSVpv(keystr(post->visibility), 0));
|
||||
hv_stores(ssn_post_hv, "instance", newSVpv(keystr(post->instance), 0));
|
||||
hv_stores(ssn_post_hv, "min_id", newSVpv(keystr(post->min_id), 0));
|
||||
hv_stores(ssn_post_hv, "max_id", newSVpv(keystr(post->max_id), 0));
|
||||
hv_stores(ssn_post_hv, "start_id", newSVpv(keystr(post->start_id), 0));
|
||||
|
||||
return ssn_post_hv;
|
||||
}
|
||||
|
||||
HV* perlify_get_values(struct get_values* get)
|
||||
{
|
||||
HV* ssn_query_hv = newHV();
|
||||
|
||||
hv_stores(ssn_query_hv, "offset", newSVpv(keystr(get->offset), 0));
|
||||
hv_stores(ssn_query_hv, "query", newSVpv(keystr(get->query), 0));
|
||||
hv_stores(ssn_query_hv, "code", newSVpv(keystr(get->code), 0));
|
||||
hvstores_int(ssn_query_hv, "type", keyint(get->type));
|
||||
|
||||
return ssn_query_hv;
|
||||
}
|
||||
|
|
13
src/query.h
13
src/query.h
|
@ -18,9 +18,11 @@
|
|||
|
||||
#ifndef QUERY_H
|
||||
#define QUERY_H
|
||||
#include "global_perl.h"
|
||||
#include <fcgi_stdio.h>
|
||||
#include <stddef.h>
|
||||
#include "key.h"
|
||||
#include "request.h"
|
||||
|
||||
struct http_query_info
|
||||
{
|
||||
|
@ -79,14 +81,19 @@ struct get_values
|
|||
struct key offset; // String
|
||||
struct key query; // String
|
||||
struct key code; // String
|
||||
struct key type; // Int
|
||||
};
|
||||
|
||||
char* read_get_data(FCGX_Request* req, struct get_values* query);
|
||||
char* read_post_data(FCGX_Request* req, struct post_values* post);
|
||||
char* read_get_data(REQUEST_T req, struct get_values* query);
|
||||
char* read_post_data(REQUEST_T req, struct post_values* post);
|
||||
/* A stupidly quick query parser */
|
||||
char* parse_query(char* begin, struct http_query_info* info);
|
||||
char* try_handle_post(FCGX_Request* req, void (*call)(struct http_query_info*, void*), void* arg);
|
||||
char* try_handle_post(REQUEST_T req, void (*call)(struct http_query_info*, void*), void* arg);
|
||||
|
||||
void free_files(struct file_array* files);
|
||||
|
||||
// Perl stuff
|
||||
HV* perlify_post_values(struct post_values* post);
|
||||
HV* perlify_get_values(struct get_values* get);
|
||||
|
||||
#endif // QUERY_H
|
||||
|
|
183
src/reply.c
183
src/reply.c
|
@ -1,183 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
|
||||
#include <pcre2.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "reply.h"
|
||||
#include "easprintf.h"
|
||||
#include "../config.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/post.ctmpl"
|
||||
|
||||
#define ID_REPLY_SIZE 256
|
||||
#define ID_RESPONSE "<input type=\"hidden\" name=\"replyid\" value=\"%s\">"
|
||||
|
||||
char* construct_post_box(struct mstdnt_status* reply_status,
|
||||
char* default_content,
|
||||
size_t* size)
|
||||
{
|
||||
#define C_S "checked"
|
||||
#define D_S "disabled"
|
||||
char* reply_html;
|
||||
char id_reply[ID_REPLY_SIZE];
|
||||
enum mstdnt_visibility_type vis = MSTDNT_VISIBILITY_PUBLIC;
|
||||
|
||||
// Put hidden post request and check visibility
|
||||
if (reply_status)
|
||||
{
|
||||
snprintf(id_reply, ID_REPLY_SIZE, ID_RESPONSE, reply_status->id);
|
||||
vis = reply_status->visibility;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mastodont-c orders the visibility type from smallest (PUBLIC) to
|
||||
* largest (LOCAL), so we take advantage of the enum values
|
||||
*/
|
||||
struct post_template tdata = {
|
||||
.prefix = config_url_prefix,
|
||||
.reply_input = reply_status ? id_reply : NULL,
|
||||
.content = default_content,
|
||||
.public_checked = vis == MSTDNT_VISIBILITY_PUBLIC ? C_S : NULL,
|
||||
// You can reply with public to unlisted posts
|
||||
.public_disabled = vis > MSTDNT_VISIBILITY_UNLISTED ? D_S : NULL,
|
||||
.unlisted_checked = vis == MSTDNT_VISIBILITY_UNLISTED ? C_S : NULL,
|
||||
.unlisted_disabled = vis > MSTDNT_VISIBILITY_UNLISTED ? D_S : NULL,
|
||||
.private_checked = vis == MSTDNT_VISIBILITY_PRIVATE ? C_S : NULL,
|
||||
.private_disabled = vis > MSTDNT_VISIBILITY_PRIVATE ? D_S : NULL,
|
||||
.direct_checked = vis == MSTDNT_VISIBILITY_DIRECT ? C_S : NULL,
|
||||
.local_checked = vis == MSTDNT_VISIBILITY_LOCAL ? C_S : NULL,
|
||||
};
|
||||
return tmpl_gen_post(&tdata, size);
|
||||
}
|
||||
|
||||
/* Some comments:
|
||||
* - Misskey does not return <span>, but we still regex to make sure it's a highlight
|
||||
* - The order of parameters in a tag can be changed (mastodon does this),
|
||||
* so we just grep for regex href
|
||||
* - Misskey/Mastodon adds an @ symbol in the href param, while pleroma adds /users and honk adds /u
|
||||
*/
|
||||
#define REGEX_REPLY "<a .*?href=\"https?:\\/\\/(.*?)\\/(?:@|users/|u/)?(.*?)?\".*?>@(?:<span>)?.*?(?:<\\/span>)?"
|
||||
|
||||
char* reply_status(struct session* ssn, char* id, struct mstdnt_status* status)
|
||||
{
|
||||
char* content = status->content;
|
||||
size_t content_len = strlen(status->content);
|
||||
char* stat_reply;
|
||||
// Regex
|
||||
pcre2_code* re;
|
||||
PCRE2_SIZE* re_results;
|
||||
pcre2_match_data* re_data;
|
||||
// Regex data
|
||||
int rc;
|
||||
int error;
|
||||
PCRE2_SIZE erroffset;
|
||||
int url_off, url_len, name_off, name_len;
|
||||
// Replies
|
||||
size_t replies_size = 0, replies_size_orig;
|
||||
char* replies = NULL;
|
||||
char* instance_domain = malloc(sizeof(config_instance_url)+sizeof("https:///")+1);
|
||||
|
||||
// sscanf instead of regex works here and requires less work, we just need to trim off the slash at the end
|
||||
if (sscanf(config_instance_url, "https://%s/", instance_domain) == 0)
|
||||
if (sscanf(config_instance_url, "http://%s/", instance_domain) == 0)
|
||||
{
|
||||
free(instance_domain);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
instance_domain[strlen(instance_domain)] = '\0';
|
||||
// Remove ports, if any. Needed for development or if
|
||||
// the server actually supports these
|
||||
char* port_val = strchr(instance_domain, ':');
|
||||
if (port_val) *port_val = '\0';
|
||||
|
||||
// Load first reply
|
||||
if (ssn->logged_in && strcmp(status->account.acct, ssn->acct.acct) != 0)
|
||||
{
|
||||
replies = malloc(replies_size = strlen(status->account.acct)+2);
|
||||
replies[0] = '@';
|
||||
strcpy(replies+1, status->account.acct);
|
||||
replies[replies_size-1] = ' ';
|
||||
}
|
||||
|
||||
// Compile regex
|
||||
re = pcre2_compile((PCRE2_SPTR)REGEX_REPLY, PCRE2_ZERO_TERMINATED, 0, &error, &erroffset, NULL);
|
||||
if (re == NULL)
|
||||
{
|
||||
fprintf(stderr, "Couldn't parse regex at offset %ld: %d\n", erroffset, error);
|
||||
free(replies);
|
||||
pcre2_code_free(re);
|
||||
}
|
||||
|
||||
re_data = pcre2_match_data_create_from_pattern(re, NULL);
|
||||
|
||||
for (int ind = 0;;)
|
||||
{
|
||||
rc = pcre2_match(re, (PCRE2_SPTR)content, content_len, ind, 0, re_data, NULL);
|
||||
if (rc < 0)
|
||||
break;
|
||||
|
||||
re_results = pcre2_get_ovector_pointer(re_data);
|
||||
|
||||
// Store to last result
|
||||
ind = re_results[5];
|
||||
|
||||
// Read out
|
||||
url_off = re_results[2];
|
||||
url_len = re_results[3] - url_off;
|
||||
name_off = re_results[4];
|
||||
name_len = re_results[5] - name_off;
|
||||
|
||||
int instance_cmp = strncmp(instance_domain, content+url_off, url_len);
|
||||
// Is this the same as us?
|
||||
// Cut off url_len by one to slice the '/' at the end
|
||||
if (instance_cmp == 0 &&
|
||||
strncmp(ssn->acct.acct, content+name_off, name_len) == 0)
|
||||
continue;
|
||||
|
||||
replies_size_orig = replies_size;
|
||||
replies_size += (instance_cmp!=0?url_len:0)+name_len+3-(instance_cmp==0); // Bool as int :^)
|
||||
|
||||
// Realloc string
|
||||
replies = realloc(replies, replies_size+1);
|
||||
|
||||
replies[replies_size_orig] = '@';
|
||||
memcpy(replies + replies_size_orig + 1, content + name_off, name_len);
|
||||
if (instance_cmp != 0)
|
||||
{
|
||||
replies[replies_size_orig+1+name_len] = '@';
|
||||
memcpy(replies + replies_size_orig + 1 + name_len + 1, content + url_off, url_len);
|
||||
}
|
||||
replies[replies_size-1] = ' ';
|
||||
}
|
||||
|
||||
if (replies)
|
||||
replies[replies_size-1] = '\0';
|
||||
|
||||
pcre2_match_data_free(re_data);
|
||||
|
||||
stat_reply = construct_post_box(status, replies, NULL);
|
||||
pcre2_code_free(re);
|
||||
free(replies);
|
||||
free(instance_domain);
|
||||
return stat_reply;
|
||||
}
|
|
@ -16,18 +16,17 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "../test.h"
|
||||
#ifndef REQUEST_H
|
||||
#define REQUEST_H
|
||||
|
||||
// Imports
|
||||
#include "mime_multipart.c"
|
||||
#include "string_test.c"
|
||||
int main()
|
||||
{
|
||||
struct test tests[] = {
|
||||
{ "Mime multipart parser", mime_multipart_test },
|
||||
{ "Strings", string_replace_test }
|
||||
};
|
||||
return iterate_tests(tests, sizeof(tests)/sizeof(tests[0]));
|
||||
}
|
||||
#ifdef SINGLE_THREADED
|
||||
#define PRINTF(str, ...) printf(str, __VA_ARGS__)
|
||||
#define PUT(str) printf(str)
|
||||
#define REQUEST_T void*
|
||||
#else
|
||||
#define PRINTF(str, ...) FCGX_FPrintF(req->out, str, __VA_ARGS__)
|
||||
#define PUT(str) FCGX_FPrintF(req->out, str)
|
||||
#define REQUEST_T FCGX_Request*
|
||||
#endif
|
||||
|
||||
#endif /* REQUEST_H */
|
|
@ -19,35 +19,23 @@
|
|||
#include "scrobble.h"
|
||||
#include "easprintf.h"
|
||||
#include "string_helpers.h"
|
||||
#include "account.h"
|
||||
|
||||
#include "../static/scrobble.ctmpl"
|
||||
|
||||
char* construct_scrobble(struct mstdnt_scrobble* scrobble, size_t* size)
|
||||
// Converts it into a perl struct
|
||||
HV* perlify_scrobble(const struct mstdnt_scrobble* const scrobble)
|
||||
{
|
||||
struct scrobble_template tdata = {
|
||||
.scrobble_id = scrobble->id,
|
||||
.avatar = scrobble->account.avatar,
|
||||
.username = scrobble->account.display_name,
|
||||
.activity = "is listening to...",
|
||||
.title_key = "Title",
|
||||
.title = scrobble->title,
|
||||
.artist_key = "Artist",
|
||||
.artist = scrobble->artist,
|
||||
.album_key = "Album",
|
||||
.album = scrobble->album,
|
||||
.length_key = "Duration",
|
||||
.length = scrobble->length
|
||||
};
|
||||
if (!scrobble) return NULL;
|
||||
HV* scrobble_hv = newHV();
|
||||
|
||||
return tmpl_gen_scrobble(&tdata, size);
|
||||
hvstores_str(scrobble_hv, "album", scrobble->album);
|
||||
hvstores_str(scrobble_hv, "artist", scrobble->artist);
|
||||
hvstores_str(scrobble_hv, "id", scrobble->id);
|
||||
hvstores_str(scrobble_hv, "title", scrobble->title);
|
||||
hvstores_int(scrobble_hv, "created_at", scrobble->created_at);
|
||||
hvstores_int(scrobble_hv, "length", scrobble->created_at);
|
||||
hvstores_ref(scrobble_hv, "account", perlify_account(&(scrobble->account)));
|
||||
|
||||
return scrobble_hv;
|
||||
}
|
||||
|
||||
static char* construct_scrobble_voidwrap(void* passed, size_t index, size_t* res)
|
||||
{
|
||||
return construct_scrobble((struct mstdnt_scrobble*)passed + index, res);
|
||||
}
|
||||
|
||||
char* construct_scrobbles(struct mstdnt_scrobble* scrobbles, size_t scrobbles_len, size_t* ret_size)
|
||||
{
|
||||
return construct_func_strings(construct_scrobble_voidwrap, scrobbles, scrobbles_len, ret_size);
|
||||
}
|
||||
PERLIFY_MULTI(scrobble, scrobbles, mstdnt_scrobble)
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
#ifndef SCROBBLE_H
|
||||
#define SCROBBLE_H
|
||||
#include <mastodont.h>
|
||||
#include "global_perl.h"
|
||||
|
||||
char* construct_scrobble(struct mstdnt_scrobble* scrobble, size_t* size);
|
||||
char* construct_scrobbles(struct mstdnt_scrobble* scrobbles, size_t scrobbles_len, size_t* ret_size);
|
||||
HV* perlify_scrobble(const struct mstdnt_scrobble* const scrobble);
|
||||
AV* perlify_scrobbles(const struct mstdnt_scrobble* const scrobble, size_t len);
|
||||
|
||||
#endif /* SCROBBLE_H */
|
||||
|
|
263
src/search.c
263
src/search.c
|
@ -17,8 +17,9 @@
|
|||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "helpers.h"
|
||||
#include "search.h"
|
||||
#include "http.h"
|
||||
#include "helpers.h"
|
||||
#include "easprintf.h"
|
||||
#include "../config.h"
|
||||
#include "string_helpers.h"
|
||||
|
@ -27,54 +28,11 @@
|
|||
#include "hashtag.h"
|
||||
#include "error.h"
|
||||
#include "account.h"
|
||||
#include "graphsnbars.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/search.ctmpl"
|
||||
#include "../static/search_all.ctmpl"
|
||||
|
||||
void search_page(FCGX_Request* req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
enum search_tab tab,
|
||||
char* content)
|
||||
{
|
||||
char* out_data;
|
||||
struct search_template tdata = {
|
||||
.prefix = config_url_prefix,
|
||||
.query = keystr(ssn->query.query),
|
||||
.accounts_active = MAKE_FOCUSED_IF(tab, SEARCH_ACCOUNTS),
|
||||
.accounts = "Accounts",
|
||||
.hashtags_active = MAKE_FOCUSED_IF(tab, SEARCH_HASHTAGS),
|
||||
.hashtags = "Hashtags",
|
||||
.statuses_active = MAKE_FOCUSED_IF(tab, SEARCH_STATUSES),
|
||||
.statuses = "Statuses",
|
||||
.results = content
|
||||
};
|
||||
out_data = tmpl_gen_search(&tdata, NULL);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = out_data,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
free(out_data);
|
||||
}
|
||||
|
||||
void content_search_all(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
char* out_data = NULL;
|
||||
char* statuses_html = NULL;
|
||||
char* accounts_html = NULL;
|
||||
char* tags_html = NULL,
|
||||
* tags_graph = NULL,
|
||||
* tags_bars = NULL,
|
||||
* tags_page = NULL;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_search_args args = {
|
||||
.account_id = NULL,
|
||||
|
@ -90,72 +48,58 @@ void content_search_all(PATH_ARGS)
|
|||
};
|
||||
struct mstdnt_search_results results = { 0 };
|
||||
|
||||
if (mastodont_search(api,
|
||||
&m_args,
|
||||
keystr(ssn->query.query),
|
||||
&storage,
|
||||
&args,
|
||||
&results) == 0)
|
||||
// Perform redirect to correct direct page
|
||||
if (keyint(ssn->query.type))
|
||||
{
|
||||
// Statuses, make sure to set the highlight word
|
||||
struct construct_statuses_args statuses_args = {
|
||||
.highlight_word = keystr(ssn->query.query),
|
||||
};
|
||||
|
||||
statuses_html = construct_statuses(ssn, api, results.statuses, results.statuses_len, &statuses_args, NULL);
|
||||
if (!statuses_html)
|
||||
statuses_html = construct_error("No statuses", E_ERROR, 1, NULL);
|
||||
|
||||
// Accounts
|
||||
accounts_html = construct_accounts(api, results.accts, results.accts_len, 0, NULL);
|
||||
if (!accounts_html)
|
||||
accounts_html = construct_error("No accounts", E_ERROR, 1, NULL);
|
||||
|
||||
// Hashtags
|
||||
tags_html = construct_hashtags(results.tags, results.tags_len, NULL);
|
||||
if (!tags_html)
|
||||
tags_html = construct_error("No hashtags", E_ERROR, 1, NULL);
|
||||
|
||||
tags_bars = construct_hashtags_graph(results.tags,
|
||||
results.tags_len,
|
||||
14,
|
||||
NULL);
|
||||
if (tags_bars)
|
||||
tags_graph = construct_bar_graph_container(tags_bars, NULL);
|
||||
|
||||
free(tags_bars);
|
||||
char* query = keystr(ssn->query.query);
|
||||
query = curl_easy_escape(api->curl, query, 0);
|
||||
char* url;
|
||||
// Note: This can be zero, which is just "nothing"
|
||||
switch (keyint(ssn->query.type))
|
||||
{
|
||||
case 1:
|
||||
easprintf(&url, "/search/statuses?q=%s", query);
|
||||
redirect(req, REDIRECT_303, url);
|
||||
break;
|
||||
case 2:
|
||||
easprintf(&url, "/search/accounts?q=%s", query);
|
||||
redirect(req, REDIRECT_303, url);
|
||||
break;
|
||||
case 3:
|
||||
easprintf(&url, "/search/hashtags?q=%s", query);
|
||||
redirect(req, REDIRECT_303, url);
|
||||
break;
|
||||
}
|
||||
free(url);
|
||||
curl_free(query);
|
||||
return;
|
||||
}
|
||||
|
||||
easprintf(&tags_page, "%s%s", STR_NULL_EMPTY(tags_graph), tags_html);
|
||||
mastodont_search(api, &m_args, keystr(ssn->query.query), &storage, &args, &results);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_search_results(&results)));
|
||||
|
||||
// Construct search page
|
||||
struct search_all_template tdata = {
|
||||
.accounts = "Accounts",
|
||||
.hashtags = "Hashtags",
|
||||
.statuses = "Statuses",
|
||||
.statuses_results = statuses_html,
|
||||
.hashtags_results = tags_page,
|
||||
.accounts_results = accounts_html
|
||||
};
|
||||
out_data = tmpl_gen_search_all(&tdata, NULL);
|
||||
PERL_STACK_SCALAR_CALL("search::content_search");
|
||||
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = out_data,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
free(out_data);
|
||||
free(statuses_html);
|
||||
free(accounts_html);
|
||||
free(tags_html);
|
||||
free(tags_graph);
|
||||
free(tags_page);
|
||||
|
||||
mstdnt_cleanup_search_results(&results);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
void content_search_statuses(PATH_ARGS)
|
||||
|
@ -178,35 +122,37 @@ void content_search_statuses(PATH_ARGS)
|
|||
};
|
||||
struct mstdnt_search_results results = { 0 };
|
||||
|
||||
if (mastodont_search(api,
|
||||
&m_args,
|
||||
keystr(ssn->query.query),
|
||||
&storage,
|
||||
&args,
|
||||
&results) == 0)
|
||||
{
|
||||
struct construct_statuses_args statuses_args = {
|
||||
.highlight_word = keystr(ssn->query.query),
|
||||
};
|
||||
statuses_html = construct_statuses(ssn, api, results.statuses, results.statuses_len, &statuses_args, NULL);
|
||||
if (!statuses_html)
|
||||
statuses_html = construct_error("No statuses", E_ERROR, 1, NULL);
|
||||
}
|
||||
else
|
||||
statuses_html = construct_error("An error occured.", E_ERROR, 1, NULL);
|
||||
mastodont_search(api, &m_args, keystr(ssn->query.query), &storage, &args, &results);
|
||||
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_search_results(&results)));
|
||||
|
||||
search_page(req, ssn, api, SEARCH_STATUSES, STR_NULL_EMPTY(statuses_html));
|
||||
|
||||
if (statuses_html) free(statuses_html);
|
||||
PERL_STACK_SCALAR_CALL("search::content_search_statuses");
|
||||
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
mstdnt_cleanup_search_results(&results);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
void content_search_accounts(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
char* accounts_html;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_search_args args = {
|
||||
.account_id = NULL,
|
||||
|
@ -222,35 +168,37 @@ void content_search_accounts(PATH_ARGS)
|
|||
};
|
||||
struct mstdnt_search_results results = { 0 };
|
||||
|
||||
if (mastodont_search(api,
|
||||
&m_args,
|
||||
keystr(ssn->query.query),
|
||||
&storage,
|
||||
&args,
|
||||
&results) == 0)
|
||||
{
|
||||
accounts_html = construct_accounts(api, results.accts, results.accts_len, 0, NULL);
|
||||
if (!accounts_html)
|
||||
accounts_html = construct_error("No accounts", E_ERROR, 1, NULL);
|
||||
}
|
||||
else
|
||||
accounts_html = construct_error("An error occured.", E_ERROR, 1, NULL);
|
||||
mastodont_search(api, &m_args, keystr(ssn->query.query), &storage, &args, &results);
|
||||
|
||||
search_page(req, ssn, api, SEARCH_ACCOUNTS, STR_NULL_EMPTY(accounts_html));
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
XPUSHs(newRV_noinc((SV*)session_hv));
|
||||
XPUSHs(newRV_noinc((SV*)template_files));
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_search_results(&results)));
|
||||
|
||||
PERL_STACK_SCALAR_CALL("search::content_search_accounts");
|
||||
|
||||
// Duplicate so we can free the TMPs
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
render_base_page(&b, req, ssn, api);
|
||||
|
||||
if (accounts_html) free(accounts_html);
|
||||
mstdnt_cleanup_search_results(&results);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
void content_search_hashtags(PATH_ARGS)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
char* tags_html;
|
||||
char* tags_graph = NULL;
|
||||
char* tags_bars = NULL;
|
||||
char* tags_page;
|
||||
struct mstdnt_storage storage = { 0 };
|
||||
struct mstdnt_search_args args = {
|
||||
.account_id = NULL,
|
||||
|
@ -266,36 +214,23 @@ void content_search_hashtags(PATH_ARGS)
|
|||
};
|
||||
struct mstdnt_search_results results = { 0 };
|
||||
|
||||
if (mastodont_search(api,
|
||||
&m_args,
|
||||
keystr(ssn->query.query),
|
||||
&storage,
|
||||
&args,
|
||||
&results) == 0)
|
||||
{
|
||||
tags_html = construct_hashtags(results.tags, results.tags_len, NULL);
|
||||
if (!tags_html)
|
||||
tags_html = construct_error("No hashtags", E_ERROR, 1, NULL);
|
||||
|
||||
tags_bars = construct_hashtags_graph(results.tags,
|
||||
results.tags_len,
|
||||
14,
|
||||
NULL);
|
||||
if (tags_bars)
|
||||
tags_graph = construct_bar_graph_container(tags_bars, NULL);
|
||||
|
||||
if (tags_bars) free(tags_bars);
|
||||
}
|
||||
else
|
||||
tags_html = construct_error("An error occured.", E_ERROR, 1, NULL);
|
||||
|
||||
easprintf(&tags_page, "%s%s", STR_NULL_EMPTY(tags_graph), tags_html);
|
||||
mastodont_search(api, &m_args, keystr(ssn->query.query), &storage, &args, &results);
|
||||
|
||||
search_page(req, ssn, api, SEARCH_HASHTAGS, tags_page);
|
||||
// TODO
|
||||
|
||||
if (tags_html) free(tags_html);
|
||||
if (tags_graph) free(tags_graph);
|
||||
free(tags_page);
|
||||
mstdnt_cleanup_search_results(&results);
|
||||
mastodont_storage_cleanup(&storage);
|
||||
// Safefree(dup);
|
||||
}
|
||||
|
||||
HV* perlify_search_results(struct mstdnt_search_results* results)
|
||||
{
|
||||
if (!results) return NULL;
|
||||
|
||||
HV* search_hv = newHV();
|
||||
hvstores_ref(search_hv, "accounts", perlify_accounts(results->accts, results->accts_len));
|
||||
hvstores_ref(search_hv, "statuses", perlify_statuses(results->statuses, results->statuses_len));
|
||||
// TODO tags
|
||||
|
||||
return search_hv;
|
||||
}
|
||||
|
|
14
src/search.h
14
src/search.h
|
@ -21,22 +21,12 @@
|
|||
#include <mastodont.h>
|
||||
#include "session.h"
|
||||
#include "path.h"
|
||||
#include "global_perl.h"
|
||||
|
||||
enum search_tab
|
||||
{
|
||||
SEARCH_STATUSES,
|
||||
SEARCH_ACCOUNTS,
|
||||
SEARCH_HASHTAGS,
|
||||
};
|
||||
|
||||
void search_page(FCGX_Request* req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
enum search_tab tab,
|
||||
char* content);
|
||||
void content_search_all(PATH_ARGS);
|
||||
void content_search_statuses(PATH_ARGS);
|
||||
void content_search_accounts(PATH_ARGS);
|
||||
void content_search_hashtags(PATH_ARGS);
|
||||
HV* perlify_search_results(struct mstdnt_search_results* results);
|
||||
|
||||
#endif /* SEARCH_H */
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "account.h"
|
||||
#include "session.h"
|
||||
#include "../config.h"
|
||||
|
||||
|
@ -29,3 +30,23 @@ const char* const get_token(struct session* ssn)
|
|||
{
|
||||
return keystr(ssn->cookies.access_token);
|
||||
}
|
||||
|
||||
HV* perlify_session(struct session* ssn)
|
||||
{
|
||||
HV* ssn_hv = newHV();
|
||||
hvstores_int(ssn_hv, "logged_in", ssn->logged_in);
|
||||
HV* ssn_post_values = perlify_post_values(&(ssn->post));
|
||||
HV* ssn_get_values = perlify_get_values(&(ssn->query));
|
||||
HV* ssn_cookie_values = perlify_cookies(&(ssn->cookies));
|
||||
HV* acct_hv = perlify_account(&(ssn->acct));
|
||||
// Config
|
||||
HV* ssn_config = perlify_config(&(ssn->config));
|
||||
hvstores_ref(ssn_hv, "config", ssn_config);
|
||||
hvstores_ref(ssn_hv, "cookies", ssn_cookie_values);
|
||||
hvstores_ref(ssn_hv, "query", ssn_get_values);
|
||||
hvstores_ref(ssn_hv, "post", ssn_post_values);
|
||||
hvstores_ref(ssn_hv, "account", acct_hv);
|
||||
|
||||
return ssn_hv;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#ifndef SESSION_H
|
||||
#define SESSION_H
|
||||
#include <mastodont.h>
|
||||
#include "global_perl.h"
|
||||
#include "query.h"
|
||||
#include "local_config.h"
|
||||
#include "cookie.h"
|
||||
|
@ -36,5 +37,6 @@ struct session
|
|||
|
||||
const char* const get_instance(struct session* ssn);
|
||||
const char* const get_token(struct session* ssn);
|
||||
HV* perlify_session(struct session* ssn);
|
||||
|
||||
#endif // SESSION_H
|
||||
|
|
896
src/status.c
896
src/status.c
File diff suppressed because it is too large
Load diff
57
src/status.h
57
src/status.h
|
@ -23,6 +23,7 @@
|
|||
#include "l10n.h"
|
||||
#include "path.h"
|
||||
#include "session.h"
|
||||
#include "global_perl.h"
|
||||
|
||||
// Flags
|
||||
#define STATUS_NOOP 0
|
||||
|
@ -52,64 +53,17 @@ void content_status_create(PATH_ARGS);
|
|||
void content_status_react(PATH_ARGS);
|
||||
|
||||
// HTML Builders
|
||||
char* construct_status(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_status* status,
|
||||
size_t* size,
|
||||
struct mstdnt_notification* notif,
|
||||
struct construct_statuses_args* args,
|
||||
uint8_t flags);
|
||||
char* construct_statuses(struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_status* statuses,
|
||||
size_t size,
|
||||
struct construct_statuses_args* args,
|
||||
size_t* ret_size);
|
||||
char* construct_interaction_buttons(struct session* ssn,
|
||||
struct mstdnt_status* status,
|
||||
size_t* size,
|
||||
uint8_t flags);
|
||||
|
||||
// Reply to
|
||||
/** Deprecated: May be used in the future for Mastodon only */
|
||||
char* get_in_reply_to(mastodont_t* api,
|
||||
struct session* ssn,
|
||||
struct mstdnt_status* status,
|
||||
size_t* size);
|
||||
char* construct_in_reply_to(struct mstdnt_status* status,
|
||||
struct mstdnt_account* account,
|
||||
size_t* size);
|
||||
|
||||
char* construct_status_interactions(char* status_id,
|
||||
int fav_count,
|
||||
int reblog_count,
|
||||
struct mstdnt_account* fav_accounts,
|
||||
size_t fav_accounts_len,
|
||||
struct mstdnt_account* reblog_accounts,
|
||||
size_t reblog_accounts_len,
|
||||
size_t* size);
|
||||
|
||||
char* construct_status_interaction_profiles(struct mstdnt_account* reblogs,
|
||||
struct mstdnt_account* favourites,
|
||||
size_t reblogs_len,
|
||||
size_t favourites_len,
|
||||
size_t* ret_size);
|
||||
char* construct_status_interaction_profile(struct interact_profile_args* args, size_t index, size_t* size);
|
||||
char* construct_status_interactions_label(char* status_id,
|
||||
int is_favourites,
|
||||
char* header,
|
||||
int val,
|
||||
size_t* size);
|
||||
char* reformat_status(struct session* ssn,
|
||||
char* content,
|
||||
struct mstdnt_emoji* emos,
|
||||
size_t emos_len);
|
||||
char* greentextify(char* content);
|
||||
char* make_mentions_local(char* content);
|
||||
|
||||
void status_view_reblogs(PATH_ARGS);
|
||||
void status_view_favourites(PATH_ARGS);
|
||||
|
||||
const char* status_visibility_str(enum l10n_locale locale, enum mstdnt_visibility_type visibility);
|
||||
|
||||
void content_status_interactions(FCGX_Request* req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
|
@ -134,4 +88,9 @@ void notice_redirect(PATH_ARGS);
|
|||
// API
|
||||
void api_status_interact(PATH_ARGS);
|
||||
|
||||
// Perl
|
||||
HV* perlify_status_pleroma(const struct mstdnt_status_pleroma* pleroma);
|
||||
HV* perlify_status(const struct mstdnt_status* status);
|
||||
AV* perlify_statuses(const struct mstdnt_status* statuses, size_t len);
|
||||
|
||||
#endif // STATUS_H
|
||||
|
|
|
@ -1,295 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
// TODO error handling
|
||||
|
||||
enum args
|
||||
{
|
||||
ARG_FILENAME = 1,
|
||||
ARG_VARIABLE
|
||||
};
|
||||
|
||||
enum tmpl_type
|
||||
{
|
||||
TMPL_INT,
|
||||
TMPL_UINT,
|
||||
TMPL_STR,
|
||||
TMPL_STRLEN,
|
||||
TMPL_FLOAT,
|
||||
};
|
||||
|
||||
struct tmpl_token
|
||||
{
|
||||
enum tmpl_type type;
|
||||
char* token;
|
||||
int used; // Internal use only
|
||||
};
|
||||
|
||||
long filesize(FILE* file)
|
||||
{
|
||||
long orig = ftell(file);
|
||||
fseek(file, 0, SEEK_END);
|
||||
long size = ftell(file);
|
||||
fseek(file, orig, SEEK_SET);
|
||||
return size;
|
||||
}
|
||||
|
||||
void chexput(const char* buf, size_t size)
|
||||
{
|
||||
for (size_t i = 0; i < size && buf; ++i)
|
||||
{
|
||||
printf("0X%hhX,", buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
char* strnws(char* str)
|
||||
{
|
||||
for (; isblank(*str); ++str);
|
||||
return str;
|
||||
}
|
||||
|
||||
char* strwsc(char* str, char stop)
|
||||
{
|
||||
for (; !isblank(*str) && *str != stop; ++str);
|
||||
return str;
|
||||
}
|
||||
|
||||
char* tkn_typetostr(enum tmpl_type tkn)
|
||||
{
|
||||
switch (tkn)
|
||||
{
|
||||
case TMPL_INT:
|
||||
return "int";
|
||||
case TMPL_STR:
|
||||
return "const char*";
|
||||
case TMPL_STRLEN:
|
||||
return "char*";
|
||||
case TMPL_UINT:
|
||||
return "unsigned";
|
||||
case TMPL_FLOAT:
|
||||
return "float";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
enum tmpl_type tkn_type(char* str)
|
||||
{
|
||||
if (strcmp(str, "string") == 0 ||
|
||||
strcmp(str, "str") == 0 ||
|
||||
strcmp(str, "%s") == 0)
|
||||
return TMPL_STR;
|
||||
else if (strcmp(str, "stringlen") == 0 ||
|
||||
strcmp(str, "strlen") == 0 ||
|
||||
strcmp(str, "%.s") == 0)
|
||||
return TMPL_STRLEN;
|
||||
else if (strcmp(str, "int") == 0 ||
|
||||
strcmp(str, "i") == 0 ||
|
||||
strcmp(str, "%d") == 0)
|
||||
return TMPL_INT;
|
||||
else if (strcmp(str, "unsigned") == 0 ||
|
||||
strcmp(str, "uint") == 0 ||
|
||||
strcmp(str, "%u") == 0)
|
||||
return TMPL_UINT;
|
||||
else if (strcmp(str, "float") == 0 ||
|
||||
strcmp(str, "%f") == 0)
|
||||
return TMPL_FLOAT;
|
||||
|
||||
// TODO Real error handling
|
||||
return TMPL_INT;
|
||||
}
|
||||
|
||||
char* parse_tmpl_token(char* buf, struct tmpl_token* tkn)
|
||||
{
|
||||
tkn->used = 0;
|
||||
char* type_begin;
|
||||
char* type_end;
|
||||
char* tkn_begin;
|
||||
char* tkn_end;
|
||||
// skip {{
|
||||
buf += 2;
|
||||
type_begin = strnws(buf);
|
||||
type_end = strwsc(type_begin, ':');
|
||||
|
||||
if (*type_end != ':') buf = strchr(buf, ':');
|
||||
else buf = type_end;
|
||||
|
||||
*type_end = '\0';
|
||||
tkn->type = tkn_type(type_begin);
|
||||
|
||||
++buf;
|
||||
tkn_begin = strnws(buf);
|
||||
tkn_end = strwsc(tkn_begin, '}');
|
||||
|
||||
if (*tkn_end == '}') buf = tkn_end + 2;
|
||||
else buf = strstr(buf, "}}") + 2;
|
||||
|
||||
*tkn_end = '\0';
|
||||
tkn->token = tkn_begin;
|
||||
return buf;
|
||||
}
|
||||
|
||||
void print_template(char* var, char* buf)
|
||||
{
|
||||
char* buf_prev = buf;
|
||||
char* buf_curr = buf;
|
||||
// Store result
|
||||
struct tmpl_token* tokens = NULL;
|
||||
size_t tokens_len = 0;
|
||||
|
||||
printf("#ifndef __%s\n"
|
||||
"#define __%s\n"
|
||||
"#include <stddef.h>\n"
|
||||
"static const char data_%s[] = {", var, var, var);
|
||||
|
||||
while (1)
|
||||
{
|
||||
buf_curr = strstr(buf_curr, "{{");
|
||||
if (!buf_curr) break;
|
||||
// Create tokens array
|
||||
tokens = realloc(tokens, sizeof(struct tmpl_token) * ++tokens_len);
|
||||
if (!tokens)
|
||||
{
|
||||
perror("realloc");
|
||||
break;
|
||||
}
|
||||
// Print up to this point
|
||||
chexput(buf_prev, buf_curr - buf_prev);
|
||||
buf_prev = buf_curr = parse_tmpl_token(buf_curr, tokens + (tokens_len-1));
|
||||
|
||||
// Print type
|
||||
switch (tokens[tokens_len-1].type)
|
||||
{
|
||||
case TMPL_INT:
|
||||
// I'm lazy so we'll use this
|
||||
chexput("%d", 2);
|
||||
break;
|
||||
case TMPL_STR:
|
||||
chexput("%s", 2);
|
||||
break;
|
||||
case TMPL_STRLEN:
|
||||
chexput("%.s", 3);
|
||||
break;
|
||||
case TMPL_UINT:
|
||||
chexput("%u", 2);
|
||||
break;
|
||||
case TMPL_FLOAT:
|
||||
chexput("%f", 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Print remainder if any
|
||||
chexput(buf_prev, strlen(buf_prev));
|
||||
puts("0};");
|
||||
|
||||
// Only create struct and function when there are tokens detected
|
||||
if (tokens_len)
|
||||
{
|
||||
printf("struct %s_template {", var);
|
||||
|
||||
int should_print = 0;
|
||||
// Print tokens
|
||||
for (size_t i = 0; i < tokens_len; ++i)
|
||||
{
|
||||
should_print = 1;
|
||||
// Check if used
|
||||
for (size_t j = 0; j < tokens_len; ++j)
|
||||
{
|
||||
if (i != j &&
|
||||
strcmp(tokens[i].token, tokens[j].token) == 0 &&
|
||||
tokens[j].used)
|
||||
should_print = 0;
|
||||
}
|
||||
if (should_print)
|
||||
{
|
||||
printf("%s %s;\n", tkn_typetostr(tokens[i].type), tokens[i].token);
|
||||
if (tokens[i].type == TMPL_STRLEN)
|
||||
printf("size_t %s_len;\n", tokens[i].token);
|
||||
tokens[i].used = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate function
|
||||
printf("};\n");
|
||||
printf("char* tmpl_gen_%s(struct %s_template* data, size_t* size);", var, var);
|
||||
|
||||
// Pipe the contents of the real function code into stderr, then we can redirect it
|
||||
// We could also just write the file directly but this works better with the Makefile
|
||||
// and I am lazy
|
||||
fprintf(stderr, "#include \"%s.ctmpl\"\n"
|
||||
"#include \"../src/easprintf.h\"\n"
|
||||
"char* tmpl_gen_%s(struct %s_template* data, size_t* size){\n"
|
||||
"char* ret;\n"
|
||||
"size_t s = easprintf(&ret, data_%s, ", var, var, var, var);
|
||||
for (size_t i = 0; i < tokens_len; ++i)
|
||||
{
|
||||
fprintf(stderr, "data->%s", tokens[i].token);
|
||||
// No (null) strings, make them empty
|
||||
if (tokens[i].type == TMPL_STR || tokens[i].type == TMPL_STRLEN)
|
||||
fprintf(stderr, "?data->%s:\"\"", tokens[i].token);
|
||||
fputs(i < tokens_len-1 ? ", " : "", stderr);
|
||||
}
|
||||
fputs(");\n"
|
||||
"if (size) *size = s;\n"
|
||||
"return ret;\n}", stderr);
|
||||
}
|
||||
|
||||
// Done!
|
||||
puts("\n#endif");
|
||||
// Cleanup
|
||||
free(tokens);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
char* buf;
|
||||
FILE* file = fopen(argv[ARG_FILENAME], "rb");
|
||||
|
||||
long size = filesize(file);
|
||||
|
||||
if (!(buf = malloc(size)))
|
||||
{
|
||||
perror("malloc");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (fread(buf, 1, size, file) != size)
|
||||
{
|
||||
fputs("Didn't read correctly!", stderr);
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
buf[size-1] = '\0';
|
||||
|
||||
print_template(argv[ARG_VARIABLE], buf);
|
||||
|
||||
|
||||
|
||||
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
80
src/test.c
80
src/test.c
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "test.h"
|
||||
#include "../config.h"
|
||||
#include "base_page.h"
|
||||
#include "easprintf.h"
|
||||
|
||||
// Pages
|
||||
#include "../static/test.ctmpl"
|
||||
|
||||
#define ENV_NOT_FOUND "<span style=\"color:red;\">ENV Not Found</span>"
|
||||
|
||||
enum env_tbl_index
|
||||
{
|
||||
ENV_HTTP_COOKIE = 0,
|
||||
ENV_PATH_INFO,
|
||||
ENV_QUERY_STRING,
|
||||
ENV_REQUEST_METHOD,
|
||||
ENV_SCRIPT_NAME,
|
||||
ENV_HTTP_REFERER,
|
||||
ENV_HTTP_USER_AGENT,
|
||||
ENV_CONTENT_LENGTH,
|
||||
};
|
||||
|
||||
#define ENV_TBL_GET(index) (env_tbl[(index)] ? env_tbl[(index)] : ENV_NOT_FOUND)
|
||||
|
||||
void content_test(PATH_ARGS)
|
||||
{
|
||||
char* env_tbl[] = {
|
||||
GET_ENV("HTTP_COOKIE", req),
|
||||
GET_ENV("PATH_INFO", req),
|
||||
GET_ENV("QUERY_STRING", req),
|
||||
GET_ENV("REQUEST_METHOD", req),
|
||||
GET_ENV("SCRIPT_NAME", req),
|
||||
GET_ENV("HTTP_REFERER", req),
|
||||
GET_ENV("HTTP_USER_AGENT", req),
|
||||
GET_ENV("CONTENT_LENGTH", req)
|
||||
};
|
||||
|
||||
char* page;
|
||||
struct test_template tdata = {
|
||||
.HTTP_COOKIE = ENV_TBL_GET(ENV_HTTP_COOKIE),
|
||||
.PATH_INFO = ENV_TBL_GET(ENV_PATH_INFO),
|
||||
.QUERY_STRING = ENV_TBL_GET(ENV_QUERY_STRING),
|
||||
.REQUEST_METHOD = ENV_TBL_GET(ENV_REQUEST_METHOD),
|
||||
.SCRIPT_NAME = ENV_TBL_GET(ENV_SCRIPT_NAME),
|
||||
.HTTP_REFERER = ENV_TBL_GET(ENV_HTTP_REFERER),
|
||||
.HTTP_USER_AGENT = ENV_TBL_GET(ENV_HTTP_USER_AGENT),
|
||||
.CONTENT_LENGTH = ENV_TBL_GET(ENV_CONTENT_LENGTH)
|
||||
};
|
||||
page = tmpl_gen_test(&tdata, NULL);
|
||||
|
||||
struct base_page b = {
|
||||
.category = BASE_CAT_NONE,
|
||||
.content = page,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
// Output
|
||||
render_base_page(&b, req, ssn, api);
|
||||
if (page) free(page);
|
||||
}
|
114
src/timeline.c
114
src/timeline.c
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "global_perl.h"
|
||||
#include "timeline.h"
|
||||
#include <stdlib.h>
|
||||
#include "helpers.h"
|
||||
|
@ -25,16 +26,11 @@
|
|||
#include "index.h"
|
||||
#include "status.h"
|
||||
#include "easprintf.h"
|
||||
#include "reply.h"
|
||||
#include "navigation.h"
|
||||
#include "query.h"
|
||||
#include "error.h"
|
||||
#include "string_helpers.h"
|
||||
|
||||
#include "../static/timeline_options.ctmpl"
|
||||
#include "../static/navigation.ctmpl"
|
||||
|
||||
void content_timeline(FCGX_Request* req,
|
||||
void content_timeline(REQUEST_T req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_storage* storage,
|
||||
|
@ -42,67 +38,34 @@ void content_timeline(FCGX_Request* req,
|
|||
size_t statuses_len,
|
||||
enum base_category cat,
|
||||
char* header_text,
|
||||
int show_post_box)
|
||||
int show_post_box,
|
||||
int fake_timeline)
|
||||
{
|
||||
size_t statuses_html_count = 0;
|
||||
char* status_format = NULL,
|
||||
* header = NULL,
|
||||
* post_box = NULL,
|
||||
* navigation_box = NULL,
|
||||
* timeline_options,
|
||||
* output = NULL,
|
||||
* start_id;
|
||||
|
||||
if (storage->error)
|
||||
status_format = construct_error(storage->error, E_ERROR, 1, NULL);
|
||||
else
|
||||
{
|
||||
// Construct statuses into HTML
|
||||
status_format = construct_statuses(ssn, api, statuses, statuses_len, NULL, &statuses_html_count);
|
||||
if (!status_format)
|
||||
status_format = construct_error("No statuses", E_NOTICE, 1, NULL);
|
||||
}
|
||||
|
||||
// Create post box
|
||||
if (show_post_box)
|
||||
post_box = construct_post_box(NULL, "", NULL);
|
||||
|
||||
if (statuses)
|
||||
{
|
||||
// If not set, set it
|
||||
start_id = keystr(ssn->post.start_id) ? keystr(ssn->post.start_id) : statuses[0].id;
|
||||
navigation_box = construct_navigation_box(start_id,
|
||||
statuses[0].id,
|
||||
statuses[statuses_len-1].id,
|
||||
NULL);
|
||||
}
|
||||
|
||||
// Create timeline options/menubar
|
||||
struct timeline_options_template todata = {
|
||||
.only_media = "Only media?",
|
||||
.replies = "Replies?",
|
||||
.only_media_active = keyint(ssn->post.only_media) ? "checked" : NULL,
|
||||
};
|
||||
|
||||
timeline_options = tmpl_gen_timeline_options(&todata, NULL);
|
||||
|
||||
// Display a header bar, usually customized for specific pages
|
||||
if (header_text)
|
||||
{
|
||||
easprintf(&header, "<div class=\"simple-page simple-page-header\"><h1>%s</h1></div>",
|
||||
header_text);
|
||||
}
|
||||
PERL_STACK_INIT;
|
||||
HV* session_hv = perlify_session(ssn);
|
||||
mXPUSHs(newRV_inc((SV*)session_hv));
|
||||
mXPUSHs(newRV_inc((SV*)template_files));
|
||||
|
||||
easprintf(&output, "%s%s%s%s%s",
|
||||
STR_NULL_EMPTY(header),
|
||||
STR_NULL_EMPTY(post_box),
|
||||
STR_NULL_EMPTY(timeline_options),
|
||||
STR_NULL_EMPTY(status_format),
|
||||
STR_NULL_EMPTY(navigation_box));
|
||||
if (statuses)
|
||||
mXPUSHs(newRV_noinc((SV*)perlify_statuses(statuses, statuses_len)));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
if (header_text)
|
||||
mXPUSHs(newSVpv(header_text, 0));
|
||||
else ARG_UNDEFINED();
|
||||
|
||||
mXPUSHi(show_post_box);
|
||||
mXPUSHi(fake_timeline);
|
||||
|
||||
PERL_STACK_SCALAR_CALL("timeline::content_timeline");
|
||||
|
||||
// Duplicate to free temps
|
||||
char* dup = PERL_GET_STACK_EXIT;
|
||||
|
||||
struct base_page b = {
|
||||
.category = cat,
|
||||
.content = output,
|
||||
.content = dup,
|
||||
.session = session_hv,
|
||||
.sidebar_left = NULL
|
||||
};
|
||||
|
||||
|
@ -112,15 +75,10 @@ void content_timeline(FCGX_Request* req,
|
|||
// Cleanup
|
||||
mastodont_storage_cleanup(storage);
|
||||
mstdnt_cleanup_statuses(statuses, statuses_len);
|
||||
free(status_format);
|
||||
free(post_box);
|
||||
free(header);
|
||||
free(timeline_options);
|
||||
free(navigation_box);
|
||||
free(output);
|
||||
Safefree(dup);
|
||||
}
|
||||
|
||||
void tl_home(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local)
|
||||
void tl_home(REQUEST_T req, struct session* ssn, mastodont_t* api, int local)
|
||||
{
|
||||
struct mstdnt_args m_args = { 0 };
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
@ -148,10 +106,10 @@ void tl_home(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local
|
|||
|
||||
mastodont_timeline_home(api, &m_args, &args, &storage, &statuses, &statuses_len);
|
||||
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_HOME, NULL, 1);
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_HOME, NULL, 1, 0);
|
||||
}
|
||||
|
||||
void tl_direct(FCGX_Request* req, struct session* ssn, mastodont_t* api)
|
||||
void tl_direct(REQUEST_T req, struct session* ssn, mastodont_t* api)
|
||||
{
|
||||
struct mstdnt_args m_args = { 0 };
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
@ -176,10 +134,10 @@ void tl_direct(FCGX_Request* req, struct session* ssn, mastodont_t* api)
|
|||
|
||||
mastodont_timeline_direct(api, &m_args, &args, &storage, &statuses, &statuses_len);
|
||||
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_DIRECT, "Direct", 0);
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_DIRECT, "Direct", 0, 0);
|
||||
}
|
||||
|
||||
void tl_public(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local, enum base_category cat)
|
||||
void tl_public(REQUEST_T req, struct session* ssn, mastodont_t* api, int local, enum base_category cat)
|
||||
{
|
||||
struct mstdnt_args m_args = { 0 };
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
@ -206,10 +164,10 @@ void tl_public(FCGX_Request* req, struct session* ssn, mastodont_t* api, int loc
|
|||
|
||||
mastodont_timeline_public(api, &m_args, &args, &storage, &statuses, &statuses_len);
|
||||
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, cat, NULL, 1);
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, cat, NULL, 1, 0);
|
||||
}
|
||||
|
||||
void tl_list(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* list_id)
|
||||
void tl_list(REQUEST_T req, struct session* ssn, mastodont_t* api, char* list_id)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
@ -233,11 +191,11 @@ void tl_list(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* lis
|
|||
|
||||
mastodont_timeline_list(api, &m_args, list_id, &args, &storage, &statuses, &statuses_len);
|
||||
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_LISTS, "List timeline", 0);
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_LISTS, "List timeline", 0, 0);
|
||||
}
|
||||
|
||||
|
||||
void tl_tag(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* tag_id)
|
||||
void tl_tag(REQUEST_T req, struct session* ssn, mastodont_t* api, char* tag_id)
|
||||
{
|
||||
struct mstdnt_args m_args;
|
||||
set_mstdnt_args(&m_args, ssn);
|
||||
|
@ -262,7 +220,7 @@ void tl_tag(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* tag_
|
|||
|
||||
easprintf(&header, "Hashtag - #%s", tag_id);
|
||||
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_NONE, header, 0);
|
||||
content_timeline(req, ssn, api, &storage, statuses, statuses_len, BASE_CAT_NONE, header, 0, 0);
|
||||
free(header);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,28 +18,67 @@
|
|||
|
||||
#ifndef TIMELINE_H
|
||||
#define TIMELINE_H
|
||||
#include <fcgi_stdio.h>
|
||||
#include <fcgiapp.h>
|
||||
#include <stddef.h>
|
||||
#include <mastodont.h>
|
||||
#include "path.h"
|
||||
#include "session.h"
|
||||
#include "base_page.h"
|
||||
#include "cgi.h"
|
||||
#include "request.h"
|
||||
|
||||
// Federated and local are here
|
||||
void tl_home(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local);
|
||||
void tl_direct(FCGX_Request* req, struct session* ssn, mastodont_t* api);
|
||||
void tl_public(FCGX_Request* req, struct session* ssn, mastodont_t* api, int local, enum base_category cat);
|
||||
void tl_list(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* list_id);
|
||||
void tl_tag(FCGX_Request* req, struct session* ssn, mastodont_t* api, char* tag);
|
||||
/** Wrapper for content_tl_federated */
|
||||
void tl_home(REQUEST_T req, struct session* ssn, mastodont_t* api, int local);
|
||||
|
||||
/** Wrapper for content_tl_direct */
|
||||
void tl_direct(REQUEST_T req, struct session* ssn, mastodont_t* api);
|
||||
|
||||
/** Wrapper for content_tl_federated */
|
||||
void tl_public(REQUEST_T req, struct session* ssn, mastodont_t* api, int local, enum base_category cat);
|
||||
|
||||
/** Wrapper for content_tl_list */
|
||||
void tl_list(REQUEST_T req, struct session* ssn, mastodont_t* api, char* list_id);
|
||||
|
||||
/** Wrapper for content_tl_tag */
|
||||
void tl_tag(REQUEST_T req, struct session* ssn, mastodont_t* api, char* tag);
|
||||
|
||||
/* ------------------------------------------------ */
|
||||
|
||||
/** Federated timeline */
|
||||
void content_tl_federated(PATH_ARGS);
|
||||
|
||||
/** Home timeline. Shows federated timeline if not logged in */
|
||||
void content_tl_home(PATH_ARGS);
|
||||
|
||||
/** Direct message timeline */
|
||||
void content_tl_direct(PATH_ARGS);
|
||||
|
||||
/** Local/instance timeline */
|
||||
void content_tl_local(PATH_ARGS);
|
||||
|
||||
/** List timeline */
|
||||
void content_tl_list(PATH_ARGS);
|
||||
|
||||
/** Hashtag timeline */
|
||||
void content_tl_tag(PATH_ARGS);
|
||||
void content_timeline(FCGX_Request* req,
|
||||
|
||||
/**
|
||||
* Used to create generic timeline content. This timeline includes other features
|
||||
* such as viewing only media, hiding muted, etc. as options on the top of the
|
||||
* timeline, so this should only be used for API's which are considered "timelines"
|
||||
* to Pleroma/Mastodon.
|
||||
*
|
||||
* @param req This request
|
||||
* @param ssn This session
|
||||
* @param api The api
|
||||
* @param storage The storage for statuses, will be cleaned up in this function, do NOT
|
||||
* cleanup yourself.
|
||||
* @param statuses The statuses, will be cleaned up in this function, do NOT cleanup yourself.
|
||||
* @param statuses_len Length of `statuses`
|
||||
* @param cat The category to "highlight" on the sidebar
|
||||
* @param header A header that is displayed above the timeline.
|
||||
* @param show_post_box If the post box should be shown or not.
|
||||
*/
|
||||
void content_timeline(REQUEST_T req,
|
||||
struct session* ssn,
|
||||
mastodont_t* api,
|
||||
struct mstdnt_storage* storage,
|
||||
|
@ -47,6 +86,7 @@ void content_timeline(FCGX_Request* req,
|
|||
size_t statuses_len,
|
||||
enum base_category cat,
|
||||
char* header,
|
||||
int show_post_box);
|
||||
int show_post_box,
|
||||
int fake_timeline);
|
||||
|
||||
#endif // TIMELINE_H
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "type_string.h"
|
||||
|
||||
// Icons
|
||||
#include "../static/like_svg.ctmpl"
|
||||
#include "../static/repeat_svg.ctmpl"
|
||||
#include "../static/follow_svg.ctmpl"
|
||||
|
||||
const char* notification_type_str(mstdnt_notification_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MSTDNT_NOTIFICATION_FOLLOW: return L10N[L10N_EN_US][L10N_NOTIF_FOLLOW];
|
||||
case MSTDNT_NOTIFICATION_FOLLOW_REQUEST: return L10N[L10N_EN_US][L10N_NOTIF_FOLLOW_REQUEST];
|
||||
case MSTDNT_NOTIFICATION_REBLOG: return L10N[L10N_EN_US][L10N_NOTIF_REPEATED];
|
||||
case MSTDNT_NOTIFICATION_FAVOURITE: return L10N[L10N_EN_US][L10N_NOTIF_LIKED];
|
||||
case MSTDNT_NOTIFICATION_POLL: return L10N[L10N_EN_US][L10N_NOTIF_POLL];
|
||||
case MSTDNT_NOTIFICATION_EMOJI_REACT: return L10N[L10N_EN_US][L10N_NOTIF_REACTED_WITH];
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
const char* notification_type_compact_str(mstdnt_notification_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MSTDNT_NOTIFICATION_FOLLOW: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_FOLLOW];
|
||||
case MSTDNT_NOTIFICATION_FOLLOW_REQUEST: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_FOLLOW_REQUEST];
|
||||
case MSTDNT_NOTIFICATION_REBLOG: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_REPEATED];
|
||||
case MSTDNT_NOTIFICATION_FAVOURITE: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_LIKED];
|
||||
case MSTDNT_NOTIFICATION_POLL: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_POLL];
|
||||
case MSTDNT_NOTIFICATION_EMOJI_REACT: return L10N[L10N_EN_US][L10N_NOTIF_COMPACT_REACTED_WITH];
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* notification_type_svg(mstdnt_notification_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MSTDNT_NOTIFICATION_FOLLOW: return data_follow_svg;
|
||||
case MSTDNT_NOTIFICATION_FOLLOW_REQUEST: return "";
|
||||
case MSTDNT_NOTIFICATION_REBLOG: return data_repeat_svg;
|
||||
case MSTDNT_NOTIFICATION_FAVOURITE: return data_like_svg;
|
||||
case MSTDNT_NOTIFICATION_POLL: return "";
|
||||
case MSTDNT_NOTIFICATION_EMOJI_REACT: return "";
|
||||
default: return "";
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Treebird - Lightweight frontend for Pleroma
|
||||
* Copyright (C) 2022 Nekobit
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TYPE_STRING_H
|
||||
#define TYPE_STRING_H
|
||||
#include <mastodont.h>
|
||||
#include "l10n.h"
|
||||
|
||||
const char* notification_type_svg(mstdnt_notification_t type);
|
||||
const char* notification_type_str(mstdnt_notification_t type);
|
||||
const char* notification_type_compact_str(mstdnt_notification_t type);
|
||||
|
||||
#endif // TYPE_STRING_H
|
|
@ -1,28 +0,0 @@
|
|||
<div class="simple-page">
|
||||
<div class="about-header">
|
||||
<h1><img id="about-icon" src="/treebird_logo.png"> <span>Treebird</span></h1>
|
||||
</div>
|
||||
<div class="about-content">
|
||||
<p>Treebird is a Pleroma frontend that is lightweight, efficient, and true to the web. It's written in C with FCGI making it simple and fast to work with and deploy. It is very tight to the philosophy of how the internet has always worked; Javascript provides extra sugar (or scripting) to improve the experience but not a requirement, while still being simple enough that anyone can use it.</p>
|
||||
|
||||
<p>Treebird was created in response to PleromaFE performance issues and ironically a lack of PleromaAPI backend support. Treebird resembles GNU Social in appearance by default, but keeps the familiarity that many are used to of PleromaFE.</p>
|
||||
|
||||
<p>Treebird was created by <a href="/@nekobit@rdrama.cc">Nekobit</a>, who created <a href="https://fossil.nekobit.net/mastodont-c/home">mastodont-c</a>, a library that can communicate with Revolver's, Pleroma's, and Mastodon's REST APIs.</p>
|
||||
|
||||
<h3>Other contributors</h3>
|
||||
<ul>
|
||||
<li><a href="/@coyote@pl.lain.sh" alt="Created Solarized themes for Treebird">Coyote</a></li>
|
||||
<li><a href="/@grumbulon@freecumextremist.com" alt="Helped with the original dark theme, and created the original treebird.dev website">Grumbulon</a></li>
|
||||
<li><a href="/@khan@sleepy.cafe" alt="Started idea of treebird">Khan</a></li>
|
||||
<li><a href="/@sam@froth.zone" alt="Testing with Nginx and documentation">SamTherapy</a></li>
|
||||
<li><a href="/@pch_xyz@seediqbale.xyz" alt="Chinese (Traditional) translations">Pacific Coast Highway (pch_xyz)</a></li>
|
||||
</ul>
|
||||
|
||||
<p>Treebird is licensed in AGPLv3, and mastodont-c is LGPLv3 licensed. Other licenses apply to libraries as well. <a href="/about/license">View the AGPLv3 license here.</a></p>
|
||||
|
||||
<a class="btn btn-single" href="https://fossil.nekobit.net/treebird/home">View the Fossil Repository</a> <a class="btn btn-single" href="/about/license">View the License</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/js/worm.js"></script>
|
|
@ -1,67 +0,0 @@
|
|||
{{%s:is_blocked}}
|
||||
{{%s:menubar}}
|
||||
<div class="account">
|
||||
<div class="acct-banner" style="background-image:url('{{%s:header}}');">
|
||||
{{%s:follows_you}}
|
||||
<div class="acct-info-data">
|
||||
<span class="acct-displayname">{{%s:display_name}}</span>
|
||||
<span class="acct-username">{{%s:acct}}</span>
|
||||
</div>
|
||||
|
||||
<span class="menu-container user-options-btn">
|
||||
Menu
|
||||
<div class="menu menu-options">
|
||||
<ul>
|
||||
<li><a class="nolink" href="{{%s:prefix}}/user/{{%s:userid}}/action/{{%s:unsubscribe}}subscribe"><input class="btn-menu" type="button" value="{{%s:subscribe_text}}"></a></li>
|
||||
<li><a class="nolink" href="{{%s:prefix}}/user/{{%s:userid}}/action/{{%s:unblock}}block"><input class="btn-menu" type="button" value="{{%s:block_text}}"></a></li>
|
||||
<li><a class="nolink" href="{{%s:prefix}}/user/{{%s:userid}}/action/{{%s:unmute}}mute"><input class="btn-menu" type="button" value="{{%s:mute_text}}"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="acct-header">
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}" class="header-btn btn">
|
||||
<span class="btn-header">{{%s:tab_statuses_text}}</span>
|
||||
<span class="btn-content">{{%d:statuses_count}}</span>
|
||||
</a>
|
||||
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}/following" class="header-btn btn">
|
||||
<span class="btn-header">{{%s:tab_following_text}}</span>
|
||||
<span class="btn-content">{{%d:following_count}}</span>
|
||||
</a>
|
||||
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}/followers" class="header-btn btn">
|
||||
<span class="btn-header">{{%s:tab_followers_text}}</span>
|
||||
<span class="btn-content">{{%d:followers_count}}</span>
|
||||
</a>
|
||||
|
||||
{{%s:follow_btn}}
|
||||
</div>
|
||||
<div class="acct-pfp-wrapper">
|
||||
<img class="acct-pfp" src="{{%s:avatar}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{%s:info}}
|
||||
|
||||
<table class="tabs ui-table">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}/statuses"><input class="tab-btn btn {{%s:tab_statuses_focused}}" type="button" value="{{%s:tab_statuses_text}}"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}/scrobbles"><input class="tab-btn btn {{%s:tab_scrobbles_focused}}" type="button" value="{{%s:tab_scrobbles_text}}"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}/media"><input class="tab-btn btn {{%s:tab_media_focused}}" type="button" value="{{%s:tab_media_text}}"></a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}/pinned"><input class="tab-btn btn {{%s:tab_pinned_focused}}" type="button" value="{{%s:tab_pinned_text}}"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="account-content">
|
||||
{{%s:acct_content}}
|
||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||
<div class="menubar">
|
||||
<a href="{{%s:prefix}}/blocked">{{ %s : blocked_str }}</a>
|
||||
<span class="bullet-separate">•</span>
|
||||
<a href="{{%s:prefix}}/muted">{{ %s : muted_str }}</a>
|
||||
<span class="bullet-separate">•</span>
|
||||
<a href="{{%s:prefix}}/favourites">{{ %s : favourited_str }}</a>
|
||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||
<a href="{{%s:prefix}}/user/{{%s:userid}}/action/{{%s:unfollow}}follow" class="follow-btn btn {{%s:active}}">
|
||||
{{%s:follow_text}}
|
||||
</a>
|
|
@ -1,3 +0,0 @@
|
|||
<div class="account-info">
|
||||
<div class="account-note">{{%s:acct_note}}</div>
|
||||
</div>
|
|
@ -1,36 +0,0 @@
|
|||
<div class="account-sidebar" {{ %s : header }}>
|
||||
<table class="acct-info">
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{%s:avatar}}" class="acct-pfp" loading="lazy">
|
||||
</td>
|
||||
<td class="acct-info-right">
|
||||
<span class="username">{{%s:username}}</span>
|
||||
<span class="acct">@<span class="acct-js-grep">{{%s:acct}}</span></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="acct-stats">
|
||||
<tr>
|
||||
<td class="header-btn btn">
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}">
|
||||
<span class="btn-header">{{%s:statuses_text}}</span>
|
||||
<span class="btn-content">{{%d:statuses_count}}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="header-btn btn">
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}/following">
|
||||
<span class="btn-header">{{%s:following_text}}</span>
|
||||
<span class="btn-content">{{%d:following_count}}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="header-btn btn">
|
||||
<a href="{{%s:prefix}}/@{{%s:acct}}/followers">
|
||||
<span class="btn-header">{{%s:followers_text}}</span>
|
||||
<span class="btn-content">{{%d:followers_count}}</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<div class="attachment-container attachment-audio">
|
||||
<!-- Here even if not sensitive -->
|
||||
<div class="sensitive-placeholder {{%s:sensitive}}"></div>
|
||||
<audio width="256" controls preload="metadata">
|
||||
<source src="{{%s:src}}">
|
||||
</video>
|
||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||
<div class="attachment-container attachment-gifv">
|
||||
<video width="256" autoplay muted>
|
||||
<source src="{{%s:src}}">
|
||||
[ GIFV ]
|
||||
</video>
|
||||
{{%s:sensitive}}
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue