cfg.site with markdown

This commit is contained in:
Jörn-Michael Miehe 2023-11-03 14:40:44 +00:00
parent 6d015420c0
commit 4fbdb94caa
8 changed files with 99 additions and 91 deletions

View file

@ -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:
""" """

View file

@ -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
View file

@ -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"

View file

@ -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"

View file

@ -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>

View file

@ -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);
} }

View file

@ -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;
} }

View file

@ -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;