WIP
This commit is contained in:
parent
35d5b02b5e
commit
f405f99729
9
dub.json
9
dub.json
|
@ -5,11 +5,16 @@
|
||||||
"copyright": "Copyright © 2019, Chris Josten",
|
"copyright": "Copyright © 2019, Chris Josten",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dyaml": "~>0.8.0",
|
"dyaml": "~>0.8.0",
|
||||||
|
"htmld": "~>0.3.7",
|
||||||
"vibe-d": "~>0.9.0"
|
"vibe-d": "~>0.9.0"
|
||||||
},
|
},
|
||||||
"description": "A blog based on Markdown and JSON",
|
"description": "A blog based on Markdown and JSON",
|
||||||
"license": "AGPLv3",
|
"license": "AGPLv3",
|
||||||
|
"mainSourceFile": "source/nl/netsoj/chris/blog/main.d",
|
||||||
"name": "mijnblog",
|
"name": "mijnblog",
|
||||||
"targetType": "executable",
|
"stringImportPaths": [
|
||||||
"stringImportPaths": ["views", "translations"]
|
"views",
|
||||||
|
"translations"
|
||||||
|
],
|
||||||
|
"targetType": "executable"
|
||||||
}
|
}
|
|
@ -7,7 +7,9 @@
|
||||||
"dyaml": "0.8.3",
|
"dyaml": "0.8.3",
|
||||||
"eventcore": "0.9.13",
|
"eventcore": "0.9.13",
|
||||||
"fswatch": "0.5.0",
|
"fswatch": "0.5.0",
|
||||||
|
"htmld": "0.3.7",
|
||||||
"libasync": "0.8.6",
|
"libasync": "0.8.6",
|
||||||
|
"libdominator": "1.1.7",
|
||||||
"libevent": "2.0.2+2.0.16",
|
"libevent": "2.0.2+2.0.16",
|
||||||
"memutils": "1.0.4",
|
"memutils": "1.0.4",
|
||||||
"mir-linux-kernel": "1.0.1",
|
"mir-linux-kernel": "1.0.1",
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Implements and holds caches for several pages.
|
||||||
|
*/
|
||||||
|
module nl.netsoj.chris.blog.cache;
|
||||||
|
|
||||||
import std.experimental.logger;
|
import std.experimental.logger;
|
||||||
import std.traits;
|
import std.traits;
|
||||||
|
|
||||||
import article;
|
import nl.netsoj.chris.blog.model.article;
|
||||||
import page;
|
import nl.netsoj.chris.blog.model.page;
|
||||||
import project;
|
import nl.netsoj.chris.blog.model.project;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,7 +26,7 @@ GenericCache!(Project, "a.title < b.title") projects;
|
||||||
* again if needed.
|
* again if needed.
|
||||||
*/
|
*/
|
||||||
struct GenericCache(T, string sortOrder)
|
struct GenericCache(T, string sortOrder)
|
||||||
if (isImplicitlyConvertible!(T, Page)) {
|
if (is(T : Page)) {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void addItem(T item) {
|
void addItem(T item) {
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
module nl.netsoj.chris.blog.constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants which are passed to templates while rendering.
|
* Constants which are passed to templates while rendering.
|
||||||
*/
|
*/
|
||||||
class Constants {
|
class Constants {
|
||||||
public static immutable string SITE_NAME = "Chris Josten's site";
|
public static immutable string SITE_NAME = "Chris Josten's site";
|
||||||
|
debug {
|
||||||
|
public static immutable string SITE_URL = "https://kortstondig.chris.netsoj.nl";
|
||||||
|
} else {
|
||||||
public static immutable string SITE_URL = "https://chris.netsoj.nl";
|
public static immutable string SITE_URL = "https://chris.netsoj.nl";
|
||||||
|
}
|
||||||
public static immutable string COPYRIGHT = "© Chris Josten, 2020";
|
public static immutable string COPYRIGHT = "© Chris Josten, 2020";
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
module nl.netsoj.chris.blog.interfaces.http;
|
||||||
|
|
||||||
|
import std.experimental.logger;
|
||||||
|
|
||||||
import vibe.d;
|
import vibe.d;
|
||||||
|
|
||||||
import cache;
|
import nl.netsoj.chris.blog.interfaces.indieauth;
|
||||||
import article;
|
import nl.netsoj.chris.blog.model.article;
|
||||||
import page;
|
import nl.netsoj.chris.blog.model.page;
|
||||||
import project;
|
import nl.netsoj.chris.blog.model.project;
|
||||||
|
import nl.netsoj.chris.blog.cache;
|
||||||
|
import nl.netsoj.chris.blog.constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output types for the content.
|
* Output types for the content.
|
||||||
|
@ -83,12 +89,24 @@ string singleResponseMixin(string arrayName, string templateName) {
|
||||||
render!("` ~ templateName ~ `", content);
|
render!("` ~ templateName ~ `", content);
|
||||||
break;
|
break;
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@translationContext!TranslateContext
|
@translationContext!TranslateContext
|
||||||
class MijnBlog {
|
class MijnBlog {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
this() {
|
||||||
|
m_indieAuth = new IndieAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IndieAuth subinterface
|
||||||
|
*/
|
||||||
|
@safe
|
||||||
|
@path("/indieweb")
|
||||||
|
@property IndieAuth indieweb() { return m_indieAuth; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates response for /posts/:slug and /palen/:slug.
|
* Generates response for /posts/:slug and /palen/:slug.
|
||||||
*/
|
*/
|
||||||
|
@ -140,8 +158,13 @@ public:
|
||||||
addCachingHeader(res);
|
addCachingHeader(res);
|
||||||
// If no slug is supplied, it will be adjusted to "index"
|
// If no slug is supplied, it will be adjusted to "index"
|
||||||
req.params.addField("slug", "index");
|
req.params.addField("slug", "index");
|
||||||
|
res.headers.addField("Link", "<" ~ Constants.SITE_URL ~ "/indieweb/auth>; rel=\"authorization_endpoint\"");
|
||||||
|
res.headers.addField("Link", "<" ~ Constants.SITE_URL ~ "/indieweb/token>; rel=\"token_endpoint\"");
|
||||||
|
res.headers.addField("Link", "<" ~ Constants.SITE_URL ~ "/indieweb/micropub>; rel=\"micropub\"");
|
||||||
mixin(singleResponseMixin("pages", "pages/page.dt"));
|
mixin(singleResponseMixin("pages", "pages/page.dt"));
|
||||||
}
|
}
|
||||||
|
private:
|
||||||
|
IndieAuth m_indieAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,7 +172,9 @@ public:
|
||||||
*/
|
*/
|
||||||
@safe
|
@safe
|
||||||
void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
|
void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
|
||||||
render!("pages/error.dt", error)(res);
|
//render!("pages/error.dt", error)(res);
|
||||||
|
import std.conv;
|
||||||
|
res.writeBody(text("Error ", error.code, ": ", error.message, "\n\n", error.debugMessage), "text/plain");
|
||||||
}
|
}
|
||||||
|
|
||||||
@trusted
|
@trusted
|
||||||
|
@ -171,4 +196,7 @@ void startHTTPServer() {
|
||||||
router.registerWebInterface(new MijnBlog);
|
router.registerWebInterface(new MijnBlog);
|
||||||
|
|
||||||
listenHTTP(settings, router);
|
listenHTTP(settings, router);
|
||||||
|
foreach(route; router.getAllRoutes()) {
|
||||||
|
infof("Path: %s", route);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
92
source/nl/netsoj/chris/blog/interfaces/indieauth.d
Normal file
92
source/nl/netsoj/chris/blog/interfaces/indieauth.d
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
module nl.netsoj.chris.blog.interfaces.indieauth;
|
||||||
|
|
||||||
|
import nl.netsoj.chris.blog.interfaces.http;
|
||||||
|
import nl.netsoj.chris.blog.interfaces.micropub;
|
||||||
|
import nl.netsoj.chris.blog.microformats.parser;
|
||||||
|
|
||||||
|
import mfd = nl.netsoj.chris.blog.microformats.definitions;
|
||||||
|
|
||||||
|
import vibe.d;
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
string logo;;
|
||||||
|
string name;
|
||||||
|
string url;
|
||||||
|
string clientId;
|
||||||
|
string redirectUri;
|
||||||
|
bool richInfo = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of https://www.w3.org/TR/indieauth/
|
||||||
|
*/
|
||||||
|
@translationContext!TranslateContext
|
||||||
|
class IndieAuth {
|
||||||
|
public:
|
||||||
|
this() {
|
||||||
|
m_microPub = new MicroPub();
|
||||||
|
}
|
||||||
|
|
||||||
|
@queryParam("response_type", "response_type")
|
||||||
|
@queryParam("me", "me")
|
||||||
|
@queryParam("client_id", "client_id")
|
||||||
|
@queryParam("redirect_uri", "redirect_uri")
|
||||||
|
@queryParam("state", "state")
|
||||||
|
void getAuth(HTTPServerRequest req, HTTPServerResponse res,
|
||||||
|
string response_type, string me, string client_id, string redirect_uri, string state) {
|
||||||
|
enforceHTTP(response_type == "code", HTTPStatus.badRequest);
|
||||||
|
|
||||||
|
URL source = URL(client_id);
|
||||||
|
HTTPClientResponse response = requestHTTP(source);
|
||||||
|
mfd.App[] parsedApps = parsePage!(mfd.App)(response.bodyReader.readAllUTF8, source);
|
||||||
|
|
||||||
|
App app;
|
||||||
|
app.name = client_id;
|
||||||
|
app.richInfo = false;
|
||||||
|
if (parsedApps.length > 0) {
|
||||||
|
auto parsedApp = parsedApps[0];
|
||||||
|
app.logo = parsedApp.logo;
|
||||||
|
app.name = parsedApp.name;
|
||||||
|
app.richInfo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.clientId = client_id;
|
||||||
|
URL redirectUrl = URL(redirect_uri);
|
||||||
|
redirectUrl.queryString = redirectUrl.queryString ~ "%scode=%s&state=%s".format(
|
||||||
|
(redirectUrl.queryString.length == 0 ? "" : "&"),
|
||||||
|
"123456", state);
|
||||||
|
app.redirectUri = redirectUrl.toString();
|
||||||
|
render!("pages/indieauth.dt", app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@safe
|
||||||
|
@path("/")
|
||||||
|
void getIndex(HTTPServerResponse res) {
|
||||||
|
res.writeBody("IndieAuth root", "text/plain");
|
||||||
|
}
|
||||||
|
|
||||||
|
@safe
|
||||||
|
void postToken(HTTPServerRequest req, HTTPServerResponse res,
|
||||||
|
string grant_type, string code, string client_id, string redirect_uri, string me) {
|
||||||
|
|
||||||
|
struct OkResponse {
|
||||||
|
string access_token;
|
||||||
|
string token_type = "Bearer";
|
||||||
|
string me;
|
||||||
|
string scope_;
|
||||||
|
}
|
||||||
|
|
||||||
|
OkResponse response;
|
||||||
|
response.me = me;
|
||||||
|
response.scope_ = "foo";
|
||||||
|
response.access_token = "baz";
|
||||||
|
|
||||||
|
res.writeJsonBody(response, HTTPStatus.ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
@safe @property
|
||||||
|
MicroPub micropub() { return m_microPub; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
MicroPub m_microPub;
|
||||||
|
}
|
14
source/nl/netsoj/chris/blog/interfaces/micropub.d
Normal file
14
source/nl/netsoj/chris/blog/interfaces/micropub.d
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
module nl.netsoj.chris.blog.interfaces.micropub;
|
||||||
|
|
||||||
|
import vibe.d;
|
||||||
|
|
||||||
|
import nl.netsoj.chris.blog.interfaces.http;
|
||||||
|
|
||||||
|
@translationContext!TranslateContext
|
||||||
|
class MicroPub {
|
||||||
|
|
||||||
|
@path("/")
|
||||||
|
void getIndex(HTTPServerRequest req, HTTPServerResponse res) {
|
||||||
|
res.writeBody("MicroPub endpoint", "text/plain");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,14 @@
|
||||||
|
module nl.netsoj.chris.blog.main;
|
||||||
|
|
||||||
import std.experimental.logger;
|
import std.experimental.logger;
|
||||||
import vibe.d;
|
import vibe.d;
|
||||||
|
|
||||||
import article;
|
import nl.netsoj.chris.blog.interfaces.http;
|
||||||
import page;
|
import nl.netsoj.chris.blog.model.article;
|
||||||
import project;
|
import nl.netsoj.chris.blog.model.page;
|
||||||
|
import nl.netsoj.chris.blog.model.project;
|
||||||
import cache;
|
import nl.netsoj.chris.blog.cache;
|
||||||
import http;
|
import nl.netsoj.chris.blog.watcher;
|
||||||
import watcher;
|
|
||||||
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
12
source/nl/netsoj/chris/blog/microformats/definitions.d
Normal file
12
source/nl/netsoj/chris/blog/microformats/definitions.d
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module nl.netsoj.chris.blog.microformats.definitions;
|
||||||
|
|
||||||
|
import nl.netsoj.chris.blog.microformats.parser;
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
@MicroFormat(MicroFormat.Type.Url)
|
||||||
|
string logo;
|
||||||
|
@MicroFormat(MicroFormat.Type.PlainText)
|
||||||
|
string name;
|
||||||
|
|
||||||
|
string url;
|
||||||
|
}
|
116
source/nl/netsoj/chris/blog/microformats/parser.d
Normal file
116
source/nl/netsoj/chris/blog/microformats/parser.d
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/**
|
||||||
|
* Parser for microformats.
|
||||||
|
* Standards: http://microformats.org/wiki/microformats2-parsing
|
||||||
|
*/
|
||||||
|
module nl.netsoj.chris.blog.microformats.parser;
|
||||||
|
|
||||||
|
import std.exception;
|
||||||
|
import std.traits;
|
||||||
|
|
||||||
|
import vibe.inet.url;
|
||||||
|
|
||||||
|
import html;
|
||||||
|
|
||||||
|
import nl.netsoj.chris.blog.url;
|
||||||
|
|
||||||
|
class MicroFormatParseException : Exception {
|
||||||
|
mixin basicExceptionCtors;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct MicroFormatProperty {
|
||||||
|
enum Type {
|
||||||
|
RootClass,
|
||||||
|
PlainText,
|
||||||
|
Url,
|
||||||
|
DateTime,
|
||||||
|
EmbeddedMarkup
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type = Type.PlainText;
|
||||||
|
|
||||||
|
string getClassPrefix() pure {
|
||||||
|
final switch(type) {
|
||||||
|
case Type.RootClass:
|
||||||
|
return "h-";
|
||||||
|
case Type.PlainText:
|
||||||
|
return "p-";
|
||||||
|
case Type.Url:
|
||||||
|
return "u-";
|
||||||
|
case Type.DateTime:
|
||||||
|
return "dt-";
|
||||||
|
case Type.EmbeddedMarkup:
|
||||||
|
return "e-";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a web page to extract to the given microformat model out of it
|
||||||
|
*/
|
||||||
|
T[] parsePage(T)(string source, URL url) {
|
||||||
|
return parsePage!T(createDocument(source), url);
|
||||||
|
}
|
||||||
|
|
||||||
|
T[] parsePage(T)(Document page, URL url)
|
||||||
|
if (isAggregateType!T) {
|
||||||
|
import std.algorithm;
|
||||||
|
import std.array;
|
||||||
|
import std.conv;
|
||||||
|
import std.range;
|
||||||
|
import std.string;
|
||||||
|
|
||||||
|
string rootClass = "h-" ~ T.stringof.toLower;
|
||||||
|
|
||||||
|
return page.querySelectorAll(".%s".format(rootClass)).map!((node){
|
||||||
|
alias PropType = MicroFormatProperty.Type;
|
||||||
|
T instance = T();
|
||||||
|
MicroFormatProperty uda;
|
||||||
|
string propertyClass;
|
||||||
|
Node propNode;
|
||||||
|
static foreach(sym; getSymbolsByUDA!(T, MicroFormatProperty)) {
|
||||||
|
uda = getUDAs!(sym, MicroFormatProperty)[0];
|
||||||
|
propertyClass = uda.getClassPrefix() ~ sym.stringof.toLower;
|
||||||
|
|
||||||
|
propNode = page.querySelector(".%s".format(propertyClass), node);
|
||||||
|
switch (uda.type) {
|
||||||
|
case PropType.PlainText:
|
||||||
|
if (propNode.firstChild && propNode.firstChild.isTextNode()) {
|
||||||
|
__traits(getMember, instance, sym.stringof) = to!string(propNode.text);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PropType.Url:
|
||||||
|
if (propNode.tag == "a" && propNode.hasAttr("href")) {
|
||||||
|
__traits(getMember, instance, sym.stringof) = resolveURL(url, to!string(propNode["href"])).toString;
|
||||||
|
} else if (propNode.tag == "img" && propNode.hasAttr("src")) {
|
||||||
|
__traits(getMember, instance, sym.stringof) = resolveURL(url, to!string(propNode["src"])).toString;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}).array;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
import std.stdio;
|
||||||
|
string page = q"eos
|
||||||
|
<div class="h-app">
|
||||||
|
<img src="/logo.png" class="u-logo">
|
||||||
|
<a href="/" class="u-url p-name">Example App</a>
|
||||||
|
</div>"
|
||||||
|
eos";
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
@MicroFormatProperty(MicroFormatProperty.Type.Url)
|
||||||
|
string logo;
|
||||||
|
@MicroFormatProperty(MicroFormatProperty.Type.PlainText)
|
||||||
|
string name;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ts = parsePage!App(page, URL("https://example.com/"));
|
||||||
|
assert(ts[0] == App ("https://example.com:443/logo.png", "Example App"));
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
module nl.netsoj.chris.blog.model.article;
|
||||||
|
|
||||||
import std.file;
|
import std.file;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
import std.string;
|
import std.string;
|
||||||
|
@ -7,8 +9,8 @@ import std.experimental.logger;
|
||||||
import dyaml;
|
import dyaml;
|
||||||
import vibe.d;
|
import vibe.d;
|
||||||
|
|
||||||
import page;
|
import nl.netsoj.chris.blog.model.page;
|
||||||
import utils;
|
import nl.netsoj.chris.blog.utils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
module nl.netsoj.chris.blog.model.page;
|
||||||
|
|
||||||
import std.exception;
|
import std.exception;
|
||||||
import std.experimental.logger;
|
import std.experimental.logger;
|
||||||
import std.file;
|
import std.file;
|
||||||
|
@ -7,7 +9,7 @@ import std.stdio;
|
||||||
import dyaml;
|
import dyaml;
|
||||||
import vibe.vibe;
|
import vibe.vibe;
|
||||||
|
|
||||||
import utils;
|
import nl.netsoj.chris.blog.utils;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
module nl.netsoj.chris.blog.model.project;
|
||||||
|
|
||||||
import std.array;
|
import std.array;
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
import std.typecons;
|
import std.typecons;
|
||||||
|
@ -5,9 +7,9 @@ import std.typecons;
|
||||||
import dyaml;
|
import dyaml;
|
||||||
import vibe.vibe;
|
import vibe.vibe;
|
||||||
|
|
||||||
import page;
|
import nl.netsoj.chris.blog.model.page;
|
||||||
import utils;
|
import nl.netsoj.chris.blog.staticpaths;
|
||||||
import staticpaths;
|
import nl.netsoj.chris.blog.utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a project, like an unfinished application
|
* Represents a project, like an unfinished application
|
||||||
|
|
1
source/nl/netsoj/chris/blog/package.d
Normal file
1
source/nl/netsoj/chris/blog/package.d
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module nl.netsoj.chris.blog;
|
|
@ -1,3 +1,5 @@
|
||||||
|
module nl.netsoj.chris.blog.staticpaths;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paths to static data.
|
* Paths to static data.
|
||||||
*/
|
*/
|
||||||
|
|
27
source/nl/netsoj/chris/blog/url.d
Normal file
27
source/nl/netsoj/chris/blog/url.d
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module nl.netsoj.chris.blog.url;
|
||||||
|
|
||||||
|
import std.exception;
|
||||||
|
|
||||||
|
import vibe.inet.url;
|
||||||
|
|
||||||
|
URL resolveURL(URL source, string other) {
|
||||||
|
URL otherUrl = URL(other);
|
||||||
|
if (otherUrl.schema.length > 0 || otherUrl.host.length > 0) {
|
||||||
|
return otherUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherUrl.schema.length == 0) {
|
||||||
|
enforce(source.schema.length > 0, "Source URL must have a scheme to resolve the other URL");
|
||||||
|
otherUrl.schema = source.schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherUrl.host.length == 0) {
|
||||||
|
otherUrl.host = source.host;
|
||||||
|
otherUrl.port = source.port;
|
||||||
|
if (!otherUrl.path.absolute) {
|
||||||
|
otherUrl.path = source.path ~ otherUrl.path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return otherUrl;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
module nl.netsoj.chris.blog.utils;
|
||||||
|
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
import std.array;
|
import std.array;
|
||||||
import std.conv;
|
import std.conv;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
module nl.netsoj.chris.blog.watcher;
|
||||||
|
|
||||||
import std.array;
|
import std.array;
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
import std.experimental.logger;
|
import std.experimental.logger;
|
||||||
|
@ -7,14 +9,14 @@ import std.traits;
|
||||||
|
|
||||||
import vibe.d;
|
import vibe.d;
|
||||||
|
|
||||||
import cache;
|
import nl.netsoj.chris.blog.cache;
|
||||||
import page;
|
import nl.netsoj.chris.blog.model.page;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads pages into memory and sets up a "watcher" to watch a directory for file changes.
|
* Loads pages into memory and sets up a "watcher" to watch a directory for file changes.
|
||||||
*/
|
*/
|
||||||
void initPages(T, C)(C *cache, const string directory)
|
void initPages(T, C)(C *cache, const string directory)
|
||||||
if (isImplicitlyConvertible!(T, Page)) {
|
if (is(T : Page)) {
|
||||||
|
|
||||||
bool addPage(string path) {
|
bool addPage(string path) {
|
||||||
try {
|
try {
|
||||||
|
@ -22,7 +24,7 @@ void initPages(T, C)(C *cache, const string directory)
|
||||||
logf("Added %s", newPage.slug);
|
logf("Added %s", newPage.slug);
|
||||||
cache.addItem(newPage);
|
cache.addItem(newPage);
|
||||||
return true;
|
return true;
|
||||||
} catch (page.ArticleParseException e) {
|
} catch (ArticleParseException e) {
|
||||||
logf("Could not parse %s: %s", path, e);
|
logf("Could not parse %s: %s", path, e);
|
||||||
return false;
|
return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -72,7 +74,7 @@ void initPages(T, C)(C *cache, const string directory)
|
||||||
try {
|
try {
|
||||||
newPage = new T(change.path.toString());
|
newPage = new T(change.path.toString());
|
||||||
cache.changeItem(newPage);
|
cache.changeItem(newPage);
|
||||||
} catch(page.ArticleParseException e) {
|
} catch(ArticleParseException e) {
|
||||||
warningf("Could not parse %s", change.path.toString());
|
warningf("Could not parse %s", change.path.toString());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
warningf("Error while updating %s: %s", change.path.toString(), e.msg);
|
warningf("Error while updating %s: %s", change.path.toString(), e.msg);
|
||||||
|
|
|
@ -12,7 +12,7 @@ block content
|
||||||
- if (articleList.length == 0)
|
- if (articleList.length == 0)
|
||||||
p No posts found
|
p No posts found
|
||||||
- else
|
- else
|
||||||
- import utils;
|
- import nl.netsoj.chris.blog.utils;
|
||||||
- foreach(article; articleList)
|
- foreach(article; articleList)
|
||||||
article
|
article
|
||||||
header
|
header
|
||||||
|
|
|
@ -14,7 +14,7 @@ block extra_meta_data
|
||||||
|
|
||||||
block content
|
block content
|
||||||
article(itemscope, itemtype="https://schema.org/BlogPosting", lang="#{content.language}")
|
article(itemscope, itemtype="https://schema.org/BlogPosting", lang="#{content.language}")
|
||||||
- import utils;
|
- import nl.netsoj.chris.blog.utils;
|
||||||
header
|
header
|
||||||
h1.title(itemprop="headline") #{content.title}
|
h1.title(itemprop="headline") #{content.title}
|
||||||
p.subtitle
|
p.subtitle
|
||||||
|
|
21
views/pages/indieauth.dt
Normal file
21
views/pages/indieauth.dt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
extends parts/page
|
||||||
|
|
||||||
|
block header
|
||||||
|
title Aanmelden - Netsoj.nl
|
||||||
|
|
||||||
|
block sidebar
|
||||||
|
|
||||||
|
block content
|
||||||
|
- if (app.richInfo)
|
||||||
|
header.project-header
|
||||||
|
img.project-icon(src="#{app.logo}", alt="Icon of #{app.name}")
|
||||||
|
div
|
||||||
|
h1.title Aanmelden met #[a(href="#{app.clientId}") #{app.name}]
|
||||||
|
span.project-description #{app.clientId}
|
||||||
|
- else
|
||||||
|
header
|
||||||
|
h1.title Aanmelden met #[a(href="#{app.clientId}") #{app.name}]
|
||||||
|
|
||||||
|
p #{app.name} wil uw identiteit bevestigen.
|
||||||
|
|
||||||
|
p #[a(href="#{app.redirectUri}") Aanvaarden en aanmelden] #[a(href="javascript:history.back();") Weigeren]
|
|
@ -1,6 +1,6 @@
|
||||||
doctype html
|
doctype html
|
||||||
html(prefix="og: http://ogp.me/ns#")
|
html(prefix="og: http://ogp.me/ns#")
|
||||||
- import constants;
|
- import nl.netsoj.chris.blog.constants;
|
||||||
- import vibe.d;
|
- import vibe.d;
|
||||||
head
|
head
|
||||||
//- Kick off loading the css as fast as possible
|
//- Kick off loading the css as fast as possible
|
||||||
|
|
Loading…
Reference in a new issue