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
|
||||
/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
|
||||
|
||||
- **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}
|
||||
![[Experience]]
|
||||
|
||||
{.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
|
||||
|
||||
- **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**:
|
||||
- ML-Specialist at [Jobware](https://jobware.de) (Paderborn; german Job-Advertising-Platform)
|
||||
- Extraction/Classification of sentences from JobAds (Requirements, Benefits,
|
||||
@ -36,7 +67,7 @@
|
||||
- Tutor "Introduction to Machine Learning"
|
||||
- Was awarded **Tutoring-Award** of the Faculty of Technology for excellent
|
||||
tutoring
|
||||
- Lecture "Intermediate Functional Programming in Haskell"
|
||||
- Lecture "[[FFPiH|Intermediate Functional Programming in Haskell]]"
|
||||
- Originally developed as student-project in cooperation with Jonas Betzendahl
|
||||
- First held in Summer 2015
|
||||
- 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:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
module Main where
|
||||
|
||||
import System.Environment (getArgs)
|
@ -8,7 +8,7 @@ Syntax nehmen.
|
||||
|
||||
### Beispiel
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data Person = P { name :: String
|
||||
, addr :: Address
|
||||
, salary :: Int }
|
||||
@ -38,7 +38,7 @@ Probleme mit diesem Code:
|
||||
|
||||
### Was wir gern hätten
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data Person = P { name :: String
|
||||
, addr :: Address
|
||||
, 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
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data Person = P { name :: String
|
||||
, addr :: Address
|
||||
, salary :: Int }
|
||||
@ -75,7 +75,7 @@ machen und wäre fertig.
|
||||
|
||||
### Getter/Setter als Lens-Methoden
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data LensR s a = L { viewR :: s -> a
|
||||
, 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
|
||||
zum setzen wird die Datenstruktur erneut traversiert:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
over :: LensR s a -> (a -> a) -> s -> s
|
||||
over ln f s = setR l (f (viewR l s)) s
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Lösung: modify-funktion hinzufügen
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data LensR s a
|
||||
= L { viewR :: s -> a
|
||||
, 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:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data LensR s a
|
||||
= L { viewR :: s -> a
|
||||
, setR :: a -> s -> s
|
||||
@ -144,7 +144,7 @@ anders aussehen.
|
||||
|
||||
## Benutzen einer Lens als Setter
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
set :: Lens' s a -> (a -> s -> s)
|
||||
set ln a s = --...umm...
|
||||
--: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
|
||||
können.
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
newtype Identity a = Identity a
|
||||
-- Id :: a -> Identity a
|
||||
|
||||
@ -167,7 +167,7 @@ instance Functor Identity where
|
||||
|
||||
somit ist set einfach nur
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
set :: Lens' s a -> (a -> s -> s)
|
||||
set ln x s
|
||||
= runIdentity (ls set_fld s)
|
||||
@ -197,7 +197,7 @@ over ln f = runIdentity . ln (Identity . f)
|
||||
|
||||
## Benutzen einer Lens als Getter
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
view :: Lens' s a -> (s -> a)
|
||||
view ln s = --...umm...
|
||||
--: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"
|
||||
und werfen das "s" am Ende weg.
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
newtype Const v a = Const v
|
||||
|
||||
getConst :: Const v a -> v
|
||||
@ -221,7 +221,7 @@ instance Functor (Const v) where
|
||||
|
||||
somit ergibt sich
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
view :: Lens' s a -> (s -> a)
|
||||
view ln s
|
||||
= getConst (ln Const s)
|
||||
@ -246,7 +246,7 @@ type Lens' s a = forall f. Functor f
|
||||
|
||||
Für unser Personen-Beispiel vom Anfang:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data Person = P { _name :: String, _salary :: Int }
|
||||
|
||||
name :: Lens' Person String
|
||||
@ -270,7 +270,7 @@ name elt_fn (P n s)
|
||||
|
||||
## Wie funktioniert das intern?
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
view name (P {_name="Fred", _salary=100})
|
||||
-- inline view-function
|
||||
= 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:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data Person = P { _name :: String, _salary :: Int }
|
||||
|
||||
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
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
import Control.Lens.TH
|
||||
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
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
import Control.Lens.TH
|
||||
|
||||
data Person = P { _name :: String
|
||||
@ -355,7 +355,7 @@ setPostcode pc p = set (addr . postcode) pc p
|
||||
|
||||
## Shortcuts mit "Line-Noise"
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
-- ...
|
||||
|
||||
setPostcode :: String -> Person -> Person
|
||||
@ -375,7 +375,7 @@ Listenkonvertierungen, -traversierungen, ...)
|
||||
Man kann mit Lenses sogar Felder emulieren, die gar nicht da sind. Angenommen
|
||||
folgender Code:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data Temp = T { _fahrenheit :: Float }
|
||||
|
||||
$(makeLenses ''Temp)
|
||||
@ -399,7 +399,7 @@ Minuten oder 37 Stunden ist)
|
||||
Das ganze kann man auch parametrisieren und auf Non-Record-Strukturen anwenden.
|
||||
Beispielhaft an einer Map verdeutlicht:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
-- from Data.Lens.At
|
||||
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
|
||||
- Web-scraper in Package hexpat-lens
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
p ^.. _HTML' . to allNodes
|
||||
. traverse . named "a"
|
||||
. 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
|
||||
Applicative.
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
type Traversal' s a = forall f. Applicative f
|
||||
=> (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
|
||||
kein Bind - dafür kann man die beliebig oft hintereinanderhängen).
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
class Functor f => Applicative f where
|
||||
pure :: a -> f a
|
||||
(<*>) :: 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:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
data Adress = A { _road :: String
|
||||
, _city :: 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:
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
addr_strs :: Traversal' Address String
|
||||
addr_strs elt_fn (A r c p)
|
||||
= ... (\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.
|
||||
Somit gibt sich
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
addr_strs :: Traversal' Address String
|
||||
addr_strs elt_fn (A r c p)
|
||||
= 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...
|
||||
Ausschnitt
|
||||
|
||||
~~~ { .haskell .numberLines }
|
||||
~~~ { .haskell }
|
||||
-- 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 :: 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
|
||||
|
||||
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
|
||||
durch stete Wiederholung. Und der beste Moment etwas zu wiederholen ist, kurz
|
||||
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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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" />
|
||||
|
||||
template:
|
||||
theme: red
|
||||
theme: purple
|
||||
|
||||
# You can add your own variables here, like editBaseUrl.
|
||||
# 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": {
|
||||
"ema": {
|
||||
"flake": false,
|
||||
"check-flake": {
|
||||
"locked": {
|
||||
"lastModified": 1660941244,
|
||||
"narHash": "sha256-gGhvmSjjr07u8uWaBIv6F5WE+VCGngSVXN9mhb4BmQo=",
|
||||
"lastModified": 1662502605,
|
||||
"narHash": "sha256-jAT55UhabAxLAVGanxjnNdzH2/oX2ZjLsL4i2jPIP+g=",
|
||||
"owner": "srid",
|
||||
"repo": "ema",
|
||||
"rev": "d74daa4d0d1f7a14cebd415787166fe7909fc33b",
|
||||
"repo": "check-flake",
|
||||
"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"
|
||||
},
|
||||
"original": {
|
||||
"owner": "srid",
|
||||
"ref": "master",
|
||||
"repo": "ema",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"emanote": {
|
||||
"inputs": {
|
||||
"check-flake": "check-flake",
|
||||
"ema": "ema",
|
||||
"flake-parts": "flake-parts",
|
||||
"flake-root": "flake-root",
|
||||
"haskell-flake": "haskell-flake",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"tailwind-haskell": "tailwind-haskell"
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"systems": "systems_2",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1661096607,
|
||||
"narHash": "sha256-DtSvkXRTsILCcWGcmP50fLupGroAP7EJVk3da+5yLvU=",
|
||||
"owner": "EmaApps",
|
||||
"lastModified": 1688491882,
|
||||
"narHash": "sha256-b3hIpvCGTIWIOvkFJbkrBmlifF8JGVlOOJElE2NKKe0=",
|
||||
"owner": "srid",
|
||||
"repo": "emanote",
|
||||
"rev": "aad6e789829ce35b042ea6fcc728ae0b76086dd8",
|
||||
"rev": "d1dd7ea4ad89dc74f8d4b90650e07e788b489bb6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "EmaApps",
|
||||
"owner": "srid",
|
||||
"repo": "emanote",
|
||||
"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": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"emanote",
|
||||
"nixpkgs"
|
||||
]
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1657102481,
|
||||
"narHash": "sha256-62Fuw8JgPub38OdgNefkIKOodM9nC3M0AG6lS+7smf4=",
|
||||
"lastModified": 1671710971,
|
||||
"narHash": "sha256-YZdt5IJrfsdUTtVB94EMsBvaJbK9ve6QaZyzRuup+sY=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "608ed3502263d6f4f886d75c48fc2b444a4ab8d8",
|
||||
"rev": "98bec08c58a9547d705f2f5e300ac8eef6665e52",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -60,49 +137,68 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"flake-root": {
|
||||
"locked": {
|
||||
"lastModified": 1661009076,
|
||||
"narHash": "sha256-phAE40gctVygRq3G3B6LhvD7u2qdQT21xsz8DdRDYFo=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "850d8a76026127ef02f040fb0dcfdb8b749dd9d9",
|
||||
"lastModified": 1671378805,
|
||||
"narHash": "sha256-yqGxyzMN2GuppwG3dTWD1oiKxi+jGYP7D1qUSc5vKhI=",
|
||||
"owner": "srid",
|
||||
"repo": "flake-root",
|
||||
"rev": "dc7ba6166e478804a9da6881aa48c45d300075cf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"owner": "srid",
|
||||
"repo": "flake-root",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1652776076,
|
||||
"narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=",
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"ref": "v1.0.0",
|
||||
"repo": "flake-utils",
|
||||
"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": {
|
||||
"locked": {
|
||||
"lastModified": 1660319056,
|
||||
"narHash": "sha256-MX6PLEtXVyXXUEk3t1e0c20XRL4m4u9TFET2X0TpTdE=",
|
||||
"lastModified": 1685469326,
|
||||
"narHash": "sha256-esxJLsGexI/J6Fc32tJd2p3K5IOBZSCHgFvegjIpc+0=",
|
||||
"owner": "srid",
|
||||
"repo": "haskell-flake",
|
||||
"rev": "1ca2be3c354ef2a3296cac7e54ae21e1d6ead6d7",
|
||||
"rev": "996f5c2cdc67285c4990df378976f9dbf26f8401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -111,49 +207,243 @@
|
||||
"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": {
|
||||
"locked": {
|
||||
"lastModified": 1659077768,
|
||||
"narHash": "sha256-P0XIHBVty6WIuIrk2DZNvLcYev9956y1prT4zL212H8=",
|
||||
"path": "/nix/store/1ha33ma070pyxw5kkcx61qi7ypzzxzah-source",
|
||||
"rev": "2a93ea177c3d7700b934bf95adfe00c435f696b8",
|
||||
"type": "path"
|
||||
"lastModified": 1678875422,
|
||||
"narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
"owner": "NixOS",
|
||||
"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": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"emanote": "emanote",
|
||||
"flake-parts": "flake-parts_2",
|
||||
"nixpkgs": [
|
||||
"emanote",
|
||||
"nixpkgs"
|
||||
]
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"systems": "systems_3"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"emanote",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1658431129,
|
||||
"narHash": "sha256-AtTxP0AMXdeU0ZTJP5R1lCtx5az3ejrmGP1klEdu1qU=",
|
||||
"owner": "srid",
|
||||
"repo": "tailwind-haskell",
|
||||
"rev": "09a102164b1a4559892277ff38efdc9b949c5433",
|
||||
"lastModified": 1675588998,
|
||||
"narHash": "sha256-CLeFLmah0mxNp/EIW0PMG3YutKxVIIs4B0f5oJhwe8E=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "70e03145e26c2f3199f4320ecd9fd343f1129c60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "srid",
|
||||
"ref": "master",
|
||||
"repo": "tailwind-haskell",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"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 = {
|
||||
emanote.url = "github:EmaApps/emanote";
|
||||
nixpkgs.follows = "emanote/nixpkgs";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
flake-parts.inputs.nixpkgs.follows = "nixpkgs";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
|
||||
systems.url = "github:nix-systems/default";
|
||||
devenv.url = "github:cachix/devenv/9ba9e3b908a12ddc6c43f88c52f2bf3c1d1e82c1"; # until https://github.com/cachix/devenv/issues/756 is fixed
|
||||
emanote.url = "github:srid/emanote";
|
||||
};
|
||||
|
||||
outputs = inputs@{ self, flake-parts, nixpkgs, ... }:
|
||||
flake-parts.lib.mkFlake { inherit self; } {
|
||||
systems = nixpkgs.lib.systems.flakeExposed;
|
||||
imports = [
|
||||
inputs.emanote.flakeModule
|
||||
nixConfig = {
|
||||
extra-trusted-public-keys = ["devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
|
||||
"srid.cachix.org-1:3clnql5gjbJNEvhA/WQp7nrZlBptwpXnUk6JAv8aB2M="
|
||||
];
|
||||
perSystem = { self', pkgs, system, ... }: {
|
||||
emanote = {
|
||||
# By default, the 'emanote' flake input is used.
|
||||
# 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
|
||||
extra-substituters = [
|
||||
"https://devenv.cachix.org"
|
||||
"https://srid.cachix.org"
|
||||
];
|
||||
};
|
||||
|
||||
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,37 +46,77 @@
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
/* Heist error element */
|
||||
strong.error {
|
||||
color: lightcoral;
|
||||
font-size: 90%;
|
||||
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>
|
||||
<!-- 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>
|
||||
@font-face {
|
||||
font-family: 'MavenPro';
|
||||
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf */
|
||||
src: url(_emanote-static/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf) format("truetype");
|
||||
font-family: 'WorkSans';
|
||||
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf */
|
||||
src: url(_emanote-static/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf) format("truetype");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'MavenPro', sans-serif;
|
||||
/* font-variation-settings: 'wght'300; */
|
||||
font-family: 'WorkSans', sans-serif;
|
||||
font-variation-settings: 'wght' 350;
|
||||
}
|
||||
|
||||
a.mavenLinkBold {
|
||||
font-variation-settings: 'wght'500;
|
||||
font-variation-settings: 'wght' 400;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-variation-settings: 'wght'500;
|
||||
font-variation-settings: 'wght' 500;
|
||||
}
|
||||
|
||||
h1,
|
||||
@ -87,12 +127,25 @@
|
||||
h6,
|
||||
header,
|
||||
.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>
|
||||
|
||||
|
||||
<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 -->
|
||||
<style>
|
||||
#stork-search-container {
|
||||
@ -106,14 +159,16 @@
|
||||
</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.stork = {
|
||||
searchShown: false,
|
||||
indexIsStale: false,
|
||||
toggleSearch: function () {
|
||||
window.emanote.stork.refreshIndex();
|
||||
document.getElementById('stork-search-container').classList.toggle('hidden');
|
||||
window.emanote.stork.searchShown = document.body.classList.toggle('stork-overflow-hidden-important');
|
||||
if (window.emanote.stork.searchShown) {
|
||||
@ -126,13 +181,25 @@
|
||||
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 baseUrl = document.currentScript.getAttribute('data-emanote-base-url') || '/';
|
||||
const indexUrl = baseUrl + '-/stork.st';
|
||||
const indexUrl = window.emanote.stork.getBaseUrl() + '-/stork.st';
|
||||
stork.register(
|
||||
indexName,
|
||||
indexUrl,
|
||||
options);
|
||||
},
|
||||
|
||||
init: function () {
|
||||
if (document.readyState !== 'complete') {
|
||||
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 => {
|
||||
@ -145,10 +212,32 @@
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Override existing on Ema's hot-reload
|
||||
stork.register(indexName, indexUrl, { forceOverwrite: true });
|
||||
// This section is called during Ema's hot reload.
|
||||
//
|
||||
// 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();
|
||||
@ -170,10 +259,6 @@
|
||||
<h1 class='pb-2 mt-2 mb-2 text-6xl text-center'>
|
||||
Tag Index
|
||||
</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='bg-gray-200 pb-2'>
|
||||
@ -203,27 +288,27 @@
|
||||
|
||||
<div>
|
||||
<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>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href='https://emanote.srid.ca' target='_blank' title='Generated by Emanote 0.7.3.0'>
|
||||
<img class='w-6 h-6 hover:text-red-700' src='_emanote-static/emanote-logo.svg' />
|
||||
<a href='https://emanote.srid.ca' target='_blank' title='Generated by Emanote 1.0.3.11'>
|
||||
<img class='w-6 h-6 hover:text-purple-700' src='_emanote-static/emanote-logo.svg' />
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</svg>
|
||||
@ -231,7 +316,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</svg>
|
||||
</a>
|
||||
@ -249,6 +334,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -46,37 +46,77 @@
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
/* Heist error element */
|
||||
strong.error {
|
||||
color: lightcoral;
|
||||
font-size: 90%;
|
||||
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>
|
||||
<!-- 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>
|
||||
@font-face {
|
||||
font-family: 'MavenPro';
|
||||
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf */
|
||||
src: url(_emanote-static/fonts/Maven_Pro/MavenPro-VariableFont_wght.ttf) format("truetype");
|
||||
font-family: 'WorkSans';
|
||||
/* FIXME: This ought to be: ${ema:emanoteStaticLayerUrl}/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf */
|
||||
src: url(_emanote-static/fonts/Work_Sans/WorkSans-VariableFont_wght.ttf) format("truetype");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'MavenPro', sans-serif;
|
||||
/* font-variation-settings: 'wght'300; */
|
||||
font-family: 'WorkSans', sans-serif;
|
||||
font-variation-settings: 'wght' 350;
|
||||
}
|
||||
|
||||
a.mavenLinkBold {
|
||||
font-variation-settings: 'wght'500;
|
||||
font-variation-settings: 'wght' 400;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-variation-settings: 'wght'500;
|
||||
font-variation-settings: 'wght' 500;
|
||||
}
|
||||
|
||||
h1,
|
||||
@ -87,12 +127,25 @@
|
||||
h6,
|
||||
header,
|
||||
.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>
|
||||
|
||||
|
||||
<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 -->
|
||||
<style>
|
||||
#stork-search-container {
|
||||
@ -106,14 +159,16 @@
|
||||
</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.stork = {
|
||||
searchShown: false,
|
||||
indexIsStale: false,
|
||||
toggleSearch: function () {
|
||||
window.emanote.stork.refreshIndex();
|
||||
document.getElementById('stork-search-container').classList.toggle('hidden');
|
||||
window.emanote.stork.searchShown = document.body.classList.toggle('stork-overflow-hidden-important');
|
||||
if (window.emanote.stork.searchShown) {
|
||||
@ -126,13 +181,25 @@
|
||||
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 baseUrl = document.currentScript.getAttribute('data-emanote-base-url') || '/';
|
||||
const indexUrl = baseUrl + '-/stork.st';
|
||||
const indexUrl = window.emanote.stork.getBaseUrl() + '-/stork.st';
|
||||
stork.register(
|
||||
indexName,
|
||||
indexUrl,
|
||||
options);
|
||||
},
|
||||
|
||||
init: function () {
|
||||
if (document.readyState !== 'complete') {
|
||||
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 => {
|
||||
@ -145,10 +212,32 @@
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Override existing on Ema's hot-reload
|
||||
stork.register(indexName, indexUrl, { forceOverwrite: true });
|
||||
// This section is called during Ema's hot reload.
|
||||
//
|
||||
// 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();
|
||||
@ -170,10 +259,6 @@
|
||||
<h1 class='pb-2 mt-2 mb-2 text-6xl text-center'>
|
||||
Task Index
|
||||
</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='w-full bg-gray-300'>
|
||||
@ -188,27 +273,27 @@
|
||||
|
||||
<div>
|
||||
<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>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href='https://emanote.srid.ca' target='_blank' title='Generated by Emanote 0.7.3.0'>
|
||||
<img class='w-6 h-6 hover:text-red-700' src='_emanote-static/emanote-logo.svg' />
|
||||
<a href='https://emanote.srid.ca' target='_blank' title='Generated by Emanote 1.0.3.11'>
|
||||
<img class='w-6 h-6 hover:text-purple-700' src='_emanote-static/emanote-logo.svg' />
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</svg>
|
||||
@ -216,7 +301,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</svg>
|
||||
</a>
|
||||
@ -234,6 +319,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</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"?>
|
||||
<!-- 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"
|
||||
viewBox="0 0 504.124 504.124" style="enable-background:new 0 0 504.124 504.124;" 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
|
||||
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"/>
|
||||
viewBox="0 0 512.003 512.003" style="enable-background:new 0 0 512.003 512.003;" xml:space="preserve">
|
||||
<polygon style="fill:#0068FF;" points="276.547,363.724 156.428,439.952 229.841,320.17 "/>
|
||||
<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
|
||||
c0-1.56,1.268-2.82,2.82-2.82l0,0c1.544,0,2.812,1.26,2.812,2.82V396.699z"/>
|
||||
<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"/>
|
||||
<polygon style="fill:#10BAFC;" points="424.343,501.551 229.841,320.17 499.021,10.446 "/>
|
||||
<polygon style="fill:#10BAFC;" points="156.428,264.439 12.979,188.915 499.021,10.446 "/>
|
||||
</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>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.3 KiB |
@ -12,7 +12,7 @@ body .tree.flipped {
|
||||
body .tree {
|
||||
overflow: auto;
|
||||
/* See more-head.tpl */
|
||||
font-family: 'MavenPro', sans-serif;
|
||||
font-family: 'WorkSans', sans-serif;
|
||||
font-variation-settings: 'wght' 475;
|
||||
|
||||
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"?>
|
||||
<!-- 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"
|
||||
viewBox="0 0 504.124 504.124" style="enable-background:new 0 0 504.124 504.124;" 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
|
||||
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"/>
|
||||
viewBox="0 0 512.003 512.003" style="enable-background:new 0 0 512.003 512.003;" xml:space="preserve">
|
||||
<polygon style="fill:#0068FF;" points="276.547,363.724 156.428,439.952 229.841,320.17 "/>
|
||||
<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
|
||||
c0-1.56,1.268-2.82,2.82-2.82l0,0c1.544,0,2.812,1.26,2.812,2.82V396.699z"/>
|
||||
<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"/>
|
||||
<polygon style="fill:#10BAFC;" points="424.343,501.551 229.841,320.17 499.021,10.446 "/>
|
||||
<polygon style="fill:#10BAFC;" points="156.428,264.439 12.979,188.915 499.021,10.446 "/>
|
||||
</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>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.3 KiB |