Init
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
.venv
|
||||
out
|
||||
node_modules
|
||||
__pycache__/
|
||||
|
||||
package-lock.json
|
||||
package.json
|
||||
|
||||
cheatsheet_inventory.json
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
||||
45
.vscode/tasks.json
vendored
Normal file
45
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Run build Script",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/.venv/bin/python",
|
||||
"args": ["src/build.py"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Watch Styles",
|
||||
"type": "shell",
|
||||
"command": "sass",
|
||||
"args": ["--watch", "styles:static/css"],
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"pattern": {
|
||||
"regexp": "^.*$",
|
||||
"file": 1,
|
||||
"location": 2,
|
||||
"message": 3
|
||||
},
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "^.*Watching.*$",
|
||||
"endsPattern": "^.*Watching.*$"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run debug server",
|
||||
"type": "shell",
|
||||
"command": "${workspaceFolder}/.venv/bin/python",
|
||||
"args": ["src/debug_server.py"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM python:3.11 AS build
|
||||
|
||||
WORKDIR /workdir
|
||||
COPY . .
|
||||
|
||||
RUN pip install --no-cache-dir -r ./requirements.txt
|
||||
RUN python3 src/build.py
|
||||
|
||||
FROM caddy:latest AS serve
|
||||
|
||||
COPY --from=build /workdir/out/ /www/
|
||||
COPY --from=build /workdir/Caddyfile /etc/caddy/Caddyfile
|
||||
|
||||
1
FSSquared
Submodule
1
FSSquared
Submodule
Submodule FSSquared added at 09a4a01729
7
LICENSE.md
Normal file
7
LICENSE.md
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
Copyright 2026 Alexander
|
||||
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.
|
||||
6
docker-compose.yml
Normal file
6
docker-compose.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
services:
|
||||
server_html:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
20
requirements.txt
Normal file
20
requirements.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
annotated-types==0.7.0
|
||||
blinker==1.9.0
|
||||
certifi==2026.1.4
|
||||
charset-normalizer==3.4.4
|
||||
click==8.3.1
|
||||
Flask==3.1.2
|
||||
idna==3.11
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
libsass==0.23.0
|
||||
livereload==2.7.1
|
||||
MarkupSafe==3.0.3
|
||||
pydantic==2.12.5
|
||||
pydantic_core==2.41.5
|
||||
requests==2.32.5
|
||||
tornado==6.5.4
|
||||
typing-inspection==0.4.2
|
||||
typing_extensions==4.15.0
|
||||
urllib3==2.6.3
|
||||
Werkzeug==3.1.5
|
||||
45
src/build.py
Normal file
45
src/build.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
import shutil
|
||||
import datetime
|
||||
import os
|
||||
|
||||
from sources import CSItem
|
||||
from inventory import load_cheatsheet_inventory, prepare_cheatsheets
|
||||
|
||||
INVENTORY_FILE = "cheatsheet_inventory.json"
|
||||
STATIC_DIR = "static"
|
||||
TEMPLATES_DIR = "templates"
|
||||
OUTPUT_DIR = "out"
|
||||
|
||||
inv_raw = load_cheatsheet_inventory(INVENTORY_FILE)
|
||||
|
||||
# Clear output directory
|
||||
shutil.rmtree(OUTPUT_DIR, ignore_errors=True)
|
||||
shutil.copytree(STATIC_DIR, OUTPUT_DIR)
|
||||
|
||||
inv: list[CSItem] = prepare_cheatsheets(inv_raw, OUTPUT_DIR)
|
||||
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(TEMPLATES_DIR),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
|
||||
index = env.get_template("index.html.j2")
|
||||
|
||||
print(f"{len(inv)} Cheatsheets")
|
||||
for i in inv:
|
||||
print("-", i)
|
||||
|
||||
thisYear = datetime.datetime.now().year
|
||||
|
||||
with open(f"{OUTPUT_DIR}/index.html", "w", encoding="utf-8") as f:
|
||||
f.write(index.render(items=inv, thisYear=thisYear))
|
||||
|
||||
with open(f"{OUTPUT_DIR}/impressum.html", "w", encoding="utf-8") as f:
|
||||
f.write(env.get_template("impressum.html.j2").render(thisYear=thisYear))
|
||||
|
||||
with open(f"{OUTPUT_DIR}/license.html", "w", encoding="utf-8") as f:
|
||||
f.write(env.get_template("license.html.j2").render(thisYear=thisYear))
|
||||
|
||||
51
src/inventory.py
Normal file
51
src/inventory.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from sources import CSInventoryConfig, CSItem, CheatsheetSourceType
|
||||
from sources.plain import process_plain_url
|
||||
from sources.gitea import process_gitea
|
||||
|
||||
|
||||
def load_cheatsheet_inventory(file: str) -> CSInventoryConfig:
|
||||
if not os.path.exists(file):
|
||||
res = CSInventoryConfig(items=[])
|
||||
else:
|
||||
res = CSInventoryConfig.model_validate_json(
|
||||
open(file, "r", encoding="utf-8").read()
|
||||
)
|
||||
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
f.write(res.model_dump_json(indent=4))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def prepare_cheatsheets(config: CSInventoryConfig, outdir: str) -> list[CSItem]:
|
||||
res: list[CSItem] = []
|
||||
|
||||
for item in config.items:
|
||||
new_items = []
|
||||
|
||||
try:
|
||||
match item.source.type:
|
||||
case CheatsheetSourceType.GITEA_SOURCE:
|
||||
new_items += process_gitea(item, outdir)
|
||||
|
||||
case CheatsheetSourceType.PLAIN_URL:
|
||||
new_items.append(process_plain_url(item, outdir))
|
||||
|
||||
case _:
|
||||
print("Unknow Source Type:", item.source.type)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
new_item = None
|
||||
|
||||
if new_items:
|
||||
for new_item in new_items:
|
||||
print("->", new_item)
|
||||
res.append(new_item)
|
||||
|
||||
|
||||
return res
|
||||
|
||||
|
||||
50
src/sources/__init__.py
Normal file
50
src/sources/__init__.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from enum import Enum
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from uuid import uuid4
|
||||
|
||||
# Configuration
|
||||
class CheatsheetSourceType(str, Enum):
|
||||
GITEA_SOURCE = "gitea"
|
||||
PLAIN_URL = "url"
|
||||
|
||||
class CSSourceBase(BaseModel):
|
||||
type: CheatsheetSourceType
|
||||
|
||||
class CSSourceGitea(CSSourceBase):
|
||||
type: Literal[CheatsheetSourceType.GITEA_SOURCE]
|
||||
base_url: str
|
||||
repo: str
|
||||
owner: str
|
||||
tag: str
|
||||
hide_repo: bool = False
|
||||
|
||||
class CSSourcePlainURL(CSSourceBase):
|
||||
type: Literal[CheatsheetSourceType.PLAIN_URL]
|
||||
url: str
|
||||
repo_url: str
|
||||
|
||||
CSSourceType = CSSourcePlainURL | CSSourceGitea
|
||||
|
||||
class CSInventoryItem(BaseModel):
|
||||
source: CSSourceType
|
||||
cache: bool
|
||||
id: str = Field(default_factory=lambda: uuid4().__str__())
|
||||
title: str
|
||||
author: str | None
|
||||
|
||||
class CSInventoryConfig(BaseModel):
|
||||
items: list[CSInventoryItem]
|
||||
|
||||
# Resulting Rendering icon
|
||||
class CSItem(BaseModel):
|
||||
url: str
|
||||
date: str
|
||||
commit: str = "N/A"
|
||||
author: str
|
||||
title: str
|
||||
id: str
|
||||
git_repo: str
|
||||
git_repo_type: str
|
||||
|
||||
128
src/sources/gitea.py
Normal file
128
src/sources/gitea.py
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
from sources import CSSourceGitea, CSItem, CSInventoryItem
|
||||
from sources.util import cache_cheatsheet, get_datestring
|
||||
import requests
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def process_gitea(item: CSInventoryItem, outdir: str) -> list[CSItem] | None:
|
||||
source: CSSourceGitea = item.source
|
||||
commit_hash = get_release_commit_sha(source.base_url, source.owner, source.repo, source.tag)
|
||||
asserts = list_release_assets(source.base_url, source.owner, source.repo, source.tag)
|
||||
|
||||
asserts = filter(lambda a: a[1].endswith(".pdf"), asserts)
|
||||
|
||||
res = []
|
||||
|
||||
for a in asserts:
|
||||
res_url = a[0]
|
||||
if item.cache:
|
||||
cache_url = cache_cheatsheet(a[0], outdir)
|
||||
if cache_url:
|
||||
res_url = cache_url
|
||||
else:
|
||||
continue
|
||||
|
||||
name = Path(a[1]).stem
|
||||
|
||||
res.append(CSItem(
|
||||
url = res_url,
|
||||
date=get_datestring(),
|
||||
commit=commit_hash[:10] if commit_hash else "",
|
||||
author=item.author if item.author else source.owner,
|
||||
title=f"{name}",
|
||||
id=item.id,
|
||||
git_repo=f"{source.base_url}/{source.owner}/{source.repo}" if not source.hide_repo else "",
|
||||
git_repo_type="Gitea"
|
||||
))
|
||||
|
||||
return res
|
||||
|
||||
def get_release_commit_sha(base_url, owner, repo, tag_name, token=None):
|
||||
"""
|
||||
Resolve the commit SHA for a Gitea release tag.
|
||||
|
||||
:param base_url: e.g. "https://gitea.example.com"
|
||||
:param owner: repo owner
|
||||
:param repo: repository name
|
||||
:param tag_name: release tag (e.g. "v1.2.3")
|
||||
:param token: optional API token
|
||||
:return: commit SHA (str)
|
||||
"""
|
||||
|
||||
headers = {}
|
||||
if token:
|
||||
headers["Authorization"] = f"token {token}"
|
||||
|
||||
session = requests.Session()
|
||||
session.headers.update(headers)
|
||||
|
||||
# 1) List tags and find the matching tag
|
||||
tags_url = f"{base_url}/api/v1/repos/{owner}/{repo}/tags"
|
||||
resp = session.get(tags_url)
|
||||
resp.raise_for_status()
|
||||
tags = resp.json()
|
||||
|
||||
tag = next((t for t in tags if t["name"] == tag_name), None)
|
||||
if not tag:
|
||||
raise ValueError(f"Tag '{tag_name}' not found")
|
||||
|
||||
# Lightweight tags usually already contain the commit SHA
|
||||
commit_sha = tag.get("commit", {}).get("sha")
|
||||
tag_obj_sha = tag.get("id")
|
||||
|
||||
# If commit.sha looks valid, return it
|
||||
if commit_sha:
|
||||
return commit_sha
|
||||
|
||||
# 2) Annotated tag: dereference via /git/tags/{sha}
|
||||
if not tag_obj_sha:
|
||||
raise RuntimeError("Tag object SHA missing; cannot dereference annotated tag")
|
||||
|
||||
git_tag_url = f"{base_url}/api/v1/repos/{owner}/{repo}/git/tags/{tag_obj_sha}"
|
||||
resp = session.get(git_tag_url)
|
||||
resp.raise_for_status()
|
||||
annotated = resp.json()
|
||||
|
||||
# The object pointed to by the tag (usually a commit)
|
||||
target = annotated.get("object", {})
|
||||
if target.get("type") != "commit":
|
||||
raise RuntimeError(f"Tag points to a {target.get('type')} instead of a commit")
|
||||
|
||||
return target.get("sha")
|
||||
|
||||
|
||||
def list_release_assets(base_url, owner, repo, tag, token=None):
|
||||
"""
|
||||
Return a list of (download_url, filename) for all assets of a Gitea release.
|
||||
|
||||
:param base_url: Gitea host URL, e.g. "https://gitea.example.com"
|
||||
:param owner: repository owner
|
||||
:param repo: repository name
|
||||
:param tag: release tag name
|
||||
:param token: optional API token
|
||||
:returns: list of (download_url, filename) tuples
|
||||
"""
|
||||
headers = {}
|
||||
if token:
|
||||
headers["Authorization"] = f"token {token}"
|
||||
|
||||
# 1) Get release by tag
|
||||
rel_url = f"{base_url}/api/v1/repos/{owner}/{repo}/releases/tags/{tag}"
|
||||
rel_resp = requests.get(rel_url, headers=headers)
|
||||
rel_resp.raise_for_status()
|
||||
release = rel_resp.json()
|
||||
|
||||
assets = release.get("assets", [])
|
||||
result = []
|
||||
|
||||
for asset in assets:
|
||||
# Gitea asset info usually contains:
|
||||
# - "browser_download_url" → direct URL
|
||||
# - "name" → filename
|
||||
download_url = asset.get("browser_download_url")
|
||||
filename = asset.get("name")
|
||||
if download_url and filename:
|
||||
result.append((download_url, filename))
|
||||
|
||||
return result
|
||||
24
src/sources/plain.py
Normal file
24
src/sources/plain.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from sources import CSInventoryItem, CSSourcePlainURL, CSItem
|
||||
from sources.util import cache_cheatsheet, get_datestring
|
||||
|
||||
def process_plain_url(item: CSInventoryItem, outdir: str) -> CSItem | None:
|
||||
source: CSSourcePlainURL = item.source
|
||||
res_url = source.url
|
||||
|
||||
if item.cache:
|
||||
cache_url = cache_cheatsheet(source.url, outdir)
|
||||
if cache_url:
|
||||
res_url = cache_url
|
||||
else:
|
||||
return None
|
||||
|
||||
return CSItem(
|
||||
url = res_url,
|
||||
date=get_datestring(),
|
||||
config=item,
|
||||
author=item.author,
|
||||
title=item.title,
|
||||
id=item.id,
|
||||
git_repo=source.repo_url,
|
||||
git_repo_type="Git"
|
||||
)
|
||||
29
src/sources/util.py
Normal file
29
src/sources/util.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import hashlib
|
||||
import requests
|
||||
import datetime
|
||||
import os
|
||||
|
||||
def get_datestring() -> str:
|
||||
return datetime.datetime.now().strftime("%d.%m.%y")
|
||||
|
||||
|
||||
def cache_cheatsheet(url, outdir: str) -> str | None:
|
||||
r = requests.get(url)
|
||||
if not r.ok and r.headers.get("Content-Type") != "application/pdf":
|
||||
return None
|
||||
|
||||
data = r.content
|
||||
|
||||
hashdata = hashlib.sha256(data)
|
||||
|
||||
filesname = os.path.join("cache", f"{hashdata.hexdigest()}.pdf")
|
||||
|
||||
if not os.path.exists(os.path.join(outdir, "cache")):
|
||||
os.mkdir(os.path.join(outdir, "cache"))
|
||||
|
||||
with open(os.path.join(outdir, filesname), "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
print("Saved file to", filesname)
|
||||
|
||||
return filesname
|
||||
99
static/css/main.css
Normal file
99
static/css/main.css
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Styles for the Formelsammlung project
|
||||
* 90's inspired aesthetic
|
||||
*/
|
||||
body {
|
||||
background-color: #e8e8e8;
|
||||
font-family: "Trebuchet MS", Arial, sans-serif;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
width: calc(100vw - 40px);
|
||||
}
|
||||
|
||||
/* Navbar styles */
|
||||
nav.navbar {
|
||||
background-color: #c0c0c0;
|
||||
border-bottom: 2px solid #999;
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: inset 1px 1px 0 #ffffff, inset -1px -1px 0 #808080;
|
||||
}
|
||||
|
||||
nav.navbar h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #0051ba;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav.navbar a {
|
||||
color: #0051ba;
|
||||
text-decoration: none;
|
||||
margin-left: 20px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
nav.navbar a:hover {
|
||||
border: 1px solid #0051ba;
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
|
||||
/* Main content area padding */
|
||||
body > h1,
|
||||
body > table {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #0051ba;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #0051ba;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: calc(100% - 40px);
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #c0c0c0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: #e8e8ff;
|
||||
}
|
||||
|
||||
/* Preserve whitespace and line breaks */
|
||||
.license-text {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: "Courier New", monospace;
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=main.css.map */
|
||||
1
static/css/main.css.map
Normal file
1
static/css/main.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["../../styles/main.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAAA;AAKA;EACI;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;;;AAGJ;AACA;EACI;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;;;AAGJ;AACA;AAAA;EAEI;EACA;;;AAGJ;EACI;EACA;EACA;EACA;EACA;;;AAGJ;EACI;EACA;EACA;EACA;;;AAGJ;EACI;;;AAGJ;EACI;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;;;AAGJ;AACA;EACI;EACA;EACA;EACA;EACA;EACA","file":"main.css"}
|
||||
100
styles/main.scss
Normal file
100
styles/main.scss
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Styles for the Formelsammlung project
|
||||
* 90's inspired aesthetic
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #e8e8e8;
|
||||
font-family: 'Trebuchet MS', Arial, sans-serif;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
|
||||
width: calc(100vw - 40px);
|
||||
}
|
||||
|
||||
/* Navbar styles */
|
||||
nav.navbar {
|
||||
background-color: #c0c0c0;
|
||||
border-bottom: 2px solid #999;
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: inset 1px 1px 0 #ffffff, inset -1px -1px 0 #808080;
|
||||
}
|
||||
|
||||
nav.navbar h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #0051ba;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav.navbar a {
|
||||
color: #0051ba;
|
||||
text-decoration: none;
|
||||
margin-left: 20px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
nav.navbar a:hover {
|
||||
border: 1px solid #0051ba;
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
|
||||
/* Main content area padding */
|
||||
body > h1,
|
||||
body > table {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #0051ba;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #0051ba;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: calc(100% - 40px);
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #c0c0c0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: #e8e8ff;
|
||||
}
|
||||
|
||||
/* Preserve whitespace and line breaks */
|
||||
.license-text {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: 'Courier New', monospace;
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
58
templates/impressum.html.j2
Normal file
58
templates/impressum.html.j2
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<title>FS²</title>
|
||||
</head>
|
||||
<body>
|
||||
{% include "navbar.j2" %}
|
||||
|
||||
<h1>Impressum</h1>
|
||||
<p>Angaben gemäß § 5 DDG</p>
|
||||
<p>
|
||||
Max Muster - Musterberuf<br>
|
||||
c/o Beispielbüro<br>
|
||||
Musterweg<br>
|
||||
12345 Musterstadt <br>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Vertreten durch: </strong><br>
|
||||
Max Muster<br>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Kontakt:</strong><br>
|
||||
Telefon: 01234-789456<br>
|
||||
Fax: 1234-56789<br>
|
||||
E-Mail: <a>max@muster.de</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Aufsichtsbehörde:</strong><br>
|
||||
Musteraufsicht Musterstadt<br>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV:</strong><br>
|
||||
Max Muster <br>
|
||||
Musterweg<br>
|
||||
12345 Musterstadt<br>
|
||||
|
||||
</p>
|
||||
<p>
|
||||
<strong>Verbraucherstreitbeilegung / Universalschlichtungsstelle</strong>
|
||||
<br>Wir nehmen nicht an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teil und sind dazu auch nicht verpflichtet.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Haftungsausschluss: </strong> <br><br>
|
||||
<strong>Haftung für Inhalte</strong><br>
|
||||
Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir gemäß § 7 Abs.1 DDG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 DDG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. Verpflichtungen zur Entfernung oder Sperrung der Nutzung von Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt. Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend entfernen.<br><br>
|
||||
|
||||
<strong>Haftung für Links</strong><br>
|
||||
Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werden wir derartige Links umgehend entfernen.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
53
templates/index.html.j2
Normal file
53
templates/index.html.j2
Normal file
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<title>FS²</title>
|
||||
</head>
|
||||
<body>
|
||||
{% include "navbar.j2" %}
|
||||
|
||||
<h1>Formel(sammlung)²</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Repo</th>
|
||||
<th>Upload Date</th>
|
||||
<th>Git commit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ item.url }}">{{ item.title }}</a>
|
||||
|
||||
{% if item.author %}
|
||||
<br>by {{ item.author }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.git_repo %}
|
||||
<a href="{{ item.git_repo }}">{{ item.git_repo_type }}</a>
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.date }}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.git_repo %}
|
||||
{{ item.commit }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
27
templates/license.html.j2
Normal file
27
templates/license.html.j2
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
<title>FS²</title>
|
||||
</head>
|
||||
<body>
|
||||
{% include "navbar.j2" %}
|
||||
|
||||
<h1>License</h1>
|
||||
|
||||
<p class="license-text">
|
||||
Copyright {{thisYear}} Alexander
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Cheatsheets are licensed under their respective licenses as indicated in their Git repositories.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
9
templates/navbar.j2
Normal file
9
templates/navbar.j2
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
<nav class="navbar">
|
||||
<h3>Formel(sammlung)²</h3>
|
||||
|
||||
<a href="index.html">Home</a>
|
||||
<a href="https://gitea.mintcalc.com/alexander/FSSquared">Gitea</a>
|
||||
<a href="impressum.html">Impressum</a>
|
||||
<a href="license.html">License</a>
|
||||
</nav>
|
||||
Reference in New Issue
Block a user