cfg.site with markdown
This commit is contained in:
parent
6d015420c0
commit
4fbdb94caa
8 changed files with 99 additions and 91 deletions
|
@ -3,7 +3,8 @@ import tomllib
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from random import Random
|
from random import Random
|
||||||
|
|
||||||
from pydantic import BaseModel, field_validator
|
from markdown import markdown
|
||||||
|
from pydantic import BaseModel, ConfigDict, field_validator
|
||||||
|
|
||||||
from .dav.webdav import WebDAV
|
from .dav.webdav import WebDAV
|
||||||
from .settings import SETTINGS
|
from .settings import SETTINGS
|
||||||
|
@ -25,6 +26,8 @@ class TransformedString(BaseModel):
|
||||||
# whitespace entfernen
|
# whitespace entfernen
|
||||||
IGNORE = "IGNORE"
|
IGNORE = "IGNORE"
|
||||||
|
|
||||||
|
# special chars
|
||||||
|
|
||||||
class __Case(str, Enum):
|
class __Case(str, Enum):
|
||||||
# unverändert
|
# unverändert
|
||||||
KEEP = "KEEP"
|
KEEP = "KEEP"
|
||||||
|
@ -78,10 +81,27 @@ class TransformedString(BaseModel):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class Puzzle(BaseModel):
|
class Site(BaseModel):
|
||||||
|
model_config = ConfigDict(validate_default=True)
|
||||||
|
|
||||||
# Titel
|
# Titel
|
||||||
title: str
|
title: str
|
||||||
|
|
||||||
|
# Untertitel
|
||||||
|
subtitle: str
|
||||||
|
|
||||||
|
# Inhalt der Seite
|
||||||
|
content: str
|
||||||
|
|
||||||
|
# Fußzeile der Seite
|
||||||
|
footer: str = "**Advent22** by [Lenaisten e.V.](//www.lenaisten.de)"
|
||||||
|
|
||||||
|
@field_validator("content", "footer", mode="after")
|
||||||
|
def parse_md(cls, v) -> str:
|
||||||
|
return markdown(v)
|
||||||
|
|
||||||
|
|
||||||
|
class Puzzle(BaseModel):
|
||||||
# Tag, an dem der Kalender startet
|
# Tag, an dem der Kalender startet
|
||||||
begin_day: int = 1
|
begin_day: int = 1
|
||||||
|
|
||||||
|
@ -108,6 +128,7 @@ class Config(BaseModel):
|
||||||
solution: TransformedString
|
solution: TransformedString
|
||||||
|
|
||||||
# Weitere Einstellungen
|
# Weitere Einstellungen
|
||||||
|
site: Site
|
||||||
puzzle: Puzzle
|
puzzle: Puzzle
|
||||||
image: Image
|
image: Image
|
||||||
|
|
||||||
|
@ -117,11 +138,6 @@ class Config(BaseModel):
|
||||||
# Serverseitiger zusätzlicher "random" seed
|
# Serverseitiger zusätzlicher "random" seed
|
||||||
random_seed: str = ""
|
random_seed: str = ""
|
||||||
|
|
||||||
# Fußzeile der Seite
|
|
||||||
footer: str = (
|
|
||||||
'<strong>Advent22</strong> by <a href="//www.lenaisten.de">Lenaisten e.V.</a>'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_config() -> Config:
|
async def get_config() -> Config:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,7 +5,7 @@ from fastapi.responses import StreamingResponse
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from ..core.calendar_config import CalendarConfig, DoorsSaved, get_calendar_config
|
from ..core.calendar_config import CalendarConfig, DoorsSaved, get_calendar_config
|
||||||
from ..core.config import Config, get_config
|
from ..core.config import Config, Site, get_config
|
||||||
from ..core.depends import get_all_event_dates, get_day_image
|
from ..core.depends import get_all_event_dates, get_day_image
|
||||||
from ..core.helpers import EventDates, api_return_ico, api_return_jpeg, load_image
|
from ..core.helpers import EventDates, api_return_ico, api_return_jpeg, load_image
|
||||||
from ._security import user_can_view_day, user_is_admin, user_visible_days
|
from ._security import user_can_view_day, user_is_admin, user_visible_days
|
||||||
|
@ -45,19 +45,15 @@ async def get_favicon(
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
# - favicon
|
@router.get("/site_config")
|
||||||
# - "user-config-model"
|
async def get_site_config(
|
||||||
|
|
||||||
|
|
||||||
@router.get("/title")
|
|
||||||
async def get_title(
|
|
||||||
cfg: Config = Depends(get_config),
|
cfg: Config = Depends(get_config),
|
||||||
) -> str:
|
) -> Site:
|
||||||
"""
|
"""
|
||||||
Lädt Kalendertitel
|
Seiteninhalt
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return cfg.puzzle.title
|
return cfg.site
|
||||||
|
|
||||||
|
|
||||||
@router.get("/doors")
|
@router.get("/doors")
|
||||||
|
@ -72,17 +68,6 @@ async def get_doors(
|
||||||
return [door for door in cal_cfg.doors if door.day in visible_days]
|
return [door for door in cal_cfg.doors if door.day in visible_days]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/footer")
|
|
||||||
async def get_footer(
|
|
||||||
cfg: Config = Depends(get_config),
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
Seiten-Fußzeile lesen
|
|
||||||
"""
|
|
||||||
|
|
||||||
return cfg.footer
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/image_{day}",
|
"/image_{day}",
|
||||||
response_class=StreamingResponse,
|
response_class=StreamingResponse,
|
||||||
|
|
17
api/poetry.lock
generated
17
api/poetry.lock
generated
|
@ -580,6 +580,21 @@ html5 = ["html5lib"]
|
||||||
htmlsoup = ["BeautifulSoup4"]
|
htmlsoup = ["BeautifulSoup4"]
|
||||||
source = ["Cython (>=0.29.35)"]
|
source = ["Cython (>=0.29.35)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown"
|
||||||
|
version = "3.5.1"
|
||||||
|
description = "Python implementation of John Gruber's Markdown."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"},
|
||||||
|
{file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
|
||||||
|
testing = ["coverage", "pyyaml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mccabe"
|
name = "mccabe"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -1376,4 +1391,4 @@ typing-extensions = ">=4.4.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.11,<3.13"
|
python-versions = ">=3.11,<3.13"
|
||||||
content-hash = "e97f2ab7e4314a0b6ce3725b8068a147a06e5ac5c9ee8e5406ca546dac3075cd"
|
content-hash = "73d5978c4787027b57334c5e5ac98df12e7b9e958bc92a5c326b512861986c57"
|
||||||
|
|
|
@ -21,6 +21,7 @@ redis = {extras = ["hiredis"], version = "^5.0.1"}
|
||||||
tomli-w = "^1.0.0"
|
tomli-w = "^1.0.0"
|
||||||
uvicorn = {extras = ["standard"], version = "^0.23.2"}
|
uvicorn = {extras = ["standard"], version = "^0.23.2"}
|
||||||
webdavclient3 = "^3.14.6"
|
webdavclient3 = "^3.14.6"
|
||||||
|
markdown = "^3.5.1"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.10.1"
|
black = "^23.10.1"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<section class="hero is-small is-primary">
|
<section class="hero is-small is-primary">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<h1 class="title is-uppercase">{{ title }}</h1>
|
<h1 class="title is-uppercase">{{ store.site_config.title }}</h1>
|
||||||
<h2 class="subtitle">{{ subtitle }}</h2>
|
<h2 class="subtitle">{{ store.site_config.subtitle }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="level">
|
<div class="level">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<p v-html="footer" />
|
<p v-html="store.site_config.footer" />
|
||||||
</div>
|
</div>
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
|
@ -50,25 +50,10 @@ import UserView from "./components/UserView.vue";
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
public title = "Adventskalender";
|
|
||||||
public subtitle = "Lorem Ipsum";
|
|
||||||
public footer = "";
|
|
||||||
public readonly store = advent22Store();
|
public readonly store = advent22Store();
|
||||||
|
|
||||||
public mounted(): void {
|
public mounted(): void {
|
||||||
Promise.all([
|
this.store.init();
|
||||||
this.$advent22.api_get<string>("user/title"),
|
|
||||||
this.subtitle, // TODO
|
|
||||||
this.$advent22.api_get<string>("user/footer"),
|
|
||||||
])
|
|
||||||
.then(([title, subtitle, footer]) => {
|
|
||||||
document.title = `${title} – ${subtitle}`;
|
|
||||||
|
|
||||||
this.title = title;
|
|
||||||
this.subtitle = subtitle;
|
|
||||||
this.footer = footer;
|
|
||||||
})
|
|
||||||
.catch(this.$advent22.alert_user_error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -37,9 +37,6 @@
|
||||||
|
|
||||||
<h3>Rätsel</h3>
|
<h3>Rätsel</h3>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Titel</dt>
|
|
||||||
<dd>{{ title }}</dd>
|
|
||||||
|
|
||||||
<dt>Offene Türchen</dt>
|
<dt>Offene Türchen</dt>
|
||||||
<dd>{{ num_user_doors }}</dd>
|
<dd>{{ num_user_doors }}</dd>
|
||||||
|
|
||||||
|
@ -47,9 +44,6 @@
|
||||||
<dd v-if="next_door === null">Kein nächstes Türchen</dd>
|
<dd v-if="next_door === null">Kein nächstes Türchen</dd>
|
||||||
<dd v-else><CountDown :millis="next_door" /></dd>
|
<dd v-else><CountDown :millis="next_door" /></dd>
|
||||||
|
|
||||||
<dt>Fußzeile</dt>
|
|
||||||
<dd class="is-family-monospace">{{ footer }}</dd>
|
|
||||||
|
|
||||||
<dt>Erstes Türchen</dt>
|
<dt>Erstes Türchen</dt>
|
||||||
<dd>{{ fmt_puzzle_date("first") }}</dd>
|
<dd>{{ fmt_puzzle_date("first") }}</dd>
|
||||||
|
|
||||||
|
@ -218,8 +212,6 @@ export default class extends Vue {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
public doors: DoorsSaved = [];
|
public doors: DoorsSaved = [];
|
||||||
public title = "";
|
|
||||||
public footer = "";
|
|
||||||
public num_user_doors = 0;
|
public num_user_doors = 0;
|
||||||
public next_door: number | null = null;
|
public next_door: number | null = null;
|
||||||
public dav_credentials: Credentials = ["", ""];
|
public dav_credentials: Credentials = ["", ""];
|
||||||
|
@ -237,22 +229,16 @@ export default class extends Vue {
|
||||||
this.$advent22.api_get<AdminConfigModel>("admin/config_model"),
|
this.$advent22.api_get<AdminConfigModel>("admin/config_model"),
|
||||||
this.$advent22.api_get<DoorsSaved>("admin/doors"),
|
this.$advent22.api_get<DoorsSaved>("admin/doors"),
|
||||||
this.$advent22.api_get<DoorsSaved>("user/doors"),
|
this.$advent22.api_get<DoorsSaved>("user/doors"),
|
||||||
this.$advent22.api_get<string>("user/title"),
|
|
||||||
this.$advent22.api_get<string>("user/footer"),
|
|
||||||
this.$advent22.api_get<number | null>("user/next_door"),
|
this.$advent22.api_get<number | null>("user/next_door"),
|
||||||
])
|
])
|
||||||
.then(
|
.then(([admin_config_model, doors, user_doors, next_door]) => {
|
||||||
([admin_config_model, doors, user_doors, title, footer, next_door]) => {
|
this.admin_config_model = admin_config_model;
|
||||||
this.admin_config_model = admin_config_model;
|
this.doors = doors;
|
||||||
this.doors = doors;
|
this.num_user_doors = user_doors.length;
|
||||||
this.num_user_doors = user_doors.length;
|
this.next_door = next_door;
|
||||||
this.title = title;
|
|
||||||
this.footer = footer;
|
|
||||||
this.next_door = next_door;
|
|
||||||
|
|
||||||
ready();
|
ready();
|
||||||
},
|
})
|
||||||
)
|
|
||||||
.catch(fail);
|
.catch(fail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,13 @@ export interface AdminConfigModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SiteConfigModel {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
content: string;
|
||||||
|
footer: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NumStrDict {
|
export interface NumStrDict {
|
||||||
[key: number]: string;
|
[key: number]: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Credentials } from "@/lib/api";
|
import { Credentials, SiteConfigModel } from "@/lib/api";
|
||||||
import { ADVENT22 } from "@/plugins/advent22";
|
import { ADVENT22 } from "@/plugins/advent22";
|
||||||
import { AxiosBasicCredentials } from "axios";
|
import { AxiosBasicCredentials } from "axios";
|
||||||
import { acceptHMRUpdate, defineStore } from "pinia";
|
import { acceptHMRUpdate, defineStore } from "pinia";
|
||||||
|
@ -9,27 +9,17 @@ const empty_creds = () => ["", ""] as Credentials;
|
||||||
export const advent22Store = defineStore({
|
export const advent22Store = defineStore({
|
||||||
id: "advent22",
|
id: "advent22",
|
||||||
|
|
||||||
state: () => {
|
state: () => ({
|
||||||
ADVENT22.api_get_blob("user/favicon")
|
api_creds: empty_creds(),
|
||||||
.then((favicon_src) => {
|
is_admin: false,
|
||||||
const link: HTMLLinkElement =
|
site_config: {
|
||||||
document.querySelector("link[rel*='icon']") ||
|
title: "Adventskalender",
|
||||||
document.createElement("link");
|
subtitle: "Lorem Ipsum",
|
||||||
link.rel = "shortcut icon";
|
content: "",
|
||||||
link.type = "image/x-icon";
|
footer: "",
|
||||||
link.href = favicon_src;
|
} as SiteConfigModel,
|
||||||
|
is_touch_device: check_touch_device(),
|
||||||
if (link.parentElement === null)
|
}),
|
||||||
document.getElementsByTagName("head")[0].appendChild(link);
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
return {
|
|
||||||
api_creds: empty_creds(),
|
|
||||||
is_admin: false,
|
|
||||||
is_touch_device: check_touch_device(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
axios_creds: (state): AxiosBasicCredentials => {
|
axios_creds: (state): AxiosBasicCredentials => {
|
||||||
|
@ -39,6 +29,29 @@ export const advent22Store = defineStore({
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
init(): void {
|
||||||
|
ADVENT22.api_get_blob("user/favicon")
|
||||||
|
.then((favicon_src) => {
|
||||||
|
const link: HTMLLinkElement =
|
||||||
|
document.querySelector("link[rel*='icon']") ||
|
||||||
|
document.createElement("link");
|
||||||
|
link.rel = "shortcut icon";
|
||||||
|
link.type = "image/x-icon";
|
||||||
|
link.href = favicon_src;
|
||||||
|
|
||||||
|
if (link.parentElement === null)
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(link);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
ADVENT22.api_get<SiteConfigModel>("user/site_config")
|
||||||
|
.then((site_config) => {
|
||||||
|
document.title = `${site_config.title} – ${site_config.subtitle}`;
|
||||||
|
this.site_config = site_config;
|
||||||
|
})
|
||||||
|
.catch(ADVENT22.alert_user_error);
|
||||||
|
},
|
||||||
|
|
||||||
login(creds: Credentials = empty_creds()): Promise<boolean> {
|
login(creds: Credentials = empty_creds()): Promise<boolean> {
|
||||||
this.api_creds = creds;
|
this.api_creds = creds;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue