Compare commits
13 Commits
1350521f11
...
main
Author | SHA1 | Date | |
---|---|---|---|
f9472b3866 | |||
a6a11ca35d | |||
80b777908b | |||
6d28558061 | |||
1fff135bba | |||
b21d384796 | |||
60e0ee9f84 | |||
c4ef11db99 | |||
b00878e020 | |||
52c70b39e3 | |||
ec3ad2d295 | |||
da8594c678 | |||
5e48571fda |
12
.envrc
@ -1 +1,11 @@
|
|||||||
use flake
|
if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then
|
||||||
|
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8="
|
||||||
|
fi
|
||||||
|
|
||||||
|
nix_direnv_watch_file devenv.nix
|
||||||
|
nix_direnv_watch_file devenv.lock
|
||||||
|
nix_direnv_watch_file devenv.yaml
|
||||||
|
if ! use flake . --impure
|
||||||
|
then
|
||||||
|
echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
|
||||||
|
fi
|
||||||
|
5
.gitignore
vendored
@ -1,2 +1,7 @@
|
|||||||
.direnv
|
.direnv
|
||||||
/result
|
/result
|
||||||
|
|
||||||
|
# Devenv
|
||||||
|
.devenv*
|
||||||
|
devenv.local.nix
|
||||||
|
|
||||||
|
1
.pre-commit-config.yaml
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/nix/store/kix2g1bws3b5gdkz88zsx3r6sszrqxaj-pre-commit-config.json
|
65
content/About.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
title: About me - Drezil
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- markdownlint-disable-next-line -->
|
||||||
|
<img class="border border-solid w32 rounded-xl lg:rounded-3xl lg:w64 float-right" style="border-color: rgb(217 70 239);" src="/About/Nicole_small.png"/>
|
||||||
|
|
||||||
|
## Work
|
||||||
|
|
||||||
|
- **October 2023 to ???**
|
||||||
|
- You could be here. The [Red Queen](https://red-queen.ug) will get *your*
|
||||||
|
project off the ground, too.
|
||||||
|
|
||||||
|
- **March 2023 to September 2023**:
|
||||||
|
- Worked for [2lambda](http://2lambda.co)
|
||||||
|
- Silicon Valley start-up trying to beat the stock-market with fancy ML-Models
|
||||||
|
- That work kickstarted my employment at [Red Queen UG](https://red-queen.ug)
|
||||||
|
where i continue doing consulting work for [2lambda](http://2lambda.co)
|
||||||
|
while also moving into a more senior role of also building up our own team
|
||||||
|
of specialists to work on different future projects.
|
||||||
|
|
||||||
|
- **Oct. 2018 to Aug. 2021**:
|
||||||
|
- ML-Specialist at [Jobware](https://jobware.de) (Paderborn; german Job-Advertising-Platform)
|
||||||
|
|
||||||
|
- **2013-2018** several jobs at my University including
|
||||||
|
- Worked 6 Months in the Workgroup "Theoretical Computer Science" on migrating
|
||||||
|
algorithms to **CUDA**
|
||||||
|
- Tutor "Introduction to Machine Learning"
|
||||||
|
- Was awarded **Tutoring-Award** of the Faculty of Technology for excellent tutoring
|
||||||
|
- [[FFPiH|Lecture "Intermediate Functional Programming in Haskell"]]#
|
||||||
|
- Development of Pandoc-Filters for effective **generation of lecture-slides**
|
||||||
|
for Mario Botsch (Leader Workgroup Computer Graphics) using Pandoc & reveal.js
|
||||||
|
|
||||||
|
## Education
|
||||||
|
|
||||||
|
- **Bachelor** "Kognitive Informatik" (Cognitive Informatics) in Bielefeld 2010-2014
|
||||||
|
- **Master** "Naturwissenschaftliche Informatik" (Informatics in the natural
|
||||||
|
sciences) 2014-2018
|
||||||
|
|
||||||
|
### Extraordinary grades (Excerpt of my Transcript)
|
||||||
|
|
||||||
|
Note: Scale of grades in Germany is 1.0 to 4.0 with 1.0 being best, 4.0 being
|
||||||
|
passing grade, 5.0 being failed grade
|
||||||
|
|
||||||
|
- **1.0 in Modern Data Analysis**
|
||||||
|
- Master course on data-analysis (time-series, core-vector-machines, gaussian
|
||||||
|
processes, ...)
|
||||||
|
- **1.0 in Computergraphics**
|
||||||
|
- Raytracing, Modern OpenGL
|
||||||
|
- **1.3 in Computer-Animation**
|
||||||
|
- Dual-Quarternion-Skinning, Character-Animation, FACS-Poses, etc.
|
||||||
|
- **1.3 in GPU-Computing (CUDA)**
|
||||||
|
- originally a 1.7 by timing (task was de-mosaicing on images, grade was
|
||||||
|
measured in ms, whereby 400ms equated to 4.0 and 100ms equated to 1.0),
|
||||||
|
but because my deep knowledge was visible in the code i was given a 1.3
|
||||||
|
after oral presentation.
|
||||||
|
- **1.0 in Parallel Algorithms and Data-Structures**
|
||||||
|
- **Ethical Hacking**
|
||||||
|
- Reverse Engineering with IDApro
|
||||||
|
|
||||||
|
## Further information
|
||||||
|
|
||||||
|
- [[Work|More details on my work-experience]]#
|
||||||
|
- [[Experience|More details of my coding]]#
|
||||||
|
- [[Extracurricular|More details of things i did beside studying at University]]#
|
@ -1,55 +1,27 @@
|
|||||||
---
|
---
|
||||||
title: Stefan Dresselhaus
|
title: Curriculum Vitae
|
||||||
|
page:
|
||||||
|
bodyHtml: |
|
||||||
|
<script>
|
||||||
|
Array.from(
|
||||||
|
document.getElementsByClassName("open")
|
||||||
|
).forEach(
|
||||||
|
(i) => {
|
||||||
|
i.querySelector('details').setAttribute("open","")
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# About me
|
{.open}
|
||||||
|
![[About]]
|
||||||
|
|
||||||
<img align="right" style='border:1px solid #000000; float:right; margin-left:20px' height='300px' src="/About/DresselhausStefan_klein2.jpg"/>
|
{.open}
|
||||||
|
![[Work]]
|
||||||
|
|
||||||
## Work
|
{.open}
|
||||||
|
![[Experience]]
|
||||||
- **Oct. 2018 to Aug. 2021**:
|
|
||||||
- ML-Specialist at [Jobware](https://jobware.de) (Paderborn; german Job-Advertising-Platform)
|
|
||||||
|
|
||||||
- **2013-2018** several jobs at my University including
|
|
||||||
- Worked 6 Months in the Workgroup "Theoretical Computer Science" on migrating
|
|
||||||
algorithms to **CUDA**
|
|
||||||
- Tutor "Introduction to Machine Learning"
|
|
||||||
- Was awarded **Tutoring-Award** of the Faculty of Technology for excellent tutoring
|
|
||||||
- [[FFPiH|Lecture "Intermediate Functional Programming in Haskell"]]#
|
|
||||||
- Development of Pandoc-Filters for effective **generation of lecture-slides**
|
|
||||||
for Mario Botsch (Leader Workgroup Computer Graphics) using Pandoc & reveal.js
|
|
||||||
|
|
||||||
## Education
|
|
||||||
|
|
||||||
- **Bachelor** "Kognitive Informatik" (Cognitive Informatics) in Bielefeld 2010-2014
|
|
||||||
- **Master** "Naturwissenschaftliche Informatik" (Informatics in the natural
|
|
||||||
sciences) 2014-2018
|
|
||||||
|
|
||||||
### Extraordinary grades (Excerpt of my Transcript)
|
|
||||||
|
|
||||||
Note: Scale of grades in Germany is 1.0 to 4.0 with 1.0 being best, 4.0 being
|
|
||||||
passing grade, 5.0 being failed grade
|
|
||||||
|
|
||||||
- **1.0 in Modern Data Analysis**
|
|
||||||
- Master course on data-analysis (time-series, core-vector-machines, gaussian
|
|
||||||
processes, ...)
|
|
||||||
- **1.0 in Computergraphics**
|
|
||||||
- Raytracing, Modern OpenGL
|
|
||||||
- **1.3 in Computer-Animation**
|
|
||||||
- Dual-Quarternion-Skinning, Character-Animation, FACS-Poses, etc.
|
|
||||||
- **1.3 in GPU-Computing (CUDA)**
|
|
||||||
- originally a 1.7 by timing (task was de-mosaicing on images, grade was
|
|
||||||
measured in ms, whereby 400ms equated to 4.0 and 100ms equated to 1.0),
|
|
||||||
but because my deep knowledge was visible in the code i was given a 1.3
|
|
||||||
after oral presentation.
|
|
||||||
- **1.0 in Parallel Algorithms and Data-Structures**
|
|
||||||
- **Ethical Hacking**
|
|
||||||
- Reverse Engineering with IDApro
|
|
||||||
|
|
||||||
## Further information
|
|
||||||
|
|
||||||
- [[Work|More details on my work-experience]]#
|
|
||||||
- [[Experience|More details of my coding]]#
|
|
||||||
- [[Extracurricular|More details of things i did beside studying at University]]#
|
|
||||||
|
|
||||||
|
{.open}
|
||||||
|
![[Extracurricular]]
|
||||||
|
Before Width: | Height: | Size: 87 KiB |
BIN
content/About/Nicole.png
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
content/About/Nicole_small.png
Normal file
After Width: | Height: | Size: 311 KiB |
@ -1,5 +1,36 @@
|
|||||||
# Work-Experience
|
# Work-Experience
|
||||||
|
|
||||||
|
- **Mar. 2023 to Sep. 2023:**
|
||||||
|
- Developer for 2Lambda.co. Role migrated from just coding stuff to
|
||||||
|
architecting and rewriting the whole software from the ground up using a
|
||||||
|
small modular approach instead of the shaky one-off systems in place.
|
||||||
|
Was later a "nanny for everything".
|
||||||
|
- Did a lot of work to have self-documenting code (i.e. generate documentation
|
||||||
|
from the actual values used in the program, not some comments that always
|
||||||
|
get out of date)
|
||||||
|
- Setting up a knowledge-base (Zettelkasten-approach) to track experiments and
|
||||||
|
hyperlink them to the documentation generated above (and due to Zettelkasten
|
||||||
|
you then get "this thing was used in Experiments a, b and c" automatically
|
||||||
|
- Technologies used:
|
||||||
|
- Clojure
|
||||||
|
- Complete application was written in Clojure
|
||||||
|
- Never touched that language before March - got up to speed in just 2
|
||||||
|
days, poked the expert on the team detailed questions about the
|
||||||
|
runtime-system after 1 month (like inlining-behavior, allocation-things,
|
||||||
|
etc.)
|
||||||
|
- Emanote
|
||||||
|
- autogenerated & linked documentation of internal modules
|
||||||
|
- integrated with manual written tutorials/notes
|
||||||
|
- crosslinking documentation of experiments with documentation of modules
|
||||||
|
- Web of knowledge
|
||||||
|
- bidirectional discovery of things tried/done in the past to optimize
|
||||||
|
finding of new strategies (meta-optimizing the decisions on what to
|
||||||
|
optimize/try)
|
||||||
|
- Infrastructure
|
||||||
|
- Organized and co-administrated the 4 Root-Servers we had
|
||||||
|
- Set up Kubernetes, Nexus, Docker, Nginx, letsencrypt-certs, dns-entries,
|
||||||
|
etc..
|
||||||
|
|
||||||
- **Oct. 2018 to Aug. 2021**:
|
- **Oct. 2018 to Aug. 2021**:
|
||||||
- ML-Specialist at [Jobware](https://jobware.de) (Paderborn; german Job-Advertising-Platform)
|
- ML-Specialist at [Jobware](https://jobware.de) (Paderborn; german Job-Advertising-Platform)
|
||||||
- Extraction/Classification of sentences from JobAds (Requirements, Benefits,
|
- Extraction/Classification of sentences from JobAds (Requirements, Benefits,
|
||||||
@ -36,7 +67,7 @@
|
|||||||
- Tutor "Introduction to Machine Learning"
|
- Tutor "Introduction to Machine Learning"
|
||||||
- Was awarded **Tutoring-Award** of the Faculty of Technology for excellent
|
- Was awarded **Tutoring-Award** of the Faculty of Technology for excellent
|
||||||
tutoring
|
tutoring
|
||||||
- Lecture "Intermediate Functional Programming in Haskell"
|
- Lecture "[[FFPiH|Intermediate Functional Programming in Haskell]]"
|
||||||
- Originally developed as student-project in cooperation with Jonas Betzendahl
|
- Originally developed as student-project in cooperation with Jonas Betzendahl
|
||||||
- First held in Summer 2015
|
- First held in Summer 2015
|
||||||
- Due to high demand held again in Summer 2016 and 2017
|
- Due to high demand held again in Summer 2016 and 2017
|
||||||
|
BIN
content/About/avatar_neu.png
Normal file
After Width: | Height: | Size: 170 KiB |
3
content/Coding/Haskell.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
template:
|
||||||
|
sidebar:
|
||||||
|
collapsed: true
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Stellen wir uns vor, dass wir eine Funktion schreiben, die einen String bekommt (mehrere Lines mit ACSII-Text) und dieses Wort-für-Wort rückwärts ausgeben soll. Das ist ein einfacher Einzeiler:
|
Stellen wir uns vor, dass wir eine Funktion schreiben, die einen String bekommt (mehrere Lines mit ACSII-Text) und dieses Wort-für-Wort rückwärts ausgeben soll. Das ist ein einfacher Einzeiler:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import System.Environment (getArgs)
|
import System.Environment (getArgs)
|
@ -8,7 +8,7 @@ Syntax nehmen.
|
|||||||
|
|
||||||
### Beispiel
|
### Beispiel
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data Person = P { name :: String
|
data Person = P { name :: String
|
||||||
, addr :: Address
|
, addr :: Address
|
||||||
, salary :: Int }
|
, salary :: Int }
|
||||||
@ -38,7 +38,7 @@ Probleme mit diesem Code:
|
|||||||
|
|
||||||
### Was wir gern hätten
|
### Was wir gern hätten
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data Person = P { name :: String
|
data Person = P { name :: String
|
||||||
, addr :: Address
|
, addr :: Address
|
||||||
, salary :: Int }
|
, salary :: Int }
|
||||||
@ -57,7 +57,7 @@ composeL :: Lens' s1 s2 -> Lens s2 a -> Lens' s1 a
|
|||||||
|
|
||||||
Mit diesen Dingen (wenn wir sie hätten) könnte man dann
|
Mit diesen Dingen (wenn wir sie hätten) könnte man dann
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data Person = P { name :: String
|
data Person = P { name :: String
|
||||||
, addr :: Address
|
, addr :: Address
|
||||||
, salary :: Int }
|
, salary :: Int }
|
||||||
@ -75,7 +75,7 @@ machen und wäre fertig.
|
|||||||
|
|
||||||
### Getter/Setter als Lens-Methoden
|
### Getter/Setter als Lens-Methoden
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data LensR s a = L { viewR :: s -> a
|
data LensR s a = L { viewR :: s -> a
|
||||||
, setR :: a -> s -> s }
|
, setR :: a -> s -> s }
|
||||||
|
|
||||||
@ -91,14 +91,14 @@ composeL (L v1 u1) (L v2 u2)
|
|||||||
Auslesen traversiert die Datenstruktur, dann wird die Funktion angewendet und
|
Auslesen traversiert die Datenstruktur, dann wird die Funktion angewendet und
|
||||||
zum setzen wird die Datenstruktur erneut traversiert:
|
zum setzen wird die Datenstruktur erneut traversiert:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
over :: LensR s a -> (a -> a) -> s -> s
|
over :: LensR s a -> (a -> a) -> s -> s
|
||||||
over ln f s = setR l (f (viewR l s)) s
|
over ln f s = setR l (f (viewR l s)) s
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
- Lösung: modify-funktion hinzufügen
|
- Lösung: modify-funktion hinzufügen
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data LensR s a
|
data LensR s a
|
||||||
= L { viewR :: s -> a
|
= L { viewR :: s -> a
|
||||||
, setR :: a -> s -> s
|
, setR :: a -> s -> s
|
||||||
@ -113,7 +113,7 @@ Neues Problem: Für jeden Spezialfall muss die Lens erweitert werden.
|
|||||||
|
|
||||||
Man kann alle Monaden abstrahieren. Functor reicht schon:
|
Man kann alle Monaden abstrahieren. Functor reicht schon:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data LensR s a
|
data LensR s a
|
||||||
= L { viewR :: s -> a
|
= L { viewR :: s -> a
|
||||||
, setR :: a -> s -> s
|
, setR :: a -> s -> s
|
||||||
@ -144,7 +144,7 @@ anders aussehen.
|
|||||||
|
|
||||||
## Benutzen einer Lens als Setter
|
## Benutzen einer Lens als Setter
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
set :: Lens' s a -> (a -> s -> s)
|
set :: Lens' s a -> (a -> s -> s)
|
||||||
set ln a s = --...umm...
|
set ln a s = --...umm...
|
||||||
--:t ln => (a -> f a) -> s -> f s
|
--:t ln => (a -> f a) -> s -> f s
|
||||||
@ -154,7 +154,7 @@ set ln a s = --...umm...
|
|||||||
Wir können für f einfach die "Identity"-Monade nehmen, die wir nachher wegcasten
|
Wir können für f einfach die "Identity"-Monade nehmen, die wir nachher wegcasten
|
||||||
können.
|
können.
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
newtype Identity a = Identity a
|
newtype Identity a = Identity a
|
||||||
-- Id :: a -> Identity a
|
-- Id :: a -> Identity a
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ instance Functor Identity where
|
|||||||
|
|
||||||
somit ist set einfach nur
|
somit ist set einfach nur
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
set :: Lens' s a -> (a -> s -> s)
|
set :: Lens' s a -> (a -> s -> s)
|
||||||
set ln x s
|
set ln x s
|
||||||
= runIdentity (ls set_fld s)
|
= runIdentity (ls set_fld s)
|
||||||
@ -197,7 +197,7 @@ over ln f = runIdentity . ln (Identity . f)
|
|||||||
|
|
||||||
## Benutzen einer Lens als Getter
|
## Benutzen einer Lens als Getter
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
view :: Lens' s a -> (s -> a)
|
view :: Lens' s a -> (s -> a)
|
||||||
view ln s = --...umm...
|
view ln s = --...umm...
|
||||||
--:t ln => (a -> f a) -> s -> f s
|
--:t ln => (a -> f a) -> s -> f s
|
||||||
@ -208,7 +208,7 @@ view ln s = --...umm...
|
|||||||
Auch hier gibt es einen netten Funktor. Wir packen das "a" einfach in das "f"
|
Auch hier gibt es einen netten Funktor. Wir packen das "a" einfach in das "f"
|
||||||
und werfen das "s" am Ende weg.
|
und werfen das "s" am Ende weg.
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
newtype Const v a = Const v
|
newtype Const v a = Const v
|
||||||
|
|
||||||
getConst :: Const v a -> v
|
getConst :: Const v a -> v
|
||||||
@ -221,7 +221,7 @@ instance Functor (Const v) where
|
|||||||
|
|
||||||
somit ergibt sich
|
somit ergibt sich
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
view :: Lens' s a -> (s -> a)
|
view :: Lens' s a -> (s -> a)
|
||||||
view ln s
|
view ln s
|
||||||
= getConst (ln Const s)
|
= getConst (ln Const s)
|
||||||
@ -246,7 +246,7 @@ type Lens' s a = forall f. Functor f
|
|||||||
|
|
||||||
Für unser Personen-Beispiel vom Anfang:
|
Für unser Personen-Beispiel vom Anfang:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data Person = P { _name :: String, _salary :: Int }
|
data Person = P { _name :: String, _salary :: Int }
|
||||||
|
|
||||||
name :: Lens' Person String
|
name :: Lens' Person String
|
||||||
@ -270,7 +270,7 @@ name elt_fn (P n s)
|
|||||||
|
|
||||||
## Wie funktioniert das intern?
|
## Wie funktioniert das intern?
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
view name (P {_name="Fred", _salary=100})
|
view name (P {_name="Fred", _salary=100})
|
||||||
-- inline view-function
|
-- inline view-function
|
||||||
= getConst (name Const (P {_name="Fred", _salary=100})
|
= getConst (name Const (P {_name="Fred", _salary=100})
|
||||||
@ -312,7 +312,7 @@ Somit ist Lens-Composition einfach nur Function-Composition (.).
|
|||||||
|
|
||||||
Der Code um die Lenses zu bauen ist für records immer Identisch:
|
Der Code um die Lenses zu bauen ist für records immer Identisch:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data Person = P { _name :: String, _salary :: Int }
|
data Person = P { _name :: String, _salary :: Int }
|
||||||
|
|
||||||
name :: Lens' Person String
|
name :: Lens' Person String
|
||||||
@ -321,7 +321,7 @@ name elt_fn (P n s) = (\n' -> P n' s) <$> (elt_fn n)
|
|||||||
|
|
||||||
Daher kann man einfach
|
Daher kann man einfach
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
import Control.Lens.TH
|
import Control.Lens.TH
|
||||||
data Person = P { _name :: String, _salary :: Int }
|
data Person = P { _name :: String, _salary :: Int }
|
||||||
|
|
||||||
@ -336,7 +336,7 @@ Will man das aber haben, muss man selbst in den Control.Lens.TH-Code schauen.
|
|||||||
|
|
||||||
## Lenses für den Beispielcode
|
## Lenses für den Beispielcode
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
import Control.Lens.TH
|
import Control.Lens.TH
|
||||||
|
|
||||||
data Person = P { _name :: String
|
data Person = P { _name :: String
|
||||||
@ -355,7 +355,7 @@ setPostcode pc p = set (addr . postcode) pc p
|
|||||||
|
|
||||||
## Shortcuts mit "Line-Noise"
|
## Shortcuts mit "Line-Noise"
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
-- ...
|
-- ...
|
||||||
|
|
||||||
setPostcode :: String -> Person -> Person
|
setPostcode :: String -> Person -> Person
|
||||||
@ -375,7 +375,7 @@ Listenkonvertierungen, -traversierungen, ...)
|
|||||||
Man kann mit Lenses sogar Felder emulieren, die gar nicht da sind. Angenommen
|
Man kann mit Lenses sogar Felder emulieren, die gar nicht da sind. Angenommen
|
||||||
folgender Code:
|
folgender Code:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data Temp = T { _fahrenheit :: Float }
|
data Temp = T { _fahrenheit :: Float }
|
||||||
|
|
||||||
$(makeLenses ''Temp)
|
$(makeLenses ''Temp)
|
||||||
@ -399,7 +399,7 @@ Minuten oder 37 Stunden ist)
|
|||||||
Das ganze kann man auch parametrisieren und auf Non-Record-Strukturen anwenden.
|
Das ganze kann man auch parametrisieren und auf Non-Record-Strukturen anwenden.
|
||||||
Beispielhaft an einer Map verdeutlicht:
|
Beispielhaft an einer Map verdeutlicht:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
-- from Data.Lens.At
|
-- from Data.Lens.At
|
||||||
at :: Ord k => k -> Lens' (Map k v) (Maybe v)
|
at :: Ord k => k -> Lens' (Map k v) (Maybe v)
|
||||||
|
|
||||||
@ -425,7 +425,7 @@ at k mb_fn m
|
|||||||
- Bitfields auf Strukturen die Bits haben (Ints, ...) in Data.Bits.Lens
|
- Bitfields auf Strukturen die Bits haben (Ints, ...) in Data.Bits.Lens
|
||||||
- Web-scraper in Package hexpat-lens
|
- Web-scraper in Package hexpat-lens
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
p ^.. _HTML' . to allNodes
|
p ^.. _HTML' . to allNodes
|
||||||
. traverse . named "a"
|
. traverse . named "a"
|
||||||
. traverse . ix "href"
|
. traverse . ix "href"
|
||||||
@ -441,7 +441,7 @@ at k mb_fn m
|
|||||||
Bisher hatten wir Lenses nur auf Funktoren F. Die nächstmächtigere Klasse ist
|
Bisher hatten wir Lenses nur auf Funktoren F. Die nächstmächtigere Klasse ist
|
||||||
Applicative.
|
Applicative.
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
type Traversal' s a = forall f. Applicative f
|
type Traversal' s a = forall f. Applicative f
|
||||||
=> (a -> f a) -> (s -> f s)
|
=> (a -> f a) -> (s -> f s)
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
@ -452,7 +452,7 @@ etwas anderes ändern. Statt eines einzelnen Focus erhalten wir viele Foci.
|
|||||||
Was ist ein Applicative überhaupt? Eine schwächere Monade (nur 1x Anwendung und
|
Was ist ein Applicative überhaupt? Eine schwächere Monade (nur 1x Anwendung und
|
||||||
kein Bind - dafür kann man die beliebig oft hintereinanderhängen).
|
kein Bind - dafür kann man die beliebig oft hintereinanderhängen).
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
class Functor f => Applicative f where
|
class Functor f => Applicative f where
|
||||||
pure :: a -> f a
|
pure :: a -> f a
|
||||||
(<*>) :: f (a -> b) -> f a -> f b
|
(<*>) :: f (a -> b) -> f a -> f b
|
||||||
@ -464,7 +464,7 @@ mf <*> mx = do { f <- mf; x <- mx; return (f x) }
|
|||||||
|
|
||||||
Recap: Was macht eine Lens:
|
Recap: Was macht eine Lens:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
data Adress = A { _road :: String
|
data Adress = A { _road :: String
|
||||||
, _city :: String
|
, _city :: String
|
||||||
, _postcode :: String }
|
, _postcode :: String }
|
||||||
@ -476,7 +476,7 @@ road elt_fn (A r c p) = (\r' -> A r' c p) <$> (elt_fn r)
|
|||||||
|
|
||||||
Wenn man nun road & city gleichzeitig bearbeiten will:
|
Wenn man nun road & city gleichzeitig bearbeiten will:
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
addr_strs :: Traversal' Address String
|
addr_strs :: Traversal' Address String
|
||||||
addr_strs elt_fn (A r c p)
|
addr_strs elt_fn (A r c p)
|
||||||
= ... (\r' c' -> A r' c' p) .. (elt_fn r) .. (elt_fn c) ..
|
= ... (\r' c' -> A r' c' p) .. (elt_fn r) .. (elt_fn c) ..
|
||||||
@ -487,7 +487,7 @@ fmap kann nur 1 Loch stopfen, aber nicht mit n Löchern umgehen. Applicative mit
|
|||||||
<*> kann das.
|
<*> kann das.
|
||||||
Somit gibt sich
|
Somit gibt sich
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
addr_strs :: Traversal' Address String
|
addr_strs :: Traversal' Address String
|
||||||
addr_strs elt_fn (A r c p)
|
addr_strs elt_fn (A r c p)
|
||||||
= pure (\r' c' -> A r' c' p) <*> (elt_fn r) <*> (elt_fn c)
|
= pure (\r' c' -> A r' c' p) <*> (elt_fn r) <*> (elt_fn c)
|
||||||
@ -551,7 +551,7 @@ Die modify-Funktion over ist auch
|
|||||||
Lens alleine definiert 39 newtypes, 34 data-types und 194 Typsynonyme...
|
Lens alleine definiert 39 newtypes, 34 data-types und 194 Typsynonyme...
|
||||||
Ausschnitt
|
Ausschnitt
|
||||||
|
|
||||||
~~~ { .haskell .numberLines }
|
~~~ { .haskell }
|
||||||
-- traverseOf :: Functor f => Iso s t a b -> (a -> f b) -> s -> f t
|
-- traverseOf :: Functor f => Iso s t a b -> (a -> f b) -> s -> f t
|
||||||
-- traverseOf :: Functor f => Lens s t a b -> (a -> f b) -> s -> f t
|
-- traverseOf :: Functor f => Lens s t a b -> (a -> f b) -> s -> f t
|
||||||
-- traverseOf :: Applicative f => Traversal s t a b -> (a -> f b) -> s -> f t
|
-- traverseOf :: Applicative f => Traversal s t a b -> (a -> f b) -> s -> f t
|
320
content/Coding/Haskell/Webapp-Example.md
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
# Webapp-Development in Haskell
|
||||||
|
|
||||||
|
Step-by-Step-Anleitung, wie man ein neues Projekt mit einer bereits erprobten
|
||||||
|
Pipeline erstellt.
|
||||||
|
|
||||||
|
## Definition der API
|
||||||
|
|
||||||
|
Erster Schritt ist immer ein wünsch-dir-was bei der Api-Defenition.
|
||||||
|
|
||||||
|
Die meisten Services haben offensichtliche Anforderungen (Schnittstellen nach
|
||||||
|
draußen, Schnittstellen intern, ...). Diese kann man immer sehr gut in einem
|
||||||
|
`Request -> Response`-Model erfassen.
|
||||||
|
|
||||||
|
Um die Anforderungen und Möglichkeiten des jeweiligen Services sauber zu
|
||||||
|
erfassen und automatisiert zu prüfen, dummy-implementationen zu bekommen und
|
||||||
|
vieles andere mehr, empfiehlt es sich den #[[OpenAPI|openapi-generator]] zu
|
||||||
|
nutzen.
|
||||||
|
|
||||||
|
Diese Definition läuft über openapi-v3 und kann z.b. mit Echtzeit-Vorschau im
|
||||||
|
http://editor.swagger.io/ erspielen. Per Default ist der noch auf openapi-v2
|
||||||
|
(aka swagger), kann aber auch v3.
|
||||||
|
|
||||||
|
Nach der Definition, was man am Ende haben möchte, muss man sich entscheiden, in
|
||||||
|
welcher Sprache man weiter entwickelt. Ich empfehle aus verschiedenen Gründen
|
||||||
|
primär 2 Sprachen: Python-Microservices (weil die ML-Libraries sehr gut sind,
|
||||||
|
allerdings Änderungen meist schwer sind und der Code wenig robust - meist nur 1
|
||||||
|
API-Endpunkt pro service) und Haskell (Stabilität, Performace, leicht zu ändern,
|
||||||
|
gut anzupassen).
|
||||||
|
|
||||||
|
Im folgenden wird (aus offensichtlichen Gründen) nur auf das Haskell-Projekt eingegangen.
|
||||||
|
|
||||||
|
## Startprojekt in Haskell
|
||||||
|
|
||||||
|
### Erstellen eines neuen Projektes
|
||||||
|
|
||||||
|
Zunächst erstellen wir in normales Haskell-Projekt ohne Funktionalität & Firlefanz:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
stack new myservice
|
||||||
|
```
|
||||||
|
|
||||||
|
Dies erstellt ein neues Verzeichnis und das generelle scaffolding.
|
||||||
|
Nach einer kurzen Anpassung der `stack.yaml` (resolver auf unserer setzen;
|
||||||
|
aktuell: `lts-17.4`) fügen wir am Ende der Datei
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
allow-newer: true
|
||||||
|
ghc-options:
|
||||||
|
"$locals": -fwrite-ide-info
|
||||||
|
```
|
||||||
|
|
||||||
|
ein.
|
||||||
|
Anschließend organisieren™ wir uns noch eine gute `.gitignore` und initialisieren
|
||||||
|
das git mittels `git init; git add .; git commit -m "initial scaffold"`
|
||||||
|
|
||||||
|
### Generierung der API
|
||||||
|
|
||||||
|
Da die API immer wieder neu generiert werden kann (und sollte!) liegt sich in
|
||||||
|
einem unterverzeichnis des Hauptprojektes.
|
||||||
|
|
||||||
|
Initial ist es das einfachste ein leeres temporäres Verzeichnis woanders zu
|
||||||
|
erstellen, die `api-doc.yml` hinein kopieren und folgendes ausführen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openapi-generator generate -g haskell -o . -i api-doc.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Dieses erstellt einem dann eine komplette library inkl. Datentypen.
|
||||||
|
Wichtig: Der Name in der `api-doc` sollte vom Namen des Services (oben `myservice`)
|
||||||
|
abweichen - entweder in Casing oder im Namen direkt. Suffixe wie API schneidet
|
||||||
|
der Generator hier leider ab.
|
||||||
|
(Wieso das ganze? Es entstehen nachher 2 libraries, `foo` & `fooAPI`. Da der
|
||||||
|
generator das API abschneidet endet man mit foo & foo und der compiler meckert,
|
||||||
|
dass er nicht weiß, welche lib gemeint ist).
|
||||||
|
|
||||||
|
danach: wie gewohnt `git init; git add .; git commit -m "initial"`. Auf dem
|
||||||
|
Server der Wahl (github, gitea, gitlab, ...) nun ein Repository erstellen (am
|
||||||
|
Besten: `myserviceAPI` - nach Konvention ist alles auf API endend autogeneriert!)
|
||||||
|
und den Anweisungen nach ein remote hinzufügen & pushen.
|
||||||
|
|
||||||
|
#### Wieder zurück im Haskell-Service
|
||||||
|
|
||||||
|
In unserem eigentlichen Service müssen wir nun die API einbinden.
|
||||||
|
Dazu erstellen wir ein Verzeichnis `libs` (Konvention) und machen ein `git
|
||||||
|
submodule add <repository-url> libs/myserviceAPI`
|
||||||
|
|
||||||
|
Git hat nun die API in das submodul gepackt und wir können das oben erstellte
|
||||||
|
temporäre Verzeichnis wieder löschen.
|
||||||
|
|
||||||
|
Anschließend müssen wir stack noch erklären, dass wir die API da nun liegen
|
||||||
|
haben und passen wieder die `stack.yaml` an, indem wir das Verzeichnis unter
|
||||||
|
packages hinzufügen.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
packages:
|
||||||
|
- .
|
||||||
|
- libs/myserviceAPI # <<
|
||||||
|
```
|
||||||
|
|
||||||
|
Nun können wir in der `package.yaml` (oder `myservice.cabal`, falls kein `hpack`
|
||||||
|
verwendet wird) unter den dependencies unsere API hinzufügen (name wie die
|
||||||
|
cabal-Datei in `libs/myserviceAPI`).
|
||||||
|
|
||||||
|
### Einbinden anderer Microservices
|
||||||
|
|
||||||
|
Funktioniert komplett analog zu dem vorgehen oben (ohne das generieren natürlich
|
||||||
|
:grin:).
|
||||||
|
`stack.yaml` editieren und zu den packages hinzufügen:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
packages:
|
||||||
|
- .
|
||||||
|
- libs/myserviceAPI
|
||||||
|
- libs/myCoolMLServiceAPI
|
||||||
|
```
|
||||||
|
|
||||||
|
in der `package.yaml` (oder der cabal) die dependencies hinzufügen und schon
|
||||||
|
haben wir die Features zur Verfügung und können gegen diese Services reden.
|
||||||
|
|
||||||
|
### Entfernen von anderen Technologien/Microservices
|
||||||
|
|
||||||
|
In git ist das entfernen von Submodules etwas frickelig, daher hier ein
|
||||||
|
copy&paste der
|
||||||
|
[GitHub-Antwort](https://gist.github.com/myusuf3/7f645819ded92bda6677):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
## Remove the submodule entry from .git/config
|
||||||
|
git submodule deinit -f path/to/submodule
|
||||||
|
|
||||||
|
## Remove the submodule directory from the superproject's .git/modules directory
|
||||||
|
rm-rf .git/modules/path/to/submodule
|
||||||
|
|
||||||
|
## Remove the entry in .gitmodules and remove the submodule directory located at path/to/submodule
|
||||||
|
git rm-f path/to/submodule
|
||||||
|
```
|
||||||
|
|
||||||
|
Falls das nicht klappt, gibt es alternative Vorschläge unter dem Link oben.
|
||||||
|
|
||||||
|
### Woher weiss ich, was wo liegt? Dokumentation? Halloo??
|
||||||
|
|
||||||
|
Keine Panik. Ein `stack haddock --open` hilft da. Das generiert die
|
||||||
|
Dokumentation für alle in der `package.yaml` (oder cabal-file) eingetragenen
|
||||||
|
dependencies inkl. aller upstream-dependencies. Man bekommt also eine komplette
|
||||||
|
lokale Dokumentation von allem. Geöffnet wird dann die Paket-Startseite inkl.
|
||||||
|
der direkten dependencies:
|
||||||
|
|
||||||
|
Es gibt 2 wichtige Pfade im Browser:
|
||||||
|
|
||||||
|
- ...../all/index.html - hier sind alle Pakete aufgeführt
|
||||||
|
- ...../index.html - hier sind nur die direkten dependencies aufgeführt.
|
||||||
|
|
||||||
|
Wenn man einen lokalen Webserver startet kann man mittels "s" auch die
|
||||||
|
interaktive Suche öffnen (Suche nach Typen, Funktionen, Signaturen, etc.). In
|
||||||
|
Bash mit `python3` geht das z.b. einfach über:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd $(stack path --local-doc-root)
|
||||||
|
python3 -m SimpleHTTPServer 8000
|
||||||
|
firefox "http://localhost:8000"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation des Services und Start
|
||||||
|
|
||||||
|
#### Loader/Bootstrapper
|
||||||
|
|
||||||
|
Generelles Vorgehen:
|
||||||
|
|
||||||
|
- in `app/Main.hs`:
|
||||||
|
Hier ist quasi immer nur eine Zeile drin: `main = myServiceMain`
|
||||||
|
|
||||||
|
Grund: Applications tauchen nicht im Haddock auf. Also haben wir ein
|
||||||
|
"src"-Modul, welches hier nur geladen & ausgeführt wird.
|
||||||
|
- in `src/MyService.hs`:
|
||||||
|
`myServiceMain :: IO ()` definieren
|
||||||
|
|
||||||
|
Für die Main kann man prinzipiell eine Main andere Services copy/pasten. Im
|
||||||
|
folgenden eine Annotierte main-Funktion - zu den einzelnen Voraussetzungen
|
||||||
|
kommen wir im Anschluss.
|
||||||
|
|
||||||
|
![[Main.hs#]]
|
||||||
|
|
||||||
|
#### Weitere Instanzen und Definitionen, die der Generator (noch) nicht macht
|
||||||
|
|
||||||
|
In der `Myservice.Types` werden ein paar hilfreiche Typen und Typ-Instanzen
|
||||||
|
definiert. Im Folgenden geht es dabei um Dinge für:
|
||||||
|
|
||||||
|
- `Envy`
|
||||||
|
- Laden von `$ENV_VAR` in Datentypen
|
||||||
|
- Definitionen für Default-Settings
|
||||||
|
- `ServerConfig`
|
||||||
|
- Definition der Server-Konfiguration & Benennung der Environment-Variablen
|
||||||
|
- `ExtraTypes`
|
||||||
|
- ggf. Paketweite extra-Typen, die der Generator nicht macht, weil sie nicht
|
||||||
|
aus der API kommen (z.B. cache)
|
||||||
|
- `Out`/`BSON`-Instanzen
|
||||||
|
- Der API-Generator generiert nur wenige Instanzen automatisch (z.B. `aeson`),
|
||||||
|
daher werden hier die fehlenden definiert.
|
||||||
|
- `BSON`: Kommunikation mit `MongoDB`
|
||||||
|
- `Out`: pretty-printing im Log
|
||||||
|
- Nur nötig, wenn man pretty-printing via `Out` statt über Generics wie z.b.
|
||||||
|
`pretty-generic` oder die automatische Show-Instanz via `prerryShow`
|
||||||
|
macht.
|
||||||
|
|
||||||
|
![[MyService_Types.hs#]]
|
||||||
|
|
||||||
|
#### Was noch zu tun ist
|
||||||
|
|
||||||
|
Den Service implementieren. Einfach ein neues Modul aufmachen (z.B.
|
||||||
|
`MyService.Handler` oder
|
||||||
|
`MyService.DieserEndpunktbereich`/`MyService.JenerEndpunktbereich`) und dort die
|
||||||
|
Funktion implementieren, die man in der `Main.hs` benutzt hat.
|
||||||
|
In dem Handler habt ihr dann keinen Stress mehr mit Validierung, networking,
|
||||||
|
logging, etc. pp. weil alles in der Main abgehandelt wurde und ihr nur noch den
|
||||||
|
"Happy-Case" implementieren müsst.
|
||||||
|
Beispiel für unseren Handler oben:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
myApiEndpointV1Post :: MonadIO m => ServerConfig -> (ClientEnv,ClientEnv) -> TQueue BS.ByteString -> ([LogItem] -> IO ()) -> Request -> m Response
|
||||||
|
myApiEndpointV1Post sc calls amqPost log req = do
|
||||||
|
liftIO . log $ [Info $ "recieved "<>pretty req] -- input-logging
|
||||||
|
liftIO . atomically . writeTQueue . LBS.toStrict $ "{\"hey Kibana, i recieved:\"" <> A.encode (pretty req) <> "}" -- log in activeMQ/Kibana
|
||||||
|
|
||||||
|
|
||||||
|
--- .... gaaaanz viel komplizierter code um die Response zu erhalten ;)
|
||||||
|
let ret = Response 1337 Nothing -- dummy-response ;)
|
||||||
|
-- gegeben wir haben eine gültige mongodb-pipe;
|
||||||
|
-- mehr logik will ich in die Beispiele nicht packen.
|
||||||
|
-- Man kann die z.b. als weiteren Wert in einer TMVar (damit man sie ändern & updaten kann) an die Funktion übergeben.
|
||||||
|
liftIO . access pipe master "DatabaseName" $ do
|
||||||
|
ifM (auth (myServiceMongoUsername sc) (myServiceMongoPassword sc)) (return ()) (liftIO . printLog . pure . Error $ "MongoDB: Login failed.")
|
||||||
|
save "DatabaseCollection" ["_id" =: 1337, "entry" =: ret] -- selbe id wie oben ;)
|
||||||
|
return ret
|
||||||
|
```
|
||||||
|
|
||||||
|
Diese dummy-Antwort führt auf, wie gut man die ganzen Sachen mischen kann.
|
||||||
|
|
||||||
|
- Logging in die Dateien/`stdout` - je nach Konfiguration
|
||||||
|
- Logging von Statistiken in Kibana
|
||||||
|
- Speichern der Antwort in der MongoDB
|
||||||
|
- Generieren einer Serverantwort und ausliefern dieser über die Schnittstelle
|
||||||
|
|
||||||
|
#### Tipps & Tricks
|
||||||
|
|
||||||
|
##### Dateien, die statisch ausgeliefert werden sollen
|
||||||
|
|
||||||
|
Hierzu erstellt man ein Verzeichnis `static/` (Konvention; ist im generator so
|
||||||
|
generiert, dass das ausgeliefert wird). Packt man hier z.b. eine `index.html`
|
||||||
|
rein, erscheint die, wenn man den Service ansurft.
|
||||||
|
|
||||||
|
##### Wie bekomme ich diese fancy Preview hin?
|
||||||
|
|
||||||
|
Der Editor, der ganz am Anfang zum Einsatz gekommen ist, braucht nur die
|
||||||
|
`api-doc.yml` um diese Ansicht zu erzeugen. Daher empfiehlt sich hier ein
|
||||||
|
angepasster Fork davon indem die Pfade in der index.html korrigiert sind. Am
|
||||||
|
einfachsten (und von den meisten services so benutzt): In meiner Implementation
|
||||||
|
liegt dann nach dem starten auf http://localhost:PORT/ui/ und kann direkt dort
|
||||||
|
getestet werden.
|
||||||
|
|
||||||
|
##### Wie sorge ich für bessere Warnungen, damit der Compiler meine Bugs fängt?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
stack build --file-watch --ghc-options '-freverse-errors -W -Wall -Wcompat' --interleaved-output
|
||||||
|
```
|
||||||
|
|
||||||
|
Was tut das?
|
||||||
|
|
||||||
|
- `--file-watch`: automatisches (minimales) kompilieren bei dateiänderungen
|
||||||
|
- `--ghc-options`
|
||||||
|
- `-freverse-errors`: Fehlermeldungen in umgekehrter Reihenfolge (Erster
|
||||||
|
Fehler ganz unten; wenig scrollen )
|
||||||
|
- `-W`: Warnungen an
|
||||||
|
- `-Wall`: Alle sinnvollen Warnungen an (im gegensatz zu `-Weverything`, was
|
||||||
|
WIRKLICH alles ist )
|
||||||
|
- `-Wcompat`: Warnungen für Sachen, die in der nächsten Compilerversion kaputt
|
||||||
|
brechen werden & vermieden werden sollten
|
||||||
|
- `--interleaved-output`: stack-log direkt ausgeben & nicht in Dateien schreiben
|
||||||
|
und die dann am ende zusammen cat\'en.
|
||||||
|
|
||||||
|
Um pro Datei Warnungen auszuschalten (z.B. weil man ganz sicher weiss, was man
|
||||||
|
tut -.-): `{-# OPTIONS_GHC -Wno-whatsoever #-}` als pragma in die Datei.
|
||||||
|
|
||||||
|
**Idealerweise sollte das Projekt keine Warnungen erzeugen.**
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
Als Beispiel sei hier ein einfaches Docker-Build mit Jenkins-CI gezeigt, weil
|
||||||
|
ich das aus Gründen rumliegen hatte. Kann man analog in fast alle anderen CI
|
||||||
|
übersetzen.
|
||||||
|
|
||||||
|
#### Docker
|
||||||
|
|
||||||
|
Die angehängten Scripte gehen von einer Standard-Einrichtung aus (statische
|
||||||
|
Sachen in static, 2-3 händische Anpassungen auf das eigene Projekt nach
|
||||||
|
auspacken). Nachher liegt dann auch unter static/version die gebaute
|
||||||
|
Versionsnummer & kann abgerufen werden. In der `Dockerfile.release` und der
|
||||||
|
`Jenkinsfile` müssen noch Anpassungen gemacht werden. Konkret:
|
||||||
|
|
||||||
|
- in der `Dockerfile.release`: alle `<<<HIER>>>`-Stellen sinnvoll befüllen
|
||||||
|
- in der `Jenkinsfile` die defs für "servicename" und "servicebinary" ausfüllen.
|
||||||
|
Binary ist das, was bei stack exec aufgerufen wird; name ist der Image-Name
|
||||||
|
für das docker-repository.
|
||||||
|
|
||||||
|
#### Jenkins
|
||||||
|
|
||||||
|
Änderungen die dann noch gemacht werden müssen:
|
||||||
|
|
||||||
|
- git-repository URL anpassen
|
||||||
|
- Environment-Vars anpassen (\$BRANCH = test & live haben keine zusatzdinger im
|
||||||
|
docker-image-repository; ansonsten hat das image \$BRANCH im Namen)
|
||||||
|
|
||||||
|
Wenn das fertig gebaut ist, liegt im test/live-repository ein docker-image
|
||||||
|
namens `servicename:version`.
|
||||||
|
|
||||||
|
### OMG! Ich muss meine API ändern. Was mache ich nun?
|
||||||
|
|
||||||
|
1. api-doc.yml bearbeiten, wie gewünscht
|
||||||
|
2. mittels generator die Api & submodule neu generieren
|
||||||
|
3. ggf. custom Änderungen übernehmen (:Gitdiffsplit hilft)
|
||||||
|
4. Alle Compilerfehler + Warnungen in der eigentlichen Applikation fixen
|
||||||
|
5. If it comipilez, ship it! (Besser nicht :grin:)
|
188
content/Coding/Haskell/Webapp-Example/Main.hs.md
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
# Webapp-Example: Main.hs
|
||||||
|
|
||||||
|
Wie man das verwendet, siehe #[[Webapp-Example]].
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
{-# OPTIONS_GHC -Wno-name-shadowing #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
module MyService where
|
||||||
|
|
||||||
|
-- generische imports aus den dependencies/base, nicht in der prelude
|
||||||
|
import Codec.MIME.Type
|
||||||
|
import Configuration.Dotenv as Dotenv
|
||||||
|
import Control.Concurrent (forkIO, threadDelay)
|
||||||
|
import Control.Concurrent.Async
|
||||||
|
import Control.Concurrent.STM
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.Catch
|
||||||
|
import Control.Monad.Except
|
||||||
|
import Conversion
|
||||||
|
import Conversion.Text ()
|
||||||
|
import Data.Binary.Builder
|
||||||
|
import Data.String (IsString (..))
|
||||||
|
import Data.Time
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Data.Time.Format
|
||||||
|
import Data.Default
|
||||||
|
import Network.HostName
|
||||||
|
import Network.HTTP.Client as HTTP hiding
|
||||||
|
(withConnection)
|
||||||
|
import Network.HTTP.Types (Status, statusCode)
|
||||||
|
import Network.Mom.Stompl.Client.Queue
|
||||||
|
import Network.Wai (Middleware)
|
||||||
|
import Network.Wai.Logger
|
||||||
|
import Network.Wai.Middleware.Cors
|
||||||
|
import Network.Wai.Middleware.RequestLogger (OutputFormat (..),
|
||||||
|
logStdout,
|
||||||
|
mkRequestLogger,
|
||||||
|
outputFormat)
|
||||||
|
import Servant.Client (mkClientEnv,
|
||||||
|
parseBaseUrl)
|
||||||
|
import System.Directory
|
||||||
|
import System.Envy
|
||||||
|
import System.IO
|
||||||
|
import System.Log.FastLogger
|
||||||
|
import Text.PrettyPrint.GenericPretty
|
||||||
|
|
||||||
|
-- generische imports, aber qualified, weil es sonst zu name-clashes kommt
|
||||||
|
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
-- import qualified Data.ByteString.Char8 as BS8
|
||||||
|
import qualified Data.ByteString.Lazy as LBS
|
||||||
|
import qualified Network.HTTP.Client.TLS as UseDefaultHTTPSSettings (tlsManagerSettings)
|
||||||
|
import qualified Network.Mom.Stompl.Client.Queue as AMQ
|
||||||
|
import qualified Network.Wai as WAI
|
||||||
|
|
||||||
|
-- Handler für den MyServiceBackend-Typen und Imports aus den Libraries
|
||||||
|
import MyService.Handler as H -- handler der H.myApiEndpointV1Post implementiert
|
||||||
|
import MyService.Types -- weitere Type (s. nächste box)
|
||||||
|
import MyServiceGen.API as MS -- aus der generierten library
|
||||||
|
|
||||||
|
|
||||||
|
myServicemain :: IO ()
|
||||||
|
myServicemain = do
|
||||||
|
-- .env-Datei ins Prozess-Environment laden, falls noch nicht von außen gesetzt
|
||||||
|
void $ loadFile $ Dotenv.Config [".env"] [] False
|
||||||
|
-- Config holen (defaults + overrides aus dem Environment)
|
||||||
|
sc@ServerConfig{..} <- decodeWithDefaults defConfig
|
||||||
|
-- Backend-Setup
|
||||||
|
-- legt sowas wie Proxy-Server fest und wo man wie dran kommt. Benötigt für das Sprechen mit anderen Microservices
|
||||||
|
let defaultHTTPSSettings = UseDefaultHTTPSSettings.tlsManagerSettings { managerResponseTimeout = responseTimeoutMicro $ 1000 * 1000 * myserviceMaxTimeout }
|
||||||
|
createBackend url proxy = do
|
||||||
|
manager <- newManager . managerSetProxy proxy
|
||||||
|
$ defaultHTTPSSettings
|
||||||
|
url' <- parseBaseUrl url
|
||||||
|
return (mkClientEnv manager url')
|
||||||
|
internalProxy = case myserviceInternalProxyUrl of
|
||||||
|
"" -> noProxy
|
||||||
|
url -> useProxy $ HTTP.Proxy (fromString url) myserviceInternalProxyPort
|
||||||
|
-- externalProxy = case myserviceExternalProxyUrl of
|
||||||
|
-- "" -> noProxy
|
||||||
|
-- url -> useProxy $ HTTP.Proxy (fromString url) myserviceExternalProxyPort
|
||||||
|
|
||||||
|
-- Definieren & Erzeugen der Funktionen um die anderen Services anzusprechen.
|
||||||
|
calls <- (,)
|
||||||
|
<$> createBackend myserviceAUri internalProxy
|
||||||
|
<*> createBackend myserviceBUri internalProxy
|
||||||
|
|
||||||
|
-- Logging-Setup
|
||||||
|
hSetBuffering stdout LineBuffering
|
||||||
|
hSetBuffering stderr LineBuffering
|
||||||
|
|
||||||
|
|
||||||
|
-- Infos holen, brauchen wir später
|
||||||
|
myName <- getHostName
|
||||||
|
today <- formatTime defaultTimeLocale "%F" . utctDay <$> getCurrentTime
|
||||||
|
|
||||||
|
|
||||||
|
-- activeMQ-Transaktional-Queue zum schreiben nachher vorbereiten
|
||||||
|
amqPost <- newTQueueIO
|
||||||
|
|
||||||
|
|
||||||
|
-- bracket a b c == erst a machen, ergebnis an c als variablen übergeben. Schmeisst c ne exception/wird gekillt/..., werden die variablen an b übergeben.
|
||||||
|
bracket
|
||||||
|
-- logfiles öffnen
|
||||||
|
(LogFiles <$> openFile ("/logs/myservice-"<>myName<>"-"<>today<>".info") AppendMode
|
||||||
|
<*> openFile (if myserviceDebug then "/logs/myservice-"<>myName<>"-"<>today<>".debug" else "/dev/null") AppendMode
|
||||||
|
<*> openFile ("/logs/myservice-"<>myName<>"-"<>today<>".error") AppendMode
|
||||||
|
<*> openFile ("/logs/myservice-"<>myName<>"-"<>today<>".timings") AppendMode
|
||||||
|
)
|
||||||
|
-- und bei exception/beendigung schlißen.h
|
||||||
|
(\(LogFiles a b c d) -> mapM_ hClose [a,b,c,d])
|
||||||
|
$ \logfiles -> do
|
||||||
|
|
||||||
|
|
||||||
|
-- logschreibe-funktionen aliasen; log ist hier abstrakt, iolog spezialisiert auf io.
|
||||||
|
let log = printLogFiles logfiles :: MonadIO m => [LogItem] -> m ()
|
||||||
|
iolog = printLogFilesIO logfiles :: [LogItem] -> IO ()
|
||||||
|
|
||||||
|
|
||||||
|
-- H.myApiEndpointV1Post ist ein Handler (alle Handler werden mit alias H importiert) und in einer eigenen Datei
|
||||||
|
-- Per Default bekommen Handler sowas wie die server-config, die Funktionen um mit anderen Services zu reden, die AMQ-Queue um ins Kibana zu loggen und eine Datei-Logging-Funktion
|
||||||
|
-- Man kann aber noch viel mehr machen - z.b. gecachte Daten übergeben, eine Talk-Instanz, etc. pp.
|
||||||
|
server = MyServiceBackend{ myApiEndpointV1Post = H.myApiEndpointV1Post sc calls amqPost log
|
||||||
|
}
|
||||||
|
config = MS.Config $ "http://" ++ myserviceHost ++ ":" ++ show myservicePort ++ "/"
|
||||||
|
iolog . pure . Info $ "Using Server configuration:"
|
||||||
|
iolog . pure . Info $ pretty sc { myserviceActivemqPassword = "******" -- Do NOT log the password ;)
|
||||||
|
, myserviceMongoPassword = "******"
|
||||||
|
}
|
||||||
|
-- alle Services starten (Hintergrund-Aktionen wie z.b. einen MongoDB-Dumper, einen Talk-Server oder wie hier die ActiveMQ
|
||||||
|
void $ forkIO $ keepActiveMQConnected sc iolog amqPost
|
||||||
|
-- logging-Framework erzeugen
|
||||||
|
loggingMW <- loggingMiddleware
|
||||||
|
-- server starten
|
||||||
|
if myserviceDebug
|
||||||
|
then runMyServiceMiddlewareServer config (cors (\_ -> Just (simpleCorsResourcePolicy {corsRequestHeaders = ["Content-Type"]})) . loggingMW . logStdout) server
|
||||||
|
else runMyServiceMiddlewareServer config (cors (\_ -> Just (simpleCorsResourcePolicy {corsRequestHeaders = ["Content-Type"]}))) server
|
||||||
|
|
||||||
|
|
||||||
|
-- Sollte bald in die Library hs-stomp ausgelagert werden
|
||||||
|
-- ist ein Beispiel für einen ActiveMQ-Dumper
|
||||||
|
keepActiveMQConnected :: ServerConfig -> ([LogItem] -> IO ()) -> TQueue BS.ByteString -> IO ()
|
||||||
|
keepActiveMQConnected sc@ServerConfig{..} printLog var = do
|
||||||
|
res <- handle (\(e :: SomeException) -> do
|
||||||
|
printLog . pure . Error $ "Exception in AMQ-Thread: "<>show e
|
||||||
|
return $ Right ()
|
||||||
|
) $ AMQ.try $ do -- catches all AMQ-Exception that we can handle. All others bubble up.
|
||||||
|
printLog . pure . Info $ "AMQ: connecting..."
|
||||||
|
withConnection myserviceActivemqHost myserviceActivemqPort [ OAuth myserviceActivemqUsername myserviceActivemqPassword
|
||||||
|
, OTmo (30*1000) {- 30 sec timeout -}
|
||||||
|
]
|
||||||
|
[] $ \c -> do
|
||||||
|
let oconv = return
|
||||||
|
printLog . pure . Info $ "AMQ: connected"
|
||||||
|
withWriter c "Chaos-Logger for Kibana" "chaos.logs" [] [] oconv $ \writer -> do
|
||||||
|
printLog . pure . Info $ "AMQ: queue created"
|
||||||
|
let postfun = writeQ writer (Type (Application "json") []) []
|
||||||
|
void $ race
|
||||||
|
(forever $ atomically (readTQueue var) >>= postfun)
|
||||||
|
(threadDelay (600*1000*1000)) -- wait 10 Minutes
|
||||||
|
-- close writer
|
||||||
|
-- close connection
|
||||||
|
-- get outside of all try/handle/...-constructions befor recursing.
|
||||||
|
case res of
|
||||||
|
Left ex -> do
|
||||||
|
printLog . pure . Error $ "AMQ: "<>show ex
|
||||||
|
keepActiveMQConnected sc printLog var
|
||||||
|
Right _ -> keepActiveMQConnected sc printLog var
|
||||||
|
|
||||||
|
|
||||||
|
-- Beispiel für eine Custom-Logging-Middleware.
|
||||||
|
-- Hier werden z.B. alle 4xx-Status-Codes inkl. Payload ins stdout-Log geschrieben.
|
||||||
|
-- Nützlich, wenn die Kollegen ihre Requests nicht ordentlich schreiben können und der Server das Format zurecht mit einem BadRequest ablehnt ;)
|
||||||
|
loggingMiddleware :: IO Middleware
|
||||||
|
loggingMiddleware = liftIO $ mkRequestLogger $ def { outputFormat = CustomOutputFormatWithDetails out }
|
||||||
|
where
|
||||||
|
out :: ZonedDate -> WAI.Request -> Status -> Maybe Integer -> NominalDiffTime -> [BS.ByteString] -> Builder -> LogStr
|
||||||
|
out _ r status _ _ payload _
|
||||||
|
| statusCode status < 300 = ""
|
||||||
|
| statusCode status > 399 && statusCode status < 500 = "Error code "<>toLogStr (statusCode status) <>" sent. Request-Payload was: "<> mconcat (toLogStr <$> payload) <> "\n"
|
||||||
|
| otherwise = toLogStr (show r) <> "\n"
|
||||||
|
|
||||||
|
```
|
83
content/Coding/Haskell/Webapp-Example/MyService_Types.hs.md
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Webapp-Example: MyService/Types.hs
|
||||||
|
|
||||||
|
Anleitung siehe #[[Webapp-Example]].
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-name-shadowing #-}
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE DeriveFunctor #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE DerivingVia #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE RankNTypes #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
module MyService.Types where
|
||||||
|
|
||||||
|
import Data.Aeson (FromJSON, ToJSON)
|
||||||
|
import Data.Text
|
||||||
|
import Data.Time.Clock
|
||||||
|
import GHC.Generics
|
||||||
|
import System.Envy
|
||||||
|
import Text.PrettyPrint (text)
|
||||||
|
import Text.PrettyPrint.GenericPretty
|
||||||
|
|
||||||
|
-- Out hat hierfür keine Instanzen, daher kurz eine einfach Definition.
|
||||||
|
instance Out Text where
|
||||||
|
doc = text . unpack
|
||||||
|
docPrec i a = text $ showsPrec i a ""
|
||||||
|
|
||||||
|
instance Out UTCTime where
|
||||||
|
doc = text . show
|
||||||
|
docPrec i a = text $ showsPrec i a ""
|
||||||
|
|
||||||
|
-- Der ServerConfig-Typ. Wird mit den defaults unten initialisiert, dann mit den Variablen aus der .env-Datei überschrieben und zum Schluss können Serveradmins diese via $MYSERVICE_FOO nochmal überschreiben.
|
||||||
|
data ServerConfig = ServerConfig
|
||||||
|
{ myserviceHost :: String -- ^ Environment: $MYSERVICE_HOST
|
||||||
|
, myservicePort :: Int -- ^ Environment: $MYSERVICE_PORT
|
||||||
|
, myserviceMaxTimeout :: Int -- ^ Environment: $MYSERVICE_MAX_TIMEOUT
|
||||||
|
, myserviceInternalProxyUrl :: String -- ^ Environment: $MYSERVICE_INTERNAL_PROXY_URL
|
||||||
|
, myserviceInternalProxyPort :: Int -- ^ Environment: $MYSERVICE_INTERNAL_PROXY_PORT
|
||||||
|
, myserviceExternalProxyUrl :: String -- ^ Environment: $MYSERVICE_EXTERNAL_PROXY_URL
|
||||||
|
, myserviceExternalProxyPort :: Int -- ^ Environment: $MYSERVICE_EXTERNAL_PROXY_PORT
|
||||||
|
, myserviceActivemqHost :: String -- ^ Environment: $MYSERVICE_ACTIVEMQ_HOST
|
||||||
|
, myserviceActivemqPort :: Int -- ^ Environment: $MYSERVICE_ACTIVEMQ_PORT
|
||||||
|
, myserviceActivemqUsername :: String -- ^ Environment: $MYSERVICE_ACTIVEMQ_USERNAME
|
||||||
|
, myserviceActivemqPassword :: String -- ^ Environment: $MYSERVICE_ACTIVEMQ_PASSWORD
|
||||||
|
, myserviceMongoUsername :: String -- ^ Environment: $MYSERVICE_MONGO_USERNAME
|
||||||
|
, myserviceMongoPassword :: String -- ^ Environment: $MYSERVICE_MONGO_PASSWORD
|
||||||
|
, myserviceDebug :: Bool -- ^ Environment: $MYSERVICE_DEBUG
|
||||||
|
} deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
-- Default-Konfigurations-Instanz für diesen Service.
|
||||||
|
instance DefConfig ServerConfig where
|
||||||
|
defConfig = ServerConfig "0.0.0.0" 8080 20
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
0
|
||||||
|
""
|
||||||
|
0
|
||||||
|
""
|
||||||
|
0
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
""
|
||||||
|
False
|
||||||
|
|
||||||
|
-- Kann auch aus dem ENV gefüllt werden
|
||||||
|
instance FromEnv ServerConfig
|
||||||
|
-- Und hübsch ausgegeben werden.
|
||||||
|
instance Out ServerConfig
|
||||||
|
|
||||||
|
|
||||||
|
instance Out Response
|
||||||
|
instance FromBSON Repsonse -- FromBSON-Instanz geht immer davon aus, dass alle keys da sind (ggf. mit null bei Nothing).
|
||||||
|
```
|
23
content/Coding/OpenAPI.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Openapi-generator
|
||||||
|
|
||||||
|
## Idee hinter einem API-Generator
|
||||||
|
|
||||||
|
[[TODO]] Idee hinter einem API-Generator
|
||||||
|
|
||||||
|
## Theorie und Praxis
|
||||||
|
|
||||||
|
[[TODO]] Theorie und Praxis
|
||||||
|
|
||||||
|
## Spezialfall in Haskell
|
||||||
|
|
||||||
|
[[TODO]] Veraltet
|
||||||
|
|
||||||
|
Wie im [[Webapp-Example]] kurz angerissen wird in Haskell nicht zwischen Server
|
||||||
|
und Client unterschieden. Daher können hier sehr viele Optimierungen bei
|
||||||
|
Änderungen passieren, die in anderen Sprachen nicht so einfach möglich sind.
|
||||||
|
|
||||||
|
Die generierte Library hat die Funktionen, die ein Client braucht direkt dabei
|
||||||
|
und man muss lediglich die Verbindung initialisieren (Callback für
|
||||||
|
Network-Requests, Serveradresse etc.) und kann dann direkt alle Funktionen
|
||||||
|
benutzen. Partial Application hilft hier massiv und man bekommt z.b. Für die
|
||||||
|
Beispiel-Tierhandlung aus dem ursprünglichem `getPet :: ` direkt so etwas wie `getPet :: PetID -> IO Pet`
|
@ -1,503 +0,0 @@
|
|||||||
# Webapp-Development in Haskell
|
|
||||||
|
|
||||||
Step-by-Step-Anleitung, wie man ein neues Projekt mit einer bereits erprobten Pipeline erstellt.
|
|
||||||
|
|
||||||
## Definition der API
|
|
||||||
|
|
||||||
Erster Schritt ist immer ein wünsch-dir-was bei der Api-Defenition.
|
|
||||||
|
|
||||||
Die meisten Services haben offensichtliche Anforderungen (Schnittstellen nach draußen, Schnittstellen intern, ...). Diese kann man immer sehr gut in einem `Request -> Response`-Model erfassen.
|
|
||||||
|
|
||||||
Diese Definition läuft über openapi-v3 und kann z.b. mit Echtzeit-Vorschau im http://editor.swagger.io/ erspielen. Per Default ist der noch auf openapi-v2 (aka swagger), kann aber auch v3.
|
|
||||||
|
|
||||||
Nach der Definition, was man am Ende haben möchte, muss man sich entscheiden, in welcher Sprache man weiter entwickelt. Ich empfehle aus verschiedenen Gründen primär 2 Sprachen: Python-Microservices (weil die ML-Libraries sehr gut sind, allerdings Änderungen meist schwer sind und der Code wenig robust - meist nur 1 API-Endpunkt pro service) und Haskell (stabilität, performace, leicht zu ändern, gut anzupassen).
|
|
||||||
|
|
||||||
Im folgenden wird (aus offensichtlichen Gründen) nur auf das Haskell-Projekt eingegangen.
|
|
||||||
|
|
||||||
## Startprojekt in Haskell
|
|
||||||
|
|
||||||
### Erstellen eines neuen Projektes
|
|
||||||
|
|
||||||
zunächst erstellen wir in normales Haskell-Projekt ohne funktionalität & firlefanz:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
stack new myservice
|
|
||||||
```
|
|
||||||
|
|
||||||
Dies erstellt ein neues Verzeichnis und das generelle scaffolding.
|
|
||||||
Nach einer kurzen anpassung der stack.yaml (resolver auf unserer setzen; aktuell: lts-17.4) fügen wir am Ende der Datei
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
allow-newer: true
|
|
||||||
ghc-options:
|
|
||||||
"$locals": -fwrite-ide-info
|
|
||||||
```
|
|
||||||
|
|
||||||
ein.
|
|
||||||
Anschließend organisieren wir uns noch eine gute `.gitignore` und initialisieren das git mittels `git init; git add .; git commit -m "initial scaffold"`
|
|
||||||
|
|
||||||
### Generierung der API
|
|
||||||
|
|
||||||
Da die API immer wieder neu generiert werden kann (und sollte!) liegt sich in einem unterverzeichnis des Haputprojektes.
|
|
||||||
|
|
||||||
Initial ist es das einfachste ein leeres temporäres Verzeichnis woanders zu erstellen, die `api-doc.yml` hinein kopieren und folgendes ausführen:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-generator generate -g haskell -o . -i api-doc.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Dieses erstellt einem dann eine komplette library inkl. Datentypen.
|
|
||||||
Wichtig: Der Name in der api-doc sollte vom Namen des Services (oben myservice) abweichen - entweder in Casing oder im Namen direkt. Suffixe wie API schneidet der Generator hier leider ab.
|
|
||||||
(Wieso das ganze? Es entstehen nachher 2 libraries, foo & fooAPI. Da der generator das API abschneidet endet man mit foo & foo und der compiler meckert, dass er nicht weiss, welche lib gemeint ist).
|
|
||||||
|
|
||||||
danach: wie gewohnt `git init; git add .; git commit -m "initial"`. Auf dem Server der Wahl (github, gitea, gitlab, ...) nun ein Repository erstellen (am Besten: myserviceAPI - alles auf API endend ist autogeneriert!) und den Anweisungen nach ein remote hinzufügen & pushen.
|
|
||||||
|
|
||||||
#### Wieder zurück im Haskell-Service
|
|
||||||
|
|
||||||
In unserem eigentlichen Service müssen wir nun die API einbinden.
|
|
||||||
Dazu erstellen wir ein Verzeichnis `libs` (konvention) und machen ein `git submodule add <repository-url> libs/myserviceAPI`
|
|
||||||
|
|
||||||
Git hat nun die API in das submodul gepackt und wir können das oben erstellte temporäre verzeichnis wieder löschen.
|
|
||||||
|
|
||||||
Anschließend müssen wir stack noch erklären, dass wir die API da nun liegen haben und passen wieder die stack.yaml an, indem wir das Verzeichnis unter packages hinzufügen.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
packages:
|
|
||||||
- .
|
|
||||||
- libs/myserviceAPI # <<
|
|
||||||
```
|
|
||||||
nun können wir in der `package.yaml` (oder `myservice.cabal`, falls kein hpack verwendet wird) unter den dependencies unsere api hinzufügen (name wie die cabal-datei in libs/myserviceAPI).
|
|
||||||
|
|
||||||
### Einbinden anderer Microservices
|
|
||||||
|
|
||||||
Funktioniert komplett analog zu dem vorgehen oben (ohne das generieren natürlich ;) ).
|
|
||||||
`stack.yaml` editieren und zu den packages hinzufügen:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
packages:
|
|
||||||
- .
|
|
||||||
- libs/myserviceAPI
|
|
||||||
- libs/myCoolMLServiceAPI
|
|
||||||
```
|
|
||||||
|
|
||||||
in der `package.yaml` (oder der cabal) die dependencies hinzufügen und schon haben wir die Features zur Verfügung und können gegen diese Services reden.
|
|
||||||
|
|
||||||
### Entfernen von anderen Technologien/Microservices
|
|
||||||
|
|
||||||
In git ist das entfernen von Submodules etwas frickelig, daher hier ein copy&paste der [GitHub-Antwort](https://gist.github.com/myusuf3/7f645819ded92bda6677):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
## Remove the submodule entry from .git/config
|
|
||||||
git submodule deinit -f path/to/submodule
|
|
||||||
|
|
||||||
## Remove the submodule directory from the superproject's .git/modules directory
|
|
||||||
rm-rf .git/modules/path/to/submodule
|
|
||||||
|
|
||||||
## Remove the entry in .gitmodules and remove the submodule directory located at path/to/submodule
|
|
||||||
git rm-f path/to/submodule
|
|
||||||
```
|
|
||||||
|
|
||||||
Falls das nicht klappt, gibt es alternative Vorschläge unter dem Link oben.
|
|
||||||
|
|
||||||
### Woher weiss ich, was wo liegt? Dokumentation? Halloo??
|
|
||||||
|
|
||||||
Keine Panik. Ein `stack haddock --open` hilft da. Das generiert die Dokumentation für alle in der `package.yaml` (oder cabal-file) eingetragenen dependencies inkl. aller upstream-dependencies. Man bekommt also eine komplette lokale Dokumentation von allem. Geöffnet wird dann die Paket-Startseite inkl. der direkten dependencies:
|
|
||||||
|
|
||||||
Es gibt 2 wichtige Pfade im Browser:
|
|
||||||
|
|
||||||
- ...../all/index.html - hier sind alle Pakete aufgeführt
|
|
||||||
- ...../index.html - hier sind nur die direkten dependencies aufgeführt.
|
|
||||||
|
|
||||||
Wenn man einen lokalen Webserver startet kann man mittels "s" auch die interaktive Suche öffnen (Suche nach Typen, Funktionen, Signaturen, etc.). In Bash mit python3 geht das z.b. einfach über:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd $(stack path --local-doc-root)
|
|
||||||
python3 -m SimpleHTTPServer 8000
|
|
||||||
firefox "http://localhost:8000"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Implementation des Services und Start
|
|
||||||
|
|
||||||
#### Loader/Bootstrapper
|
|
||||||
|
|
||||||
Generelles Vorgehen:
|
|
||||||
- in app/Main.hs:
|
|
||||||
Hier ist quasi immer nur eine Zeile drin: `main = myServiceMain`
|
|
||||||
|
|
||||||
Grund: Applications tauchen nicht im Haddock auf. Also haben wir ein "src"-Modul, welches hier nur geladen & ausgeführt wird.
|
|
||||||
- in src/MyService.hs:
|
|
||||||
`myServiceMain :: IO ()` definieren
|
|
||||||
|
|
||||||
Für die Main kann man prinzipiell eine Main andere Services copy/pasten. Im folgenden eine Annotierte main-Funktion - zu den einzelnen Vorraussetzungen kommen wir im Anschluss.
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
{-# OPTIONS_GHC -Wno-name-shadowing #-}
|
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
|
||||||
{-# LANGUAGE LambdaCase #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE RankNTypes #-}
|
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
|
||||||
module MyService where
|
|
||||||
|
|
||||||
-- generische imports aus den dependencies/base, nicht in der prelude
|
|
||||||
import Codec.MIME.Type
|
|
||||||
import Configuration.Dotenv as Dotenv
|
|
||||||
import Control.Concurrent (forkIO, threadDelay)
|
|
||||||
import Control.Concurrent.Async
|
|
||||||
import Control.Concurrent.STM
|
|
||||||
import Control.Monad
|
|
||||||
import Control.Monad.Catch
|
|
||||||
import Control.Monad.Except
|
|
||||||
import Conversion
|
|
||||||
import Conversion.Text ()
|
|
||||||
import Data.Binary.Builder
|
|
||||||
import Data.String (IsString (..))
|
|
||||||
import Data.Time
|
|
||||||
import Data.Time.Clock
|
|
||||||
import Data.Time.Format
|
|
||||||
import Data.Default
|
|
||||||
import Network.HostName
|
|
||||||
import Network.HTTP.Client as HTTP hiding
|
|
||||||
(withConnection)
|
|
||||||
import Network.HTTP.Types (Status, statusCode)
|
|
||||||
import Network.Mom.Stompl.Client.Queue
|
|
||||||
import Network.Wai (Middleware)
|
|
||||||
import Network.Wai.Logger
|
|
||||||
import Network.Wai.Middleware.Cors
|
|
||||||
import Network.Wai.Middleware.RequestLogger (OutputFormat (..),
|
|
||||||
logStdout,
|
|
||||||
mkRequestLogger,
|
|
||||||
outputFormat)
|
|
||||||
import Servant.Client (mkClientEnv,
|
|
||||||
parseBaseUrl)
|
|
||||||
import System.Directory
|
|
||||||
import System.Envy
|
|
||||||
import System.IO
|
|
||||||
import System.Log.FastLogger
|
|
||||||
import Text.PrettyPrint.GenericPretty
|
|
||||||
|
|
||||||
-- generische imports, aber qualified, weil es sonst zu name-clashes kommt
|
|
||||||
|
|
||||||
import qualified Data.ByteString as BS
|
|
||||||
-- import qualified Data.ByteString.Char8 as BS8
|
|
||||||
import qualified Data.ByteString.Lazy as LBS
|
|
||||||
import qualified Network.HTTP.Client.TLS as UseDefaultHTTPSSettings (tlsManagerSettings)
|
|
||||||
import qualified Network.Mom.Stompl.Client.Queue as AMQ
|
|
||||||
import qualified Network.Wai as WAI
|
|
||||||
|
|
||||||
-- Handler für den MyServiceBackend-Typen und Imports aus den Libraries
|
|
||||||
import MyService.Handler as H -- handler der H.myApiEndpointV1Post implementiert
|
|
||||||
import MyService.Types -- weitere Type (s. nächste box)
|
|
||||||
import MyServiceGen.API as MS -- aus der generierten library
|
|
||||||
|
|
||||||
|
|
||||||
myServicemain :: IO ()
|
|
||||||
myServicemain = do
|
|
||||||
-- .env-Datei ins Prozess-Environment laden, falls noch nicht von außen gesetzt
|
|
||||||
void $ loadFile $ Dotenv.Config [".env"] [] False
|
|
||||||
-- Config holen (defaults + overrides aus dem Environment)
|
|
||||||
sc@ServerConfig{..} <- decodeWithDefaults defConfig
|
|
||||||
-- Backend-Setup
|
|
||||||
-- legt sowas wie Proxy-Server fest und wo man wie dran kommt. Benötigt für das Sprechen mit anderen Microservices
|
|
||||||
let defaultHTTPSSettings = UseDefaultHTTPSSettings.tlsManagerSettings { managerResponseTimeout = responseTimeoutMicro $ 1000 * 1000 * myserviceMaxTimeout }
|
|
||||||
createBackend url proxy = do
|
|
||||||
manager <- newManager . managerSetProxy proxy
|
|
||||||
$ defaultHTTPSSettings
|
|
||||||
url' <- parseBaseUrl url
|
|
||||||
return (mkClientEnv manager url')
|
|
||||||
internalProxy = case myserviceInternalProxyUrl of
|
|
||||||
"" -> noProxy
|
|
||||||
url -> useProxy $ HTTP.Proxy (fromString url) myserviceInternalProxyPort
|
|
||||||
-- externalProxy = case myserviceExternalProxyUrl of
|
|
||||||
-- "" -> noProxy
|
|
||||||
-- url -> useProxy $ HTTP.Proxy (fromString url) myserviceExternalProxyPort
|
|
||||||
|
|
||||||
-- Definieren & Erzeugen der Funktionen um die anderen Services anzusprechen.
|
|
||||||
calls <- (,)
|
|
||||||
<$> createBackend myserviceAUri internalProxy
|
|
||||||
<*> createBackend myserviceBUri internalProxy
|
|
||||||
|
|
||||||
-- Logging-Setup
|
|
||||||
hSetBuffering stdout LineBuffering
|
|
||||||
hSetBuffering stderr LineBuffering
|
|
||||||
|
|
||||||
|
|
||||||
-- Infos holen, brauchen wir später
|
|
||||||
myName <- getHostName
|
|
||||||
today <- formatTime defaultTimeLocale "%F" . utctDay <$> getCurrentTime
|
|
||||||
|
|
||||||
|
|
||||||
-- activeMQ-Transaktional-Queue zum schreiben nachher vorbereiten
|
|
||||||
amqPost <- newTQueueIO
|
|
||||||
|
|
||||||
|
|
||||||
-- bracket a b c == erst a machen, ergebnis an c als variablen übergeben. Schmeisst c ne exception/wird gekillt/..., werden die variablen an b übergeben.
|
|
||||||
bracket
|
|
||||||
-- logfiles öffnen
|
|
||||||
(LogFiles <$> openFile ("/logs/myservice-"<>myName<>"-"<>today<>".info") AppendMode
|
|
||||||
<*> openFile (if myserviceDebug then "/logs/myservice-"<>myName<>"-"<>today<>".debug" else "/dev/null") AppendMode
|
|
||||||
<*> openFile ("/logs/myservice-"<>myName<>"-"<>today<>".error") AppendMode
|
|
||||||
<*> openFile ("/logs/myservice-"<>myName<>"-"<>today<>".timings") AppendMode
|
|
||||||
)
|
|
||||||
-- und bei exception/beendigung schlißen.h
|
|
||||||
(\(LogFiles a b c d) -> mapM_ hClose [a,b,c,d])
|
|
||||||
$ \logfiles -> do
|
|
||||||
|
|
||||||
|
|
||||||
-- logschreibe-funktionen aliasen; log ist hier abstrakt, iolog spezialisiert auf io.
|
|
||||||
let log = printLogFiles logfiles :: MonadIO m => [LogItem] -> m ()
|
|
||||||
iolog = printLogFilesIO logfiles :: [LogItem] -> IO ()
|
|
||||||
|
|
||||||
|
|
||||||
-- H.myApiEndpointV1Post ist ein Handler (alle Handler werden mit alias H importiert) und in einer eigenen Datei
|
|
||||||
-- Per Default bekommen Handler sowas wie die server-config, die Funktionen um mit anderen Services zu reden, die AMQ-Queue um ins Kibana zu loggen und eine Datei-Logging-Funktion
|
|
||||||
-- Man kann aber noch viel mehr machen - z.b. gecachte Daten übergeben, eine Talk-Instanz, etc. pp.
|
|
||||||
server = MyServiceBackend{ myApiEndpointV1Post = H.myApiEndpointV1Post sc calls amqPost log
|
|
||||||
}
|
|
||||||
config = MS.Config $ "http://" ++ myserviceHost ++ ":" ++ show myservicePort ++ "/"
|
|
||||||
iolog . pure . Info $ "Using Server configuration:"
|
|
||||||
iolog . pure . Info $ pretty sc { myserviceActivemqPassword = "******" -- Do NOT log the password ;)
|
|
||||||
, myserviceMongoPassword = "******"
|
|
||||||
}
|
|
||||||
-- alle Services starten (Hintergrund-Aktionen wie z.b. einen MongoDB-Dumper, einen Talk-Server oder wie hier die ActiveMQ
|
|
||||||
void $ forkIO $ keepActiveMQConnected sc iolog amqPost
|
|
||||||
-- logging-Framework erzeugen
|
|
||||||
loggingMW <- loggingMiddleware
|
|
||||||
-- server starten
|
|
||||||
if myserviceDebug
|
|
||||||
then runMyServiceMiddlewareServer config (cors (\_ -> Just (simpleCorsResourcePolicy {corsRequestHeaders = ["Content-Type"]})) . loggingMW . logStdout) server
|
|
||||||
else runMyServiceMiddlewareServer config (cors (\_ -> Just (simpleCorsResourcePolicy {corsRequestHeaders = ["Content-Type"]}))) server
|
|
||||||
|
|
||||||
|
|
||||||
-- Sollte bald in die Library hs-stomp ausgelagert werden
|
|
||||||
-- ist ein Beispiel für einen ActiveMQ-Dumper
|
|
||||||
keepActiveMQConnected :: ServerConfig -> ([LogItem] -> IO ()) -> TQueue BS.ByteString -> IO ()
|
|
||||||
keepActiveMQConnected sc@ServerConfig{..} printLog var = do
|
|
||||||
res <- handle (\(e :: SomeException) -> do
|
|
||||||
printLog . pure . Error $ "Exception in AMQ-Thread: "<>show e
|
|
||||||
return $ Right ()
|
|
||||||
) $ AMQ.try $ do -- catches all AMQ-Exception that we can handle. All others bubble up.
|
|
||||||
printLog . pure . Info $ "AMQ: connecting..."
|
|
||||||
withConnection myserviceActivemqHost myserviceActivemqPort [ OAuth myserviceActivemqUsername myserviceActivemqPassword
|
|
||||||
, OTmo (30*1000) {- 30 sec timeout -}
|
|
||||||
]
|
|
||||||
[] $ \c -> do
|
|
||||||
let oconv = return
|
|
||||||
printLog . pure . Info $ "AMQ: connected"
|
|
||||||
withWriter c "Chaos-Logger for Kibana" "chaos.logs" [] [] oconv $ \writer -> do
|
|
||||||
printLog . pure . Info $ "AMQ: queue created"
|
|
||||||
let postfun = writeQ writer (Type (Application "json") []) []
|
|
||||||
void $ race
|
|
||||||
(forever $ atomically (readTQueue var) >>= postfun)
|
|
||||||
(threadDelay (600*1000*1000)) -- wait 10 Minutes
|
|
||||||
-- close writer
|
|
||||||
-- close connection
|
|
||||||
-- get outside of all try/handle/...-constructions befor recursing.
|
|
||||||
case res of
|
|
||||||
Left ex -> do
|
|
||||||
printLog . pure . Error $ "AMQ: "<>show ex
|
|
||||||
keepActiveMQConnected sc printLog var
|
|
||||||
Right _ -> keepActiveMQConnected sc printLog var
|
|
||||||
|
|
||||||
|
|
||||||
-- Beispiel für eine Custom-Logging-Middleware.
|
|
||||||
-- Hier werden z.B. alle 4xx-Status-Codes inkl. Payload ins stdout-Log geschrieben.
|
|
||||||
-- Nützlich, wenn die Kollegen ihre Requests nicht ordentlich schreiben können und der Server das Format zurecht mit einem BadRequest ablehnt ;)
|
|
||||||
loggingMiddleware :: IO Middleware
|
|
||||||
loggingMiddleware = liftIO $ mkRequestLogger $ def { outputFormat = CustomOutputFormatWithDetails out }
|
|
||||||
where
|
|
||||||
out :: ZonedDate -> WAI.Request -> Status -> Maybe Integer -> NominalDiffTime -> [BS.ByteString] -> Builder -> LogStr
|
|
||||||
out _ r status _ _ payload _
|
|
||||||
| statusCode status < 300 = ""
|
|
||||||
| statusCode status > 399 && statusCode status < 500 = "Error code "<>toLogStr (statusCode status) <>" sent. Request-Payload was: "<> mconcat (toLogStr <$> payload) <> "\n"
|
|
||||||
| otherwise = toLogStr (show r) <> "\n"
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Weitere Instanzen und Definitionen, die der Generator (noch) nicht macht
|
|
||||||
|
|
||||||
In der `Myservice.Types` werden ein paar hilfreiche Typen und Typinstanzen definiert. Im Folgenden geht es dabei um Dinge für:
|
|
||||||
|
|
||||||
- Envy
|
|
||||||
- Laden von $ENV_VAR in Datentypen
|
|
||||||
- Definitionen für Default-Settings
|
|
||||||
- ServerConfig
|
|
||||||
- Definition der Server-Konfiguration & Benennung der Environment-Variablen
|
|
||||||
- ExtraTypes
|
|
||||||
- ggf. Paketweite extra-Typen, die der Generator nicht macht, weil sie nicht aus der API kommen (z.B. cache)
|
|
||||||
- Out/BSON-Instanzen
|
|
||||||
- Der API-Generator generiert nur wenige Instanzen automatisch (z.B. Aeson), daher werden hier die fehlenden definiert.
|
|
||||||
- BSON: Kommunakation mit MongoDB
|
|
||||||
- Out: pretty-printing im Log
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
|
||||||
{-# OPTIONS_GHC -Wno-name-shadowing #-}
|
|
||||||
{-# LANGUAGE DeriveAnyClass #-}
|
|
||||||
{-# LANGUAGE DeriveFunctor #-}
|
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE DerivingVia #-}
|
|
||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
|
||||||
{-# LANGUAGE GADTs #-}
|
|
||||||
{-# LANGUAGE LambdaCase #-}
|
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE RankNTypes #-}
|
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
|
||||||
module MyService.Types where
|
|
||||||
|
|
||||||
import Data.Aeson (FromJSON, ToJSON)
|
|
||||||
import Data.Text
|
|
||||||
import Data.Time.Clock
|
|
||||||
import GHC.Generics
|
|
||||||
import System.Envy
|
|
||||||
import Text.PrettyPrint (text)
|
|
||||||
import Text.PrettyPrint.GenericPretty
|
|
||||||
|
|
||||||
-- Out hat hierfür keine Instanzen, daher kurz eine einfach Definition.
|
|
||||||
instance Out Text where
|
|
||||||
doc = text . unpack
|
|
||||||
docPrec i a = text $ showsPrec i a ""
|
|
||||||
|
|
||||||
instance Out UTCTime where
|
|
||||||
doc = text . show
|
|
||||||
docPrec i a = text $ showsPrec i a ""
|
|
||||||
|
|
||||||
-- Der ServerConfig-Typ. Wird mit den defaults unten initialisiert, dann mit den Variablen aus der .env-Datei überschrieben und zum Schluss können Serveradmins diese via $MYSERVICE_FOO nochmal überschreiben.
|
|
||||||
data ServerConfig = ServerConfig
|
|
||||||
{ myserviceHost :: String -- ^ Environment: $MYSERVICE_HOST
|
|
||||||
, myservicePort :: Int -- ^ Environment: $MYSERVICE_PORT
|
|
||||||
, myserviceMaxTimeout :: Int -- ^ Environment: $MYSERVICE_MAX_TIMEOUT
|
|
||||||
, myserviceInternalProxyUrl :: String -- ^ Environment: $MYSERVICE_INTERNAL_PROXY_URL
|
|
||||||
, myserviceInternalProxyPort :: Int -- ^ Environment: $MYSERVICE_INTERNAL_PROXY_PORT
|
|
||||||
, myserviceExternalProxyUrl :: String -- ^ Environment: $MYSERVICE_EXTERNAL_PROXY_URL
|
|
||||||
, myserviceExternalProxyPort :: Int -- ^ Environment: $MYSERVICE_EXTERNAL_PROXY_PORT
|
|
||||||
, myserviceActivemqHost :: String -- ^ Environment: $MYSERVICE_ACTIVEMQ_HOST
|
|
||||||
, myserviceActivemqPort :: Int -- ^ Environment: $MYSERVICE_ACTIVEMQ_PORT
|
|
||||||
, myserviceActivemqUsername :: String -- ^ Environment: $MYSERVICE_ACTIVEMQ_USERNAME
|
|
||||||
, myserviceActivemqPassword :: String -- ^ Environment: $MYSERVICE_ACTIVEMQ_PASSWORD
|
|
||||||
, myserviceMongoUsername :: String -- ^ Environment: $MYSERVICE_MONGO_USERNAME
|
|
||||||
, myserviceMongoPassword :: String -- ^ Environment: $MYSERVICE_MONGO_PASSWORD
|
|
||||||
, myserviceDebug :: Bool -- ^ Environment: $MYSERVICE_DEBUG
|
|
||||||
} deriving (Show, Eq, Generic)
|
|
||||||
|
|
||||||
-- Default-Konfigurations-Instanz für diesen Service.
|
|
||||||
instance DefConfig ServerConfig where
|
|
||||||
defConfig = ServerConfig "0.0.0.0" 8080 20
|
|
||||||
""
|
|
||||||
""
|
|
||||||
""
|
|
||||||
0
|
|
||||||
""
|
|
||||||
0
|
|
||||||
""
|
|
||||||
0
|
|
||||||
""
|
|
||||||
""
|
|
||||||
""
|
|
||||||
""
|
|
||||||
False
|
|
||||||
|
|
||||||
-- Kann auch aus dem ENV gefüllt werden
|
|
||||||
instance FromEnv ServerConfig
|
|
||||||
-- Und hübsch ausgegeben werden.
|
|
||||||
instance Out ServerConfig
|
|
||||||
|
|
||||||
|
|
||||||
instance Out Response
|
|
||||||
instance FromBSON Repsonse -- FromBSON-Instanz geht immer davon aus, dass alle keys da sind (ggf. mit null bei Nothing).
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Was noch zu tun ist
|
|
||||||
|
|
||||||
Den Service implementieren. Einfach ein neues Modul aufmachen (z.B. `MyService.Handler` oder `MyService.DieserEndpunktbereich`/`MyService.JenerEndpunktbereich`) und dort die Funktion implementieren, die man in der `Main.hs` benutzt hat.
|
|
||||||
In dem Handler habt ihr dann keinen Stress mehr mit validierung, networking, logging, etc. pp. weil alles in der Main abgehandelt wurde und ihr nur noch den "Happy-Case" implementieren müsst.
|
|
||||||
Beispiel für unseren Handler oben:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
myApiEndpointV1Post :: MonadIO m => ServerConfig -> (ClientEnv,ClientEnv) -> TQueue BS.ByteString -> ([LogItem] -> IO ()) -> Request -> m Response
|
|
||||||
myApiEndpointV1Post sc calls amqPost log req = do
|
|
||||||
liftIO . log $ [Info $ "recieved "<>pretty req] -- input-logging
|
|
||||||
liftIO . atomically . writeTQueue . LBS.toStrict $ "{\"hey Kibana, i recieved:\"" <> A.encode (pretty req) <> "}" -- log in activeMQ/Kibana
|
|
||||||
|
|
||||||
|
|
||||||
--- .... gaaaanz viel komplizierter code um die Response zu erhalten ;)
|
|
||||||
let ret = Response 1337 Nothing -- dummy-response ;)
|
|
||||||
-- gegeben wir haben eine gültige mongodb-pipe;
|
|
||||||
-- mehr logik will ich in die Beispiele nicht packen.
|
|
||||||
-- Man kann die z.b. als weiteren Wert in einer TMVar (damit man sie ändern & updaten kann) an die Funktion übergeben.
|
|
||||||
liftIO . access pipe master "DatabaseName" $ do
|
|
||||||
ifM (auth (myServiceMongoUsername sc) (myServiceMongoPassword sc)) (return ()) (liftIO . printLog . pure . Error $ "MongoDB: Login failed.")
|
|
||||||
save "DatabaseCollection" ["_id" =: 1337, "entry" =: ret] -- selbe id wie oben ;)
|
|
||||||
return ret
|
|
||||||
```
|
|
||||||
|
|
||||||
Diese dummy-Antwort führt auf, wie gut man die ganzen Sachen mischen kann.
|
|
||||||
|
|
||||||
- Logging in die Dateien/stdout nach config
|
|
||||||
- Logging von Statistiker in Kibana
|
|
||||||
- Speichern der Antwort in der MongoDB
|
|
||||||
- Generieren einer Serverantwort und ausliefern dieser über die Schnittstelle
|
|
||||||
|
|
||||||
#### Tipps & Tricks
|
|
||||||
|
|
||||||
##### Dateien, die statisch ausgeliefert werden sollen
|
|
||||||
|
|
||||||
Hierzu erstellt man ein Verzeichnis `static/` (konvention; ist im generator so generiert, dass das ausgeliefert wird). Packt man hier z.b. eine `index.html` rein, erscheint die, wenn man den Service ansurft.
|
|
||||||
|
|
||||||
##### Wie bekomme ich diese fancy Preview hin?
|
|
||||||
|
|
||||||
Der Editor, der ganz am Anfang zum Einsatz gekommen ist, braucht nur die `api-doc.yml` um diese Ansicht zu erzeugen.
|
|
||||||
Daher empfielt sich hier ein angepasster Fork davon indem die Pfade in der index.html korrigiert sind. Am einfachsten (und von den meisten services so benutzt):
|
|
||||||
In meiner Implementation liegt dann nach dem starten auf http://localhost:PORT/ui/ und kann direkt dort getestet werden.
|
|
||||||
|
|
||||||
##### Wie sorge ich für bessere Warnungen, damit der Compiler meine Bugs fängt?
|
|
||||||
|
|
||||||
```bash
|
|
||||||
stack build --file-watch --ghc-options '-freverse-errors -W -Wall -Wcompat' --interleaved-output
|
|
||||||
```
|
|
||||||
|
|
||||||
Was tut das?
|
|
||||||
|
|
||||||
- `--file-watch`: automatisches (minimales) kompilieren bei dateiänderungen
|
|
||||||
- `--ghc-options`
|
|
||||||
- `-freverse-errors`: Fehlermeldungen in umgekehrter Reihenfolge (Erster Fehler ganz unten; wenig scrollen )
|
|
||||||
- `-W`: Warnungen an
|
|
||||||
- `-Wall`: Alle sinnvollen Warnungen an (im gegensatz zu `-Weverything`, was WIRKLICH alles ist )
|
|
||||||
- `-Wcompat`: Warnungen für Sachen, die in der nächsten Compilerversion kaputt brechen werden & vermieden werden sollten
|
|
||||||
- `--interleaved-output`: stack-log direkt ausgeben & nicht in dateien schreiben und die dann am ende zusammen cat\'en.
|
|
||||||
|
|
||||||
Um pro Datei Warnungen auszuschalten (z.B. weil man ganz sicher weiss, was man tut -.-): `{-# OPTIONS_GHC -Wno-whatsoever #-}` als Pragma in die Datei.
|
|
||||||
|
|
||||||
**Idealerweise sollte das Projekt keine Warnungen erzeugen.**
|
|
||||||
|
|
||||||
### Deployment
|
|
||||||
|
|
||||||
Als Beispiel sei hier ein einfaches Docker-Build mit Jenkins-CI gezeigt, weil ich das aus Gründen rumliegen hatte. Kann man analog in fast alle anderen CI übrsetzen.
|
|
||||||
|
|
||||||
#### Docker
|
|
||||||
|
|
||||||
Die angehängten Scripte gehen von einer Standard-Einrichtung aus (statische sachen in static, 2-3 händische Anpassungen auf das eigene Projekt nach auspacken). Nachher liegt dann auch unter static/version die gebaute Versionsnummer & kann abgerufen werden.
|
|
||||||
In der Dockerfile.release und der Jenkinsfile müssen noch anpassungen gemacht werden. Konkret:
|
|
||||||
|
|
||||||
- in der Dockerfile.release: alle `<<<HIER>>>`-Stellen sinnvoll befüllen
|
|
||||||
- in der Jenkinsfile die defs für "servicename" und "servicebinary" ausfüllen. Binary ist das, was bei stack exec aufgerufen wird; name ist der Image-Name für das docker-repository.
|
|
||||||
|
|
||||||
#### Jenkins
|
|
||||||
|
|
||||||
Änderungen die dann noch gemacht werden müssen:
|
|
||||||
- git-repository url anpassen
|
|
||||||
- Environment-Vars anpasses ($BRANCH = test & live haben keine zusatzdinger im docker-image-repository; ansonsten hat das image $BRANCH im namen)
|
|
||||||
|
|
||||||
Wenn das durchgebaut ist, liegt im test/live-repository ein docker-image namens `servicename:version`.
|
|
||||||
|
|
||||||
### OMG! Ich muss meine API ändern. Was mache ich nun?
|
|
||||||
|
|
||||||
1. api-doc.yml bearbeiten, wie gewünscht
|
|
||||||
2. mittels generator die Api & submodule neu generieren
|
|
||||||
3. ggf. custom Änderungen übernehmen (:Gitdiffsplit hilft)
|
|
||||||
4. Alle Compilerfehler + Warnungen in der eigentlichen Applikation fixen
|
|
||||||
5. If it comipilez, ship it! (Besser nicht ;) )
|
|
55
content/Health/Issues.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: Mental Health
|
||||||
|
---
|
||||||
|
|
||||||
|
In modern times many people struggle with mental health issues - and a am by no
|
||||||
|
means an exception. The main issue is, that most people just don't talk about it
|
||||||
|
and suffer alone, thinking they are alone, and everyone else is just doing fine
|
||||||
|
in this hellscape of a modern society. At least that is what you see on several
|
||||||
|
social media platforms like Instagram etc.
|
||||||
|
|
||||||
|
So even despite my exceptional `[citation needed]` successes that can be seen in
|
||||||
|
my [[Work|work]]# i always struggled with issues even to the point of total
|
||||||
|
breakdown. Of course i am also guilty of painting a rosy picture of me - just
|
||||||
|
look at [[Experience|a summary of my experiences]]# or the
|
||||||
|
awesome [[Extracurricular|things i did at university]]#. If you only read that it
|
||||||
|
is hard to believe that i basically had to delay my studies from 2007 to 2010
|
||||||
|
because i wasn't even really able to leave the house.
|
||||||
|
|
||||||
|
Only thanks to the not-that-awful system in Germany and massive financial help
|
||||||
|
from my parents i was even able to pursue this way.
|
||||||
|
|
||||||
|
## What are my issues?
|
||||||
|
|
||||||
|
Well.. it is not completely clear, what all my issues are. Many of those are
|
||||||
|
related, some are just the result of coping mechanisms of ignoring other issues.
|
||||||
|
|
||||||
|
Currently i am successfully diagnosed with
|
||||||
|
|
||||||
|
- social anxiety
|
||||||
|
- ADHD
|
||||||
|
- transgenderism
|
||||||
|
|
||||||
|
and suspected of
|
||||||
|
|
||||||
|
- autism (asperger)
|
||||||
|
|
||||||
|
All in all: when i feel well, am good rested and have nothing critical coming up
|
||||||
|
i am more of what i would call a "high functioning Autist, but not THAT far on
|
||||||
|
the spectrum". But it is funny that finding out who i really am i met more
|
||||||
|
people that i thought who basically had the same issue and a similar biography
|
||||||
|
then me. Some of them get the autism-diagnosis first, others the ADHD one -
|
||||||
|
since until some time ago those diagnosis were mutually exclusive.
|
||||||
|
|
||||||
|
That's also why many people like me are only really diagnosed as adults, because
|
||||||
|
autism hides many effects of ADHD and vice-versa - depending on which one is
|
||||||
|
currently dominating. It is basically 2 modes: do everything all at once and
|
||||||
|
start everything that grabs your attention - or do a deep dive into a single
|
||||||
|
thing. And the exact opposite: The ADHD part being bored by the autism-project,
|
||||||
|
the autism-part is completely overwhelmed by the ADHD chaos. Both then leading
|
||||||
|
to exhaustion, not being able to do anything .. and basically feeling guilty for
|
||||||
|
the things you did not manage to finish.
|
||||||
|
|
||||||
|
## The early days
|
||||||
|
|
||||||
|
[[TODO]]
|
9
content/Opinions/Editors.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Editors
|
||||||
|
|
||||||
|
Better said: "why neovim is currently my favorite editor" :wink:
|
||||||
|
|
||||||
|
## Current Config
|
||||||
|
|
||||||
|
You can find my current Config along with other things in my [gitea snippet-git](https://gitea.dresselhaus.cloud/Drezil/snippets).
|
||||||
|
|
||||||
|
[[TODO]]: write more awesomesauce :wink:
|
35
content/Opinions/Layout.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: Keyboard-Layouts
|
||||||
|
---
|
||||||
|
|
||||||
|
# My Keyboard-Layout
|
||||||
|
|
||||||
|
Since around 2006 i basically write only using the
|
||||||
|
[NEO2](https://neo-layout.org)-Layout. There are many advantages that are not obvious to
|
||||||
|
an onlooker right away.
|
||||||
|
|
||||||
|
Don't get me wrong. I still can type QWERTZ - just because you learn an
|
||||||
|
additional layout does not mean that you forget everything from before.
|
||||||
|
|
||||||
|
The secret sauce lies in the deeper layers. Especially layer 3 having all the
|
||||||
|
"hard to reach" things like brackets, braces, etc. right on the home row.
|
||||||
|
And the 4th layer is *magic* for text-navigation. Left hand has the full
|
||||||
|
navigation, right hand has the complete Numpad - even on laptop-keyboards that
|
||||||
|
are lacking those.
|
||||||
|
|
||||||
|
For me as a person having the usual German Keyboard with AltGr this just means:
|
||||||
|
|
||||||
|
- Putting the thumb down on AltGr - it is above there anyway.
|
||||||
|
- Use left hand as normal arrow-keys (that work EVERYWHERE because they are just
|
||||||
|
arrow keys)
|
||||||
|
- Also use Home/End/PgUp/PgDown/...
|
||||||
|
|
||||||
|
Before i always had to switch over or hope that a thing had support for
|
||||||
|
vi-style "hjkl".
|
||||||
|
|
||||||
|
Thats why i also prefer [[Opinions/Editors|Neovim]] as my primary editor - just not having to
|
||||||
|
touch your mouse at any time for anything is such a godsend :smile:
|
||||||
|
|
||||||
|
Best thing: If you don't want to switch, there is also a "Neo-QWERTZ"-variant ..
|
||||||
|
where you can just try the deeper layers while not leaving your QWERTZ-layout
|
||||||
|
behind. But i have just seen and never tried it. Your experience may be sub-par.
|
178
content/Stuff/Bielefeldverschwoerung.md
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# Die Bielefeld-Verschwörung
|
||||||
|
|
||||||
|
Kopie des vermutlichen Originals von (vermutlich) Achim Held aus 1994.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Warnung:** Diese Seite enthält Material, von dem SIE nicht wollen, dass es
|
||||||
|
bekannt wird. Speichern Sie diese Seite nicht auf Ihrer lokalen Platte ab, denn
|
||||||
|
sonst sind Sie auch dran, wenn SIE plötzlich bei Ihnen vor der Tür stehen; und
|
||||||
|
das passiert schneller als man denkt. Auch sollten Sie versuchen, alle Hinweise
|
||||||
|
darauf, dass Sie diese Seite jemals gelesen haben, zu vernichten. Tragen Sie
|
||||||
|
diese Seite auf keinen Fall in ihre Hotlist/Bookmarks/etc... ein!
|
||||||
|
|
||||||
|
Vielen Dank für die Beachtung aller Sicherheitsvorschriften.
|
||||||
|
|
||||||
|
## Die Geschichte der Entdeckung
|
||||||
|
|
||||||
|
Vor einigen Jahren fiel es einigen Unerschrockenen zum ersten Mal auf, dass in
|
||||||
|
den Medien immer wieder von einer Stadt namens 'Bielefeld' die Rede war, dass
|
||||||
|
aber niemand jemanden aus Bielefeld kannte, geschweige denn selbst schon einmal
|
||||||
|
dort war. Zuerst hielten sie dies für eine belanglose Sache, aber dann machte es
|
||||||
|
sie doch neugierig. Sie unterhielten sich mit anderen darüber, ohne zu ahnen,
|
||||||
|
dass dies bereits ein Fehler war: Aus heutiger Sicht steht fest, dass jemand
|
||||||
|
geplaudert haben muss, denn sofort darauf wurden SIE aktiv. Plötzlich tauchten
|
||||||
|
Leute auf, die vorgaben, schon einmal in Bielefeld gewesen zu sein; sogar
|
||||||
|
Personen, die vormals noch laut Zweifel geäußert hatten, berichteten jetzt
|
||||||
|
davon, sich mit eigenen Augen von der Existenz vergewissert zu haben - immer
|
||||||
|
hatten diese Personen bei ihren Berichten einen seltsam starren Blick. Doch da
|
||||||
|
war es schon zu spät - die Saat des Zweifels war gesät. Weitere Personen stießen
|
||||||
|
zu der Kerngruppe der Zweifler, immer noch nicht sicher, was oder wem man da auf
|
||||||
|
der Spur war.
|
||||||
|
|
||||||
|
Dann, im Oktober 1993, der Durchbruch: Auf der Fahrt von Essen nach Kiel auf der
|
||||||
|
A2 erhielten vier der hartnäckigsten Streiter für die Aufdeckung der
|
||||||
|
Verschwörung ein Zeichen: Jemand hatte auf allen Schildern den Namen 'Bielefeld'
|
||||||
|
mit orangem Klebeband durchgestrichen. Da wußte die Gruppe: Man ist nicht
|
||||||
|
alleine, es gibt noch andere, im Untergrund arbeitende Zweifler, womöglich über
|
||||||
|
ganz Deutschland verteilt, die auch vor spektakulären Aktionen nicht
|
||||||
|
zurückschrecken. Von da an war uns klar: Wir müssen diese Scharade aufdecken,
|
||||||
|
koste es, was es wolle!
|
||||||
|
Das Ausmaß der Verschwörung
|
||||||
|
|
||||||
|
Der Aufwand, mit dem die Täuschung der ganzen Welt betrieben wird, ist enorm.
|
||||||
|
Die Medien, von denen ja bekannt ist, dass sie unter IHRER Kontrolle stehen,
|
||||||
|
berichten tagaus, tagein von Bielefeld, als sei dies eine Stadt wie jede andere,
|
||||||
|
um der Bevölkerung das Gefühl zu geben, hier sei alles ganz normal. Aber auch
|
||||||
|
handfestere Beweise werden gefälscht: SIE kaufen hunderttausende von Autos,
|
||||||
|
versehen sie mit gefälschten 'BI-'Kennzeichen und lassen diese durch ganz
|
||||||
|
Deutschland fahren. SIE stellen, wie bereits oben geschildert, entlang der
|
||||||
|
Autobahnen große Schilder auf, auf denen Bielefeld erwähnt wird. SIE
|
||||||
|
veröffentlichen Zeitungen, die angeblich in Bielefeld gedruckt werden.
|
||||||
|
Anscheinend haben SIE auch die Deutsche Post AG in Ihrer Hand, denn auch im PLZB
|
||||||
|
findet man einen Eintrag für Bielefeld; und ebenso wird bei der Telekom ein
|
||||||
|
komplettes Ortsnetz für Bielefeld simuliert. Einige Leute behaupten sogar in
|
||||||
|
Bielefeld studiert zu haben und können auch gut gefälschte Diplome u.ä. der
|
||||||
|
angeblich existenten Uni Bielefeld vorweisen. Auch Bundeskanzler Gerhard
|
||||||
|
Schröder behauptet, 1965 das "Westfalen-Kolleg" in Bielefeld besucht zu haben,
|
||||||
|
wie seinem Lebenslauf unter dem Link Bildungsweg zu entnehmen ist.
|
||||||
|
|
||||||
|
Aber auch vor dem Internet machen SIE nicht halt. SIE vergeben Mail-Adressen für
|
||||||
|
die Domain uni-bielefeld.de, und SIE folgen auch den neuesten Trends: SIE bieten
|
||||||
|
im WWW eine ["Stadtinfo über Bielefeld"](https://bielefeld.de) an, sogar mit
|
||||||
|
Bildern; das Vorgarten-Foto, das dem Betrachter als "Botanischer Garten"
|
||||||
|
verkauft werden sollte, ist nach der Entlarvung auf dieser Seite jedoch
|
||||||
|
inzwischen wieder entfernt worden. Aber auch die noch vorhandenen Bilder sind
|
||||||
|
sogar für den Laien als Fotomontagen zu erkennen. Wir sind noch nicht dahinter
|
||||||
|
gekommen, wo der Rechner steht, auf dem die Domains .bielefeld.de und
|
||||||
|
uni-bielefeld.de gefälscht werden; wir arbeiten daran. Inzwischen wurde auch von
|
||||||
|
einem IHRER Agenten - der Täter ist uns bekannt - versucht, diese WWW-Seite zu
|
||||||
|
sabotieren, ich konnte den angerichteten Schaden jedoch zum Glück wieder
|
||||||
|
beheben.
|
||||||
|
|
||||||
|
Ein anonymer Informant, der ganz offensichtlich zu IHNEN zu gehören scheint oder
|
||||||
|
zumindest gute Kontakte zu IHNEN hat, hat mich kürzlich in einer Mail auf die
|
||||||
|
nächste Stufe IHRER Planung hingewiesen: "Ich schätze, spätestens in 10 Jahren
|
||||||
|
wird es heißen: Bielefeld muss Hauptstadt werden." Was das bedeutet, muss ja
|
||||||
|
wohl nicht extra betont werden.
|
||||||
|
|
||||||
|
Die schrecklichste Maßnahme, die SIE ergriffen haben, ist aber zweifelsohne
|
||||||
|
immer noch die Gehirnwäsche, der immer wieder harmlose Menschen unterzogen
|
||||||
|
werden, die dann anschließend auch die Existenz von Bielefeld propagieren. Immer
|
||||||
|
wieder verschwinden Menschen, gerade solche, die sich öffentlich zu ihren
|
||||||
|
Bielefeldzweifeln bekannt haben, nur um dann nach einiger Zeit wieder
|
||||||
|
aufzutauchen und zu behaupten, sie seien in Bielefeld gewesen. Womöglich wurden
|
||||||
|
einige Opfer sogar mit Telenosestrahlen behandelt. Diesen armen Menschen konnten
|
||||||
|
wir bisher nicht helfen. Wir haben allerdings inzwischen einen Verdacht, wo
|
||||||
|
diese Gehirnwäsche durchgeführt wird: Im sogenannten Bielefeld-Zentrum, wobei
|
||||||
|
SIE sogar die Kaltblütigkeit besitzen, den Weg zu diesem Ort des Schreckens von
|
||||||
|
der Autobahn aus mit großen Schildern auszuschildern. Wir sind sprachlos,
|
||||||
|
welchen Einfluß SIE haben.
|
||||||
|
|
||||||
|
Inzwischen sind - wohl auch durch mehrere Berichte in den wenigen nicht von
|
||||||
|
IHNEN kontrollierten Medien - mehr und mehr Leute wachsamer geworden und machen
|
||||||
|
uns auf weitere Aspekte der Verschwörung aufmerksam. So berichtet zum Beispiel
|
||||||
|
Holger Blaschka:
|
||||||
|
|
||||||
|
"Auch der DFB ist in diesen gewaltigen Skandal verwickelt, spielt in der ersten
|
||||||
|
Liga doch ein Verein, den SIE Arminia Bielefeld getauft haben, der innert 2
|
||||||
|
Jahren aus dem Nichts der Amateur-Regionen im bezahlten Fußball auftauchte und
|
||||||
|
jetzt im Begriff ist, sich zu IHRER besten Waffe gegen all die Zweifler zu
|
||||||
|
entwickeln. Den Gästefans wird vorgetäuscht mit ihren Bussen nach Bielefeld zu
|
||||||
|
kommen, wo sie von IHNEN abgefangen werden, um direkt ins Stadion geleitet zu
|
||||||
|
werden. Es besteht keine Chance sich die Stadt näher anzuschauen, und auch die
|
||||||
|
Illusion des Heimpublikums wird durch eine größere Menge an bezahlten Statisten
|
||||||
|
aufrechterhalten. Selbst ehemalige Top-Spieler, die Ihren Leistungszenit bei
|
||||||
|
weitem überschritten haben, werden zu diesem Zweck von IHNEN mißbraucht. Mit
|
||||||
|
genialen Manövern, u.a. vorgetäuschten Faustschlägen und Aufständen gegen das
|
||||||
|
Präsidium eines baldigen Drittligisten wurde von langer Hand die wohl
|
||||||
|
aufwendigste Täuschung aller Zeiten inszeniert. Es gibt noch mehr Beweise: Das
|
||||||
|
sich im Rohbau befindende Stadion, das gefälschte und verpanschte Bier und nicht
|
||||||
|
zuletzt die Tatsache, dass dieser Verein nur einen Sponsor hat. SIE, getarnt als
|
||||||
|
Modefirma Gerry Weber."
|
||||||
|
|
||||||
|
## Was steckt dahinter?
|
||||||
|
|
||||||
|
Dies ist die Frage, auf die wir auch nach jahrelangen Untersuchungen immer noch
|
||||||
|
keine befriedigende Antwort geben können. Allerdings gibt es einige Indizien,
|
||||||
|
die auf bestimmte Gruppierungen hinweisen:
|
||||||
|
|
||||||
|
- Es könnte eine Gruppe um den Sternenbruder und Weltenlehrer Ashtar Sheran
|
||||||
|
dahinterstecken, die an der Stelle, an der Bielefeld liegen soll, ihre Landung
|
||||||
|
vorbereiten, die - einschlägiger Fachliteratur zufolge - kurz bevorsteht. Zu
|
||||||
|
dieser Gruppe sollen auch Elvis und Kurt Cobain gehören, die beide - vom
|
||||||
|
schwedischen Geheimdienst gedeckt - noch am Leben sind.
|
||||||
|
- An der Stelle, an der Bielefeld liegen soll, hält die CIA John F. Kennedy seit
|
||||||
|
dem angeblichen Attentat versteckt, damit er nichts über die vorgetäuschte
|
||||||
|
Mondlandung der NASA erzählen kann. Inwieweit die Reichsflugscheibenmacht von
|
||||||
|
ihrer Mond- oder Marsbasis aus da mitspielt, können wir nicht sagen, da alle
|
||||||
|
Beweise beim Abschuß der schwer bewaffneten Marssonde Observer vernichtet
|
||||||
|
wurden. Informationen hierüber besitzt vielleicht der Vatikan, der seit den
|
||||||
|
50er Jahren regelmäßig mit tachyonenangetriebenen Schiffen zum Mars fliegt.
|
||||||
|
- Der MOSSAD in Zusammenarbeit mit dem OMEGA-Sektor planen an dieser Stelle die
|
||||||
|
Errichtung eines geheimen Forschungslabors, weil sich genau an diesem Ort zwei
|
||||||
|
noch nicht dokumentierte Ley-Linien kreuzen. Dort könnte auch der Jahrtausende
|
||||||
|
alte Tunnel nach Amerika und Australien (via Atlantis) seinen Eingang haben.
|
||||||
|
Wichtige Mitwisser, namentlich Uwe Barschel und Olof Palme, wurden von den mit
|
||||||
|
dem MOSSAD zusammenarbeitenden Geheimdiensten, darunter der Stasi und der
|
||||||
|
weniger bekannten 'Foundation', frühzeitig ausgeschaltet.
|
||||||
|
- An der Stelle liegt die Höhle eines der schlafenden Drachen aus dem Vierten
|
||||||
|
Zeitalter, die auf das Erwachen der Magie am 24. Dezember 2011 (siehe hierzu
|
||||||
|
den Maya-Kalender) warten. Beschützt wird diese Stelle von den Rittern des
|
||||||
|
Ordenskreuzes AAORRAC, die sich inzwischen mit der Herstellung von
|
||||||
|
programmiertem Wasser beschäftigen - nach einen Rezept, das sie unter brutaler
|
||||||
|
Folter von Ann Johnson bekommen haben. Diese hatte es bekanntlich von hohen
|
||||||
|
Lichtwesen aus dem All erhalten, um die Menschheit vor außerirdischen
|
||||||
|
Implantaten bis Stufe 3 zu schützen.
|
||||||
|
|
||||||
|
## Was können wir tun?
|
||||||
|
|
||||||
|
Zum einen können wir alle an den Bundestag, das Europaparlament und die UNO
|
||||||
|
schreiben, um endlich zu erreichen, dass SIE nicht mehr von den Politikern
|
||||||
|
gedeckt werden. Da aber zu befürchten ist, dass SIE die Politik - so wie auch
|
||||||
|
das organisierte Verbrechen und die großen Weltreligionen - unter Kontrolle
|
||||||
|
haben, sind die Erfolgschancen dieses Weges doch eher zweifelhaft.
|
||||||
|
|
||||||
|
Eine weitere Möglichkeit besteht darin, dass sich alle Bielefeldzweifler treffen
|
||||||
|
und gemeinsam durch transzendentale Meditation (TM) soviel positive Ausstrahlung
|
||||||
|
erzeugen, dass der Schwindel auffliegt. Eine ähnliche Vorgehensweise hat in
|
||||||
|
Washington, D.C. für eine Senkung der Verbrechensrate um über 20% gesorgt.
|
||||||
|
Besonders effektiv ist dies im Zusammenwirken mit Hopi-Kerzen im Ohr und
|
||||||
|
Yogischem Schweben.
|
||||||
|
|
||||||
|
Ab und zu nimmt in einer der eigentlich von IHNEN kontrollierten Zeitungen ein
|
||||||
|
Redakteur allen Mut zusammen und riskiert es, in einer der Ausgaben zumindest
|
||||||
|
andeutungsweise auf die Verschwörung hinzuweisen. So wurde in der FAZ Bielefeld
|
||||||
|
als "Die Mutter aller Un-Städte" bezeichnet, und die taz überschrieb einen
|
||||||
|
Artikel mit "Das Bermuda-Dreieck bei Bielefeld". Auf Nachfrage bekommt man dann
|
||||||
|
natürlich zu hören, das habe man alles ganz anders gemeint, bei der taz hieß es
|
||||||
|
sogar, es hätte in Wirklichkeit "Bitterfeld" heißen sollen, aber für einen
|
||||||
|
kurzen Moment wurden die Leser darauf aufmerksam gemacht, dass mit Bielefeld
|
||||||
|
etwas nicht stimmt. An dem Mut dieser Redakteure, über deren weiteres Schicksal
|
||||||
|
uns leider nichts bekannt ist, sollten wir uns alle ein Beispiel nehmen.
|
||||||
|
|
||||||
|
Das, was wir alle aber für uns im kleinen tun können, ist folgendes: Kümmert
|
||||||
|
euch um die bedauernswerten Opfer der Gehirnwäsche, umsorgt sie, macht ihnen
|
||||||
|
behutsam klar, dass sie einer Fehlinformation unterliegen. Und, bekennt euch
|
||||||
|
alle immer offen, damit SIE merken, dass wir uns nicht länger täuschen lassen:
|
||||||
|
**Bielefeld gibt es nicht!!!**
|
5
content/TODO.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: Todo
|
||||||
|
---
|
||||||
|
|
||||||
|
Todo List in backlinks below vvv
|
@ -20,7 +20,7 @@ geklatscht hat).
|
|||||||
## Etwas Lerntheorie
|
## Etwas Lerntheorie
|
||||||
|
|
||||||
Es gibt einen sehr schönen
|
Es gibt einen sehr schönen
|
||||||
[Talk](https://yow.eventer.com/yow-2014-1222/stop-treading-water-learning-to-learn-by-edward-kmett-1750)
|
[Talk](https://www.youtube.com/watch?v=Z8KcCU-p8QA)
|
||||||
von Edwand Kmett in dem er über seine Erfahrungen berichtet. Kurzum: Man lernt
|
von Edwand Kmett in dem er über seine Erfahrungen berichtet. Kurzum: Man lernt
|
||||||
durch stete Wiederholung. Und der beste Moment etwas zu wiederholen ist, kurz
|
durch stete Wiederholung. Und der beste Moment etwas zu wiederholen ist, kurz
|
||||||
bevor man es vergisst. Das stimmt ziemlich genau mit meiner Erfahrung überein.
|
bevor man es vergisst. Das stimmt ziemlich genau mit meiner Erfahrung überein.
|
||||||
@ -193,11 +193,6 @@ Es ist keine Schande so ein Problem zu haben und es gibt genug, die sich damit
|
|||||||
rumschlagen. Aber man ist hier an der Uni auch nicht alleine damit. Es gibt
|
rumschlagen. Aber man ist hier an der Uni auch nicht alleine damit. Es gibt
|
||||||
zahlreiche Hilfsangebote.
|
zahlreiche Hilfsangebote.
|
||||||
|
|
||||||
Ein kleiner Hinweis hier noch auf das
|
|
||||||
[Prüfungsangst-Stipendium](http://www.eurocentres.com/de/pr%C3%BCfungsangst-stipendium),
|
|
||||||
dass einem eine Belohnung gibt, wenn man sich seinen Ängsten stellt und sie
|
|
||||||
überwindet. :)
|
|
||||||
|
|
||||||
## Schlusswort
|
## Schlusswort
|
||||||
|
|
||||||
Viel Erfolg bei euren Prüfungen. Falls euch dieser Artikel geholfen hat oder ihr
|
Viel Erfolg bei euren Prüfungen. Falls euch dieser Artikel geholfen hat oder ihr
|
||||||
|
@ -4,3 +4,5 @@ Unsortierte Einsichten und Erfahrungen. Archiviert zum verlinken, späteren
|
|||||||
Überdenken oder Diskutieren.
|
Überdenken oder Diskutieren.
|
||||||
|
|
||||||
Keine Garantie auf Richtigkeit oder Trollfreiheit :grin:
|
Keine Garantie auf Richtigkeit oder Trollfreiheit :grin:
|
||||||
|
|
||||||
|
<a rel="me" href="https://toot.kif.rocks/@Drezil"></a>
|
||||||
|
@ -9,7 +9,7 @@ page:
|
|||||||
<snippet var="js.highlightjs" />
|
<snippet var="js.highlightjs" />
|
||||||
|
|
||||||
template:
|
template:
|
||||||
theme: red
|
theme: purple
|
||||||
|
|
||||||
# You can add your own variables here, like editBaseUrl.
|
# You can add your own variables here, like editBaseUrl.
|
||||||
# See after-note.tpl to see where editBaseUrl gets used.
|
# See after-note.tpl to see where editBaseUrl gets used.
|
||||||
|
14
content/templates/filters/embed-note.tpl
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<section title="Embedded note" class="p-4 mx-2 mb-2 bg-white border-2 rounded-lg shadow-inner">
|
||||||
|
<details>
|
||||||
|
<summary class="flex items-center justify-center text-2xl italic bg-${theme}-50 rounded py-1 px-2 mb-3" >
|
||||||
|
<header style="display:list-item">
|
||||||
|
<a href="${ema:note:url}">
|
||||||
|
<ema:note:title />
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
</summary>
|
||||||
|
<div>
|
||||||
|
<apply template="/templates/components/pandoc" />
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</section>
|
13
fixPermissions.bash
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
chgrp -R www-data static_gen/
|
||||||
|
|
||||||
|
fix_dirs()
|
||||||
|
{
|
||||||
|
local IFS=$'\n'
|
||||||
|
for line in `find static_gen/ -type d`; do
|
||||||
|
chmod g+s ${line}
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_dirs
|
416
flake.lock
generated
@ -1,57 +1,134 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"ema": {
|
"check-flake": {
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1660941244,
|
"lastModified": 1662502605,
|
||||||
"narHash": "sha256-gGhvmSjjr07u8uWaBIv6F5WE+VCGngSVXN9mhb4BmQo=",
|
"narHash": "sha256-jAT55UhabAxLAVGanxjnNdzH2/oX2ZjLsL4i2jPIP+g=",
|
||||||
"owner": "srid",
|
"owner": "srid",
|
||||||
"repo": "ema",
|
"repo": "check-flake",
|
||||||
"rev": "d74daa4d0d1f7a14cebd415787166fe7909fc33b",
|
"rev": "48a17393ed4fcd523399d6602c283775b5127295",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "srid",
|
||||||
|
"repo": "check-flake",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devenv": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"nix": "nix",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"pre-commit-hooks": "pre-commit-hooks"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1688728027,
|
||||||
|
"narHash": "sha256-qyMvHtzm7BRtwA5TBE+syQbIDNyEpfqmsHwEV+PBe+o=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"rev": "9ba9e3b908a12ddc6c43f88c52f2bf3c1d1e82c1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"rev": "9ba9e3b908a12ddc6c43f88c52f2bf3c1d1e82c1",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ema": {
|
||||||
|
"inputs": {
|
||||||
|
"check-flake": [
|
||||||
|
"emanote",
|
||||||
|
"check-flake"
|
||||||
|
],
|
||||||
|
"flake-parts": [
|
||||||
|
"emanote",
|
||||||
|
"flake-parts"
|
||||||
|
],
|
||||||
|
"flake-root": [
|
||||||
|
"emanote",
|
||||||
|
"flake-root"
|
||||||
|
],
|
||||||
|
"haskell-flake": [
|
||||||
|
"emanote",
|
||||||
|
"haskell-flake"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"emanote",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"treefmt-nix": [
|
||||||
|
"emanote",
|
||||||
|
"treefmt-nix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1678746319,
|
||||||
|
"narHash": "sha256-vOh7o0AK2PohGa1LPTAGIPmua1beR67XowEcaI8UPhg=",
|
||||||
|
"owner": "srid",
|
||||||
|
"repo": "ema",
|
||||||
|
"rev": "b46a08d7a26491b9801642ab9c13e94f929c6a90",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "srid",
|
"owner": "srid",
|
||||||
"ref": "master",
|
|
||||||
"repo": "ema",
|
"repo": "ema",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"emanote": {
|
"emanote": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"check-flake": "check-flake",
|
||||||
"ema": "ema",
|
"ema": "ema",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
|
"flake-root": "flake-root",
|
||||||
"haskell-flake": "haskell-flake",
|
"haskell-flake": "haskell-flake",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"tailwind-haskell": "tailwind-haskell"
|
"systems": "systems_2",
|
||||||
|
"treefmt-nix": "treefmt-nix"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1661096607,
|
"lastModified": 1688491882,
|
||||||
"narHash": "sha256-DtSvkXRTsILCcWGcmP50fLupGroAP7EJVk3da+5yLvU=",
|
"narHash": "sha256-b3hIpvCGTIWIOvkFJbkrBmlifF8JGVlOOJElE2NKKe0=",
|
||||||
"owner": "EmaApps",
|
"owner": "srid",
|
||||||
"repo": "emanote",
|
"repo": "emanote",
|
||||||
"rev": "aad6e789829ce35b042ea6fcc728ae0b76086dd8",
|
"rev": "d1dd7ea4ad89dc74f8d4b90650e07e788b489bb6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "EmaApps",
|
"owner": "srid",
|
||||||
"repo": "emanote",
|
"repo": "emanote",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1673956053,
|
||||||
|
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-parts": {
|
"flake-parts": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
"emanote",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1657102481,
|
"lastModified": 1671710971,
|
||||||
"narHash": "sha256-62Fuw8JgPub38OdgNefkIKOodM9nC3M0AG6lS+7smf4=",
|
"narHash": "sha256-YZdt5IJrfsdUTtVB94EMsBvaJbK9ve6QaZyzRuup+sY=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "608ed3502263d6f4f886d75c48fc2b444a4ab8d8",
|
"rev": "98bec08c58a9547d705f2f5e300ac8eef6665e52",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -60,49 +137,68 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-parts_2": {
|
"flake-root": {
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1661009076,
|
"lastModified": 1671378805,
|
||||||
"narHash": "sha256-phAE40gctVygRq3G3B6LhvD7u2qdQT21xsz8DdRDYFo=",
|
"narHash": "sha256-yqGxyzMN2GuppwG3dTWD1oiKxi+jGYP7D1qUSc5vKhI=",
|
||||||
"owner": "hercules-ci",
|
"owner": "srid",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-root",
|
||||||
"rev": "850d8a76026127ef02f040fb0dcfdb8b749dd9d9",
|
"rev": "dc7ba6166e478804a9da6881aa48c45d300075cf",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "hercules-ci",
|
"owner": "srid",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-root",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1652776076,
|
"lastModified": 1685518550,
|
||||||
"narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=",
|
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8",
|
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"ref": "v1.0.0",
|
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gitignore": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"devenv",
|
||||||
|
"pre-commit-hooks",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1660459072,
|
||||||
|
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"haskell-flake": {
|
"haskell-flake": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1660319056,
|
"lastModified": 1685469326,
|
||||||
"narHash": "sha256-MX6PLEtXVyXXUEk3t1e0c20XRL4m4u9TFET2X0TpTdE=",
|
"narHash": "sha256-esxJLsGexI/J6Fc32tJd2p3K5IOBZSCHgFvegjIpc+0=",
|
||||||
"owner": "srid",
|
"owner": "srid",
|
||||||
"repo": "haskell-flake",
|
"repo": "haskell-flake",
|
||||||
"rev": "1ca2be3c354ef2a3296cac7e54ae21e1d6ead6d7",
|
"rev": "996f5c2cdc67285c4990df378976f9dbf26f8401",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -111,49 +207,243 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lowdown-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1633514407,
|
||||||
|
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
|
||||||
|
"owner": "kristapsdz",
|
||||||
|
"repo": "lowdown",
|
||||||
|
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "kristapsdz",
|
||||||
|
"repo": "lowdown",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix": {
|
||||||
|
"inputs": {
|
||||||
|
"lowdown-src": "lowdown-src",
|
||||||
|
"nixpkgs": [
|
||||||
|
"devenv",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1676545802,
|
||||||
|
"narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=",
|
||||||
|
"owner": "domenkozar",
|
||||||
|
"repo": "nix",
|
||||||
|
"rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "domenkozar",
|
||||||
|
"ref": "relaxed-flakes",
|
||||||
|
"repo": "nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1659077768,
|
"lastModified": 1678875422,
|
||||||
"narHash": "sha256-P0XIHBVty6WIuIrk2DZNvLcYev9956y1prT4zL212H8=",
|
"narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=",
|
||||||
"path": "/nix/store/1ha33ma070pyxw5kkcx61qi7ypzzxzah-source",
|
"owner": "NixOS",
|
||||||
"rev": "2a93ea177c3d7700b934bf95adfe00c435f696b8",
|
"repo": "nixpkgs",
|
||||||
"type": "path"
|
"rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459",
|
||||||
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"id": "nixpkgs",
|
"owner": "NixOS",
|
||||||
"type": "indirect"
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"dir": "lib",
|
||||||
|
"lastModified": 1671359686,
|
||||||
|
"narHash": "sha256-3MpC6yZo+Xn9cPordGz2/ii6IJpP2n8LE8e/ebUXLrs=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "04f574a1c0fde90b51bf68198e2297ca4e7cccf4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"dir": "lib",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-regression": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1643052045,
|
||||||
|
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-stable": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1685801374,
|
||||||
|
"narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "c37ca420157f4abc31e26f436c1145f8951ff373",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-23.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1687245362,
|
||||||
|
"narHash": "sha256-+f9tH+k3u9lSS136M2LCsl5NJTNPvhmHEiVOcypiu1E=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "205ee073b053fc4d87d5adf2ebd44ebbef7bca4d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696039360,
|
||||||
|
"narHash": "sha256-g7nIUV4uq1TOVeVIDEZLb005suTWCUjSY0zYOlSBsyE=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "32dcb45f66c0487e92db8303a798ebc548cadedc",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-23.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pre-commit-hooks": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": [
|
||||||
|
"devenv",
|
||||||
|
"flake-compat"
|
||||||
|
],
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"nixpkgs": [
|
||||||
|
"devenv",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs-stable": "nixpkgs-stable"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1688056373,
|
||||||
|
"narHash": "sha256-2+SDlNRTKsgo3LBRiMUcoEUb6sDViRNQhzJquZ4koOI=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "pre-commit-hooks.nix",
|
||||||
|
"rev": "5843cf069272d92b60c3ed9e55b7a8989c01d4c7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "pre-commit-hooks.nix",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"devenv": "devenv",
|
||||||
"emanote": "emanote",
|
"emanote": "emanote",
|
||||||
"flake-parts": "flake-parts_2",
|
"nixpkgs": "nixpkgs_3",
|
||||||
"nixpkgs": [
|
"systems": "systems_3"
|
||||||
"emanote",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tailwind-haskell": {
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"treefmt-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"emanote",
|
"emanote",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1658431129,
|
"lastModified": 1675588998,
|
||||||
"narHash": "sha256-AtTxP0AMXdeU0ZTJP5R1lCtx5az3ejrmGP1klEdu1qU=",
|
"narHash": "sha256-CLeFLmah0mxNp/EIW0PMG3YutKxVIIs4B0f5oJhwe8E=",
|
||||||
"owner": "srid",
|
"owner": "numtide",
|
||||||
"repo": "tailwind-haskell",
|
"repo": "treefmt-nix",
|
||||||
"rev": "09a102164b1a4559892277ff38efdc9b949c5433",
|
"rev": "70e03145e26c2f3199f4320ecd9fd343f1129c60",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "srid",
|
"owner": "numtide",
|
||||||
"ref": "master",
|
"repo": "treefmt-nix",
|
||||||
"repo": "tailwind-haskell",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
96
flake.nix
@ -1,38 +1,78 @@
|
|||||||
{
|
{
|
||||||
nixConfig.extra-substituters = "https://cache.garnix.io";
|
|
||||||
nixConfig.extra-trusted-public-keys = "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=";
|
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
emanote.url = "github:EmaApps/emanote";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
|
||||||
nixpkgs.follows = "emanote/nixpkgs";
|
systems.url = "github:nix-systems/default";
|
||||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
devenv.url = "github:cachix/devenv/9ba9e3b908a12ddc6c43f88c52f2bf3c1d1e82c1"; # until https://github.com/cachix/devenv/issues/756 is fixed
|
||||||
flake-parts.inputs.nixpkgs.follows = "nixpkgs";
|
emanote.url = "github:srid/emanote";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs@{ self, flake-parts, nixpkgs, ... }:
|
nixConfig = {
|
||||||
flake-parts.lib.mkFlake { inherit self; } {
|
extra-trusted-public-keys = ["devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
|
||||||
systems = nixpkgs.lib.systems.flakeExposed;
|
"srid.cachix.org-1:3clnql5gjbJNEvhA/WQp7nrZlBptwpXnUk6JAv8aB2M="
|
||||||
imports = [
|
|
||||||
inputs.emanote.flakeModule
|
|
||||||
];
|
];
|
||||||
perSystem = { self', pkgs, system, ... }: {
|
extra-substituters = [
|
||||||
emanote = {
|
"https://devenv.cachix.org"
|
||||||
# By default, the 'emanote' flake input is used.
|
"https://srid.cachix.org"
|
||||||
# package = inputs.emanote.packages.${system}.default;
|
|
||||||
sites."default" = {
|
|
||||||
path = ./content;
|
|
||||||
pathString = "./content";
|
|
||||||
port = 8080;
|
|
||||||
baseUrl = "/";
|
|
||||||
prettyUrls = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
buildInputs = [
|
|
||||||
pkgs.nixpkgs-fmt
|
|
||||||
pkgs.zk
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, devenv, systems, emanote, ... } @ inputs:
|
||||||
|
let
|
||||||
|
forEachSystem = nixpkgs.lib.genAttrs (import systems);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells = forEachSystem
|
||||||
|
(system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = devenv.lib.mkShell {
|
||||||
|
inherit inputs pkgs;
|
||||||
|
modules = [
|
||||||
|
({ pkgs, emanote, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# https://devenv.sh/basics/
|
||||||
|
|
||||||
|
# https://devenv.sh/packages/
|
||||||
|
packages = [
|
||||||
|
pkgs.git
|
||||||
|
pkgs.docker
|
||||||
|
emanote.packages.${system}.emanote
|
||||||
|
];
|
||||||
|
|
||||||
|
enterShell = ''
|
||||||
|
echo 'Hello from "Emanote" environment.'
|
||||||
|
git --version
|
||||||
|
echo -n "Emanote version " && emanote --version
|
||||||
|
'';
|
||||||
|
|
||||||
|
# https://devenv.sh/languages/
|
||||||
|
# languages.nix.enable = true;
|
||||||
|
|
||||||
|
# https://devenv.sh/pre-commit-hooks/
|
||||||
|
pre-commit.hooks.shellcheck.enable = true;
|
||||||
|
pre-commit.hooks.markdownlint.enable = true;
|
||||||
|
pre-commit.settings.markdownlint.config = {
|
||||||
|
"MD041"= false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# https://devenv.sh/processes/
|
||||||
|
# processes.ping.exec = "ping example.com";
|
||||||
|
|
||||||
|
processes = {
|
||||||
|
emanote.exec = "cd $DEVENV_ROOT/content && emanote run --port=8081";
|
||||||
|
};
|
||||||
|
|
||||||
|
scripts.showDoc.exec = "sensible-browser http://127.0.0.1:8081/";
|
||||||
|
scripts.generate.exec = "cd $DEVENV_ROOT/content && emanote gen $DEVENV_ROOT/static_gen";
|
||||||
|
scripts.nix-cleanup.exec = "nix-env --delete-generations old && nix-store --gc && nix-collect-garbage -d";
|
||||||
|
|
||||||
|
# See full reference at https://devenv.sh/reference/options/
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -46,33 +46,73 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<link href='tailwind.css?instanceId=faa07eb7-0f7a-4cb2-8347-d9aa01265a0e' rel='stylesheet' type='text/css' />
|
<link href='tailwind.css?instanceId=e98d22b9-5039-4bd9-8072-cac8aca20ee6' rel='stylesheet' type='text/css' />
|
||||||
|
|
||||||
<!-- Heist error element -->
|
|
||||||
<style>
|
<style>
|
||||||
|
/* Heist error element */
|
||||||
strong.error {
|
strong.error {
|
||||||
color: lightcoral;
|
color: lightcoral;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* External link icon */
|
||||||
|
a[data-linkicon=""]::after {
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
a[data-linkicon=none]::after {
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
a[data-linkicon="external"]::after {
|
||||||
|
content: url('data:image/svg+xml,\
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="0.7em" viewBox="0 0 20 20"> \
|
||||||
|
<g style="stroke:gray;stroke-width:1"> \
|
||||||
|
<line x1="5" y1="5" x2="5" y2="14" /> \
|
||||||
|
<line x1="14" y1="9" x2="14" y2="14" /> \
|
||||||
|
<line x1="5" y1="14" x2="14" y2="14" /> \
|
||||||
|
<line x1="5" y1="5" x2="9" y2="5" /> \
|
||||||
|
<line x1="10" y1="2" x2="17" y2="2" /> \
|
||||||
|
<line x1="17" y1="2" x2="17" y2="9" /> \
|
||||||
|
<line x1="10" y1="9" x2="17" y2="2" style="stroke-width:1.0" /> \
|
||||||
|
</g> \
|
||||||
|
</svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
a[data-linkicon="external"][href^="mailto:"]::after {
|
||||||
|
content: url('data:image/svg+xml,\
|
||||||
|
<svg \
|
||||||
|
xmlns="http://www.w3.org/2000/svg" \
|
||||||
|
height="0.7em" \
|
||||||
|
fill="none" \
|
||||||
|
viewBox="0 0 24 24" \
|
||||||
|
stroke="gray" \
|
||||||
|
stroke-width="2"> \
|
||||||
|
<path \
|
||||||
|
stroke-linecap="round" \
|
||||||
|
stroke-linejoin="round" \
|
||||||
|
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /> \
|
||||||
|
</svg>');
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<!-- What goes in this file will appear on near the end of <head>--><link rel='preload' href='_emanote-static/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf' as='font' type='font/ttf' crossorigin />
|
<!-- What goes in this file will appear on near the end of <head>--><link rel='preload' href='_emanote-static/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf' as='font' type='font/ttf' crossorigin />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'MavenPro';
|
font-family: 'WorkSans';
|
||||||
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf */
|
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf */
|
||||||
src: url(_emanote-static/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf) format("truetype");
|
src: url(_emanote-static/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf) format("truetype");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'MavenPro', sans-serif;
|
font-family: 'WorkSans', sans-serif;
|
||||||
/* font-variation-settings: 'wght'300; */
|
font-variation-settings: 'wght' 350;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.mavenLinkBold {
|
a.mavenLinkBold {
|
||||||
font-variation-settings: 'wght'500;
|
font-variation-settings: 'wght' 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
@ -87,12 +127,25 @@
|
|||||||
h6,
|
h6,
|
||||||
header,
|
header,
|
||||||
.header-font {
|
.header-font {
|
||||||
font-family: 'MavenPro', sans-serif;
|
font-family: 'WorkSans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-variation-settings: 'wght' 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-variation-settings: 'wght' 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-variation-settings: 'wght' 300;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<head-main></head-main>
|
<head-main></head-main>
|
||||||
<link rel='stylesheet' href='https://files.stork-search.net/releases/v1.5.0/flat.css' />
|
<link rel='stylesheet' href='_emanote-static/stork/flat.css' />
|
||||||
<!-- Custom Stork-search styling for Emanote -->
|
<!-- Custom Stork-search styling for Emanote -->
|
||||||
<style>
|
<style>
|
||||||
#stork-search-container {
|
#stork-search-container {
|
||||||
@ -106,14 +159,16 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<script src='https://files.stork-search.net/releases/v1.5.0/stork.js'></script>
|
<script src='_emanote-static/stork/stork.js'></script>
|
||||||
|
|
||||||
|
|
||||||
<script data-emanote-base-url='/'>
|
<script id='emanote-stork' data-emanote-base-url='/'>
|
||||||
window.emanote = {};
|
window.emanote = {};
|
||||||
window.emanote.stork = {
|
window.emanote.stork = {
|
||||||
searchShown: false,
|
searchShown: false,
|
||||||
|
indexIsStale: false,
|
||||||
toggleSearch: function () {
|
toggleSearch: function () {
|
||||||
|
window.emanote.stork.refreshIndex();
|
||||||
document.getElementById('stork-search-container').classList.toggle('hidden');
|
document.getElementById('stork-search-container').classList.toggle('hidden');
|
||||||
window.emanote.stork.searchShown = document.body.classList.toggle('stork-overflow-hidden-important');
|
window.emanote.stork.searchShown = document.body.classList.toggle('stork-overflow-hidden-important');
|
||||||
if (window.emanote.stork.searchShown) {
|
if (window.emanote.stork.searchShown) {
|
||||||
@ -126,13 +181,25 @@
|
|||||||
window.emanote.stork.searchShown = false;
|
window.emanote.stork.searchShown = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function () {
|
getBaseUrl: function () {
|
||||||
|
const baseUrl = document.getElementById("emanote-stork").getAttribute('data-emanote-base-url') || '/';
|
||||||
|
return baseUrl;
|
||||||
|
},
|
||||||
|
|
||||||
|
registerIndex: function (options) {
|
||||||
const indexName = 'emanote-search'; // used to match input[data-stork] attribute value
|
const indexName = 'emanote-search'; // used to match input[data-stork] attribute value
|
||||||
const baseUrl = document.currentScript.getAttribute('data-emanote-base-url') || '/';
|
const indexUrl = window.emanote.stork.getBaseUrl() + '-/stork.st';
|
||||||
const indexUrl = baseUrl + '-/stork.st';
|
stork.register(
|
||||||
|
indexName,
|
||||||
|
indexUrl,
|
||||||
|
options);
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
if (document.readyState !== 'complete') {
|
if (document.readyState !== 'complete') {
|
||||||
window.addEventListener('load', function () {
|
window.addEventListener('load', function () {
|
||||||
stork.register(indexName, indexUrl);
|
stork.initialize(window.emanote.stork.getBaseUrl() + '_emanote-static/stork/stork.wasm');
|
||||||
|
window.emanote.stork.registerIndex();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
@ -145,10 +212,32 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Override existing on Ema's hot-reload
|
// This section is called during Ema's hot reload.
|
||||||
stork.register(indexName, indexUrl, { forceOverwrite: true });
|
//
|
||||||
|
// Mark the current index as stale, and refresh it *only when* the
|
||||||
|
// user actually invokes search.
|
||||||
|
//
|
||||||
|
// We do not refresh the index *right away*, as that will cause
|
||||||
|
// memory leaks in the browser. See
|
||||||
|
// https://github.com/srid/emanote/issues/411#issuecomment-1402056235
|
||||||
|
console.log("stork: Marking index as stale");
|
||||||
|
window.emanote.stork.markIndexAsStale();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
markIndexAsStale: function () {
|
||||||
|
window.emanote.stork.indexIsStale = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshIndex: function () {
|
||||||
|
if (window.emanote.stork.indexIsStale) {
|
||||||
|
console.log("stork: Reloading index");
|
||||||
|
window.emanote.stork.indexIsStale = false;
|
||||||
|
// NOTE: This will leak memory. See the comment above.
|
||||||
|
window.emanote.stork.registerIndex({ forceOverwrite: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.emanote.stork.init();
|
window.emanote.stork.init();
|
||||||
@ -170,10 +259,6 @@
|
|||||||
<h1 class='pb-2 mt-2 mb-2 text-6xl text-center'>
|
<h1 class='pb-2 mt-2 mb-2 text-6xl text-center'>
|
||||||
Tag Index
|
Tag Index
|
||||||
</h1>
|
</h1>
|
||||||
<div class='flex justify-center items-center mb-2'>
|
|
||||||
<a target='_blank' class='italic underline' href='https://github.com/srid/emanote/discussions/50'>Experimental
|
|
||||||
feature</a> !
|
|
||||||
</div>
|
|
||||||
<div class='pb-2 mx-auto my-4 lg:max-w-screen-md '>
|
<div class='pb-2 mx-auto my-4 lg:max-w-screen-md '>
|
||||||
|
|
||||||
<div class='bg-gray-200 pb-2'>
|
<div class='bg-gray-200 pb-2'>
|
||||||
@ -203,27 +288,27 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a href='' title='Go to Home page'>
|
<a href='' title='Go to Home page'>
|
||||||
<svg xmlns='http://www.w3.org/2000/svg' class='w-6 h-6 hover:text-red-700' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
<svg xmlns='http://www.w3.org/2000/svg' class='w-6 h-6 hover:text-purple-700' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
||||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6'></path>
|
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6'></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href='-/all' title='View Index'>
|
<a href='-/all' title='View Index'>
|
||||||
<svg class='w-6 h-6 hover:text-red-700' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
|
<svg class='w-6 h-6 hover:text-purple-700' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
|
||||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4'>
|
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4'>
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href='https://emanote.srid.ca' target='_blank' title='Generated by Emanote 0.7.3.0'>
|
<a href='https://emanote.srid.ca' target='_blank' title='Generated by Emanote 1.0.3.11'>
|
||||||
<img class='w-6 h-6 hover:text-red-700' src='_emanote-static/emanote-logo.svg' />
|
<img class='w-6 h-6 hover:text-purple-700' src='_emanote-static/emanote-logo.svg' />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href='-/tags' title='View tags'>
|
<a href='-/tags' title='View tags'>
|
||||||
<svg class='w-6 h-6 hover:text-red-700' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
|
<svg class='w-6 h-6 hover:text-purple-700' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
|
||||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z'>
|
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z'>
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
@ -231,7 +316,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href='-/tasks' title='View tasks'>
|
<a href='-/tasks' title='View tasks'>
|
||||||
<svg xmlns='http://www.w3.org/2000/svg' class='w-6 h-6 hover:text-red-700' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
<svg xmlns='http://www.w3.org/2000/svg' class='w-6 h-6 hover:text-purple-700' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
||||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'></path>
|
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
@ -249,6 +334,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -46,33 +46,73 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<link href='tailwind.css?instanceId=faa07eb7-0f7a-4cb2-8347-d9aa01265a0e' rel='stylesheet' type='text/css' />
|
<link href='tailwind.css?instanceId=e98d22b9-5039-4bd9-8072-cac8aca20ee6' rel='stylesheet' type='text/css' />
|
||||||
|
|
||||||
<!-- Heist error element -->
|
|
||||||
<style>
|
<style>
|
||||||
|
/* Heist error element */
|
||||||
strong.error {
|
strong.error {
|
||||||
color: lightcoral;
|
color: lightcoral;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* External link icon */
|
||||||
|
a[data-linkicon=""]::after {
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
a[data-linkicon=none]::after {
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
a[data-linkicon="external"]::after {
|
||||||
|
content: url('data:image/svg+xml,\
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="0.7em" viewBox="0 0 20 20"> \
|
||||||
|
<g style="stroke:gray;stroke-width:1"> \
|
||||||
|
<line x1="5" y1="5" x2="5" y2="14" /> \
|
||||||
|
<line x1="14" y1="9" x2="14" y2="14" /> \
|
||||||
|
<line x1="5" y1="14" x2="14" y2="14" /> \
|
||||||
|
<line x1="5" y1="5" x2="9" y2="5" /> \
|
||||||
|
<line x1="10" y1="2" x2="17" y2="2" /> \
|
||||||
|
<line x1="17" y1="2" x2="17" y2="9" /> \
|
||||||
|
<line x1="10" y1="9" x2="17" y2="2" style="stroke-width:1.0" /> \
|
||||||
|
</g> \
|
||||||
|
</svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
a[data-linkicon="external"][href^="mailto:"]::after {
|
||||||
|
content: url('data:image/svg+xml,\
|
||||||
|
<svg \
|
||||||
|
xmlns="http://www.w3.org/2000/svg" \
|
||||||
|
height="0.7em" \
|
||||||
|
fill="none" \
|
||||||
|
viewBox="0 0 24 24" \
|
||||||
|
stroke="gray" \
|
||||||
|
stroke-width="2"> \
|
||||||
|
<path \
|
||||||
|
stroke-linecap="round" \
|
||||||
|
stroke-linejoin="round" \
|
||||||
|
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /> \
|
||||||
|
</svg>');
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<!-- What goes in this file will appear on near the end of <head>--><link rel='preload' href='_emanote-static/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf' as='font' type='font/ttf' crossorigin />
|
<!-- What goes in this file will appear on near the end of <head>--><link rel='preload' href='_emanote-static/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf' as='font' type='font/ttf' crossorigin />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'MavenPro';
|
font-family: 'WorkSans';
|
||||||
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf */
|
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf */
|
||||||
src: url(_emanote-static/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf) format("truetype");
|
src: url(_emanote-static/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf) format("truetype");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'MavenPro', sans-serif;
|
font-family: 'WorkSans', sans-serif;
|
||||||
/* font-variation-settings: 'wght'300; */
|
font-variation-settings: 'wght' 350;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.mavenLinkBold {
|
a.mavenLinkBold {
|
||||||
font-variation-settings: 'wght'500;
|
font-variation-settings: 'wght' 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
@ -87,12 +127,25 @@
|
|||||||
h6,
|
h6,
|
||||||
header,
|
header,
|
||||||
.header-font {
|
.header-font {
|
||||||
font-family: 'MavenPro', sans-serif;
|
font-family: 'WorkSans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-variation-settings: 'wght' 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-variation-settings: 'wght' 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-variation-settings: 'wght' 300;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<head-main></head-main>
|
<head-main></head-main>
|
||||||
<link rel='stylesheet' href='https://files.stork-search.net/releases/v1.5.0/flat.css' />
|
<link rel='stylesheet' href='_emanote-static/stork/flat.css' />
|
||||||
<!-- Custom Stork-search styling for Emanote -->
|
<!-- Custom Stork-search styling for Emanote -->
|
||||||
<style>
|
<style>
|
||||||
#stork-search-container {
|
#stork-search-container {
|
||||||
@ -106,14 +159,16 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<script src='https://files.stork-search.net/releases/v1.5.0/stork.js'></script>
|
<script src='_emanote-static/stork/stork.js'></script>
|
||||||
|
|
||||||
|
|
||||||
<script data-emanote-base-url='/'>
|
<script id='emanote-stork' data-emanote-base-url='/'>
|
||||||
window.emanote = {};
|
window.emanote = {};
|
||||||
window.emanote.stork = {
|
window.emanote.stork = {
|
||||||
searchShown: false,
|
searchShown: false,
|
||||||
|
indexIsStale: false,
|
||||||
toggleSearch: function () {
|
toggleSearch: function () {
|
||||||
|
window.emanote.stork.refreshIndex();
|
||||||
document.getElementById('stork-search-container').classList.toggle('hidden');
|
document.getElementById('stork-search-container').classList.toggle('hidden');
|
||||||
window.emanote.stork.searchShown = document.body.classList.toggle('stork-overflow-hidden-important');
|
window.emanote.stork.searchShown = document.body.classList.toggle('stork-overflow-hidden-important');
|
||||||
if (window.emanote.stork.searchShown) {
|
if (window.emanote.stork.searchShown) {
|
||||||
@ -126,13 +181,25 @@
|
|||||||
window.emanote.stork.searchShown = false;
|
window.emanote.stork.searchShown = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function () {
|
getBaseUrl: function () {
|
||||||
|
const baseUrl = document.getElementById("emanote-stork").getAttribute('data-emanote-base-url') || '/';
|
||||||
|
return baseUrl;
|
||||||
|
},
|
||||||
|
|
||||||
|
registerIndex: function (options) {
|
||||||
const indexName = 'emanote-search'; // used to match input[data-stork] attribute value
|
const indexName = 'emanote-search'; // used to match input[data-stork] attribute value
|
||||||
const baseUrl = document.currentScript.getAttribute('data-emanote-base-url') || '/';
|
const indexUrl = window.emanote.stork.getBaseUrl() + '-/stork.st';
|
||||||
const indexUrl = baseUrl + '-/stork.st';
|
stork.register(
|
||||||
|
indexName,
|
||||||
|
indexUrl,
|
||||||
|
options);
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function () {
|
||||||
if (document.readyState !== 'complete') {
|
if (document.readyState !== 'complete') {
|
||||||
window.addEventListener('load', function () {
|
window.addEventListener('load', function () {
|
||||||
stork.register(indexName, indexUrl);
|
stork.initialize(window.emanote.stork.getBaseUrl() + '_emanote-static/stork/stork.wasm');
|
||||||
|
window.emanote.stork.registerIndex();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
@ -145,10 +212,32 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Override existing on Ema's hot-reload
|
// This section is called during Ema's hot reload.
|
||||||
stork.register(indexName, indexUrl, { forceOverwrite: true });
|
//
|
||||||
|
// Mark the current index as stale, and refresh it *only when* the
|
||||||
|
// user actually invokes search.
|
||||||
|
//
|
||||||
|
// We do not refresh the index *right away*, as that will cause
|
||||||
|
// memory leaks in the browser. See
|
||||||
|
// https://github.com/srid/emanote/issues/411#issuecomment-1402056235
|
||||||
|
console.log("stork: Marking index as stale");
|
||||||
|
window.emanote.stork.markIndexAsStale();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
markIndexAsStale: function () {
|
||||||
|
window.emanote.stork.indexIsStale = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshIndex: function () {
|
||||||
|
if (window.emanote.stork.indexIsStale) {
|
||||||
|
console.log("stork: Reloading index");
|
||||||
|
window.emanote.stork.indexIsStale = false;
|
||||||
|
// NOTE: This will leak memory. See the comment above.
|
||||||
|
window.emanote.stork.registerIndex({ forceOverwrite: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.emanote.stork.init();
|
window.emanote.stork.init();
|
||||||
@ -170,10 +259,6 @@
|
|||||||
<h1 class='pb-2 mt-2 mb-2 text-6xl text-center'>
|
<h1 class='pb-2 mt-2 mb-2 text-6xl text-center'>
|
||||||
Task Index
|
Task Index
|
||||||
</h1>
|
</h1>
|
||||||
<div class='flex justify-center items-center mb-2'>
|
|
||||||
<a target='_blank' class='italic underline' href='https://github.com/srid/emanote/discussions/50'>Experimental
|
|
||||||
feature</a> !
|
|
||||||
</div>
|
|
||||||
<div class='pb-2 mx-auto my-4 lg:max-w-screen-md '>
|
<div class='pb-2 mx-auto my-4 lg:max-w-screen-md '>
|
||||||
|
|
||||||
<div class='w-full bg-gray-300'>
|
<div class='w-full bg-gray-300'>
|
||||||
@ -188,27 +273,27 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a href='' title='Go to Home page'>
|
<a href='' title='Go to Home page'>
|
||||||
<svg xmlns='http://www.w3.org/2000/svg' class='w-6 h-6 hover:text-red-700' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
<svg xmlns='http://www.w3.org/2000/svg' class='w-6 h-6 hover:text-purple-700' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
||||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6'></path>
|
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6'></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href='-/all' title='View Index'>
|
<a href='-/all' title='View Index'>
|
||||||
<svg class='w-6 h-6 hover:text-red-700' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
|
<svg class='w-6 h-6 hover:text-purple-700' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
|
||||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4'>
|
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4'>
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href='https://emanote.srid.ca' target='_blank' title='Generated by Emanote 0.7.3.0'>
|
<a href='https://emanote.srid.ca' target='_blank' title='Generated by Emanote 1.0.3.11'>
|
||||||
<img class='w-6 h-6 hover:text-red-700' src='_emanote-static/emanote-logo.svg' />
|
<img class='w-6 h-6 hover:text-purple-700' src='_emanote-static/emanote-logo.svg' />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href='-/tags' title='View tags'>
|
<a href='-/tags' title='View tags'>
|
||||||
<svg class='w-6 h-6 hover:text-red-700' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
|
<svg class='w-6 h-6 hover:text-purple-700' fill='none' stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
|
||||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z'>
|
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z'>
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
@ -216,7 +301,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href='-/tasks' title='View tasks'>
|
<a href='-/tasks' title='View tasks'>
|
||||||
<svg xmlns='http://www.w3.org/2000/svg' class='w-6 h-6 hover:text-red-700' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
<svg xmlns='http://www.w3.org/2000/svg' class='w-6 h-6 hover:text-purple-700' fill='none' viewBox='0 0 24 24' stroke='currentColor'>
|
||||||
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'></path>
|
<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'></path>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
@ -234,6 +319,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Before Width: | Height: | Size: 87 KiB |
BIN
static_gen/About/Nicole.png
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
static_gen/About/Nicole_small.png
Normal file
After Width: | Height: | Size: 311 KiB |
BIN
static_gen/About/avatar_neu.png
Normal file
After Width: | Height: | Size: 170 KiB |
1653
static_gen/Coding/Haskell.html
Normal file
1726
static_gen/Coding/Haskell/Advantages.html
Normal file
1715
static_gen/Coding/Haskell/Code Snippets.html
Normal file
1797
static_gen/Coding/Haskell/FFPiH.html
Normal file
1922
static_gen/Coding/Haskell/Webapp-Example/Main.hs.html
Normal file
1817
static_gen/Coding/Haskell/Webapp-Example/MyService_Types.hs.html
Normal file
1433
static_gen/Coding/OpenAPI.html
Normal file
1451
static_gen/Health/Issues.html
Normal file
1404
static_gen/Opinions.html
Normal file
1477
static_gen/Stuff/Bielefeldverschwoerung.html
Normal file
1431
static_gen/TODO.html
Normal file
1616
static_gen/Uni/Extracurricular.html
Normal file
@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -xe
|
|
||||||
|
|
||||||
echo "Deprecation Warning: this script should not be used anymore"
|
|
@ -1,64 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
viewBox="0 0 504.124 504.124" style="enable-background:new 0 0 504.124 504.124;" xml:space="preserve">
|
viewBox="0 0 512.003 512.003" style="enable-background:new 0 0 512.003 512.003;" xml:space="preserve">
|
||||||
<path style="fill:#EA0C5C;" d="M482.951,396.006c0,8.531-6.908,15.447-15.447,15.447H95.461c-8.515,0-15.439-6.916-15.439-15.447
|
<polygon style="fill:#0068FF;" points="276.547,363.724 156.428,439.952 229.841,320.17 "/>
|
||||||
v-64.37c0-8.531,6.924-15.447,15.439-15.447h372.043c8.539,0,15.447,6.916,15.447,15.447L482.951,396.006L482.951,396.006z"/>
|
|
||||||
<path style="fill:#BC0852;" d="M467.512,316.197c8.539,0,15.447,6.916,15.447,15.447v64.37c0,8.531-6.908,15.447-15.447,15.447
|
|
||||||
H95.461"/>
|
|
||||||
<path style="fill:#FF7171;" d="M80.022,331.643c0-8.531,6.924-15.447,15.439-15.447h372.043c8.539,0,15.447,6.916,15.447,15.447"/>
|
|
||||||
<g>
|
<g>
|
||||||
<path style="fill:#F2DB06;" d="M399.991,396.699c0,1.552-1.268,2.812-2.812,2.812l0,0c-1.552,0-2.82-1.26-2.82-2.812V330.95
|
<polygon style="fill:#10BAFC;" points="424.343,501.551 229.841,320.17 499.021,10.446 "/>
|
||||||
c0-1.56,1.268-2.82,2.82-2.82l0,0c1.544,0,2.812,1.26,2.812,2.82V396.699z"/>
|
<polygon style="fill:#10BAFC;" points="156.428,264.439 12.979,188.915 499.021,10.446 "/>
|
||||||
<path style="fill:#F2DB06;" d="M417.84,396.699c0,1.552-1.26,2.812-2.812,2.812l0,0c-1.56,0-2.82-1.26-2.82-2.812V330.95
|
|
||||||
c0-1.56,1.26-2.82,2.82-2.82l0,0c1.552,0,2.812,1.26,2.812,2.82V396.699z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M435.689,396.699c0,1.552-1.252,2.812-2.82,2.812l0,0c-1.544,0-2.804-1.26-2.804-2.812V330.95
|
|
||||||
c0-1.56,1.26-2.82,2.804-2.82l0,0c1.568,0,2.82,1.26,2.82,2.82V396.699z"/>
|
|
||||||
</g>
|
|
||||||
<path style="fill:#038462;" d="M424.102,302.089c0,8.523-6.916,15.447-15.447,15.447H36.613c-8.531,0-15.447-6.924-15.447-15.447
|
|
||||||
v-64.37c0-8.539,6.916-15.447,15.447-15.447h372.043c8.531,0,15.447,6.908,15.447,15.447V302.089z"/>
|
|
||||||
<path style="fill:#047769;" d="M408.655,222.272c8.531,0,15.447,6.908,15.447,15.447v64.37c0,8.523-6.916,15.447-15.447,15.447
|
|
||||||
H36.613"/>
|
|
||||||
<path style="fill:#2EAF8D;" d="M21.174,237.719c0-8.539,6.916-15.447,15.447-15.447h372.035c8.531,0,15.447,6.908,15.447,15.447"/>
|
|
||||||
<g>
|
|
||||||
<path style="fill:#F2DB06;" d="M341.134,302.775c0,1.552-1.26,2.812-2.82,2.812l0,0c-1.544,0-2.796-1.26-2.796-2.812v-65.757
|
|
||||||
c0-1.552,1.252-2.812,2.796-2.812l0,0c1.56,0,2.82,1.26,2.82,2.812V302.775z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M358.984,302.775c0,1.552-1.26,2.812-2.828,2.812l0,0c-1.544,0-2.804-1.26-2.804-2.812v-65.757
|
|
||||||
c0-1.552,1.26-2.812,2.804-2.812l0,0c1.568,0,2.828,1.26,2.828,2.812V302.775z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M376.833,302.775c0,1.552-1.268,2.812-2.812,2.812l0,0c-1.56,0-2.82-1.26-2.82-2.812v-65.757
|
|
||||||
c0-1.552,1.26-2.812,2.82-2.812l0,0c1.544,0,2.812,1.26,2.812,2.812V302.775z"/>
|
|
||||||
</g>
|
|
||||||
<path style="fill:#115989;" d="M424.102,488.678c0,8.523-6.916,15.447-15.447,15.447H36.613c-8.531,0-15.447-6.924-15.447-15.447
|
|
||||||
v-64.37c0-8.531,6.916-15.447,15.447-15.447h372.043c8.531,0,15.447,6.916,15.447,15.447V488.678z"/>
|
|
||||||
<path style="fill:#003B5B;" d="M408.655,408.861c8.531,0,15.447,6.916,15.447,15.447v64.37c0,8.523-6.916,15.447-15.447,15.447
|
|
||||||
H36.613"/>
|
|
||||||
<path style="fill:#158ACC;" d="M21.174,424.308c0-8.531,6.916-15.447,15.447-15.447h372.035c8.531,0,15.447,6.916,15.447,15.447"/>
|
|
||||||
<g>
|
|
||||||
<path style="fill:#F2DB06;" d="M341.134,489.371c0,1.552-1.26,2.812-2.82,2.812l0,0c-1.544,0-2.796-1.26-2.796-2.812v-65.757
|
|
||||||
c0-1.536,1.252-2.812,2.796-2.812l0,0c1.56,0,2.82,1.276,2.82,2.812V489.371z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M358.984,489.371c0,1.552-1.26,2.812-2.828,2.812l0,0c-1.544,0-2.804-1.26-2.804-2.812v-65.757
|
|
||||||
c0-1.536,1.26-2.812,2.804-2.812l0,0c1.568,0,2.828,1.276,2.828,2.812V489.371z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M376.833,489.371c0,1.552-1.268,2.812-2.812,2.812l0,0c-1.56,0-2.82-1.26-2.82-2.812v-65.757
|
|
||||||
c0-1.536,1.26-2.812,2.82-2.812l0,0c1.544,0,2.812,1.276,2.812,2.812V489.371z"/>
|
|
||||||
</g>
|
|
||||||
<circle style="fill:#FFAA00;" cx="252.062" cy="131.853" r="92.987"/>
|
|
||||||
<path style="fill:#FF8500;" d="M252.062,38.882c51.358,0,92.987,41.622,92.987,92.979s-41.63,92.979-92.987,92.979"/>
|
|
||||||
<path style="fill:#54391E;" d="M252.566,57.038c-4.151,0-7.507-3.363-7.507-7.507c0-17.258-14.052-31.319-31.327-31.319
|
|
||||||
c-4.151,0-7.507-3.356-7.507-7.515c0-4.151,3.356-7.507,7.507-7.507c25.553,0,46.34,20.787,46.34,46.34
|
|
||||||
C260.081,53.675,256.725,57.038,252.566,57.038z"/>
|
|
||||||
<path style="fill:#382413;" d="M213.741,17.275c-0.819,0-1.575,0.221-2.332,0.473c0.756,0.244,1.512,0.473,2.332,0.473
|
|
||||||
c17.266,0,31.327,14.052,31.327,31.319c0,4.143,3.356,7.507,7.507,7.507c2.725,0,5.01-1.52,6.333-3.694
|
|
||||||
C254.197,32.722,235.757,17.275,213.741,17.275z"/>
|
|
||||||
<path style="fill:#7EC441;" d="M305.799,3.9c-12.768-8.271-34.068-3.119-50.105,12.902c-16.03,16.037-13.383,29.562-3.718,40.921
|
|
||||||
L305.799,3.9z"/>
|
|
||||||
<path style="fill:#559E1B;" d="M305.696,3.79c8.263,12.761,3.119,34.068-12.91,50.105c-16.03,16.03-29.554,13.375-40.921,3.718
|
|
||||||
L305.696,3.79z"/>
|
|
||||||
<g>
|
|
||||||
<path style="fill:#F4C951;" d="M236.285,55.187c7.593,7.593-9.051,17.912-25.687,34.54s-26.947,33.264-34.54,25.679
|
|
||||||
c-7.57-7.578-0.236-27.199,16.384-43.827C209.078,54.943,228.691,47.609,236.285,55.187z"/>
|
|
||||||
<circle style="fill:#F4C951;" cx="177.129" cy="129.971" r="4.072"/>
|
|
||||||
<circle style="fill:#F4C951;" cx="176.814" cy="147.828" r="1.252"/>
|
|
||||||
</g>
|
</g>
|
||||||
|
<polygon style="fill:#0084FF;" points="156.428,264.439 156.428,439.952 229.841,320.17 499.021,10.446 "/>
|
||||||
|
<path d="M409.804,197.83c-2.509,0-5.026-0.898-7.027-2.72c-4.269-3.883-4.582-10.491-0.699-14.76l0.164-0.181
|
||||||
|
c3.883-4.268,10.493-4.582,14.76-0.698c4.27,3.883,4.582,10.491,0.699,14.76l-0.164,0.181
|
||||||
|
C415.475,196.679,412.644,197.83,409.804,197.83z"/>
|
||||||
|
<path d="M318.886,298.027c-2.505,0-5.018-0.895-7.019-2.71c-4.274-3.879-4.594-10.487-0.716-14.759l68.202-75.162
|
||||||
|
c3.877-4.274,10.487-4.594,14.759-0.717c4.274,3.878,4.594,10.487,0.717,14.759L326.627,294.6
|
||||||
|
C324.565,296.871,321.731,298.027,318.886,298.027z"/>
|
||||||
|
<path d="M503.407,0.963c-2.579-1.193-5.448-1.251-7.987-0.32c0-0.002,0-0.003-0.001-0.005L9.377,179.106
|
||||||
|
c-3.876,1.423-6.551,4.996-6.823,9.116c-0.275,4.12,1.904,8.015,5.558,9.939l137.867,72.584v169.208
|
||||||
|
c0,4.679,3.111,8.787,7.612,10.057c0.94,0.265,1.895,0.393,2.837,0.393c1.992,0,3.923-0.589,5.586-1.644
|
||||||
|
c0.004,0.006,0.008,0.01,0.014,0.017l113.3-71.9l141.89,132.319c1.966,1.833,4.523,2.807,7.127,2.807c1.15,0,2.31-0.19,3.431-0.58
|
||||||
|
c3.66-1.273,6.315-4.467,6.898-8.299l74.678-491.105C510.051,7.42,507.628,2.915,503.407,0.963z M434.512,45.265l-279.03,206.867
|
||||||
|
l-116.834-61.51L434.512,45.265z M166.877,269.699l261.434-193.82L221.954,313.315c-0.386,0.444-0.718,0.915-1.016,1.401
|
||||||
|
c-0.002-0.003-0.004-0.004-0.007-0.007l-54.055,88.198V269.699H166.877z M188.896,406.972l43.17-70.439l27.465,25.613
|
||||||
|
L188.896,406.972z M416.988,480.407L244.345,319.408l238.93-274.914L416.988,480.407z"/>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.3 KiB |
@ -12,7 +12,7 @@ body .tree.flipped {
|
|||||||
body .tree {
|
body .tree {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
/* See more-head.tpl */
|
/* See more-head.tpl */
|
||||||
font-family: 'MavenPro', sans-serif;
|
font-family: 'WorkSans', sans-serif;
|
||||||
font-variation-settings: 'wght' 475;
|
font-variation-settings: 'wght' 475;
|
||||||
|
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
|
191
static_gen/_emanote-static/stork/flat.css
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
.stork-wrapper-flat {
|
||||||
|
position: relative;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 1em;
|
||||||
|
|
||||||
|
--stork-blue-2: #a5d8ff;
|
||||||
|
--stork-blue-3: #74c0fc;
|
||||||
|
--stork-blue-4: #4dabf7;
|
||||||
|
--stork-blue-5: #339af0;
|
||||||
|
--stork-blue-7: #1c7ed6;
|
||||||
|
--stork-gray-8: #343a40;
|
||||||
|
--stork-gray-9: #212529;
|
||||||
|
--stork-yellow-2: #ffec99;
|
||||||
|
|
||||||
|
--stork-border-color: hsl(0, 0%, 80%);
|
||||||
|
--stork-background-color: hsla(0, 0%, 97%);
|
||||||
|
--stork-text-color: var(--stork-gray-9);
|
||||||
|
|
||||||
|
--stork-input-height: 2.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat *,
|
||||||
|
.stork-wrapper-flat *:before,
|
||||||
|
.stork-wrapper-flat *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-input {
|
||||||
|
width: 100%;
|
||||||
|
height: var(--stork-input-height);
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.4em 0.8em;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid var(--stork-border-color);
|
||||||
|
border-radius: calc(var(--stork-input-height) / 2);
|
||||||
|
background-color: var(--stork-background-color);
|
||||||
|
color: var(--stork-text-color);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-progress {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
bottom: 1px;
|
||||||
|
background-color: var(--stork-blue-5);
|
||||||
|
box-shadow: 0 0 8px var(--stork-blue-4);
|
||||||
|
height: 1px;
|
||||||
|
transition: width 0.25s ease, opacity 0.4s ease 0.4s;
|
||||||
|
margin-left: calc(var(--stork-input-height) / 2);
|
||||||
|
max-width: calc(100% - var(--stork-input-height));
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-output {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 100;
|
||||||
|
color: var(--stork-text-color);
|
||||||
|
font-weight: 400;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-attribution a:link,
|
||||||
|
.stork-wrapper-flat .stork-attribution a:visited {
|
||||||
|
color: var(--stork-blue-7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-output-visible {
|
||||||
|
border: 2px solid var(--stork-border-color);
|
||||||
|
background: var(--stork-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-message {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
color: var(--stork-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-attribution {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: var(--stork-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-results {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
list-style-type: none;
|
||||||
|
max-height: 25em;
|
||||||
|
overflow-y: scroll;
|
||||||
|
border-top: 1px solid var(--stork-border-color);
|
||||||
|
border-bottom: 1px solid var(--stork-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-result:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--stork-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-result.selected {
|
||||||
|
background: var(--stork-blue-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-result a:link {
|
||||||
|
padding: 1em;
|
||||||
|
display: block;
|
||||||
|
color: currentcolor;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-result p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.95em;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--stork-text-color);
|
||||||
|
|
||||||
|
/* Flexbox container for the title and the score, when debugging */
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-excerpt-container {
|
||||||
|
margin-top: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-excerpt {
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--stork-gray-8);
|
||||||
|
|
||||||
|
/* Flexbox container for the title and the score, when debugging */
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-excerpt:not(:last-of-type) {
|
||||||
|
margin-bottom: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-highlight {
|
||||||
|
background-color: var(--stork-yellow-2);
|
||||||
|
padding: 0 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-error {
|
||||||
|
outline: 2px solid #c92a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-close-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
margin: 0.5em 0.5em;
|
||||||
|
height: 1.4em;
|
||||||
|
width: 1.4em;
|
||||||
|
padding: 0px;
|
||||||
|
background: hsl(0, 0%, 85%);
|
||||||
|
border: 1px solid hsla(0, 0%, 70%);
|
||||||
|
font-size: 1em;
|
||||||
|
color: hsl(0, 0%, 50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-close-button svg {
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-close-button:hover {
|
||||||
|
background: hsla(0, 0%, 78%);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stork-wrapper-flat .stork-close-button:active {
|
||||||
|
background: hsla(0, 0%, 65%);
|
||||||
|
}
|
2
static_gen/_emanote-static/stork/stork.js
Normal file
1
static_gen/_emanote-static/stork/stork.js.map
Normal file
BIN
static_gen/_emanote-static/stork/stork.wasm
Normal file
@ -1,64 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
viewBox="0 0 504.124 504.124" style="enable-background:new 0 0 504.124 504.124;" xml:space="preserve">
|
viewBox="0 0 512.003 512.003" style="enable-background:new 0 0 512.003 512.003;" xml:space="preserve">
|
||||||
<path style="fill:#EA0C5C;" d="M482.951,396.006c0,8.531-6.908,15.447-15.447,15.447H95.461c-8.515,0-15.439-6.916-15.439-15.447
|
<polygon style="fill:#0068FF;" points="276.547,363.724 156.428,439.952 229.841,320.17 "/>
|
||||||
v-64.37c0-8.531,6.924-15.447,15.439-15.447h372.043c8.539,0,15.447,6.916,15.447,15.447L482.951,396.006L482.951,396.006z"/>
|
|
||||||
<path style="fill:#BC0852;" d="M467.512,316.197c8.539,0,15.447,6.916,15.447,15.447v64.37c0,8.531-6.908,15.447-15.447,15.447
|
|
||||||
H95.461"/>
|
|
||||||
<path style="fill:#FF7171;" d="M80.022,331.643c0-8.531,6.924-15.447,15.439-15.447h372.043c8.539,0,15.447,6.916,15.447,15.447"/>
|
|
||||||
<g>
|
<g>
|
||||||
<path style="fill:#F2DB06;" d="M399.991,396.699c0,1.552-1.268,2.812-2.812,2.812l0,0c-1.552,0-2.82-1.26-2.82-2.812V330.95
|
<polygon style="fill:#10BAFC;" points="424.343,501.551 229.841,320.17 499.021,10.446 "/>
|
||||||
c0-1.56,1.268-2.82,2.82-2.82l0,0c1.544,0,2.812,1.26,2.812,2.82V396.699z"/>
|
<polygon style="fill:#10BAFC;" points="156.428,264.439 12.979,188.915 499.021,10.446 "/>
|
||||||
<path style="fill:#F2DB06;" d="M417.84,396.699c0,1.552-1.26,2.812-2.812,2.812l0,0c-1.56,0-2.82-1.26-2.82-2.812V330.95
|
|
||||||
c0-1.56,1.26-2.82,2.82-2.82l0,0c1.552,0,2.812,1.26,2.812,2.82V396.699z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M435.689,396.699c0,1.552-1.252,2.812-2.82,2.812l0,0c-1.544,0-2.804-1.26-2.804-2.812V330.95
|
|
||||||
c0-1.56,1.26-2.82,2.804-2.82l0,0c1.568,0,2.82,1.26,2.82,2.82V396.699z"/>
|
|
||||||
</g>
|
|
||||||
<path style="fill:#038462;" d="M424.102,302.089c0,8.523-6.916,15.447-15.447,15.447H36.613c-8.531,0-15.447-6.924-15.447-15.447
|
|
||||||
v-64.37c0-8.539,6.916-15.447,15.447-15.447h372.043c8.531,0,15.447,6.908,15.447,15.447V302.089z"/>
|
|
||||||
<path style="fill:#047769;" d="M408.655,222.272c8.531,0,15.447,6.908,15.447,15.447v64.37c0,8.523-6.916,15.447-15.447,15.447
|
|
||||||
H36.613"/>
|
|
||||||
<path style="fill:#2EAF8D;" d="M21.174,237.719c0-8.539,6.916-15.447,15.447-15.447h372.035c8.531,0,15.447,6.908,15.447,15.447"/>
|
|
||||||
<g>
|
|
||||||
<path style="fill:#F2DB06;" d="M341.134,302.775c0,1.552-1.26,2.812-2.82,2.812l0,0c-1.544,0-2.796-1.26-2.796-2.812v-65.757
|
|
||||||
c0-1.552,1.252-2.812,2.796-2.812l0,0c1.56,0,2.82,1.26,2.82,2.812V302.775z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M358.984,302.775c0,1.552-1.26,2.812-2.828,2.812l0,0c-1.544,0-2.804-1.26-2.804-2.812v-65.757
|
|
||||||
c0-1.552,1.26-2.812,2.804-2.812l0,0c1.568,0,2.828,1.26,2.828,2.812V302.775z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M376.833,302.775c0,1.552-1.268,2.812-2.812,2.812l0,0c-1.56,0-2.82-1.26-2.82-2.812v-65.757
|
|
||||||
c0-1.552,1.26-2.812,2.82-2.812l0,0c1.544,0,2.812,1.26,2.812,2.812V302.775z"/>
|
|
||||||
</g>
|
|
||||||
<path style="fill:#115989;" d="M424.102,488.678c0,8.523-6.916,15.447-15.447,15.447H36.613c-8.531,0-15.447-6.924-15.447-15.447
|
|
||||||
v-64.37c0-8.531,6.916-15.447,15.447-15.447h372.043c8.531,0,15.447,6.916,15.447,15.447V488.678z"/>
|
|
||||||
<path style="fill:#003B5B;" d="M408.655,408.861c8.531,0,15.447,6.916,15.447,15.447v64.37c0,8.523-6.916,15.447-15.447,15.447
|
|
||||||
H36.613"/>
|
|
||||||
<path style="fill:#158ACC;" d="M21.174,424.308c0-8.531,6.916-15.447,15.447-15.447h372.035c8.531,0,15.447,6.916,15.447,15.447"/>
|
|
||||||
<g>
|
|
||||||
<path style="fill:#F2DB06;" d="M341.134,489.371c0,1.552-1.26,2.812-2.82,2.812l0,0c-1.544,0-2.796-1.26-2.796-2.812v-65.757
|
|
||||||
c0-1.536,1.252-2.812,2.796-2.812l0,0c1.56,0,2.82,1.276,2.82,2.812V489.371z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M358.984,489.371c0,1.552-1.26,2.812-2.828,2.812l0,0c-1.544,0-2.804-1.26-2.804-2.812v-65.757
|
|
||||||
c0-1.536,1.26-2.812,2.804-2.812l0,0c1.568,0,2.828,1.276,2.828,2.812V489.371z"/>
|
|
||||||
<path style="fill:#F2DB06;" d="M376.833,489.371c0,1.552-1.268,2.812-2.812,2.812l0,0c-1.56,0-2.82-1.26-2.82-2.812v-65.757
|
|
||||||
c0-1.536,1.26-2.812,2.82-2.812l0,0c1.544,0,2.812,1.276,2.812,2.812V489.371z"/>
|
|
||||||
</g>
|
|
||||||
<circle style="fill:#FFAA00;" cx="252.062" cy="131.853" r="92.987"/>
|
|
||||||
<path style="fill:#FF8500;" d="M252.062,38.882c51.358,0,92.987,41.622,92.987,92.979s-41.63,92.979-92.987,92.979"/>
|
|
||||||
<path style="fill:#54391E;" d="M252.566,57.038c-4.151,0-7.507-3.363-7.507-7.507c0-17.258-14.052-31.319-31.327-31.319
|
|
||||||
c-4.151,0-7.507-3.356-7.507-7.515c0-4.151,3.356-7.507,7.507-7.507c25.553,0,46.34,20.787,46.34,46.34
|
|
||||||
C260.081,53.675,256.725,57.038,252.566,57.038z"/>
|
|
||||||
<path style="fill:#382413;" d="M213.741,17.275c-0.819,0-1.575,0.221-2.332,0.473c0.756,0.244,1.512,0.473,2.332,0.473
|
|
||||||
c17.266,0,31.327,14.052,31.327,31.319c0,4.143,3.356,7.507,7.507,7.507c2.725,0,5.01-1.52,6.333-3.694
|
|
||||||
C254.197,32.722,235.757,17.275,213.741,17.275z"/>
|
|
||||||
<path style="fill:#7EC441;" d="M305.799,3.9c-12.768-8.271-34.068-3.119-50.105,12.902c-16.03,16.037-13.383,29.562-3.718,40.921
|
|
||||||
L305.799,3.9z"/>
|
|
||||||
<path style="fill:#559E1B;" d="M305.696,3.79c8.263,12.761,3.119,34.068-12.91,50.105c-16.03,16.03-29.554,13.375-40.921,3.718
|
|
||||||
L305.696,3.79z"/>
|
|
||||||
<g>
|
|
||||||
<path style="fill:#F4C951;" d="M236.285,55.187c7.593,7.593-9.051,17.912-25.687,34.54s-26.947,33.264-34.54,25.679
|
|
||||||
c-7.57-7.578-0.236-27.199,16.384-43.827C209.078,54.943,228.691,47.609,236.285,55.187z"/>
|
|
||||||
<circle style="fill:#F4C951;" cx="177.129" cy="129.971" r="4.072"/>
|
|
||||||
<circle style="fill:#F4C951;" cx="176.814" cy="147.828" r="1.252"/>
|
|
||||||
</g>
|
</g>
|
||||||
|
<polygon style="fill:#0084FF;" points="156.428,264.439 156.428,439.952 229.841,320.17 499.021,10.446 "/>
|
||||||
|
<path d="M409.804,197.83c-2.509,0-5.026-0.898-7.027-2.72c-4.269-3.883-4.582-10.491-0.699-14.76l0.164-0.181
|
||||||
|
c3.883-4.268,10.493-4.582,14.76-0.698c4.27,3.883,4.582,10.491,0.699,14.76l-0.164,0.181
|
||||||
|
C415.475,196.679,412.644,197.83,409.804,197.83z"/>
|
||||||
|
<path d="M318.886,298.027c-2.505,0-5.018-0.895-7.019-2.71c-4.274-3.879-4.594-10.487-0.716-14.759l68.202-75.162
|
||||||
|
c3.877-4.274,10.487-4.594,14.759-0.717c4.274,3.878,4.594,10.487,0.717,14.759L326.627,294.6
|
||||||
|
C324.565,296.871,321.731,298.027,318.886,298.027z"/>
|
||||||
|
<path d="M503.407,0.963c-2.579-1.193-5.448-1.251-7.987-0.32c0-0.002,0-0.003-0.001-0.005L9.377,179.106
|
||||||
|
c-3.876,1.423-6.551,4.996-6.823,9.116c-0.275,4.12,1.904,8.015,5.558,9.939l137.867,72.584v169.208
|
||||||
|
c0,4.679,3.111,8.787,7.612,10.057c0.94,0.265,1.895,0.393,2.837,0.393c1.992,0,3.923-0.589,5.586-1.644
|
||||||
|
c0.004,0.006,0.008,0.01,0.014,0.017l113.3-71.9l141.89,132.319c1.966,1.833,4.523,2.807,7.127,2.807c1.15,0,2.31-0.19,3.431-0.58
|
||||||
|
c3.66-1.273,6.315-4.467,6.898-8.299l74.678-491.105C510.051,7.42,507.628,2.915,503.407,0.963z M434.512,45.265l-279.03,206.867
|
||||||
|
l-116.834-61.51L434.512,45.265z M166.877,269.699l261.434-193.82L221.954,313.315c-0.386,0.444-0.718,0.915-1.016,1.401
|
||||||
|
c-0.002-0.003-0.004-0.004-0.007-0.007l-54.055,88.198V269.699H166.877z M188.896,406.972l43.17-70.439l27.465,25.613
|
||||||
|
L188.896,406.972z M416.988,480.407L244.345,319.408l238.93-274.914L416.988,480.407z"/>
|
||||||
<g>
|
<g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.3 KiB |