Initial commit
15
.gitignore
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
.dub
|
||||||
|
docs.json
|
||||||
|
__dummy.html
|
||||||
|
docs/
|
||||||
|
/mijnblog
|
||||||
|
mijnblog.so
|
||||||
|
mijnblog.dylib
|
||||||
|
mijnblog.dll
|
||||||
|
mijnblog.a
|
||||||
|
mijnblog.lib
|
||||||
|
mijnblog-test-*
|
||||||
|
*.exe
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.lst
|
2
default.nix
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
pkgs.callPackage ./mijnblog.nix {}
|
13
dub.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"Chris Josten"
|
||||||
|
],
|
||||||
|
"copyright": "Copyright © 2019, Chris Josten",
|
||||||
|
"dependencies": {
|
||||||
|
"dyaml": "~>0.8.0",
|
||||||
|
"vibe-d": "~>0.8.6"
|
||||||
|
},
|
||||||
|
"description": "A blog based on Markdown and JSON",
|
||||||
|
"license": "AGPLv3",
|
||||||
|
"name": "mijnblog"
|
||||||
|
}
|
21
dub.selections.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"fileVersion": 1,
|
||||||
|
"versions": {
|
||||||
|
"botan": "1.12.10",
|
||||||
|
"botan-math": "1.0.3",
|
||||||
|
"diet-ng": "1.6.0",
|
||||||
|
"dyaml": "0.8.0",
|
||||||
|
"eventcore": "0.8.48",
|
||||||
|
"fswatch": "0.5.0",
|
||||||
|
"libasync": "0.8.4",
|
||||||
|
"libevent": "2.0.2+2.0.16",
|
||||||
|
"memutils": "0.4.13",
|
||||||
|
"mir-linux-kernel": "1.0.1",
|
||||||
|
"openssl": "1.1.6+1.0.1g",
|
||||||
|
"stdx-allocator": "2.77.5",
|
||||||
|
"taggedalgebraic": "0.11.7",
|
||||||
|
"tinyendian": "0.2.0",
|
||||||
|
"vibe-core": "1.7.0",
|
||||||
|
"vibe-d": "0.8.6"
|
||||||
|
}
|
||||||
|
}
|
20
dub.selections.json.bak
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"fileVersion": 1,
|
||||||
|
"versions": {
|
||||||
|
"botan": "1.12.10",
|
||||||
|
"botan-math": "1.0.3",
|
||||||
|
"diet-ng": "1.5.0",
|
||||||
|
"dyaml": "0.8.0",
|
||||||
|
"eventcore": "0.8.43",
|
||||||
|
"libasync": "0.8.4",
|
||||||
|
"libevent": "2.0.2+2.0.16",
|
||||||
|
"memutils": "0.4.13",
|
||||||
|
"mir-linux-kernel": "1.0.1",
|
||||||
|
"openssl": "1.1.6+1.0.1g",
|
||||||
|
"stdx-allocator": "2.77.5",
|
||||||
|
"taggedalgebraic": "0.11.4",
|
||||||
|
"tinyendian": "0.2.0",
|
||||||
|
"vibe-core": "1.6.2",
|
||||||
|
"vibe-d": "0.8.6"
|
||||||
|
}
|
||||||
|
}
|
194
dub.selections.nix
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
# This file was generated by https://github.com/lionello/dub2nix v0.2.1
|
||||||
|
[ {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/D-Programming-Deimos/libevent.git";
|
||||||
|
rev = "v2.0.2+2.0.16";
|
||||||
|
sha256 = "1axv0pv5w26i61m191570ydfxb3gghgn8yh654qjyxi0cxvn92bf";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2016-11-29T18:50:57-08:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/hyv33akqdvhk978p1inmy0r64vimhrdl-libevent";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/kiith-sa/tinyendian.git";
|
||||||
|
rev = "v0.2.0";
|
||||||
|
sha256 = "086gf5aga52wr5rj2paq54daj8lafn980x77b706vvvqaz2mlis8";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2018-06-10T11:04:28+02:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/9c7fsmi5am84j6dq2mp3va306x3ay291-tinyendian";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/libmir/mir-linux-kernel.git";
|
||||||
|
rev = "v1.0.1";
|
||||||
|
sha256 = "1gcaavsni47352nvw9s41zkaswd582cafq3931i0zinhd9s3clag";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2018-08-04T16:03:31+07:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/qz6axwci8kb1477j9dzgq85nscjfyvj1-mir-linux-kernel";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/etcimon/botan-math.git";
|
||||||
|
rev = "v1.0.3";
|
||||||
|
sha256 = "0jbpgpd1sjkp5075xh0k7m4m2d3yahi5scx3ql35fj4yns2mn2fq";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2016-08-23T10:45:25-04:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/dic7r8jab936cgqc9fck032r49nvgv07-botan-math";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/etcimon/memutils.git";
|
||||||
|
rev = "v0.4.13";
|
||||||
|
sha256 = "12mssymhimfsw6flkchqg0ql3qrs91dlh88xjdw5il8rb4h4ddfz";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2018-10-09T11:51:45-04:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/hszwaxwm26bqp3h761j3kq1hsbs3hpra-memutils";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/s-ludwig/taggedalgebraic.git";
|
||||||
|
rev = "v0.11.7";
|
||||||
|
sha256 = "1q8pg3r1zw72114y76kgr73iylp77m4wrl180r6d632vc0x8r58f";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2019-11-01T09:00:47+01:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/x1qwbzxp7nx0fpwvr4v30j0nnwf6pjcx-taggedalgebraic";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/vibe-d/vibe-core.git";
|
||||||
|
rev = "v1.7.0";
|
||||||
|
sha256 = "0mp8daspzn0ww9jr6nf0zwdj6ff6j7l4j5h6h450z18zn7kjkd8x";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2019-09-17T23:40:53+02:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/g46p3lfzkcsrdcvxv25a2g2vhcfghdgr-vibe-core";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/vibe-d/vibe.d.git";
|
||||||
|
rev = "v0.8.6";
|
||||||
|
sha256 = "1zqan3rsjmkxnq8wxnzrj4vw912lnnp2fphzfz1sriyysj59b28f";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2019-10-03T17:14:48+02:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/wsskmj5cs3p6nlkfc80z8bm90nxxvy0n-vibe.d";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/wilzbach/stdx-allocator.git";
|
||||||
|
rev = "v2.77.5";
|
||||||
|
sha256 = "03av8zp5p6vf6fg005xbmwnjfw96jyrr4dcj4m56c4a3vx7v72pk";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2018-12-23T13:54:22+01:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/b3h25asfh205wzwjfzjf2k2kkccpp96k-stdx-allocator";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/etcimon/botan.git";
|
||||||
|
rev = "v1.12.10";
|
||||||
|
sha256 = "02zrq9wcb1hhsl9psgkvi65g6c7g990yy9d75gjgbz4b1wb61nwf";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2018-06-22T15:33:04-04:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/a1431sfkpz8dk0jlix5zn90g19av1lnc-botan";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/rejectedsoftware/diet-ng.git";
|
||||||
|
rev = "v1.6.0";
|
||||||
|
sha256 = "02pj2rf2qfi0acnw496nr480hj47ggy4isfhsvdajd6al4h03a6x";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2019-08-16T19:55:02+02:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/jld9bk9vr9k2pfb3li1zcvhilka8gdzl-diet-ng";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/WebFreak001/FSWatch.git";
|
||||||
|
rev = "v0.5.0";
|
||||||
|
sha256 = "1msv98hvxg1nr44yp8pm1as3byx0ibxfpr662564zdq5afci49fc";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2019-06-13T21:48:00+02:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/88dmp1a4cygp3phm1xb7m663jpqhih42-FSWatch";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/D-Programming-Deimos/openssl.git";
|
||||||
|
rev = "v1.1.6+1.0.1g";
|
||||||
|
sha256 = "0ramqjyq4v7xpqwf4nf4ddmsg6yk2fbzn2d1yj4b6dla3q5lv9i3";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2017-11-05T20:15:26+01:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/jbfb5in6gzqs2byn1hdc1625yh8sx6j2-openssl";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/vibe-d/eventcore.git";
|
||||||
|
rev = "v0.8.48";
|
||||||
|
sha256 = "1ifx4sr04x7mvw1w9g3n72szy83q487ya0yi212czys4bgwdvkiz";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2019-10-25T22:46:08+02:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/65g2z4y29z49cfjvan2dz01nyd4x5mdp-eventcore";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/kiith-sa/D-YAML.git";
|
||||||
|
rev = "v0.8.0";
|
||||||
|
sha256 = "1my9yck316q0kqalxkhfpffgvrfskin4qw3171md4iz3qbhwb799";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2019-05-26T20:26:26+02:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/v30av8a9fqrvhqz09qqiljbcr88dbx0z-D-YAML";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
fetch = {
|
||||||
|
type = "git";
|
||||||
|
url = "https://github.com/etcimon/libasync.git";
|
||||||
|
rev = "v0.8.4";
|
||||||
|
sha256 = "13v3dg0838j9h377cs9bq2nk71nrchx6kix9mwiy0fhw7sf7nxb4";
|
||||||
|
fetchSubmodules = false;
|
||||||
|
date = "2019-02-28T10:02:56-05:00";
|
||||||
|
deepClone = false;
|
||||||
|
leaveDotGit = false;
|
||||||
|
path = "/nix/store/6b552a8zdl8z99gc11xgj95k52vwvlcw-libasync";
|
||||||
|
};
|
||||||
|
} ]
|
12
mijnblog.nix
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{pkgs}:
|
||||||
|
with (import ./mkDub.nix {
|
||||||
|
inherit pkgs;
|
||||||
|
});
|
||||||
|
mkDubDerivation {
|
||||||
|
src = ./.;
|
||||||
|
dubJSON = ./dub.json;
|
||||||
|
selections = ./dub.selections.nix;
|
||||||
|
version = "0.0.1";
|
||||||
|
buildInputs = [ pkgs.openssl ];
|
||||||
|
propagatedBuildInputs = [ pkgs.nix-prefetch-git ];
|
||||||
|
}
|
121
mkDub.nix
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {},
|
||||||
|
stdenv ? pkgs.stdenv,
|
||||||
|
rdmd ? pkgs.rdmd,
|
||||||
|
dmd ? pkgs.dmd,
|
||||||
|
dub ? pkgs.dub }:
|
||||||
|
|
||||||
|
with stdenv;
|
||||||
|
let
|
||||||
|
# Filter function to remove the .dub package folder from src
|
||||||
|
filterDub = name: type: let baseName = baseNameOf (toString name); in ! (
|
||||||
|
type == "directory" && baseName == ".dub"
|
||||||
|
);
|
||||||
|
|
||||||
|
# Convert a GIT rev string (tag) to a simple semver version
|
||||||
|
rev-to-version = builtins.replaceStrings ["v" "refs/tags/v"] ["" ""];
|
||||||
|
|
||||||
|
dep2src = dubDep: pkgs.fetchgit { inherit (dubDep.fetch) url rev sha256 fetchSubmodules; };
|
||||||
|
|
||||||
|
# Fetch a dependency (source only for now)
|
||||||
|
fromDub = dubDep: mkDerivation rec {
|
||||||
|
name = "${src.name}-${version}";
|
||||||
|
version = rev-to-version dubDep.fetch.rev;
|
||||||
|
nativeBuildInputs = [ rdmd dmd dub ];
|
||||||
|
src = dep2src dubDep;
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
export HOME=$PWD
|
||||||
|
dub build -b=release
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
# outputs = [ "lib" ];
|
||||||
|
|
||||||
|
# installPhase = ''
|
||||||
|
# runHook preInstall
|
||||||
|
# mkdir -p $out/bin
|
||||||
|
# runHook postInstall
|
||||||
|
# '';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Adds a local package directory (e.g. a git repository) to Dub
|
||||||
|
dub-add-local = dubDep: "dub add-local ${(fromDub dubDep).src.outPath} ${rev-to-version dubDep.fetch.rev}";
|
||||||
|
|
||||||
|
# The target output of the Dub package
|
||||||
|
targetOf = package: "${package.targetPath or "."}/${package.targetName or package.name}";
|
||||||
|
|
||||||
|
# Remove reference to build tools and library sources
|
||||||
|
disallowedReferences = deps: [ dmd rdmd dub ] ++ builtins.map dep2src deps;
|
||||||
|
|
||||||
|
removeExpr = refs: ''remove-references-to ${lib.concatMapStrings (ref: " -t ${ref}") refs}'';
|
||||||
|
|
||||||
|
in {
|
||||||
|
inherit fromDub;
|
||||||
|
|
||||||
|
mkDubDerivation = lib.makeOverridable ({
|
||||||
|
src,
|
||||||
|
nativeBuildInputs ? [],
|
||||||
|
dubJSON ? src + "/dub.json",
|
||||||
|
selections ? src + "/dub.selections.nix",
|
||||||
|
deps ? import selections,
|
||||||
|
passthru ? {},
|
||||||
|
package ? lib.importJSON dubJSON,
|
||||||
|
...
|
||||||
|
} @ attrs: stdenv.mkDerivation (attrs // {
|
||||||
|
|
||||||
|
pname = package.name;
|
||||||
|
|
||||||
|
nativeBuildInputs = [ rdmd dmd dub pkgs.removeReferencesTo ] ++ nativeBuildInputs;
|
||||||
|
disallowedReferences = disallowedReferences deps;
|
||||||
|
|
||||||
|
passthru = passthru // {
|
||||||
|
inherit dub dmd rdmd pkgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
src = lib.cleanSourceWith {
|
||||||
|
filter = filterDub;
|
||||||
|
src = lib.cleanSource src;
|
||||||
|
};
|
||||||
|
|
||||||
|
preFixup = ''
|
||||||
|
find $out/bin -type f -exec ${removeExpr (disallowedReferences deps)} '{}' + || true
|
||||||
|
'';
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
export HOME=$PWD
|
||||||
|
${lib.concatMapStringsSep "\n" dub-add-local deps}
|
||||||
|
dub build -b release --combined --skip-registry=all
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
checkPhase = ''
|
||||||
|
runHook preCheck
|
||||||
|
|
||||||
|
export HOME=$PWD
|
||||||
|
${lib.concatMapStringsSep "\n" dub-add-local deps}
|
||||||
|
dub test --combined --skip-registry=all
|
||||||
|
|
||||||
|
runHook postCheck
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp -r "${targetOf package}" $out/bin
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = lib.optionalAttrs (package ? description) {
|
||||||
|
description = package.description;
|
||||||
|
} // attrs.meta or {};
|
||||||
|
} // lib.optionalAttrs (!(attrs ? version)) {
|
||||||
|
# Use name from dub.json, unless pname and version are specified
|
||||||
|
name = package.name;
|
||||||
|
}));
|
||||||
|
}
|
BIN
public/static/img/256x256
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/static/img/chris-icoon.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/static/img/logo.gif
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
public/static/img/logo.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
public/static/img/logo.png.bak
Normal file
After Width: | Height: | Size: 17 KiB |
1238
public/static/script/prism.js
Normal file
139
public/static/style/base.css
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
@media (max-width: 850px) {
|
||||||
|
body {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > main {
|
||||||
|
min-width: calc(100% - 2px - 4em);
|
||||||
|
}
|
||||||
|
|
||||||
|
body > nav {
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:root {
|
||||||
|
font-family: "sans-serif";
|
||||||
|
--colour-bg: #f0f0f0;
|
||||||
|
--colour-bg-main: #ddd;
|
||||||
|
--colour-bg-code: #ccc;
|
||||||
|
--colour-fg: #000000;
|
||||||
|
--colour-fg-highlight: #7f0602;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--colour-bg);
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > nav {
|
||||||
|
flex: 0 0 160px;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > nav > header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body > nav > header > img {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > main {
|
||||||
|
flex: 1 1;
|
||||||
|
padding: 2em;
|
||||||
|
/* width: 600px; */
|
||||||
|
border-left: var(--colour-fg-highlight) dotted 1px;
|
||||||
|
border-right: var(--colour-fg-highlight) dotted 1px;
|
||||||
|
max-width: 600px;
|
||||||
|
background-color: var(--colour-bg-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, .title {
|
||||||
|
margin: 0;
|
||||||
|
/* font-size: 2em; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px dotted black;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
border-top: 1px dotted black;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav ul {
|
||||||
|
padding: 0em;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MARKDOWN */
|
||||||
|
|
||||||
|
:not(pre) > code {
|
||||||
|
background-color: var(--colour-bg-code);
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.sourceCode, blockquote {
|
||||||
|
scrollbar-color: var(--colour-fg) var(--colour-bg-code);
|
||||||
|
scrollbar-width: thin;
|
||||||
|
background-color: var(--colour-bg-code);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.sourceCode {
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, blockquote {
|
||||||
|
border-left: 1px solid var(--colour-fg-highlight);
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-right: 1em;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote footer {
|
||||||
|
text-align: right;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
background-color: var(--colour-bg-code);
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure > img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure > figcaption {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--colour-fg-highlight);
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--colour-fg);
|
||||||
|
text-decoration: underline solid;
|
||||||
|
}
|
62
public/static/style/highlight.css
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||||
|
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
||||||
|
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||||
|
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||||
|
div.sourceCode { margin: 1em 0; }
|
||||||
|
pre.sourceCode { margin: 0; }
|
||||||
|
@media screen {
|
||||||
|
div.sourceCode { overflow: auto; }
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
pre > code.sourceCode { white-space: pre-wrap; }
|
||||||
|
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||||||
|
}
|
||||||
|
pre.numberSource code
|
||||||
|
{ counter-reset: source-line 0; }
|
||||||
|
pre.numberSource code > span
|
||||||
|
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||||
|
pre.numberSource code > span > a:first-child::before
|
||||||
|
{ content: counter(source-line);
|
||||||
|
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||||
|
border: none; display: inline-block;
|
||||||
|
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||||
|
-khtml-user-select: none; -moz-user-select: none;
|
||||||
|
-ms-user-select: none; user-select: none;
|
||||||
|
padding: 0 4px; width: 4em;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||||||
|
div.sourceCode
|
||||||
|
{ }
|
||||||
|
@media screen {
|
||||||
|
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||||
|
}
|
||||||
|
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||||||
|
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||||||
|
code span.at { color: #7d9029; } /* Attribute */
|
||||||
|
code span.bn { color: #40a070; } /* BaseN */
|
||||||
|
code span.bu { } /* BuiltIn */
|
||||||
|
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||||||
|
code span.ch { color: #4070a0; } /* Char */
|
||||||
|
code span.cn { color: #880000; } /* Constant */
|
||||||
|
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||||||
|
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||||
|
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||||||
|
code span.dt { color: #902000; } /* DataType */
|
||||||
|
code span.dv { color: #40a070; } /* DecVal */
|
||||||
|
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||||||
|
code span.ex { } /* Extension */
|
||||||
|
code span.fl { color: #40a070; } /* Float */
|
||||||
|
code span.fu { color: #06287e; } /* Function */
|
||||||
|
code span.im { } /* Import */
|
||||||
|
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||||||
|
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||||||
|
code span.op { color: #666666; } /* Operator */
|
||||||
|
code span.ot { color: #007020; } /* Other */
|
||||||
|
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||||||
|
code span.sc { color: #4070a0; } /* SpecialChar */
|
||||||
|
code span.ss { color: #bb6688; } /* SpecialString */
|
||||||
|
code span.st { color: #4070a0; } /* String */
|
||||||
|
code span.va { color: #19177c; } /* Variable */
|
||||||
|
code span.vs { color: #4070a0; } /* VerbatimString */
|
||||||
|
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
3
public/static/style/mobile.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background: red;
|
||||||
|
}
|
45
public/static/style/old.css
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
body {
|
||||||
|
background-image: url(old/chip.jpg);
|
||||||
|
cursor: url(old/cursor.gif), auto;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
cursor: url(old/cursor-over.gif), auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > nav {
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > nav ul {
|
||||||
|
padding-left: 2em;
|
||||||
|
list-style-image: url(old/bullet.gif);
|
||||||
|
}
|
||||||
|
|
||||||
|
body > main {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
border-image: url(old/skull-border.gif) 33% / 2em round;
|
||||||
|
/*border-left-width: 10px !important;
|
||||||
|
border-right-width: 10px !important;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 100%;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
background: #0000ff;
|
||||||
|
border-left: #0000cc 6px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: blue;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
BIN
public/static/style/old/bullet.gif
Normal file
After Width: | Height: | Size: 326 B |
BIN
public/static/style/old/chip.jpg
Normal file
After Width: | Height: | Size: 95 KiB |
BIN
public/static/style/old/cursor-over.gif
Normal file
After Width: | Height: | Size: 299 B |
BIN
public/static/style/old/cursor.gif
Normal file
After Width: | Height: | Size: 299 B |
BIN
public/static/style/old/skull-border.gif
Normal file
After Width: | Height: | Size: 247 KiB |
BIN
public/static/style/old/skull.gif
Normal file
After Width: | Height: | Size: 39 KiB |
195
public/static/style/prism.css
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
/* PrismJS 1.17.1
|
||||||
|
https://prismjs.com/download.html#themes=prism-solarizedlight&languages=markup+css+clike+javascript+c+cpp+d+java+json&plugins=line-numbers */
|
||||||
|
/*
|
||||||
|
Solarized Color Schemes originally by Ethan Schoonover
|
||||||
|
http://ethanschoonover.com/solarized
|
||||||
|
|
||||||
|
Ported for PrismJS by Hector Matos
|
||||||
|
Website: https://krakendev.io
|
||||||
|
Twitter Handle: https://twitter.com/allonsykraken)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
SOLARIZED HEX
|
||||||
|
--------- -------
|
||||||
|
base03 #002b36
|
||||||
|
base02 #073642
|
||||||
|
base01 #586e75
|
||||||
|
base00 #657b83
|
||||||
|
base0 #839496
|
||||||
|
base1 #93a1a1
|
||||||
|
base2 #eee8d5
|
||||||
|
base3 #fdf6e3
|
||||||
|
yellow #b58900
|
||||||
|
orange #cb4b16
|
||||||
|
red #dc322f
|
||||||
|
magenta #d33682
|
||||||
|
violet #6c71c4
|
||||||
|
blue #268bd2
|
||||||
|
cyan #2aa198
|
||||||
|
green #859900
|
||||||
|
*/
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: #657b83; /* base00 */
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||||
|
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||||
|
background: #073642; /* base02 */
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||||
|
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||||
|
background: #073642; /* base02 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 1em;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre) > code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background-color: #fdf6e3; /* base3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre) > code[class*="language-"] {
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: #93a1a1; /* base1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #586e75; /* base01 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #268bd2; /* blue */
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.builtin,
|
||||||
|
.token.url,
|
||||||
|
.token.inserted {
|
||||||
|
color: #2aa198; /* cyan */
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
color: #657b83; /* base00 */
|
||||||
|
background: #eee8d5; /* base2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.keyword {
|
||||||
|
color: #859900; /* green */
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function,
|
||||||
|
.token.class-name {
|
||||||
|
color: #b58900; /* yellow */
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important,
|
||||||
|
.token.variable {
|
||||||
|
color: #cb4b16; /* orange */
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important,
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"].line-numbers {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 3.8em;
|
||||||
|
counter-reset: linenumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"].line-numbers > code {
|
||||||
|
position: relative;
|
||||||
|
white-space: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers .line-numbers-rows {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
left: -3.8em;
|
||||||
|
width: 3em; /* works for line-numbers below 1000 lines */
|
||||||
|
letter-spacing: -1px;
|
||||||
|
border-right: 1px solid #999;
|
||||||
|
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers-rows > span {
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
counter-increment: linenumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-numbers-rows > span:before {
|
||||||
|
content: counter(linenumber);
|
||||||
|
color: #999;
|
||||||
|
display: block;
|
||||||
|
padding-right: 0.8em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
16
shell.nix
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
with import <nixpkgs> {};
|
||||||
|
|
||||||
|
let
|
||||||
|
pkg = import ./default.nix { inherit pkgs; };
|
||||||
|
|
||||||
|
in mkShell {
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
# additional runtime dependencies go here
|
||||||
|
] ++ pkg.buildInputs ++ pkg.propagatedBuildInputs;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
# additional dev dependencies go here
|
||||||
|
] ++ pkg.nativeBuildInputs;
|
||||||
|
|
||||||
|
}
|
144
source/app.d
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import std.experimental.logger;
|
||||||
|
import std.range;
|
||||||
|
import std.string;
|
||||||
|
import std.stdio;
|
||||||
|
import std.typecons;
|
||||||
|
|
||||||
|
import vibe.d;
|
||||||
|
|
||||||
|
import article;
|
||||||
|
import page;
|
||||||
|
import project;
|
||||||
|
import watcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal list of articles by slug.
|
||||||
|
*/
|
||||||
|
Article[string] articles;
|
||||||
|
Page[string] pages;
|
||||||
|
Project[string] projects;
|
||||||
|
|
||||||
|
immutable string articleSortPred = "a.firstPublished > b.firstPublished";
|
||||||
|
Article*[] articleList;
|
||||||
|
immutable string pageSortedPred = "a.title < b.title";
|
||||||
|
Page*[] pageList;
|
||||||
|
Project[] projectList;
|
||||||
|
|
||||||
|
enum OutputType {
|
||||||
|
HTML,
|
||||||
|
MARKDOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
const string MIME_MARKDOWN = "text/markdown";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's the document type for the given slug based on extension
|
||||||
|
* and returns the slug without extension and the document type.
|
||||||
|
*/
|
||||||
|
private OutputType getOutputType(ref string slug) {
|
||||||
|
if (slug.endsWith(".md")) {
|
||||||
|
slug = chomp(slug, ".md");
|
||||||
|
return OutputType.MARKDOWN;
|
||||||
|
} else if (slug.endsWith(".html")){
|
||||||
|
// If explicitly asking for HTML, we'll return HTML
|
||||||
|
slug = chomp(slug, ".html");
|
||||||
|
return OutputType.HTML;
|
||||||
|
} else {
|
||||||
|
// If in the future, for any reason, we no longer use HTML
|
||||||
|
// this allows to us to keep the current urls with an option
|
||||||
|
// to change the output in the future.
|
||||||
|
return OutputType.HTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getSingle(T, string templ)(ref T[string] array, HTTPServerRequest req, HTTPServerResponse res) {
|
||||||
|
string slug = req.params["slug"];
|
||||||
|
OutputType outputType = getOutputType(slug);
|
||||||
|
|
||||||
|
enforceHTTP(slug in array, HTTPStatus.notFound, "Page not found");
|
||||||
|
T content = array[slug];
|
||||||
|
res.headers["Cache-Control"] = "public";
|
||||||
|
switch(outputType) with (OutputType) {
|
||||||
|
case MARKDOWN:
|
||||||
|
res.writeBody(content.contentSource, MIME_MARKDOWN);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case HTML:
|
||||||
|
res.render!(templ, content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates response for /posts/:slug and /palen/:slug.
|
||||||
|
*/
|
||||||
|
void articleGetSingle(HTTPServerRequest req, HTTPServerResponse res) {
|
||||||
|
getSingle!(Article, "pages/article.dt")(articles, req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates response for /posts/ and /palen/
|
||||||
|
*/
|
||||||
|
void articleGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
|
||||||
|
res.headers["Cache-Control"] = "public";
|
||||||
|
render!("pages/article-list.dt", articleList)(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
void projectGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
|
||||||
|
res.headers["Cache-Control"] = "public";
|
||||||
|
render!("pages/project-list.dt", projectList)(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate response for a page
|
||||||
|
*/
|
||||||
|
void pageGet(HTTPServerRequest req, HTTPServerResponse res) {
|
||||||
|
if (("slug" in req.params) is null) {
|
||||||
|
req.params.addField("slug", "index");
|
||||||
|
}
|
||||||
|
getSingle!(Page, "pages/page.dt")(pages, req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates response whenever an error occurs.
|
||||||
|
*/
|
||||||
|
@safe
|
||||||
|
void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
|
||||||
|
render!("pages/error.dt", error)(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
//articles["hello-world"] = new Article("hello-world.yamd");
|
||||||
|
|
||||||
|
HTTPServerSettings settings = new HTTPServerSettings;
|
||||||
|
settings.bindAddresses = ["0.0.0.0"];
|
||||||
|
settings.port = 3465;
|
||||||
|
settings.serverString = "zeg ik lekker niet";
|
||||||
|
settings.errorPageHandler = toDelegate(&errorPage);
|
||||||
|
settings.keepAliveTimeout = dur!"seconds"(60);
|
||||||
|
debug {
|
||||||
|
settings.accessLogToConsole = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
URLRouter router = new URLRouter;
|
||||||
|
router.get("/posts/:slug", &articleGetSingle);
|
||||||
|
router.get("/palen/:slug", &articleGetSingle);
|
||||||
|
router.get("/posts/", &articleGetOverview);
|
||||||
|
router.get("/palen/", &articleGetOverview);
|
||||||
|
router.get("/projects/", &projectGetOverview);
|
||||||
|
router.get("/projecten/", &projectGetOverview);
|
||||||
|
router.get("/static/*", serveStaticFiles("./public/"));
|
||||||
|
router.get("/:slug", &pageGet);
|
||||||
|
router.get("/", &pageGet);
|
||||||
|
|
||||||
|
listenHTTP(settings, router);
|
||||||
|
runTask({
|
||||||
|
initPages!(Page, pageSortedPred)(pages, pageList, "pages");
|
||||||
|
});
|
||||||
|
runTask({
|
||||||
|
initPages!(Article, articleSortPred)(articles, articleList, "articles");
|
||||||
|
});
|
||||||
|
runApplication();
|
||||||
|
}
|
85
source/article.d
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import std.file;
|
||||||
|
import std.stdio;
|
||||||
|
import std.string;
|
||||||
|
import std.datetime.date;
|
||||||
|
import std.experimental.logger;
|
||||||
|
|
||||||
|
import dyaml;
|
||||||
|
import page;
|
||||||
|
import vibe.d;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an article on the blog
|
||||||
|
*/
|
||||||
|
class Article : Page {
|
||||||
|
private string m_author;
|
||||||
|
private string m_title;
|
||||||
|
private string m_slug;
|
||||||
|
private DateTime m_firstPublished;
|
||||||
|
private string m_excerpt;
|
||||||
|
/**
|
||||||
|
* Time that the file was last updated
|
||||||
|
*/
|
||||||
|
private DateTime m_updated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an article from a file.
|
||||||
|
*/
|
||||||
|
this(string file) {
|
||||||
|
this.m_headerShift = 1;
|
||||||
|
super(file);
|
||||||
|
// Find the first header and mark everything up to that as
|
||||||
|
if (m_excerpt is null) {
|
||||||
|
// an excerpt, used in search results.
|
||||||
|
const long seperatorIndex = indexOf(m_contentSource, "---\n");
|
||||||
|
this.m_excerpt = this.m_contentSource[seperatorIndex + 4..$];
|
||||||
|
long firstHeaderIndex = indexOf(this.m_excerpt, '#');
|
||||||
|
if (firstHeaderIndex >= 0) {
|
||||||
|
this.m_excerpt = this.m_excerpt[0..firstHeaderIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the metadata specific to Articles.
|
||||||
|
*/
|
||||||
|
@safe
|
||||||
|
override protected void loadHeader(Node headerNode) {
|
||||||
|
super.loadHeader(headerNode);
|
||||||
|
if (headerNode.containsKey("author")) {
|
||||||
|
this.m_author = headerNode["author"].as!string;
|
||||||
|
} else {
|
||||||
|
this.m_author = "<unknown author>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("excerpt" in headerNode) {
|
||||||
|
this.m_excerpt = headerNode["excerpt"].as!string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("firstPublished" in headerNode) {
|
||||||
|
try {
|
||||||
|
this.m_firstPublished = cast(DateTime) headerNode["firstPublished"].as!SysTime;
|
||||||
|
} catch(DateTimeException e) {
|
||||||
|
warningf("%s: invalid date format", this.m_slug);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.m_firstPublished= DateTime.fromSimpleString("1970-Jan-01 00:00:00");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("updated" in headerNode) {
|
||||||
|
try {
|
||||||
|
this.m_updated = cast(DateTime) headerNode["updated"].as!SysTime();
|
||||||
|
} catch(DateTimeException e) {
|
||||||
|
warningf("%s: invalid date format", this.m_slug);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.m_updated = this.m_firstPublished;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@property string excerpt() { return m_excerpt; }
|
||||||
|
@property string author() { return m_author; }
|
||||||
|
@property DateTime firstPublished() { return m_firstPublished; }
|
||||||
|
@property DateTime updated() { return m_updated; }
|
||||||
|
}
|
119
source/page.d
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import std.exception;
|
||||||
|
import std.experimental.logger;
|
||||||
|
import std.file;
|
||||||
|
import std.process;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
import dyaml;
|
||||||
|
import vibe.vibe;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a page has syntax errors e.g.
|
||||||
|
*/
|
||||||
|
class ArticleParseException : Exception {
|
||||||
|
mixin basicExceptionCtors;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a page on the blog
|
||||||
|
*/
|
||||||
|
class Page {
|
||||||
|
/**
|
||||||
|
* Internal name of the article. Usually the file name.
|
||||||
|
*/
|
||||||
|
protected string m_name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slug either manually assigned or generated based on file name.
|
||||||
|
* Only used in the url.
|
||||||
|
*/
|
||||||
|
protected string m_slug;
|
||||||
|
protected string m_title;
|
||||||
|
protected string m_content;
|
||||||
|
protected string m_contentSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option for the markdown parser: the amount of levels the header found in the markdown should
|
||||||
|
* be shifted. For example, 0 means H1 -> H1, 1 means H1 -> H2, 2 means H1 -> H3 and so on.
|
||||||
|
*/
|
||||||
|
protected int m_headerShift = 1;
|
||||||
|
|
||||||
|
private bool hasCalledSuper = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a page from a file
|
||||||
|
*/
|
||||||
|
this(string file) {
|
||||||
|
this.m_name = file;
|
||||||
|
this.m_contentSource = readText(file);
|
||||||
|
// Find the seperator and split the string in two
|
||||||
|
const long seperatorIndex = indexOf(m_contentSource, "---\n");
|
||||||
|
enforce!ArticleParseException(seperatorIndex >= 0);
|
||||||
|
string header = m_contentSource[0..seperatorIndex];
|
||||||
|
|
||||||
|
Node node = Loader.fromString(header).load();
|
||||||
|
loadHeader(node);
|
||||||
|
assert(hasCalledSuper);
|
||||||
|
|
||||||
|
this.m_content = Page.parseMarkdown(m_contentSource[seperatorIndex + 4..$],
|
||||||
|
this.m_headerShift);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse metadata from the header.
|
||||||
|
* Params:
|
||||||
|
* headerNode = the YAML node to parse the header metadata from.
|
||||||
|
*/
|
||||||
|
@safe
|
||||||
|
protected void loadHeader(Node headerNode){
|
||||||
|
if (headerNode.containsKey("title")) {
|
||||||
|
this.m_title = headerNode["title"].as!string;
|
||||||
|
} else {
|
||||||
|
warningf("%s does not contain a title", this.m_name);
|
||||||
|
}
|
||||||
|
if (headerNode.containsKey("slug")) {
|
||||||
|
this.m_slug = headerNode["slug"].as!string;
|
||||||
|
} else {
|
||||||
|
this.m_slug = this.m_title;
|
||||||
|
infof("%s does not have a slug. Using %s", this.m_name, this.m_slug);
|
||||||
|
}
|
||||||
|
hasCalledSuper = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts pandoc to convert MarkDown to HTML
|
||||||
|
* Params:
|
||||||
|
* source = The MarkDown source as a string (not a path!)
|
||||||
|
* shiftHeader = (Optional) The amount a header needs to be shifted. If for example, it
|
||||||
|
* is set to 1, first level headings within MarkDown become second level
|
||||||
|
* headers within HTML.
|
||||||
|
*/
|
||||||
|
public static string parseMarkdown(string source, int shiftHeader = 0) {
|
||||||
|
string[] args = ["pandoc",
|
||||||
|
"-f", "markdown",
|
||||||
|
"-t", "html"];
|
||||||
|
|
||||||
|
if (shiftHeader != 0) args ~= "--shift-heading-level-by=" ~ to!string(shiftHeader);
|
||||||
|
|
||||||
|
ProcessPipes pandoc = pipeProcess(args);
|
||||||
|
pandoc.stdin.write(source);
|
||||||
|
pandoc.stdin.writeln();
|
||||||
|
pandoc.stdin.flush();
|
||||||
|
pandoc.stdin.close();
|
||||||
|
string result;
|
||||||
|
string line;
|
||||||
|
while ((line = pandoc.stdout.readln()) !is null) {
|
||||||
|
result ~= line;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property string name() { return m_name; }
|
||||||
|
@property string title() { return m_title; }
|
||||||
|
@property string slug() { return m_slug; }
|
||||||
|
@property string content() { return m_content; }
|
||||||
|
@property string contentSource() { return m_contentSource; }
|
||||||
|
}
|
46
source/project.d
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import std.array;
|
||||||
|
import std.algorithm;
|
||||||
|
|
||||||
|
import dyaml;
|
||||||
|
import vibe.vibe;
|
||||||
|
|
||||||
|
import page;
|
||||||
|
import utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a project, like an unfinished application
|
||||||
|
*/
|
||||||
|
class Project : Page {
|
||||||
|
protected string m_state;
|
||||||
|
protected string[] m_platforms;
|
||||||
|
protected string[] m_technologies;
|
||||||
|
protected string m_icon;
|
||||||
|
protected string[] m_images;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a project from a file
|
||||||
|
*/
|
||||||
|
this(string file) {
|
||||||
|
super(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@safe
|
||||||
|
override protected void loadHeader(Node headerNode) {
|
||||||
|
super.loadHeader(headerNode);
|
||||||
|
this.m_state = headerNode.getOr!string("state", "unknown");
|
||||||
|
this.m_platforms = headerNode.getOr!(Node[])("platforms", [])
|
||||||
|
.map!(x => x.get!string).array;
|
||||||
|
this.m_technologies = headerNode.getOr!(Node[])("technologies", [])
|
||||||
|
.map!(x => x.get!string).array;
|
||||||
|
this.m_icon = headerNode.getOr!string("icon", "");
|
||||||
|
this.m_images = headerNode.getOr!(Node[])("images", [])
|
||||||
|
.map!(x => x.get!string).array;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property string state() { return m_state; }
|
||||||
|
@property string[] platforms() { return m_platforms; }
|
||||||
|
@property string[] technologies() { return m_technologies; }
|
||||||
|
@property string icon() { return m_icon; }
|
||||||
|
@property string[] images() { return m_images; }
|
||||||
|
|
||||||
|
}
|
16
source/utils.d
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import std.conv;
|
||||||
|
import std.datetime;
|
||||||
|
|
||||||
|
import dyaml;
|
||||||
|
|
||||||
|
string toHumanString(DateTime value) {
|
||||||
|
return to!string(value.year) ~ "-" ~ to!string(ubyte(value.month)) ~ "-" ~ to!string(value.day);
|
||||||
|
}
|
||||||
|
|
||||||
|
T getOr(T)(Node node, string key, T or) {
|
||||||
|
if (key in node) {
|
||||||
|
return node[key].get!T;
|
||||||
|
} else {
|
||||||
|
return or;
|
||||||
|
}
|
||||||
|
}
|
115
source/watcher.d
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import std.array;
|
||||||
|
import std.algorithm;
|
||||||
|
import std.experimental.logger;
|
||||||
|
import std.file;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
import vibe.d;
|
||||||
|
|
||||||
|
import app;
|
||||||
|
import page;
|
||||||
|
|
||||||
|
void initPages(T, string sortPred)(ref T[string] array, ref T*[] sortedRange, const string directory) {
|
||||||
|
|
||||||
|
bool addPage(string path) {
|
||||||
|
try {
|
||||||
|
T newPage = new T(path);
|
||||||
|
logf("Added %s", newPage.slug);
|
||||||
|
array[newPage.slug] = newPage;
|
||||||
|
return true;
|
||||||
|
} catch (page.ArticleParseException e) {
|
||||||
|
logf("Could not parse %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial scan
|
||||||
|
void scan(NativePath path, int level = 0) {
|
||||||
|
logf("Scanning %s", path.toString());
|
||||||
|
foreach(file; iterateDirectory(path)) {
|
||||||
|
if (file.isDirectory) {
|
||||||
|
scan(path ~ file.name, level + 1);
|
||||||
|
} else {
|
||||||
|
addPage((path ~ file.name).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sortThings() {
|
||||||
|
sortedRange = sort!(sortPred)(array.values)
|
||||||
|
.map!"&a".array;
|
||||||
|
logf("sorted: %s", sortedRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existsFile(getWorkingDirectory() ~ directory)) {
|
||||||
|
createDirectory(getWorkingDirectory() ~ directory);
|
||||||
|
}
|
||||||
|
scan(getWorkingDirectory() ~ directory);
|
||||||
|
sortThings();
|
||||||
|
|
||||||
|
DirectoryWatcher watcher = watchDirectory(getWorkingDirectory() ~ directory, true);
|
||||||
|
//auto watcher = FileWatch((getWorkingDirectory() ~ directory).toString(), true);
|
||||||
|
|
||||||
|
bool shouldStop = false;
|
||||||
|
while (!shouldStop) {
|
||||||
|
// Try to reduce changes to only one DirectoryChangeType per change
|
||||||
|
DirectoryChange[] changes;
|
||||||
|
shouldStop = !watcher.readChanges(changes);
|
||||||
|
foreach(change; changes) {
|
||||||
|
logf("=======[New changes]======");
|
||||||
|
string[] changeTypes = ["added", "removed", "modified"];
|
||||||
|
logf("Path: %s, type: %s", change.path.toString(), changeTypes[change.type]);
|
||||||
|
if (endsWith(change.path.toString(), ".kate-swp")) continue;
|
||||||
|
switch (change.type) with (DirectoryChangeType){
|
||||||
|
case added:
|
||||||
|
try {
|
||||||
|
addPage(change.path.toString());
|
||||||
|
} catch(Exception e) {
|
||||||
|
warningf("Error while updating %s: %s", change.path.toString(), e.msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case modified:
|
||||||
|
T newPage;
|
||||||
|
try {
|
||||||
|
newPage = new T(change.path.toString());
|
||||||
|
log(newPage.slug);
|
||||||
|
if (newPage.slug in array) {
|
||||||
|
log("Slug not changed");
|
||||||
|
array[newPage.slug] = newPage;
|
||||||
|
} else {
|
||||||
|
log("Slug changed");
|
||||||
|
foreach(item; array) {
|
||||||
|
if (item.name == change.path.toString()) {
|
||||||
|
logf("Removed %s, which is the old slug of %s", newPage.slug, item.slug);
|
||||||
|
array.remove(item.slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logf("Modified %s", newPage.slug);
|
||||||
|
array[newPage.slug] = newPage;
|
||||||
|
} catch(page.ArticleParseException e) {
|
||||||
|
warningf("Could not parse %s", change.path.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case removed:
|
||||||
|
try {
|
||||||
|
foreach(item; array.byValue) {
|
||||||
|
logf(" - %s", item.name);
|
||||||
|
if (item.name == change.path.toString()) {
|
||||||
|
logf("Removed %s", item.slug);
|
||||||
|
array.remove(item.slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(Exception e) {}
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
log("Current state:");
|
||||||
|
//sortedRange = sort!("a."~sortProp~" < b."~sortProp)(array.values);
|
||||||
|
sortThings();
|
||||||
|
foreach (item; array) {
|
||||||
|
logf("%s - %s", item.name, item.slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
views/pages/article-list.dt
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
extends /parts/page.dt
|
||||||
|
|
||||||
|
block header
|
||||||
|
title Posts - Netsoj.nl
|
||||||
|
|
||||||
|
block sidebar
|
||||||
|
include /parts/menu.dt
|
||||||
|
|
||||||
|
block content
|
||||||
|
header
|
||||||
|
h1.title Post list
|
||||||
|
- import vibe.d;
|
||||||
|
- if (articleList.length == 0)
|
||||||
|
p No posts found
|
||||||
|
- else
|
||||||
|
- import utils;
|
||||||
|
- foreach(article; articleList)
|
||||||
|
article
|
||||||
|
header
|
||||||
|
a(href="/posts/#{article.slug}", rel="bookmark")
|
||||||
|
h2.title #{article.title}
|
||||||
|
p.subtitle
|
||||||
|
| By #{article.author} on #{article.firstPublished.toHumanString}
|
||||||
|
- if (article.firstPublished != article.updated)
|
||||||
|
|, updated on #{article.updated.toHumanString}
|
||||||
|
p #{article.excerpt}…
|
17
views/pages/article.dt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
extends /parts/page
|
||||||
|
block header
|
||||||
|
title #{content.title} - Netsoj.nl
|
||||||
|
|
||||||
|
block sidebar
|
||||||
|
include /parts/menu.dt
|
||||||
|
|
||||||
|
block content
|
||||||
|
article
|
||||||
|
- import utils;
|
||||||
|
header
|
||||||
|
h1.title #{content.title}
|
||||||
|
p.subtitle
|
||||||
|
| By #{content.author} on #{content.firstPublished.toHumanString}
|
||||||
|
- if (content.firstPublished != content.updated)
|
||||||
|
|, updated on #{content.updated.toHumanString}
|
||||||
|
| !{content.content}
|
12
views/pages/error.dt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
extends /parts/page.dt
|
||||||
|
|
||||||
|
block header
|
||||||
|
title #{error.message} - Netsoj.nl
|
||||||
|
|
||||||
|
block sidebar
|
||||||
|
include /parts/menu.dt
|
||||||
|
|
||||||
|
block content
|
||||||
|
header
|
||||||
|
h1.title #{error.message}
|
||||||
|
p #{error.debugMessage}
|
12
views/pages/page.dt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
extends /parts/page.dt
|
||||||
|
|
||||||
|
block header
|
||||||
|
title #{content.title} - Netsoj.nl
|
||||||
|
|
||||||
|
block sidebar
|
||||||
|
include /parts/menu.dt
|
||||||
|
|
||||||
|
block content
|
||||||
|
header
|
||||||
|
h1.title #{content.title}
|
||||||
|
| !{content.content}
|
25
views/pages/project-list.dt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
extends /parts/page.dt
|
||||||
|
|
||||||
|
block header
|
||||||
|
title Projects - Netsoj.nl
|
||||||
|
|
||||||
|
block sidebar
|
||||||
|
include /parts/menu.dt
|
||||||
|
|
||||||
|
block content
|
||||||
|
header
|
||||||
|
h1.title Projects
|
||||||
|
p Note: I'm not a graphic designer, so please ignore the icons.
|
||||||
|
- if (projectList.length == 0)
|
||||||
|
p No projects found
|
||||||
|
- foreach(project; projectList)
|
||||||
|
div.project
|
||||||
|
img(src="#{project.icon}")
|
||||||
|
h2 #{project.title}
|
||||||
|
p.platforms
|
||||||
|
- foreach(platform; project.platforms)
|
||||||
|
span.platform #{platform}
|
||||||
|
p.technologies
|
||||||
|
- foreach(technology; project.technologies)
|
||||||
|
span.platform #{technology}
|
||||||
|
|
0
views/parts/article-fragment.dt
Normal file
14
views/parts/menu.dt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
- void menuItem(string text, string link)
|
||||||
|
li
|
||||||
|
a(href="#{link}") #{text}
|
||||||
|
|
||||||
|
header
|
||||||
|
img.logo(src="/static/img/logo.png")
|
||||||
|
p Chris's webstekkie
|
||||||
|
ul
|
||||||
|
- menuItem("home", "/");
|
||||||
|
- menuItem("posts", "/posts/");
|
||||||
|
- menuItem("projects", "/projects/");
|
||||||
|
|
||||||
|
footer
|
||||||
|
p ©Chris Josten, 2020
|
16
views/parts/page.dt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
meta(name="viewport", content="width=device-width; initial-scale=1")
|
||||||
|
link(rel="stylesheet", href="/static/style/base.css")
|
||||||
|
link(rel="stylesheet", href="/static/style/highlight.css")
|
||||||
|
link(rel="alternate stylesheet", href="/static/style/old.css", title="1999")
|
||||||
|
link(rel="shortcut icon", href="/static/img/logo.png")
|
||||||
|
meta(name="theme-color", content="#7f0602")
|
||||||
|
|
||||||
|
block header
|
||||||
|
body
|
||||||
|
nav
|
||||||
|
block sidebar
|
||||||
|
main
|
||||||
|
block content
|