#include "website.h"
#define PROJECT_PREVIEW_LINE_LIMIT 20
static void render_code_article(String *html, const char *input, int in_sz)
{
html->add("<div class=\"file-code\"><code>");
int start = 0;
bool was_spec = false;
char prev = 0;
for (int i = 0; i < in_sz; i++) {
char c = input[i];
if (c == '\n' || c == '\t') {
if (i > start) {
if (was_spec)
html->add(&input[start], i-start);
else
html->add_and_escape(&input[start], i-start);
start = i;
while (start < in_sz && (input[start] == '\n' || input[start] == '\t'))
start++;
}
if (c == '\n') {
if (prev == '\n') html->add_and_escape(&c, 1);
html->add("</code><code>");
}
else if (c == '\t') {
html->add(" ");
}
}
else if (NEEDS_ESCAPE(c)) {
if (!was_spec && i > start) {
html->add(&input[start], i-start);
start = i;
}
was_spec = true;
}
else {
if (was_spec && i > start) {
html->add_and_escape(&input[start], i-start);
start = i;
}
was_spec = false;
}
prev = c;
}
if (start < in_sz) {
if (was_spec)
html->add_and_escape(&input[start], in_sz-start);
else
html->add(&input[start], in_sz-start);
}
html->add("</code></div></article>");
}
void serve_projects_overview(Filesystem& fs, Response& response)
{
String *html = &response.html;
html->reserve(2048);
html->add("<!DOCTYPE html><html><head>" HTML_METAS "<title>Projects</title><style>");
fs.add_file_to_html(html, "client/banner.css");
fs.add_file_to_html(html, "client/article.css");
fs.add_file_to_html(html, "client/proj-overview.css");
html->add("</style></head><body>");
add_banner(fs, html, NAV_IDX_PROJECTS);
html->add("<div id=\"projects\">");
int projects = fs.lookup_dir("content/projects");
int proj = fs.dirs[projects].first_dir.modified;
String md_path;
md_path.add("content/projects");
while (proj >= 0) {
int f = fs.dirs[proj].first_file.alpha;
while (f >= 0) {
FS_File& file = fs.files[f];
char *fname = fs.name_pool.at(file.name_idx);
if (!caseless_match(fname, "readme.md")) {
f = file.next.alpha;
continue;
}
break;
}
if (f < 0) {
proj = fs.dirs[proj].next.modified;
continue;
}
html->add("<a class=\"proj-box\" href=\"/projects/");
char *pname = fs.name_pool.at(fs.dirs[proj].name_idx);
html->add_and_escape(pname, 0);
html->add("\"><div class=\"proj-overlay\"></div>");
md_path.add("/");
md_path.add(pname);
int pname_len = strlen(pname);
//fs.refresh_file(f);
html->add("<article class=\"proj-md\">");
Markdown_Params md_params = {
.path = md_path.data(),
.line_limit = PROJECT_PREVIEW_LINE_LIMIT,
.disable_anchors = true
};
produce_markdown_html(fs, *html, (const char*)fs.files[f].buffer, fs.files[f].size, md_params);
html->add("</article></a>");
md_path.scrub(pname_len + 1);
proj = fs.dirs[proj].next.modified;
}
html->add("</div></body></html>");
}
static void add_directory_html(String *html, Filesystem& fs, int didx, char *path_buf, int path_len, bool raw_url)
{
if (didx < 0)
return;
html->add("<ul>");
int d = fs.dirs[didx].first_dir.alpha;
while (d > 0) {
char *name = fs.name_pool.at(fs.dirs[d].name_idx);
int name_len = strlen(name);
// TODO: html->add("<details open>" instead of "<details>") if directory is ancestor of current file
html->add("<details><summary class=\"proj-tree-item\">");
html->add_and_escape(name, name_len);
html->add("</summary>");
memcpy(&path_buf[path_len], name, name_len);
path_buf[path_len + name_len] = '/';
add_directory_html(html, fs, d, path_buf, path_len + name_len + 1, raw_url);
html->add("</details>");
d = fs.dirs[d].next.alpha;
}
int f = fs.dirs[didx].first_file.alpha;
while (f > 0) {
char *name = fs.name_pool.at(fs.files[f].name_idx);
int name_len = strlen(name);
html->add("<li class=\"proj-tree-item\"><a href=\"");
int pos = 9;
html->add_and_escape(path_buf, pos);
if (raw_url)
html->add("-raw");
if (path_len > pos)
html->add_and_escape(path_buf + pos, path_len - pos);
html->add_and_escape(name);
html->add("\">");
html->add_and_escape(name);
html->add("</a></li>");
f = fs.files[f].next.alpha;
}
html->add("</ul>");
}
void serve_specific_project(Filesystem& fs, Response& response, char *project_type, int slash_pos, int name_len)
{
char dir_path[1024];
String path(dir_path, 1024);
path.add("content/projects/");
char *name = &project_type[slash_pos + 1];
bool show_raw_file = slash_pos == 12 && project_type[8] == '-' && project_type[9] == 'r' && project_type[10] == 'a' && project_type[11] == 'w';
if (name_len > 4) {
char *e = &name[name_len-4];
if (e[0] == '.' && (e[1] == 'z' || e[1] == 'Z') && (e[2] == 'i' || e[2] == 'I') && (e[3] == 'p' || e[3] == 'P')) {
path.add(name, name_len - 4);
int dir_idx = fs.lookup_dir(path.data());
if (dir_idx >= 0) {
write_zip_as_response(fs, dir_idx, response);
return;
}
path.scrub(name_len - 4);
}
}
int proj_len = 0;
while (proj_len < name_len && name[proj_len] && name[proj_len] != '/')
proj_len++;
int path_start = proj_len;
while (path_start < name_len && name[path_start] && name[path_start] == '/')
path_start++;
path.add(name, proj_len);
path.add('/');
int proj_dir = fs.lookup_dir(path.data());
if (proj_dir < 0) {
serve_404(fs, response);
return;
}
String *html = &response.html;
html->reserve(2048);
html->add("<!DOCTYPE html><html class=\"full\"><head>" HTML_METAS "<title>");
html->add_and_escape(name, name_len);
html->add("</title><style>");
fs.add_file_to_html(html, "client/banner.css");
fs.add_file_to_html(html, "client/article.css");
fs.add_file_to_html(html, "client/project.css");
html->add("</style><script>");
fs.add_file_to_html(html, "client/project.js");
html->add(
"</script></head><body class=\"full\">"
"<div id=\"proj-screen\">"
);
add_banner(fs, html, NAV_IDX_PROJECTS);
html->add(
"<div id=\"proj-page\"><div id=\"proj-sidebar\">"
"<h2 id=\"proj-name\" class=\"link\"><a href=\"/projects/"
);
html->add_and_escape(name, proj_len);
html->add("\">");
html->add_and_escape(name, proj_len);
html->add("</a></h2>");
html->add(
"<hr class=\"no-margin-top-bottom\">"
"<div id=\"proj-tree\">"
);
const int proj_root = 7; // skips past "content" (length 7), starts at a '/'
add_directory_html(html, fs, proj_dir, path.data() + proj_root, 11+proj_len, show_raw_file);
path.data()[path.len] = 0;
html->add("</div><hr />");
html->add("<a id=\"proj-download\" href=\"/projects/");
html->add_and_escape(name, proj_len);
html->add(".zip\"><div class=\"button\">DOWNLOAD</div>");
html->add(
"</a></div>"
"<div id=\"proj-sb-toggle\" onclick=\"toggle_project_sidebar();\">"
"<div id=\"proj-sb-shape-outer\" class=\"proj-sb-toggle-shape\">"
"<div id=\"proj-sb-shape-inner\" class=\"proj-sb-toggle-shape\">"
"</div></div></div><div id=\"proj-main\">"
);
if (path_start < name_len) {
path.add(&name[path_start], name_len - path_start);
int fidx = fs.lookup_file(path.data());
if (fidx < 0) {
html->add("<div id=\"proj-content\"><h1>Could not open file: ");
html->add_and_escape(path.data(), path.len);
html->add("</h1></div>");
}
else {
HTML_Type file_type = { NULL };
if ((fs.files[fidx].flags & FS_FLAG_ASCII) == 0) {
char *fname = fs.name_pool.at(fs.files[fidx].name_idx);
int len = strlen(fname);
char *p = &fname[len-1];
while (p > fname && *p != '.')
p--;
file_type = lookup_ext(p);
// if the file is a text file and it's not meant to be an iframe (webpage), ignore it
if (file_type.mime && file_type.tag && !memcmp(file_type.mime, "text", 4) && strcmp(file_type.tag, "iframe"))
file_type.tag = nullptr;
}
html->add("<div id=\"proj-header\"><div id=\"file-hdr-margin\">");
if (file_type.tag) {
html->add("<a id=\"file-hdr-type-select\" href=\"");
html->add(show_raw_file ? "/projects/" : "/projects-raw/");
html->add_and_escape(name, name_len);
html->add("\"><div class=\"button\">");
html->add(show_raw_file ? "blob" : "raw");
html->add("</div></a>");
}
html->add("</div><div class=\"link\"><a href=\"/content/projects/");
html->add_and_escape(name, name_len);
html->add("\"><h3>");
html->add_and_escape(&name[path_start], name_len - path_start);
html->add("</h3></a></div></div>");
if (!show_raw_file && file_type.tag) {
const char *blob_type = "outer";
bool use_inner = !strings_match(file_type.tag, "iframe", 6);
if (use_inner) {
html->add("<div id=\"proj-blob-outer\">");
blob_type = file_type.tag;
}
html->add("<");
html->add(file_type.tag);
html->add(" id=\"proj-blob-");
html->add(blob_type);
html->add("\" src=\"/");
html->add_and_escape(path.data());
html->add("\"></");
html->add(file_type.tag);
html->add(">");
if (use_inner)
html->add("</div>");
}
else {
html->add("<div id=\"proj-content\">");
render_code_article(html, (const char*)fs.files[fidx].buffer, fs.files[fidx].size);
html->add("</div>");
}
}
}
else {
int f = fs.dirs[proj_dir].first_file.alpha;
while (f >= 0) {
FS_File& file = fs.files[f];
char *fname = fs.name_pool.at(file.name_idx);
if (!caseless_match(fname, "readme.md")) {
f = file.next.alpha;
continue;
}
break;
}
if (f >= 0) {
html->add("<div id=\"proj-content\"><article class=\"proj-md\">");
Markdown_Params md_params = {
.path = path.data()
};
produce_markdown_html(fs, *html, (const char*)fs.files[f].buffer, fs.files[f].size, md_params);
html->add("</article></div>");
}
}
html->add("</div></div></div></body></html>");
}