Basic Oogway setup.
3
.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
.idea/
|
|
||||||
static/blog/
|
|
||||||
geodb/
|
|
||||||
|
|||||||
25
Dockerfile
@@ -1,25 +0,0 @@
|
|||||||
FROM golang AS build
|
|
||||||
COPY . /go/src/github.com/Kugelschieber/marvinblum
|
|
||||||
WORKDIR /go/src/github.com/Kugelschieber/marvinblum
|
|
||||||
RUN apt-get update && apt-get upgrade -y
|
|
||||||
|
|
||||||
ENV GOPATH=/go
|
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
RUN go build -ldflags "-s -w" main.go
|
|
||||||
|
|
||||||
FROM alpine
|
|
||||||
RUN apk update && \
|
|
||||||
apk upgrade && \
|
|
||||||
apk add --no-cache && \
|
|
||||||
apk add ca-certificates && \
|
|
||||||
rm -rf /var/cache/apk/*
|
|
||||||
COPY --from=build /go/src/github.com/Kugelschieber/marvinblum /app
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# default config
|
|
||||||
ENV MB_LOGLEVEL=info
|
|
||||||
ENV MB_ALLOWED_ORIGINS=*
|
|
||||||
ENV MB_HOST=0.0.0.0:8888
|
|
||||||
|
|
||||||
EXPOSE 8888
|
|
||||||
CMD ["/app/main"]
|
|
||||||
21
LICENSE
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Marvin Blum
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
# marvinblum.de
|
# marvinblum.de
|
||||||
|
|
||||||
My website.
|
My website build using [Oogway](https://github.com/emvi/oogway).
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
BIN
assets/favicon/android-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/favicon/android-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/favicon/android-icon-36x36.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/favicon/android-icon-48x48.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/favicon/android-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/favicon/android-icon-96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/favicon/apple-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/favicon/apple-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/favicon/apple-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/favicon/apple-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/favicon/apple-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/favicon/apple-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/favicon/apple-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/favicon/apple-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/favicon/apple-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/favicon/apple-icon-precomposed.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/favicon/apple-icon.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
2
assets/favicon/browserconfig.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
|
||||||
BIN
assets/favicon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 816 B |
BIN
assets/favicon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/favicon/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/favicon/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
41
assets/favicon/manifest.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "App",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "\/android-icon-36x36.png",
|
||||||
|
"sizes": "36x36",
|
||||||
|
"type": "image\/png",
|
||||||
|
"density": "0.75"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "\/android-icon-48x48.png",
|
||||||
|
"sizes": "48x48",
|
||||||
|
"type": "image\/png",
|
||||||
|
"density": "1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "\/android-icon-72x72.png",
|
||||||
|
"sizes": "72x72",
|
||||||
|
"type": "image\/png",
|
||||||
|
"density": "1.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "\/android-icon-96x96.png",
|
||||||
|
"sizes": "96x96",
|
||||||
|
"type": "image\/png",
|
||||||
|
"density": "2.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "\/android-icon-144x144.png",
|
||||||
|
"sizes": "144x144",
|
||||||
|
"type": "image\/png",
|
||||||
|
"density": "3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "\/android-icon-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image\/png",
|
||||||
|
"density": "4.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
assets/favicon/ms-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/favicon/ms-icon-150x150.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/favicon/ms-icon-310x310.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
assets/favicon/ms-icon-70x70.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
0
assets/style.css
Normal file
206
blog/blog.go
@@ -1,206 +0,0 @@
|
|||||||
package blog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Kugelschieber/marvinblum/tpl"
|
|
||||||
emvi "github.com/emvi/api-go"
|
|
||||||
"github.com/emvi/logbuch"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
blogCacheTime = time.Minute * 15
|
|
||||||
blogFileCache = "static/blog"
|
|
||||||
maxLatestArticles = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
linkRegex = regexp.MustCompile(`(?iU)href="/read/([^"]+)"`)
|
|
||||||
attachmentRegex = regexp.MustCompile(`(?iU)(href|src)="([^"]+)/api/v1/content/([^"]+)"`)
|
|
||||||
attachmentURLRegex = regexp.MustCompile(`(?iU)(href|src)="([^"]+/api/v1/content/)([^"]+)"`)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Blog struct {
|
|
||||||
client *emvi.Client
|
|
||||||
articles []emvi.Article // sorted by date published (descending)
|
|
||||||
articleMap map[string]emvi.Article // id -> article
|
|
||||||
articlesYear map[int][]emvi.Article // year -> articleMap
|
|
||||||
nextUpdate time.Time
|
|
||||||
cache *tpl.Cache
|
|
||||||
m sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBlog(cache *tpl.Cache) *Blog {
|
|
||||||
logbuch.Info("Initializing blog")
|
|
||||||
b := &Blog{cache: cache}
|
|
||||||
b.client = emvi.NewClient(os.Getenv("MB_EMVI_CLIENT_ID"),
|
|
||||||
os.Getenv("MB_EMVI_CLIENT_SECRET"),
|
|
||||||
os.Getenv("MB_EMVI_ORGA"),
|
|
||||||
nil)
|
|
||||||
b.nextUpdate = time.Now().Add(blogCacheTime)
|
|
||||||
|
|
||||||
if err := os.MkdirAll(blogFileCache, 0755); err != nil {
|
|
||||||
logbuch.Error("Error creating blog file cache directory", logbuch.Fields{"err": err})
|
|
||||||
}
|
|
||||||
|
|
||||||
b.loadArticles()
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blog *Blog) GetArticle(id string) emvi.Article {
|
|
||||||
blog.refreshIfRequired()
|
|
||||||
return blog.articleMap[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blog *Blog) GetArticles() map[int][]emvi.Article {
|
|
||||||
blog.refreshIfRequired()
|
|
||||||
return blog.articlesYear
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blog *Blog) GetLatestArticles() []emvi.Article {
|
|
||||||
blog.refreshIfRequired()
|
|
||||||
articles := make([]emvi.Article, 0, 3)
|
|
||||||
i := 1
|
|
||||||
|
|
||||||
for _, v := range blog.articles {
|
|
||||||
articles = append(articles, v)
|
|
||||||
i++
|
|
||||||
|
|
||||||
if i > maxLatestArticles {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return articles
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blog *Blog) loadArticles() {
|
|
||||||
blog.m.Lock()
|
|
||||||
defer blog.m.Unlock()
|
|
||||||
logbuch.Info("Refreshing blog articleMap...")
|
|
||||||
articles, offset, count := make([]emvi.Article, 0), 0, 1
|
|
||||||
var err error
|
|
||||||
|
|
||||||
for count > 0 {
|
|
||||||
var results []emvi.Article
|
|
||||||
results, _, err = blog.client.FindArticles("", &emvi.ArticleFilter{
|
|
||||||
BaseSearch: emvi.BaseSearch{Offset: offset},
|
|
||||||
Tags: "blog",
|
|
||||||
SortPublished: emvi.SortDescending,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logbuch.Error("Error loading blog article", logbuch.Fields{"err": err})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += len(results)
|
|
||||||
count = len(results)
|
|
||||||
|
|
||||||
for _, article := range results {
|
|
||||||
articles = append(articles, article)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for i, article := range articles {
|
|
||||||
article.LatestArticleContent = blog.loadArticle(article)
|
|
||||||
articles[i] = article
|
|
||||||
}
|
|
||||||
|
|
||||||
blog.setArticles(articles)
|
|
||||||
}
|
|
||||||
|
|
||||||
blog.nextUpdate = time.Now().Add(blogCacheTime)
|
|
||||||
logbuch.Info("Done", logbuch.Fields{"count": len(articles)})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blog *Blog) loadArticle(article emvi.Article) *emvi.ArticleContent {
|
|
||||||
existingArticle := blog.articleMap[article.Id]
|
|
||||||
var content *emvi.ArticleContent
|
|
||||||
|
|
||||||
if len(existingArticle.Id) == 0 || !existingArticle.ModTime.Equal(article.ModTime) {
|
|
||||||
var err error
|
|
||||||
_, content, _, err = blog.client.GetArticle(article.Id, article.LatestArticleContent.LanguageId, 0)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logbuch.Error("Error loading article", logbuch.Fields{"err": err, "id": article.Id})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
blog.downloadAttachments(article.Id, content.Content)
|
|
||||||
content.Content = linkRegex.ReplaceAllString(content.Content, `href="/blog/$1"`)
|
|
||||||
content.Content = attachmentRegex.ReplaceAllString(content.Content, fmt.Sprintf(`$1="/static/blog/%s/$3"`, article.Id))
|
|
||||||
logbuch.Debug("Article loaded", logbuch.Fields{"id": article.Id})
|
|
||||||
} else {
|
|
||||||
content = existingArticle.LatestArticleContent
|
|
||||||
logbuch.Debug("Article up to date, skipping refreshing cache", logbuch.Fields{"id": article.Id})
|
|
||||||
}
|
|
||||||
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blog *Blog) downloadAttachments(id, content string) {
|
|
||||||
if _, err := os.Stat(filepath.Join(blogFileCache, id)); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(filepath.Join(blogFileCache, id), 0755); err != nil {
|
|
||||||
logbuch.Error("Error creating article file cache directory", logbuch.Fields{"err": err, "id": id})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results := attachmentURLRegex.FindAllStringSubmatch(content, -1)
|
|
||||||
|
|
||||||
for _, attachment := range results {
|
|
||||||
if len(attachment) == 4 {
|
|
||||||
resp, err := http.Get(attachment[2] + attachment[3])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logbuch.Error("Error downloading blog attachment", logbuch.Fields{"err": err, "id": id})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logbuch.Error("Error reading blog attachment body", logbuch.Fields{"err": err, "id": id})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := resp.Body.Close(); err != nil {
|
|
||||||
logbuch.Error("Error closing response body on attachment download", logbuch.Fields{"err": err, "id": id})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(filepath.Join(blogFileCache, id, attachment[3]), data, 0755); err != nil {
|
|
||||||
logbuch.Error("Error saving blog attachment on disk", logbuch.Fields{"err": err, "id": id})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blog *Blog) setArticles(articles []emvi.Article) {
|
|
||||||
blog.articles = articles
|
|
||||||
blog.articleMap = make(map[string]emvi.Article)
|
|
||||||
blog.articlesYear = make(map[int][]emvi.Article)
|
|
||||||
|
|
||||||
for _, article := range articles {
|
|
||||||
if blog.articlesYear[article.Published.Year()] == nil {
|
|
||||||
blog.articlesYear[article.Published.Year()] = make([]emvi.Article, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
blog.articlesYear[article.Published.Year()] = append(blog.articlesYear[article.Published.Year()], article)
|
|
||||||
blog.articleMap[article.Id] = article
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (blog *Blog) refreshIfRequired() {
|
|
||||||
if blog.nextUpdate.Before(time.Now()) {
|
|
||||||
blog.cache.Clear()
|
|
||||||
blog.loadArticles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
config.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[server]
|
||||||
|
host = "localhost"
|
||||||
|
port = 8080
|
||||||
9
content/index.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{{partial "head" .}}
|
||||||
|
|
||||||
|
<h2>Marvin Blum — Rheda-Wiedenbrück, Germany</h2>
|
||||||
|
<h1>Full-Stack Software Developer</h1>
|
||||||
|
<p>Hi, my name is Marvin. I'm a full-stack software engineer from Germany, open-source and Linux enthusiast, and co-founder of Emvi, where we build and maintain software projects for clients, and work on existing software.</p>
|
||||||
|
<p>I’m building Pirsch Analytics — a privacy-friendly, cookie-less web analytics solution made for personal websites, freelancers, and agencies.</p>
|
||||||
|
<button>Get in touch</button>
|
||||||
|
|
||||||
|
{{partial "end" .}}
|
||||||
18
dev.sh
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# This file is for local development only!
|
|
||||||
# It configures and starts the website for local development.
|
|
||||||
# The "secret" for the Emvi API can be shared, as it gives access to public content only.
|
|
||||||
|
|
||||||
export MB_LOGLEVEL=debug
|
|
||||||
export MB_ALLOWED_ORIGINS=*
|
|
||||||
export MB_HOST=localhost:8080
|
|
||||||
export MB_HOT_RELOAD=true
|
|
||||||
export MB_EMVI_CLIENT_ID=3fBBn144yvSF9R3dPC8l
|
|
||||||
export MB_EMVI_CLIENT_SECRET=
|
|
||||||
export MB_EMVI_ORGA=marvin
|
|
||||||
export MB_PIRSCH_CLIENT_ID=gEb3pvgxZvZzFRlOTdMgPtyLvNYgeVKe
|
|
||||||
export MB_PIRSCH_CLIENT_SECRET=E7UqJehmxgnVuw81oq6ZhJAx9vCHqMimCUFfil7UFgbGhgQVVINqU7JqHBgaUvHg
|
|
||||||
export MB_PIRSCH_HOSTNAME=marvinblum.de
|
|
||||||
|
|
||||||
go run main.go
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
|
||||||
traefik:
|
|
||||||
image: "traefik:v2.3"
|
|
||||||
container_name: traefik
|
|
||||||
restart: always
|
|
||||||
networks:
|
|
||||||
- traefik-internal
|
|
||||||
command:
|
|
||||||
- "--api.dashboard=true"
|
|
||||||
- "--providers.docker=true"
|
|
||||||
- "--providers.docker.exposedbydefault=false"
|
|
||||||
- "--providers.docker.network=marvinblum_traefik-internal"
|
|
||||||
- "--entrypoints.web.address=:80"
|
|
||||||
- "--entrypoints.websecure.address=:443"
|
|
||||||
- "--certificatesresolvers.tls-resolver.acme.httpchallenge=true"
|
|
||||||
- "--certificatesresolvers.tls-resolver.acme.httpchallenge.entrypoint=web"
|
|
||||||
- "--certificatesresolvers.tls-resolver.acme.email=marvin@marvinblum.de"
|
|
||||||
- "--certificatesresolvers.tls-resolver.acme.storage=/letsencrypt/acme.json"
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- /root/marvinblum/letsencrypt:/letsencrypt
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.port=9999"
|
|
||||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.traefik.rule=Host(`traefik.marvinblum.de`)"
|
|
||||||
- "traefik.http.routers.traefik.service=api@internal"
|
|
||||||
- "traefik.http.routers.traefik.tls.certresolver=tls-resolver"
|
|
||||||
- "traefik.http.routers.traefik.middlewares=traefik-auth"
|
|
||||||
- "traefik.http.middlewares.traefik-auth.basicauth.users=marvinblum:$$apr1$$u.IJozER$$DoY0zwzgAciDpPs4vZvxY/"
|
|
||||||
# Global redirection: http to https
|
|
||||||
- 'traefik.http.routers.http-catchall.rule=HostRegexp(`{host:(www\.)?.+}`)'
|
|
||||||
- "traefik.http.routers.http-catchall.entrypoints=web"
|
|
||||||
- "traefik.http.routers.http-catchall.middlewares=wwwtohttps"
|
|
||||||
# Global redirection: https (www.) to https
|
|
||||||
- 'traefik.http.routers.wwwsecure-catchall.rule=HostRegexp(`{host:(www\.).+}`)'
|
|
||||||
- "traefik.http.routers.wwwsecure-catchall.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.wwwsecure-catchall.tls=true"
|
|
||||||
- "traefik.http.routers.wwwsecure-catchall.middlewares=wwwtohttps"
|
|
||||||
# middleware: http(s)://(www.) to https://
|
|
||||||
- 'traefik.http.middlewares.wwwtohttps.redirectregex.regex=^https?://(?:www\.)?(.+)'
|
|
||||||
- 'traefik.http.middlewares.wwwtohttps.redirectregex.replacement=https://$${1}'
|
|
||||||
- 'traefik.http.middlewares.wwwtohttps.redirectregex.permanent=true'
|
|
||||||
marvinblum:
|
|
||||||
image: kugel/marvinblum
|
|
||||||
container_name: marvinblum
|
|
||||||
restart: always
|
|
||||||
depends_on:
|
|
||||||
- traefik
|
|
||||||
networks:
|
|
||||||
- traefik-internal
|
|
||||||
env_file:
|
|
||||||
- secrets.env
|
|
||||||
environment:
|
|
||||||
MB_EMVI_CLIENT_ID: 3fBBn144yvSF9R3dPC8l
|
|
||||||
MB_EMVI_ORGA: marvin
|
|
||||||
MB_PIRSCH_CLIENT_ID: mkiAzI2ZGjGBv8fpwh1A09fCJ8G1YFgx
|
|
||||||
MB_PIRSCH_HOSTNAME: marvinblum.de
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.port=8888"
|
|
||||||
- "traefik.http.routers.marvinblum.rule=Host(`marvinblum.de`) || Host(`www.marvinblum.de`)"
|
|
||||||
- "traefik.http.routers.marvinblum.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.marvinblum.tls=true"
|
|
||||||
- "traefik.http.routers.marvinblum.tls.certresolver=tls-resolver"
|
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik-internal:
|
|
||||||
driver: bridge
|
|
||||||
18
go.mod
@@ -1,18 +0,0 @@
|
|||||||
module github.com/Kugelschieber/marvinblum
|
|
||||||
|
|
||||||
go 1.15
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/emvi/api-go v0.2.2
|
|
||||||
github.com/emvi/logbuch v1.1.1
|
|
||||||
github.com/gorilla/mux v1.8.0
|
|
||||||
github.com/gosimple/slug v1.9.0
|
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
|
||||||
github.com/lib/pq v1.9.0
|
|
||||||
github.com/pirsch-analytics/pirsch-go-sdk v0.0.0-20201215183417-0e2a519a0dd1
|
|
||||||
github.com/rs/cors v1.7.0
|
|
||||||
github.com/stretchr/testify v1.6.1 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
|
||||||
)
|
|
||||||
33
go.sum
@@ -1,33 +0,0 @@
|
|||||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/emvi/api-go v0.2.2 h1:NrZNl0o0xAbgfK1dFsRt/BKLesHLVjC4OKXHKTZsIis=
|
|
||||||
github.com/emvi/api-go v0.2.2/go.mod h1:g9RdDC3s5ebCknAHQQ5PjoM2vRFSyyGoOUX3QkDKU+o=
|
|
||||||
github.com/emvi/logbuch v1.1.1/go.mod h1:J2Wgbr3BuSc1JO+D2MBVh6q3WPVSK5GzktwWz8pvkKw=
|
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
|
|
||||||
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
|
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
|
|
||||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/pirsch-analytics/pirsch-go-sdk v0.0.0-20201215183417-0e2a519a0dd1 h1:yn6F902YFyEMfXQIARsTXLW42TQKuH+BrKk0MjQu/ps=
|
|
||||||
github.com/pirsch-analytics/pirsch-go-sdk v0.0.0-20201215183417-0e2a519a0dd1/go.mod h1:PF2vnJw8FYcXQe6OTPQQcGn8l/agkpl7T4YO9d2aPSE=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
|
||||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
|
||||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
201
main.go
@@ -1,201 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/Kugelschieber/marvinblum/blog"
|
|
||||||
"github.com/Kugelschieber/marvinblum/tpl"
|
|
||||||
"github.com/NYTimes/gziphandler"
|
|
||||||
emvi "github.com/emvi/api-go"
|
|
||||||
"github.com/emvi/logbuch"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
"github.com/pirsch-analytics/pirsch-go-sdk"
|
|
||||||
"github.com/rs/cors"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
staticDir = "static"
|
|
||||||
staticDirPrefix = "/static/"
|
|
||||||
logTimeFormat = "2006-01-02_15:04:05"
|
|
||||||
envPrefix = "MB_"
|
|
||||||
shutdownTimeout = time.Second * 30
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
client *pirsch.Client
|
|
||||||
tplCache *tpl.Cache
|
|
||||||
blogInstance *blog.Blog
|
|
||||||
)
|
|
||||||
|
|
||||||
func serveAbout() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
go hit(r)
|
|
||||||
tplCache.Render(w, "about.html", struct {
|
|
||||||
Articles []emvi.Article
|
|
||||||
}{
|
|
||||||
blogInstance.GetLatestArticles(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveLegal() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
go hit(r)
|
|
||||||
tplCache.Render(w, "legal.html", nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveBlogPage() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
go hit(r)
|
|
||||||
tplCache.Render(w, "blog.html", struct {
|
|
||||||
Articles map[int][]emvi.Article
|
|
||||||
}{
|
|
||||||
blogInstance.GetArticles(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveBlogArticle() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
slug := strings.Split(vars["slug"], "-")
|
|
||||||
|
|
||||||
if len(slug) == 0 {
|
|
||||||
http.Redirect(w, r, "/notfound", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
article := blogInstance.GetArticle(slug[len(slug)-1])
|
|
||||||
|
|
||||||
if len(article.Id) == 0 {
|
|
||||||
http.Redirect(w, r, "/notfound", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// track the hit if the article was found, otherwise we don't care
|
|
||||||
go hit(r)
|
|
||||||
|
|
||||||
tplCache.RenderWithoutCache(w, "article.html", struct {
|
|
||||||
Title string
|
|
||||||
Content template.HTML
|
|
||||||
Published time.Time
|
|
||||||
}{
|
|
||||||
article.LatestArticleContent.Title,
|
|
||||||
template.HTML(article.LatestArticleContent.Content),
|
|
||||||
article.Published,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveTracking() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Redirect(w, r, "https://marvinblum.pirsch.io/", http.StatusFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveNotFound() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
tplCache.Render(w, "notfound.html", nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupRouter() *mux.Router {
|
|
||||||
router := mux.NewRouter()
|
|
||||||
router.PathPrefix(staticDirPrefix).Handler(http.StripPrefix(staticDirPrefix, gziphandler.GzipHandler(http.FileServer(http.Dir(staticDir)))))
|
|
||||||
router.Handle("/blog/{slug}", serveBlogArticle())
|
|
||||||
router.Handle("/blog", serveBlogPage())
|
|
||||||
router.Handle("/legal", serveLegal())
|
|
||||||
router.Handle("/tracking", serveTracking())
|
|
||||||
router.Handle("/", serveAbout())
|
|
||||||
router.NotFoundHandler = serveNotFound()
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureLog() {
|
|
||||||
logbuch.SetFormatter(logbuch.NewFieldFormatter(logTimeFormat, "\t\t"))
|
|
||||||
logbuch.Info("Configure logging...")
|
|
||||||
level := strings.ToLower(os.Getenv("MB_LOGLEVEL"))
|
|
||||||
|
|
||||||
if level == "debug" {
|
|
||||||
logbuch.SetLevel(logbuch.LevelDebug)
|
|
||||||
} else if level == "info" {
|
|
||||||
logbuch.SetLevel(logbuch.LevelInfo)
|
|
||||||
} else {
|
|
||||||
logbuch.SetLevel(logbuch.LevelWarning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logEnvConfig() {
|
|
||||||
for _, e := range os.Environ() {
|
|
||||||
if strings.HasPrefix(e, envPrefix) {
|
|
||||||
pair := strings.Split(e, "=")
|
|
||||||
logbuch.Info(pair[0] + "=" + pair[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureCors(router *mux.Router) http.Handler {
|
|
||||||
logbuch.Info("Configuring CORS...")
|
|
||||||
|
|
||||||
origins := strings.Split(os.Getenv("MB_ALLOWED_ORIGINS"), ",")
|
|
||||||
c := cors.New(cors.Options{
|
|
||||||
AllowedOrigins: origins,
|
|
||||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
|
||||||
AllowedHeaders: []string{"*"},
|
|
||||||
AllowCredentials: true,
|
|
||||||
Debug: strings.ToLower(os.Getenv("MB_CORS_LOGLEVEL")) == "debug",
|
|
||||||
})
|
|
||||||
return c.Handler(router)
|
|
||||||
}
|
|
||||||
|
|
||||||
func start(handler http.Handler) {
|
|
||||||
logbuch.Info("Starting server...")
|
|
||||||
var server http.Server
|
|
||||||
server.Handler = handler
|
|
||||||
server.Addr = os.Getenv("MB_HOST")
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
sigint := make(chan os.Signal)
|
|
||||||
signal.Notify(sigint, os.Interrupt)
|
|
||||||
<-sigint
|
|
||||||
logbuch.Info("Shutting down server...")
|
|
||||||
ctx, _ := context.WithTimeout(context.Background(), shutdownTimeout)
|
|
||||||
|
|
||||||
if err := server.Shutdown(ctx); err != nil {
|
|
||||||
logbuch.Fatal("Error shutting down server gracefully", logbuch.Fields{"err": err})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
|
||||||
logbuch.Fatal("Error starting server", logbuch.Fields{"err": err})
|
|
||||||
}
|
|
||||||
|
|
||||||
logbuch.Info("Server shut down")
|
|
||||||
}
|
|
||||||
|
|
||||||
func hit(r *http.Request) {
|
|
||||||
if err := client.Hit(r); err != nil {
|
|
||||||
logbuch.Warn("Error sending page hit to pirsch", logbuch.Fields{"err": err})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
configureLog()
|
|
||||||
logEnvConfig()
|
|
||||||
client = pirsch.NewClient(os.Getenv("MB_PIRSCH_CLIENT_ID"),
|
|
||||||
os.Getenv("MB_PIRSCH_CLIENT_SECRET"),
|
|
||||||
os.Getenv("MB_PIRSCH_HOSTNAME"),
|
|
||||||
nil)
|
|
||||||
tplCache = tpl.NewCache()
|
|
||||||
blogInstance = blog.NewBlog(tplCache)
|
|
||||||
router := setupRouter()
|
|
||||||
corsConfig := configureCors(router)
|
|
||||||
start(corsConfig)
|
|
||||||
}
|
|
||||||
2
partials/end.html
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
partials/head.html
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<base href="/" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="copyright" content="Marvin Blum" />
|
||||||
|
<meta name="author" content="Marvin Blum" />
|
||||||
|
<meta name="title" content="Marvin Blum — Full-Stack Software Developer" />
|
||||||
|
<meta name="description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||||
|
<meta name="twitter:card" content="profile" />
|
||||||
|
<meta name="twitter:site" content="@m5blum" />
|
||||||
|
<meta name="twitter:title" content="Marvin Blum" />
|
||||||
|
<meta name="twitter:description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||||
|
<meta name="twitter:image" content="https://marvinblum.de/assets/avatar.png" />
|
||||||
|
<meta property="og:url" content="https://marvinblum.de/" />
|
||||||
|
<meta property="og:title" content="Marvin Blum" />
|
||||||
|
<meta property="og:description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
||||||
|
<meta property="og:image" content="https://marvinblum.de/assets/avatar.png" />
|
||||||
|
<meta name="msapplication-TileColor" content="#ffffff">
|
||||||
|
<meta name="msapplication-TileImage" content="assets/favicon/ms-icon-144x144.png">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<link rel="apple-touch-icon" sizes="57x57" href="assets/favicon/apple-icon-57x57.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href="assets/favicon/apple-icon-60x60.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="assets/favicon/apple-icon-72x72.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="assets/favicon/apple-icon-76x76.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="assets/favicon/apple-icon-114x114.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href="assets/favicon/apple-icon-120x120.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="assets/favicon/apple-icon-144x144.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="assets/favicon/apple-icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-icon-180x180.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-192x192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="assets/favicon/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="assets/favicon/manifest.json">
|
||||||
|
<link rel="stylesheet" type="text/css" href="assets/style.css" />
|
||||||
|
<title>Marvin Blum — Full-Stack Software Developer</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
MB_EMVI_CLIENT_SECRET=
|
|
||||||
MB_PIRSCH_CLIENT_SECRET=
|
|
||||||
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,281 +0,0 @@
|
|||||||
/*! concrete.css v1.3.0 | MIT License | github.com/louismerlin/concrete.css/ */
|
|
||||||
|
|
||||||
/* The Basics */
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 62.5%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 400;
|
|
||||||
background: white;
|
|
||||||
color: #121212;
|
|
||||||
font-family: Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, ::after, ::before {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #121212;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote, dl, figure, form, ol, p, pre, table, ul {
|
|
||||||
margin-bottom: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A Cool Container */
|
|
||||||
|
|
||||||
.container {
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 66rem;
|
|
||||||
padding: 0 1rem;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The Button */
|
|
||||||
|
|
||||||
.button {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
.button,
|
|
||||||
input[type="button"],
|
|
||||||
input[type="reset"],
|
|
||||||
input[type="submit"] {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-radius: 0;
|
|
||||||
background: white;
|
|
||||||
line-height: 2.2rem;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
color: #121212;
|
|
||||||
border: 0.3rem solid #121212;
|
|
||||||
padding: 0.4rem 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.filled,
|
|
||||||
.button.filled,
|
|
||||||
input[type="button"].filled,
|
|
||||||
input[type="reset"].filled,
|
|
||||||
input[type="submit"].filled {
|
|
||||||
color: white;
|
|
||||||
background: #121212;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button,
|
|
||||||
button,
|
|
||||||
dd,
|
|
||||||
dt,
|
|
||||||
li,
|
|
||||||
input[type="button"],
|
|
||||||
input[type="reset"],
|
|
||||||
input[type="submit"] {
|
|
||||||
margin-bottom: 1.0rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The List */
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The Form */
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
border-width: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
label, legend {
|
|
||||||
display: block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="email"],
|
|
||||||
input[type="number"],
|
|
||||||
input[type="password"],
|
|
||||||
input[type="search"],
|
|
||||||
input[type="tel"],
|
|
||||||
input[type="text"],
|
|
||||||
input[type="url"],
|
|
||||||
textarea,
|
|
||||||
select {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0.1rem solid #121212;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
box-sizing: inherit;
|
|
||||||
padding: .6rem 1.0rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset, input, select, textarea {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The Table */
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
border-spacing: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td, th {
|
|
||||||
padding: 0.6rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
border-bottom: 0.1rem solid #121212;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
border-bottom: 0.3rem solid #121212;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The Blockquote and the Code */
|
|
||||||
|
|
||||||
blockquote, pre {
|
|
||||||
border-left: 0.3rem solid #121212;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
border: 0.1rem dotted #121212;
|
|
||||||
border-left: 0.3rem solid #121212;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre > code {
|
|
||||||
padding: 1rem 1.4rem;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
white-space: pre;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The Progress Bar */
|
|
||||||
|
|
||||||
progress {
|
|
||||||
-moz-appearance: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
border-radius: 0;
|
|
||||||
display: block;
|
|
||||||
height: 1rem;
|
|
||||||
border: 0.1rem solid #121212;
|
|
||||||
background: white;
|
|
||||||
color: #121212;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
progress::-webkit-progress-bar {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
progress::-webkit-progress-value {
|
|
||||||
background-color: #121212;
|
|
||||||
}
|
|
||||||
progress::-moz-progress-bar {
|
|
||||||
background-color: #121212;
|
|
||||||
}
|
|
||||||
progress::-ms-fill {
|
|
||||||
background-color: #121212;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The Break Line */
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 0.2rem solid #121212;
|
|
||||||
border-bottom-width: 0.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark Mode */
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
body {
|
|
||||||
background: #121212;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
button,
|
|
||||||
.button,
|
|
||||||
input[type="button"],
|
|
||||||
input[type="reset"],
|
|
||||||
input[type="submit"] {
|
|
||||||
background: #121212;
|
|
||||||
color: white;
|
|
||||||
border-color: white;
|
|
||||||
}
|
|
||||||
button.filled,
|
|
||||||
.button.filled,
|
|
||||||
input[type="button"].filled,
|
|
||||||
input[type="reset"].filled,
|
|
||||||
input[type="submit"].filled {
|
|
||||||
color: #121212;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
input[type="email"],
|
|
||||||
input[type="number"],
|
|
||||||
input[type="password"],
|
|
||||||
input[type="search"],
|
|
||||||
input[type="tel"],
|
|
||||||
input[type="text"],
|
|
||||||
input[type="url"],
|
|
||||||
textarea,
|
|
||||||
select {
|
|
||||||
color: white;
|
|
||||||
border-color: white;
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
border-bottom-color: white;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
border-bottom-color: white;
|
|
||||||
}
|
|
||||||
blockquote, pre {
|
|
||||||
border-left-color: white;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
border-color: white;
|
|
||||||
border-left-color: white;
|
|
||||||
}
|
|
||||||
progress {
|
|
||||||
border-color: white;
|
|
||||||
background: #121212;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
progress::-webkit-progress-bar {
|
|
||||||
background-color: #121212;
|
|
||||||
}
|
|
||||||
progress::-webkit-progress-value {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
progress::-moz-progress-bar {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
progress::-ms-fill {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
hr {
|
|
||||||
border-color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<browserconfig>
|
|
||||||
<msapplication>
|
|
||||||
<tile>
|
|
||||||
<square150x150logo src="/mstile-150x150.png"/>
|
|
||||||
<TileColor>#da532c</TileColor>
|
|
||||||
</tile>
|
|
||||||
</msapplication>
|
|
||||||
</browserconfig>
|
|
||||||
|
Before Width: | Height: | Size: 567 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
@@ -1,218 +0,0 @@
|
|||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
|
||||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
|
||||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="460.000000pt" height="460.000000pt" viewBox="0 0 460.000000 460.000000"
|
|
||||||
preserveAspectRatio="xMidYMid meet">
|
|
||||||
<metadata>
|
|
||||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
|
||||||
</metadata>
|
|
||||||
<g transform="translate(0.000000,460.000000) scale(0.100000,-0.100000)"
|
|
||||||
fill="#000000" stroke="none">
|
|
||||||
<path d="M2000 4199 c-80 -8 -163 -31 -185 -50 -5 -5 0 -9 14 -9 36 0 14 -18
|
|
||||||
-32 -25 -23 -4 -59 -18 -79 -31 -21 -13 -42 -24 -47 -24 -6 0 -16 -9 -24 -20
|
|
||||||
-7 -10 -32 -23 -56 -29 -48 -10 -91 -45 -91 -72 0 -10 -9 -21 -20 -24 -11 -3
|
|
||||||
-20 -15 -20 -26 0 -10 -6 -19 -13 -19 -15 0 -65 -40 -51 -40 5 0 -8 -11 -28
|
|
||||||
-23 l-37 -23 19 -29 c13 -20 16 -34 9 -46 -5 -10 -9 -27 -9 -38 0 -12 -4 -21
|
|
||||||
-10 -21 -5 0 -10 -7 -10 -17 0 -15 1 -15 18 0 10 10 24 17 31 17 17 0 -9 -46
|
|
||||||
-53 -93 -20 -21 -37 -48 -37 -60 -1 -12 -2 -33 -3 -47 -1 -14 -7 -25 -13 -25
|
|
||||||
-7 0 -13 -4 -12 -9 0 -5 -3 -12 -8 -15 -5 -3 -19 -23 -32 -43 -29 -49 -17 -70
|
|
||||||
15 -27 25 34 32 33 14 -2 -8 -13 -7 -19 0 -19 7 0 3 -12 -7 -27 -28 -41 -44
|
|
||||||
-83 -33 -83 9 0 4 -145 -5 -161 -13 -20 30 -293 45 -284 3 2 6 -15 7 -38 1
|
|
||||||
-23 4 -82 5 -132 2 -49 7 -97 10 -105 3 -8 14 -44 23 -80 19 -70 30 -90 51
|
|
||||||
-90 11 0 14 17 14 70 0 79 20 210 32 210 4 0 8 -5 8 -11 0 -27 75 -168 107
|
|
||||||
-201 90 -92 268 -126 391 -74 34 15 83 42 108 61 25 19 50 33 55 29 5 -3 9 0
|
|
||||||
9 7 0 7 3 10 6 6 8 -8 42 23 64 58 43 71 91 271 72 302 -4 6 -18 14 -31 18
|
|
||||||
-26 8 -81 59 -81 75 0 6 -5 10 -12 10 -6 0 -9 2 -6 5 7 7 -58 75 -72 75 -6 0
|
|
||||||
-22 7 -35 16 -14 9 -51 20 -82 24 -32 5 -99 14 -148 21 -101 14 -141 4 -160
|
|
||||||
-39 -5 -12 -17 -22 -27 -22 -25 0 -49 -11 -43 -20 3 -5 15 -6 28 -3 19 4 18 2
|
|
||||||
-5 -12 -16 -8 -28 -20 -28 -25 0 -6 -7 -10 -16 -10 -8 0 -13 4 -9 9 3 5 1 12
|
|
||||||
-4 15 -5 4 -8 -2 -7 -11 3 -19 -28 -42 -56 -43 -10 0 -18 6 -18 13 -4 122 -2
|
|
||||||
238 3 244 12 11 8 23 -7 23 -19 0 -29 30 -15 47 7 7 9 16 5 20 -3 3 2 15 11
|
|
||||||
25 14 15 15 21 4 27 -10 7 -10 13 -2 29 9 16 8 24 -4 36 -8 8 -11 17 -6 20 8
|
|
||||||
6 25 64 22 79 -1 5 2 6 7 3 4 -2 13 4 20 15 7 10 8 19 3 19 -6 0 -3 15 6 33
|
|
||||||
77 154 59 139 173 141 74 2 122 20 143 53 5 8 12 11 18 8 5 -4 9 -2 9 3 0 5
|
|
||||||
14 9 32 10 18 0 50 8 73 18 92 38 112 45 163 54 30 5 65 19 78 29 14 11 32 17
|
|
||||||
40 14 8 -3 14 -1 14 4 0 10 57 18 201 29 37 2 77 7 90 10 46 11 285 13 334 3
|
|
||||||
28 -6 61 -12 75 -15 93 -19 219 -123 268 -221 9 -18 19 -33 23 -33 4 0 10 -11
|
|
||||||
13 -24 11 -43 106 -121 106 -86 0 6 -4 10 -10 10 -15 0 -12 40 3 41 6 1 22 0
|
|
||||||
34 0 12 -1 26 5 31 15 12 22 4 54 -14 54 -9 0 -14 11 -14 30 0 17 -6 33 -14
|
|
||||||
36 -8 3 -17 16 -20 29 -3 13 -18 30 -34 37 -16 8 -26 21 -25 32 1 13 -2 15
|
|
||||||
-10 7 -18 -18 -27 -13 -27 15 0 14 -4 23 -9 19 -5 -3 -27 15 -48 40 -20 25
|
|
||||||
-43 43 -50 41 -6 -3 -27 13 -45 34 -18 21 -49 57 -68 79 -19 23 -39 41 -44 41
|
|
||||||
-5 0 -20 10 -33 22 -19 17 -23 19 -17 5 9 -24 -3 -21 -38 8 -43 36 -132 54
|
|
||||||
-371 75 -163 14 -292 32 -297 41 -9 14 -89 17 -180 8z m319 -51 c11 -11 12
|
|
||||||
-10 6 0 -6 11 -3 12 11 4 11 -5 47 -12 82 -16 35 -4 73 -11 85 -16 18 -7 14
|
|
||||||
-9 -20 -9 -30 -1 -43 -5 -43 -15 0 -8 -6 -17 -12 -19 -7 -3 25 -6 72 -7 141
|
|
||||||
-2 186 -10 141 -25 -12 -3 -21 -14 -20 -23 0 -11 3 -12 6 -4 7 17 23 15 23 -2
|
|
||||||
0 -10 -16 -17 -47 -21 -27 -3 -82 -10 -124 -16 -139 -20 -288 3 -264 40 6 11
|
|
||||||
-4 14 -35 12 -10 0 -11 1 -2 5 6 3 10 10 6 15 -4 8 69 15 117 11 12 -1 16 6
|
|
||||||
15 23 -1 23 -16 37 -16 15 0 -5 -7 -10 -15 -10 -8 0 -15 6 -15 14 0 16 -80 33
|
|
||||||
-99 21 -6 -3 -11 -2 -11 2 0 5 -12 8 -27 7 -16 0 -37 2 -48 5 -11 3 25 9 80
|
|
||||||
12 55 3 109 6 121 7 11 1 26 -4 33 -10z m291 -44 c0 -2 -9 -4 -20 -4 -11 0
|
|
||||||
-20 4 -20 9 0 5 9 7 20 4 11 -3 20 -7 20 -9z m-362 -1 c-10 -2 -26 -2 -35 0
|
|
||||||
-10 3 -2 5 17 5 19 0 27 -2 18 -5z m672 -88 c0 -5 -5 -3 -10 5 -5 8 -10 20
|
|
||||||
-10 25 0 6 5 3 10 -5 5 -8 10 -19 10 -25z m-230 5 c0 -5 -7 -10 -15 -10 -8 0
|
|
||||||
-15 5 -15 10 0 6 7 10 15 10 8 0 15 -4 15 -10z m-933 -1134 c-7 -17 22 -25
|
|
||||||
108 -33 76 -6 88 -17 40 -38 -31 -12 -40 -13 -47 -3 -4 8 -8 8 -8 2 0 -6 -36
|
|
||||||
-16 -81 -23 -65 -10 -86 -10 -110 1 -16 7 -29 16 -29 20 0 15 43 46 80 57 22
|
|
||||||
6 40 16 40 21 0 6 3 10 6 10 3 0 4 -6 1 -14z m-273 -72 l-49 -46 -3 -81 c-2
|
|
||||||
-45 1 -93 7 -107 9 -24 10 -24 11 10 5 81 11 106 32 122 22 16 23 15 45 -24
|
|
||||||
13 -22 23 -49 23 -59 0 -11 8 -20 18 -21 9 0 24 -1 32 -2 8 -1 16 -9 18 -19 2
|
|
||||||
-9 8 -17 14 -17 6 0 23 -14 38 -30 24 -26 34 -30 85 -31 45 -1 59 -5 62 -19 6
|
|
||||||
-22 140 -55 190 -45 l35 6 -21 -29 c-59 -84 -148 -126 -266 -126 -104 0 -168
|
|
||||||
25 -235 93 -67 68 -101 145 -108 249 -6 90 14 149 66 192 56 46 60 36 6 -16z
|
|
||||||
m-217 -36 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z m620 6 c12 -3
|
|
||||||
24 -12 27 -19 4 -11 -11 -14 -72 -12 -42 0 -88 -3 -102 -7 -22 -8 -23 -7 -11
|
|
||||||
8 23 28 106 44 158 30z m23 -114 c0 -5 -4 -10 -10 -10 -5 0 -10 5 -10 10 0 6
|
|
||||||
5 10 10 10 6 0 10 -4 10 -10z"/>
|
|
||||||
<path d="M2448 4193 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
|
||||||
<path d="M2523 4184 c3 -3 33 -12 67 -19 70 -15 72 -7 2 12 -52 13 -79 16 -69
|
|
||||||
7z"/>
|
|
||||||
<path d="M2674 4146 c23 -18 36 -20 36 -6 0 6 -4 10 -9 10 -5 0 -18 3 -28 6
|
|
||||||
-16 5 -16 4 1 -10z"/>
|
|
||||||
<path d="M2702 2976 c-19 -5 -40 -21 -50 -38 -9 -15 -24 -28 -32 -28 -9 0 -29
|
|
||||||
-13 -45 -30 -16 -16 -33 -30 -38 -30 -5 0 -37 -21 -70 -46 -50 -37 -65 -44
|
|
||||||
-86 -38 -34 10 -101 11 -101 2 0 -5 1 -8 3 -9 1 0 25 -6 52 -13 36 -9 53 -20
|
|
||||||
62 -37 14 -31 23 -109 12 -109 -5 0 -9 5 -9 12 0 6 -3 9 -6 5 -3 -3 -3 -19 1
|
|
||||||
-36 4 -21 13 -31 25 -31 10 0 22 -9 25 -20 9 -28 71 -119 107 -157 17 -17 58
|
|
||||||
-44 92 -59 52 -24 74 -29 146 -29 47 0 105 6 129 13 66 20 43 25 -30 6 -116
|
|
||||||
-30 -270 8 -341 83 -30 32 -103 171 -94 180 3 4 6 0 6 -7 0 -11 3 -11 11 -3
|
|
||||||
15 15 3 47 -16 40 -20 -8 -31 74 -16 124 30 101 200 177 411 184 91 3 112 1
|
|
||||||
159 -18 121 -49 167 -141 143 -284 -6 -37 -10 -68 -8 -70 2 -2 15 41 29 94 17
|
|
||||||
61 34 103 46 113 11 8 21 30 23 50 3 35 3 35 -57 51 -68 18 -151 54 -160 69
|
|
||||||
-3 5 -15 10 -25 10 -10 0 -25 7 -34 15 -8 8 -23 15 -33 16 -10 0 -13 3 -5 6 6
|
|
||||||
2 12 8 12 13 0 11 -49 19 -55 9 -5 -7 -23 -6 -125 4 -14 2 -40 -1 -58 -7z"/>
|
|
||||||
<path d="M3294 2929 c2 -25 9 -56 16 -70 13 -23 12 -24 -7 -6 -20 17 -20 16
|
|
||||||
-22 -40 -1 -36 -7 -60 -15 -65 -11 -6 -11 -17 1 -65 8 -32 14 -69 14 -83 0
|
|
||||||
-21 16 -131 34 -242 2 -15 1 -29 -4 -32 -8 -5 -13 -96 -6 -96 9 0 55 52 55 61
|
|
||||||
0 6 -4 8 -9 5 -4 -3 0 25 11 62 21 73 23 117 7 205 -11 61 -6 82 17 73 8 -3
|
|
||||||
14 1 15 12 1 9 5 4 10 -13 8 -30 8 -26 -4 65 -1 8 -7 24 -14 35 -6 11 -12 25
|
|
||||||
-13 30 -2 6 -4 15 -5 20 -12 40 -12 53 0 49 7 -3 14 5 17 20 3 14 1 23 -3 20
|
|
||||||
-5 -3 -9 1 -9 9 0 8 -12 23 -27 33 -15 9 -35 27 -45 38 -16 20 -17 20 -14 -25z
|
|
||||||
m50 -181 c-12 -19 -44 13 -44 44 l0 32 25 -34 c13 -18 22 -37 19 -42z"/>
|
|
||||||
<path d="M2640 2794 c-19 -7 -43 -13 -53 -13 -10 -1 -20 -8 -23 -16 -4 -8 -13
|
|
||||||
-15 -21 -15 -8 0 -11 -4 -8 -10 3 -5 2 -10 -2 -10 -5 0 -9 -16 -9 -35 0 -32 6
|
|
||||||
-40 28 -36 8 1 -4 -36 -22 -70 -19 -35 13 -52 118 -63 29 -3 32 -1 32 25 0 27
|
|
||||||
1 28 49 24 46 -4 51 -2 80 31 17 19 31 45 31 58 0 12 3 26 6 29 11 10 64 -26
|
|
||||||
64 -43 0 -11 11 -15 40 -16 47 -2 51 9 13 33 -16 10 -36 25 -46 34 -17 16
|
|
||||||
-102 40 -147 43 -27 1 -53 36 -32 43 6 3 12 9 12 14 0 13 -69 9 -110 -7z m13
|
|
||||||
-131 c2 -10 8 -26 12 -35 5 -14 2 -18 -13 -18 -27 0 -35 15 -28 46 7 28 22 32
|
|
||||||
29 7z m151 -4 c-8 -14 -24 -10 -24 6 0 9 6 12 15 9 8 -4 12 -10 9 -15z m-137
|
|
||||||
-101 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z"/>
|
|
||||||
<path d="M3502 2556 c-1 -14 2 -26 7 -26 5 0 8 -11 7 -24 -2 -14 1 -30 6 -38
|
|
||||||
5 -8 8 5 6 35 -2 55 -22 96 -26 53z"/>
|
|
||||||
<path d="M1225 2520 c-8 -14 4 -110 14 -110 12 0 13 108 2 114 -5 4 -13 2 -16
|
|
||||||
-4z"/>
|
|
||||||
<path d="M3115 2471 c-7 -23 -88 -109 -125 -133 -21 -14 -22 -16 -4 -12 42 11
|
|
||||||
158 143 141 161 -3 2 -8 -5 -12 -16z"/>
|
|
||||||
<path d="M1304 2298 c3 -10 1 -18 -4 -18 -6 0 -10 -9 -10 -20 0 -13 7 -20 19
|
|
||||||
-20 23 0 36 -16 21 -25 -12 -7 -4 -25 12 -25 15 0 3 52 -23 92 -11 18 -19 25
|
|
||||||
-15 16z"/>
|
|
||||||
<path d="M1955 2130 c3 -5 11 -10 16 -10 6 0 7 5 4 10 -3 6 -11 10 -16 10 -6
|
|
||||||
0 -7 -4 -4 -10z"/>
|
|
||||||
<path d="M2063 2104 c-7 -3 -13 -14 -13 -25 0 -10 -4 -19 -10 -19 -20 0 -9
|
|
||||||
-21 18 -33 15 -6 31 -17 35 -24 12 -19 30 -16 45 7 7 11 21 20 32 20 26 0 25
|
|
||||||
13 -4 45 -24 25 -77 40 -103 29z"/>
|
|
||||||
<path d="M2416 2081 c-3 -5 13 -11 37 -13 40 -5 55 -3 46 6 -11 9 -78 16 -83
|
|
||||||
7z"/>
|
|
||||||
<path d="M2033 1926 c4 -10 7 -20 7 -22 0 -2 7 -4 15 -4 8 0 15 -4 15 -10 0
|
|
||||||
-5 5 -10 11 -10 16 0 6 45 -11 45 -8 0 -21 5 -29 9 -11 7 -13 5 -8 -8z"/>
|
|
||||||
<path d="M1840 1840 c0 -19 3 -21 12 -12 9 9 9 15 0 24 -9 9 -12 7 -12 -12z"/>
|
|
||||||
<path d="M1790 1835 c-10 -12 -10 -15 4 -15 9 0 16 7 16 15 0 8 -2 15 -4 15
|
|
||||||
-2 0 -9 -7 -16 -15z"/>
|
|
||||||
<path d="M1895 1840 c-3 -5 3 -10 15 -10 12 0 18 5 15 10 -3 6 -10 10 -15 10
|
|
||||||
-5 0 -12 -4 -15 -10z"/>
|
|
||||||
<path d="M2110 1782 c0 -5 -6 -9 -12 -9 -7 0 -37 -5 -67 -12 -29 -6 -72 -9
|
|
||||||
-96 -5 -33 4 -44 2 -49 -11 -3 -10 -11 -13 -18 -9 -12 7 25 -38 65 -79 15 -16
|
|
||||||
21 -15 18 5 0 3 12 0 27 -7 35 -18 131 -35 137 -25 3 5 14 6 25 3 15 -4 20 0
|
|
||||||
20 14 0 13 8 19 25 21 14 1 25 -3 25 -8 0 -7 8 -6 23 1 12 7 51 14 87 15 36 2
|
|
||||||
101 5 145 8 48 2 82 0 86 -6 8 -12 119 0 119 13 0 5 -8 9 -17 10 -16 0 -43 5
|
|
||||||
-70 10 -4 1 -16 -1 -27 -6 -10 -4 -60 -3 -110 4 -51 6 -99 9 -108 6 -21 -8
|
|
||||||
-90 24 -81 38 3 6 1 7 -6 3 -6 -4 -18 0 -27 8 -9 9 -19 14 -24 11 -5 -3 -11
|
|
||||||
-1 -15 5 -7 12 -75 14 -75 2z"/>
|
|
||||||
<path d="M1573 1643 c-21 -8 -15 -30 10 -37 21 -7 21 -7 2 -15 -27 -11 -45 -3
|
|
||||||
-38 17 5 13 3 14 -7 5 -7 -6 -19 -9 -26 -6 -19 7 -18 -5 2 -24 9 -10 13 -25
|
|
||||||
10 -39 -3 -12 -2 -26 4 -29 5 -3 10 -13 10 -21 0 -15 40 -106 50 -114 4 -3 15
|
|
||||||
-21 26 -40 10 -19 21 -37 25 -40 16 -15 77 -125 70 -128 -11 -4 -1 -45 13 -54
|
|
||||||
5 -4 12 -22 14 -40 1 -18 12 -40 23 -50 22 -19 6 -31 -23 -17 -13 7 -15 5 -11
|
|
||||||
-11 6 -22 11 -24 32 -11 9 6 19 4 28 -4 15 -16 17 -45 3 -45 -7 0 -7 -6 0 -20
|
|
||||||
14 -26 34 -26 27 0 -3 11 -1 18 4 14 5 -3 9 -11 9 -18 0 -7 11 -23 25 -36 14
|
|
||||||
-13 25 -20 25 -16 0 4 10 1 23 -6 l22 -12 -25 -6 -25 -6 27 -6 c15 -3 25 -1
|
|
||||||
22 4 -3 5 23 6 62 2 50 -5 65 -4 60 4 -10 16 28 16 57 0 14 -7 72 -11 157 -11
|
|
||||||
164 1 195 5 246 32 21 12 43 21 47 21 12 0 81 32 87 41 3 3 19 15 35 25 17 11
|
|
||||||
51 41 75 68 25 27 53 57 63 67 9 11 17 23 17 27 0 5 12 19 26 33 38 36 65 84
|
|
||||||
58 104 -6 14 -3 16 14 11 17 -6 21 -4 16 8 -4 10 -1 16 7 16 7 0 10 3 7 6 -4
|
|
||||||
3 -18 -2 -32 -12 -25 -16 -28 -16 -46 1 -11 10 -23 15 -27 12 -3 -4 -1 -7 5
|
|
||||||
-7 6 0 16 -9 22 -20 9 -17 8 -19 -7 -13 -10 4 -29 17 -42 29 l-25 23 -11 -22
|
|
||||||
c-8 -17 -21 -23 -53 -25 -23 -2 -42 -8 -42 -13 0 -6 4 -8 9 -4 5 3 12 1 15 -4
|
|
||||||
11 -17 -10 -21 -30 -6 -26 19 -34 19 -28 -2 5 -17 5 -17 -9 0 -17 20 -35 23
|
|
||||||
-33 5 0 -7 -3 -13 -9 -13 -5 0 -10 8 -9 18 1 23 -17 22 -23 -3 -3 -11 -9 -17
|
|
||||||
-14 -14 -5 3 -9 -2 -9 -10 0 -9 9 -21 20 -28 23 -15 27 -40 5 -32 -8 4 -15 2
|
|
||||||
-15 -4 0 -6 -13 -24 -29 -40 -18 -17 -26 -33 -21 -38 5 -5 -4 -9 -23 -9 -24 0
|
|
||||||
-38 -7 -54 -29 -13 -17 -23 -36 -23 -44 0 -8 -5 -10 -12 -5 -7 4 -34 6 -61 5
|
|
||||||
-31 -1 -49 2 -51 10 -2 7 -7 22 -12 33 -6 18 -8 18 -11 3 -6 -24 -25 -23 -53
|
|
||||||
2 -16 14 -26 17 -30 9 -4 -6 -10 4 -14 23 -5 26 -9 30 -16 18 -8 -13 -10 -12
|
|
||||||
-10 3 0 9 -6 17 -12 18 -7 0 -28 20 -46 45 -26 36 -30 49 -22 64 5 10 10 24
|
|
||||||
10 30 0 15 -43 30 -51 18 -3 -5 -9 -3 -12 6 -5 11 0 16 14 16 29 0 16 15 -36
|
|
||||||
39 -45 21 -55 37 -34 58 13 13 -23 53 -63 71 -27 11 -32 20 -29 52 1 11 -9 15
|
|
||||||
-40 14 -25 0 -43 4 -46 13 -3 7 -9 10 -14 7 -6 -3 -17 0 -26 8 -12 9 -15 9 -9
|
|
||||||
1 4 -7 2 -13 -4 -13 -6 0 -8 14 -4 39 6 39 6 40 -40 62 -45 21 -48 21 -60 4
|
|
||||||
-12 -16 -13 -16 -19 -1 -7 16 -13 18 -34 9z m10 -87 c4 -10 1 -13 -8 -9 -8 3
|
|
||||||
-12 9 -9 14 7 12 11 11 17 -5z m37 -22 c0 -14 -4 -23 -9 -20 -5 3 -7 15 -4 26
|
|
||||||
7 28 13 25 13 -6z m1206 -299 c2 -10 5 -29 9 -42 3 -13 1 -23 -4 -23 -13 0
|
|
||||||
-17 8 -25 54 -5 28 -4 37 5 34 7 -2 13 -13 15 -23z m57 -15 c3 6 4 5 3 -2 -4
|
|
||||||
-18 -28 -16 -34 2 -4 13 -3 13 10 2 11 -9 18 -9 21 -2z m-242 -48 c-7 -2 -18
|
|
||||||
1 -23 6 -8 8 -4 9 13 5 13 -4 18 -8 10 -11z"/>
|
|
||||||
<path d="M2985 1340 c3 -6 -1 -13 -10 -16 -12 -5 -13 -9 -4 -14 6 -5 17 -3 24
|
|
||||||
3 13 13 9 37 -7 37 -5 0 -6 -5 -3 -10z"/>
|
|
||||||
<path d="M3117 1283 c-3 -5 -7 -35 -11 -68 -4 -33 -9 -82 -12 -108 l-6 -49 30
|
|
||||||
16 c65 34 201 7 236 -46 8 -13 13 -38 11 -58 -2 -19 -4 -41 -4 -47 -1 -7 -12
|
|
||||||
-13 -25 -13 -15 0 -26 -7 -29 -17 -4 -14 -5 -12 -6 5 -1 14 5 22 15 22 9 0 14
|
|
||||||
6 11 13 -2 6 -11 11 -21 9 -9 -2 -16 4 -16 13 0 18 -16 20 -24 3 -3 -7 -5 -1
|
|
||||||
-3 14 3 17 -4 36 -20 54 -12 15 -23 24 -23 19 0 -4 -6 -3 -12 1 -7 5 -35 9
|
|
||||||
-63 9 l-50 0 -9 -83 c-6 -53 -5 -100 1 -127 11 -47 9 -154 -2 -200 -15 -57
|
|
||||||
-78 -151 -149 -220 -99 -98 -201 -187 -234 -205 -15 -8 -41 -24 -57 -36 -42
|
|
||||||
-28 -150 -79 -212 -99 -38 -12 -70 -14 -125 -10 -83 7 -186 32 -208 50 -8 7
|
|
||||||
-28 13 -45 13 -16 0 -43 6 -60 13 -27 11 -54 17 -96 22 -8 1 -23 10 -33 20
|
|
||||||
-10 9 -21 17 -26 17 -14 0 -148 138 -155 159 -3 11 -15 23 -26 26 -17 6 -26
|
|
||||||
35 -20 68 1 4 -6 10 -14 13 -8 4 -15 12 -15 20 0 8 -5 14 -11 14 -6 0 -9 7 -5
|
|
||||||
15 3 8 1 15 -4 15 -6 0 -10 6 -10 14 0 8 -5 16 -12 18 -6 2 -13 15 -14 29 -2
|
|
||||||
13 -9 53 -15 89 -8 44 -10 129 -5 273 4 141 3 206 -4 202 -5 -3 -10 -12 -10
|
|
||||||
-19 0 -8 -6 -19 -13 -25 -14 -11 -37 -76 -43 -121 -5 -42 -6 -224 0 -240 5
|
|
||||||
-14 15 -90 22 -155 1 -16 3 -54 3 -83 1 -53 2 -54 85 -140 108 -112 178 -198
|
|
||||||
205 -250 12 -23 20 -42 17 -42 -5 0 -72 27 -111 45 -17 7 -51 23 -78 34 -26
|
|
||||||
11 -47 22 -47 24 0 2 26 2 58 1 l57 -3 -65 15 c-63 15 -132 42 -138 55 -1 3
|
|
||||||
-16 21 -32 39 -17 19 -30 40 -30 48 0 8 -12 29 -27 47 -16 18 -40 48 -56 66
|
|
||||||
-15 19 -36 45 -47 58 -11 13 -20 29 -20 36 0 7 -7 25 -15 42 -26 52 -14 220
|
|
||||||
16 239 5 3 9 12 9 20 0 14 7 28 89 193 23 46 40 85 38 88 -9 8 -101 -24 -167
|
|
||||||
-58 -74 -38 -197 -154 -254 -238 -20 -29 -36 -57 -36 -61 0 -4 -25 -24 -55
|
|
||||||
-45 -59 -39 -85 -77 -85 -123 0 -28 -6 -32 -107 -77 -60 -26 -171 -75 -248
|
|
||||||
-110 -77 -35 -180 -78 -230 -95 -50 -17 -117 -45 -150 -63 l-60 -32 -3 -117
|
|
||||||
-3 -118 625 0 624 0 16 35 c24 51 36 52 36 5 0 -29 4 -40 13 -37 6 2 12 16 11
|
|
||||||
30 -2 37 25 35 39 -3 l11 -30 613 0 c653 0 638 -1 696 50 16 14 57 45 91 70
|
|
||||||
34 25 90 65 124 90 34 25 85 61 113 81 27 20 98 81 156 135 124 115 219 179
|
|
||||||
312 209 21 6 20 5 -4 -32 -13 -21 -29 -43 -35 -50 -5 -7 -10 -15 -10 -19 0 -3
|
|
||||||
-9 -18 -20 -34 -11 -15 -16 -31 -12 -36 4 -5 3 -6 -2 -2 -8 7 -46 -32 -46 -47
|
|
||||||
0 -3 -13 -20 -30 -38 -16 -18 -30 -35 -30 -39 0 -4 -9 -10 -20 -13 -11 -3 -20
|
|
||||||
-12 -20 -19 0 -7 -11 -18 -25 -24 -14 -6 -25 -15 -25 -20 0 -6 -54 -51 -119
|
|
||||||
-97 -8 -5 -31 -27 -53 -48 -40 -39 -110 -71 -140 -63 -10 2 -18 0 -18 -6 0 -5
|
|
||||||
9 -12 20 -15 11 -3 18 -9 15 -13 -3 -5 -15 -6 -27 -2 -14 3 -19 2 -14 -6 5 -9
|
|
||||||
228 -12 872 -12 l864 0 0 123 0 122 -61 27 c-55 25 -61 30 -54 50 4 13 10 42
|
|
||||||
12 65 6 49 0 58 -114 191 -68 79 -106 132 -159 218 -39 64 -186 240 -236 282
|
|
||||||
-29 24 -75 55 -102 69 -65 33 -194 56 -271 48 -55 -6 -75 -1 -211 44 -121 41
|
|
||||||
-164 51 -217 51 -36 0 -68 -3 -70 -7z m253 -413 c0 -5 -7 -7 -15 -4 -11 5 -13
|
|
||||||
2 -9 -10 4 -11 1 -16 -10 -16 -14 0 -15 4 -6 20 11 20 40 28 40 10z"/>
|
|
||||||
<path d="M2450 1085 c0 -8 5 -15 10 -15 6 0 10 7 10 15 0 8 -4 15 -10 15 -5 0
|
|
||||||
-10 -7 -10 -15z"/>
|
|
||||||
<path d="M2705 61 c-3 -6 4 -9 15 -8 11 1 20 5 20 9 0 11 -28 10 -35 -1z"/>
|
|
||||||
<path d="M2760 60 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
|
|
||||||
-10 -4 -10 -10z"/>
|
|
||||||
<path d="M2790 60 c0 -5 7 -10 16 -10 8 0 12 5 9 10 -3 6 -10 10 -16 10 -5 0
|
|
||||||
-9 -4 -9 -10z"/>
|
|
||||||
<path d="M2835 50 c3 -5 10 -10 16 -10 5 0 9 5 9 10 0 6 -7 10 -16 10 -8 0
|
|
||||||
-12 -4 -9 -10z"/>
|
|
||||||
<path d="M2835 10 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 -7
|
|
||||||
-4 -4 -10z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "",
|
|
||||||
"short_name": "",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/android-chrome-192x192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/android-chrome-384x384.png",
|
|
||||||
"sizes": "384x384",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"theme_color": "#ffffff",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
"display": "standalone"
|
|
||||||
}
|
|
||||||
7
static/js/Chart-v2.9.3.bundle.min.js
vendored
349
static/normalize.css
vendored
@@ -1,349 +0,0 @@
|
|||||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
|
||||||
|
|
||||||
/* Document
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct the line height in all browsers.
|
|
||||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
html {
|
|
||||||
line-height: 1.15; /* 1 */
|
|
||||||
-webkit-text-size-adjust: 100%; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sections
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the margin in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the `main` element consistently in IE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
main {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Correct the font size and margin on `h1` elements within `section` and
|
|
||||||
* `article` contexts in Chrome, Firefox, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2em;
|
|
||||||
margin: 0.67em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grouping content
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Add the correct box sizing in Firefox.
|
|
||||||
* 2. Show the overflow in Edge and IE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
box-sizing: content-box; /* 1 */
|
|
||||||
height: 0; /* 1 */
|
|
||||||
overflow: visible; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
|
||||||
* 2. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
pre {
|
|
||||||
font-family: monospace, monospace; /* 1 */
|
|
||||||
font-size: 1em; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Text-level semantics
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the gray background on active links in IE 10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Remove the bottom border in Chrome 57-
|
|
||||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbr[title] {
|
|
||||||
border-bottom: none; /* 1 */
|
|
||||||
text-decoration: underline; /* 2 */
|
|
||||||
text-decoration: underline dotted; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
|
||||||
* 2. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp {
|
|
||||||
font-family: monospace, monospace; /* 1 */
|
|
||||||
font-size: 1em; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the correct font size in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
|
||||||
* all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Embedded content
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the border on images inside links in IE 10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img {
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forms
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Change the font styles in all browsers.
|
|
||||||
* 2. Remove the margin in Firefox and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit; /* 1 */
|
|
||||||
font-size: 100%; /* 1 */
|
|
||||||
line-height: 1.15; /* 1 */
|
|
||||||
margin: 0; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the overflow in IE.
|
|
||||||
* 1. Show the overflow in Edge.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input { /* 1 */
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
|
||||||
* 1. Remove the inheritance of text transform in Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select { /* 1 */
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[type="button"],
|
|
||||||
[type="reset"],
|
|
||||||
[type="submit"] {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the inner border and padding in Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button::-moz-focus-inner,
|
|
||||||
[type="button"]::-moz-focus-inner,
|
|
||||||
[type="reset"]::-moz-focus-inner,
|
|
||||||
[type="submit"]::-moz-focus-inner {
|
|
||||||
border-style: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restore the focus styles unset by the previous rule.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button:-moz-focusring,
|
|
||||||
[type="button"]:-moz-focusring,
|
|
||||||
[type="reset"]:-moz-focusring,
|
|
||||||
[type="submit"]:-moz-focusring {
|
|
||||||
outline: 1px dotted ButtonText;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Correct the padding in Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
padding: 0.35em 0.75em 0.625em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct the text wrapping in Edge and IE.
|
|
||||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
|
||||||
* 3. Remove the padding so developers are not caught out when they zero out
|
|
||||||
* `fieldset` elements in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
legend {
|
|
||||||
box-sizing: border-box; /* 1 */
|
|
||||||
color: inherit; /* 2 */
|
|
||||||
display: table; /* 1 */
|
|
||||||
max-width: 100%; /* 1 */
|
|
||||||
padding: 0; /* 3 */
|
|
||||||
white-space: normal; /* 1 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
|
||||||
*/
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the default vertical scrollbar in IE 10+.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Add the correct box sizing in IE 10.
|
|
||||||
* 2. Remove the padding in IE 10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type="checkbox"],
|
|
||||||
[type="radio"] {
|
|
||||||
box-sizing: border-box; /* 1 */
|
|
||||||
padding: 0; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type="number"]::-webkit-inner-spin-button,
|
|
||||||
[type="number"]::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct the odd appearance in Chrome and Safari.
|
|
||||||
* 2. Correct the outline style in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type="search"] {
|
|
||||||
-webkit-appearance: textfield; /* 1 */
|
|
||||||
outline-offset: -2px; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the inner padding in Chrome and Safari on macOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type="search"]::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
* 2. Change font properties to `inherit` in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
-webkit-appearance: button; /* 1 */
|
|
||||||
font: inherit; /* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Interactive
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
details {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Add the correct display in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Misc
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the correct display in IE 10+.
|
|
||||||
*/
|
|
||||||
|
|
||||||
template {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the correct display in IE 10.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
138
static/style.css
@@ -1,138 +0,0 @@
|
|||||||
html, body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
max-width: 700px;
|
|
||||||
padding: 100px 20px 400px 20px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title div {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title h1 {
|
|
||||||
font-size: 48px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
margin-top: 0;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title img {
|
|
||||||
max-width: 100px;
|
|
||||||
max-height: 100px;
|
|
||||||
border-radius: 100%;
|
|
||||||
display: block;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu .button {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracking {
|
|
||||||
width: 100%;
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracking-form {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracking-form input {
|
|
||||||
margin: 3px 6px 3px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
border: 1px dotted #121212;
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed .image {
|
|
||||||
max-width: 200px;
|
|
||||||
max-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed .info {
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed .info .title {
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed .info .url {
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.break-line-anywhere {
|
|
||||||
line-break: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin: 80px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
|
||||||
body {
|
|
||||||
padding: 40px 20px 80px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title h1 {
|
|
||||||
font-size: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title h2 {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title img {
|
|
||||||
max-width: 72px;
|
|
||||||
max-height: 72px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tracking-form {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
{{template "head.html"}}
|
|
||||||
|
|
||||||
<div class="title">
|
|
||||||
<div>
|
|
||||||
<h1>marvin blum</h1>
|
|
||||||
<h2>welcome to my website!</h2>
|
|
||||||
</div>
|
|
||||||
<img src="../static/avatar_100.jpg" alt="Marvin Blum" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "menu.html"}}
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Who Am I?</h2>
|
|
||||||
<p>
|
|
||||||
I'm a full stack software engineer from Germany, open source and Linux enthusiast and co-founder of <a href="https://emvi.com/" target="_blank">Emvi</a>.
|
|
||||||
In love with Go, but fluent in a lot of programming languages.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Latest Blog Posts</h2>
|
|
||||||
{{range $article := .Articles}}
|
|
||||||
<p>
|
|
||||||
<small>{{format $article.Published "2. January 2006"}}</small>
|
|
||||||
<br />
|
|
||||||
<a href="/blog/{{slug $article.LatestArticleContent.Title}}-{{$article.Id}}">{{$article.LatestArticleContent.Title}}</a>
|
|
||||||
</p>
|
|
||||||
{{else}}
|
|
||||||
<p>There are no blog posts yet...</p>
|
|
||||||
{{end}}
|
|
||||||
<p>
|
|
||||||
<a href="/blog">View all</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Projects</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://emvi.com/" target="_blank">Emvi</a> a note taking, collaboration and knowledge management tool for personal use and teams of any size
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/pirsch-analytics/pirsch" target="_blank">pirsch</a> a server side, no-cookie and privacy focused tracking library for Golang
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/emvi/logbuch" target="_blank">logbuch</a> a simple Golang logging library
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/emvi/null" target="_blank">null</a> a Golang library for nullable types supporting databases and json
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/emvi/hide" target="_blank">hide</a> a Golang library to obscure IDs on the API layer
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://github.com/assetto-corsa-web/accweb" target="_blank">accweb</a> a web interface to manage game servers
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Skills</h2>
|
|
||||||
<ul>
|
|
||||||
<li>Go (Golang)</li>
|
|
||||||
<li>JavaScript (Vue, Node)</li>
|
|
||||||
<li>HTML, CSS, Sass and all the web fuzz</li>
|
|
||||||
<li>Java</li>
|
|
||||||
<li>PHP</li>
|
|
||||||
<li>Linux</li>
|
|
||||||
<li>Docker</li>
|
|
||||||
<li>Kubernetes</li>
|
|
||||||
<li>... and more</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Work</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://pirsch.io/" target="_blank">Pirsch</a> co-founder of a privacy-friendly, open-source, web analytics SaaS and library
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://emvi.com/" target="_blank">Emvi</a> co-founder of a note taking, collaboration and knowledge management SaaS startup
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://skalar.marketing/" target="_blank">skalar marketing</a> as a full stack developer and web designer
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://www.bertelsmann.com/divisions/arvato/#st-1" target="_blank">arvato</a> as a Java developer
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
some freelancing from time to time
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{{template "end.html"}}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{{template "head.html"}}
|
|
||||||
{{template "menu.html"}}
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h1>{{.Title}}</h1>
|
|
||||||
<small>Published on {{format .Published "2. January 2006"}}</small>
|
|
||||||
{{.Content}}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{{template "end.html"}}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{{template "head.html"}}
|
|
||||||
{{template "menu.html"}}
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h1>Blog</h1>
|
|
||||||
</section>
|
|
||||||
{{range $year, $articles := .Articles}}
|
|
||||||
<section>
|
|
||||||
<h2>{{$year}}</h2>
|
|
||||||
|
|
||||||
{{range $article := $articles}}
|
|
||||||
<p>
|
|
||||||
<small>{{format $article.Published "2. January 2006"}}</small>
|
|
||||||
<br />
|
|
||||||
<a href="/blog/{{slug $article.LatestArticleContent.Title}}-{{$article.Id}}">{{$article.LatestArticleContent.Title}}</a>
|
|
||||||
</p>
|
|
||||||
{{end}}
|
|
||||||
</section>
|
|
||||||
{{else}}
|
|
||||||
<section>
|
|
||||||
<p>There are no blog posts yet...</p>
|
|
||||||
</section>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{template "end.html"}}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<hr />
|
|
||||||
<section>
|
|
||||||
<p>
|
|
||||||
Would you like to see more? Read my blog articles on <a href="https://emvi.com/blog" target="_blank">Emvi</a>, my project page on <a href="https://github.com/Kugelschieber" target="_blank">GitHub</a> or send me a <a href="mailto:marvin@marvinblum.de">mail</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This page uses <a href="https://concrete.style/" target="_blank">concrete</a> for styling. Check it out!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This page does not use cookies. <a href="/legal">Legal</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<base href="/" />
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta name="copyright" content="Marvin Blum" />
|
|
||||||
<meta name="author" content="Marvin Blum" />
|
|
||||||
<meta name="title" content="Marvin Blum" />
|
|
||||||
<meta name="description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
|
||||||
<meta name="msapplication-TileColor" content="#000000" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta name="twitter:card" content="profile" />
|
|
||||||
<meta name="twitter:site" content="@m5blum" />
|
|
||||||
<meta name="twitter:title" content="Marvin Blum" />
|
|
||||||
<meta name="twitter:description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
|
||||||
<meta name="twitter:image" content="https://marvinblum.de/avatar.png" />
|
|
||||||
<meta property="og:url" content="https://marvinblum.de/" />
|
|
||||||
<meta property="og:title" content="Marvin Blum" />
|
|
||||||
<meta property="og:description" content="A full stack software engineer from Germany, open source and Linux enthusiast and co-founder of Emvi." />
|
|
||||||
<meta property="og:image" content="https://marvinblum.de/avatar.png" />
|
|
||||||
<title>marvin blum</title>
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="../static/favicon/apple-touch-icon.png" />
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="../static/favicon/favicon-32x32.png" />
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="../static/favicon/favicon-16x16.png" />
|
|
||||||
<link rel="manifest" href="../static/favicon/site.webmanifest" />
|
|
||||||
<link rel="mask-icon" href="../static/favicon/safari-pinned-tab.svg" color="#5bbad5" />
|
|
||||||
<meta name="msapplication-TileColor" content="#da532c" />
|
|
||||||
<meta name="theme-color" content="#ffffff" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="../static/normalize.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="../static/concrete.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="../static/style.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{{template "head.html"}}
|
|
||||||
{{template "menu.html"}}
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h1>Legal</h1>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>According to §5 TMG</h2>
|
|
||||||
<p>
|
|
||||||
Marvin Blum<br />
|
|
||||||
Gerhard-Hauptmannstraße 3<br />
|
|
||||||
33378 Rheda-Wiedenbrück, Germany<br />
|
|
||||||
<a href="mailto:marvin@marvinblum.de">marvin@marvinblum.de</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Cookie Policy</h2>
|
|
||||||
<p>
|
|
||||||
This page does not use cookies.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{{template "end.html"}}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<div class="menu">
|
|
||||||
<a href="/" class="button filled">About</a>
|
|
||||||
<a href="/blog" class="button filled">Blog</a>
|
|
||||||
<a href="mailto:marvin@marvinblum.de" class="button">Contact me</a>
|
|
||||||
<a href="https://github.com/Kugelschieber" target="_blank" class="button">GitHub</a>
|
|
||||||
<a href="https://twitter.com/m5blum" target="_blank" class="button">Twitter</a>
|
|
||||||
</div>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{{template "head.html"}}
|
|
||||||
{{template "menu.html"}}
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Page not found</h2>
|
|
||||||
<p>
|
|
||||||
Nothing to see here...<br />
|
|
||||||
<a href="/">Return home</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{{template "end.html"}}
|
|
||||||
119
tpl/template.go
@@ -1,119 +0,0 @@
|
|||||||
package tpl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/emvi/logbuch"
|
|
||||||
"github.com/gosimple/slug"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
templateDir = "template/*"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Cache struct {
|
|
||||||
tpl *template.Template
|
|
||||||
cache map[string][]byte
|
|
||||||
hotReload bool
|
|
||||||
m sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCache() *Cache {
|
|
||||||
cache := &Cache{
|
|
||||||
cache: make(map[string][]byte),
|
|
||||||
hotReload: os.Getenv("MB_HOT_RELOAD") == "true",
|
|
||||||
}
|
|
||||||
logbuch.Debug("Template cache hot reload", logbuch.Fields{"hot_reload": cache.hotReload})
|
|
||||||
cache.load()
|
|
||||||
return cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *Cache) load() {
|
|
||||||
logbuch.Debug("Loading templates")
|
|
||||||
funcMap := template.FuncMap{
|
|
||||||
"slug": slug.Make,
|
|
||||||
"format": func(t time.Time, layout string) string { return t.Format(layout) },
|
|
||||||
"multiply": func(a, b float64) float64 { return a * b },
|
|
||||||
"divide": func(a, b float64) float64 { return a / b },
|
|
||||||
"round": func(f float64) string { return fmt.Sprintf("%.2f", f) },
|
|
||||||
"intRange": intRange,
|
|
||||||
"float64": func(i int) float64 { return float64(i) },
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
cache.tpl, err = template.New("").Funcs(funcMap).ParseGlob(templateDir)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logbuch.Fatal("Error loading template", logbuch.Fields{"err": err})
|
|
||||||
}
|
|
||||||
|
|
||||||
logbuch.Debug("Templates loaded", logbuch.Fields{"hot_reload": cache.hotReload})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *Cache) Render(w http.ResponseWriter, name string, data interface{}) {
|
|
||||||
cache.m.RLock()
|
|
||||||
|
|
||||||
if cache.cache[name] == nil || cache.hotReload {
|
|
||||||
cache.m.RUnlock()
|
|
||||||
cache.m.Lock()
|
|
||||||
defer cache.m.Unlock()
|
|
||||||
logbuch.Debug("Rendering template", logbuch.Fields{"name": name})
|
|
||||||
|
|
||||||
if cache.hotReload {
|
|
||||||
logbuch.Debug("Reloading templates")
|
|
||||||
cache.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
if err := cache.tpl.ExecuteTemplate(&buffer, name, data); err != nil {
|
|
||||||
logbuch.Error("Error executing template", logbuch.Fields{"err": err, "name": name})
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.cache[name] = buffer.Bytes()
|
|
||||||
} else {
|
|
||||||
cache.m.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := w.Write(cache.cache[name]); err != nil {
|
|
||||||
logbuch.Error("Error sending response to client", logbuch.Fields{"err": err, "template": name})
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *Cache) RenderWithoutCache(w http.ResponseWriter, name string, data interface{}) {
|
|
||||||
if cache.hotReload {
|
|
||||||
logbuch.Debug("Reloading templates")
|
|
||||||
cache.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cache.tpl.ExecuteTemplate(w, name, data); err != nil {
|
|
||||||
logbuch.Error("Error executing template", logbuch.Fields{"err": err, "name": name})
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *Cache) Clear() {
|
|
||||||
cache.m.Lock()
|
|
||||||
defer cache.m.Unlock()
|
|
||||||
cache.cache = make(map[string][]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func intRange(start, end int) []int {
|
|
||||||
if end-start < 0 {
|
|
||||||
return []int{}
|
|
||||||
}
|
|
||||||
|
|
||||||
r := make([]int, end-start)
|
|
||||||
|
|
||||||
for i := 0; i < end-start; i++ {
|
|
||||||
r[i] = start + i
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||