Compare commits
5 Commits
V1.2.0
...
6c7c1f01c9
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c7c1f01c9 | |||
| 7b751b4d47 | |||
| 03584c4b97 | |||
| 0d03f21a4c | |||
| d0d5bad803 |
@@ -4,8 +4,8 @@
|
||||
"author": "Florian Heinz",
|
||||
"description": "Private Flask app for meals, shopping and gentle food planning",
|
||||
"tagline": "einfach essen planen",
|
||||
"version": "1.2.0",
|
||||
"upstreamVersion": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"upstreamVersion": "1.2.1",
|
||||
"healthCheckPath": "/",
|
||||
"httpPort": 8000,
|
||||
"manifestVersion": 2,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Nouri 1.2.1
|
||||
|
||||
Nouri 1.2.1 ist ein Feinschliff-Release auf Basis von 1.2.0. Der Schwerpunkt lag auf einer ruhigeren mobilen Navigation, klareren Theme-Bedienelementen und besser lesbaren Tageszeiten-Icons in Hell und Dunkel.
|
||||
|
||||
## Neu in 1.2.1
|
||||
|
||||
### Mobile Navigation ruhiger abgestimmt
|
||||
|
||||
- Der `Mehr`-Button in der mobilen Bottom-Navigation bleibt im Ruhezustand jetzt neutral.
|
||||
- Wenn `Mehr` geöffnet ist, nutzt der Button dieselbe aktive Markierung wie die übrige Navigation.
|
||||
- `Hell` und `Abmelden` wirken im mobilen Dark Mode zurückhaltender und sind nicht mehr unnötig stark eingefärbt.
|
||||
|
||||
### Besseres Theme-Umschalten
|
||||
|
||||
- Für den Wechsel zwischen Hell und Dunkel gibt es jetzt eigene Sonne- und Mond-Icons.
|
||||
- Die Theme-Anzeige schaltet in Mobile und Desktop sichtbar mit um.
|
||||
- Die Bedienelemente für den Darstellungswechsel wirken dadurch klarer und weniger technisch.
|
||||
|
||||
### Eigene Icons für Tageszeiten
|
||||
|
||||
- `Frühstück`, `Mittagessen`, `Abendessen` und die Snack-Zeiten haben jetzt eigene Symbole statt eines gemeinsamen Standardsymbols.
|
||||
- Die Icons wurden aus `heinz.marketing` übernommen und lokal ins Projekt eingebunden.
|
||||
- Dadurch sind Tageszeiten im Tagesplan und in der Wochenansicht schneller erfassbar.
|
||||
|
||||
### Icons auf Mobile lesbarer gemacht
|
||||
|
||||
- Die Tageszeiten-Kacheln nutzen jetzt quadratischere Icon-Flächen mit abgerundeten Ecken.
|
||||
- Die Symbole wurden vergrößert und farblich klarer abgestimmt.
|
||||
- Im Dark Mode wirken die Icon-Flächen weniger verwaschen.
|
||||
- Im Light Mode wurde der Kontrast erhöht, damit die Symbole nicht mehr im Kartenhintergrund verschwinden.
|
||||
|
||||
## Technische Änderungen
|
||||
|
||||
- Cloudron-Version und Upstream-Version stehen jetzt auf `1.2.1`.
|
||||
- Die interne Schema-Version und der App-Version-Fallback wurden auf `1.2.1` angehoben.
|
||||
|
||||
## Betroffene Bereiche
|
||||
|
||||
- Mobile Navigation
|
||||
- Theme-Umschaltung
|
||||
- Tagesplan
|
||||
- Wochenansicht
|
||||
- Cloudron-Paketierung
|
||||
@@ -36,6 +36,14 @@ from .main import main_bp
|
||||
WEEKDAY_NAMES = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
|
||||
WEEKDAY_SHORT_NAMES = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
|
||||
DEFAULT_RELEASE_URL = "https://git.hnz.io/hnzio/nouri-App/releases"
|
||||
DAYPART_ICON_CLASSES = {
|
||||
"breakfast": "icon-daypart-breakfast",
|
||||
"morning-snack": "icon-daypart-morning-snack",
|
||||
"lunch": "icon-daypart-lunch",
|
||||
"afternoon-snack": "icon-daypart-afternoon-snack",
|
||||
"dinner": "icon-daypart-dinner",
|
||||
"late-snack": "icon-daypart-late-snack",
|
||||
}
|
||||
|
||||
|
||||
def load_secret_key(data_dir: Path) -> str:
|
||||
@@ -74,7 +82,7 @@ def load_app_version(root_dir: Path) -> str:
|
||||
).strip()
|
||||
if manifest_version:
|
||||
return manifest_version
|
||||
return "1.2.0"
|
||||
return "1.2.1"
|
||||
|
||||
|
||||
def load_release_url() -> str:
|
||||
@@ -149,6 +157,7 @@ def create_app() -> Flask:
|
||||
"push_available": bool(app.config["VAPID_PUBLIC_KEY"] and app.config["VAPID_PRIVATE_KEY"]),
|
||||
"weekday_name": lambda value: WEEKDAY_NAMES[value.weekday()],
|
||||
"weekday_short_name": lambda value: WEEKDAY_SHORT_NAMES[value.weekday()],
|
||||
"daypart_icon_class": lambda slug: DAYPART_ICON_CLASSES.get(slug, "icon-calendar"),
|
||||
"is_admin": lambda: bool(getattr(g, "user", None)) and g.user["role"] == "admin",
|
||||
"asset_url": asset_url,
|
||||
"image_url": lambda filename, variant="md": image_url(
|
||||
|
||||
@@ -10,7 +10,7 @@ from werkzeug.security import generate_password_hash
|
||||
|
||||
from .constants import DAYPARTS, DEFAULT_CATEGORIES, DEFAULT_CATEGORY_BUILDERS
|
||||
|
||||
CURRENT_SCHEMA_VERSION = "1.2.0"
|
||||
CURRENT_SCHEMA_VERSION = "1.2.1"
|
||||
|
||||
|
||||
def get_db() -> sqlite3.Connection:
|
||||
|
||||
@@ -2033,7 +2033,7 @@ def fetch_plan_entries_for_range_export(start_date: date, end_date: date, *, mod
|
||||
|
||||
|
||||
def format_week_pdf_entry(entry: dict, *, mode: str) -> str:
|
||||
label = entry["item_name"]
|
||||
label = format_pdf_cell_label(entry["item_name"])
|
||||
if mode == "household":
|
||||
if entry.get("target_name"):
|
||||
return f"{label} (Für {entry['target_name']})"
|
||||
@@ -2046,6 +2046,23 @@ def format_week_pdf_entry(entry: dict, *, mode: str) -> str:
|
||||
return label
|
||||
|
||||
|
||||
def format_pdf_cell_label(label: str) -> str:
|
||||
cleaned = " ".join((label or "").split())
|
||||
if not cleaned:
|
||||
return ""
|
||||
|
||||
if " - " in cleaned:
|
||||
return cleaned.replace(" - ", "\n")
|
||||
|
||||
if ", " in cleaned and len(cleaned) > 20:
|
||||
return cleaned.replace(", ", ",\n")
|
||||
|
||||
if "-" in cleaned:
|
||||
return "-\n".join(part for part in cleaned.split("-") if part)
|
||||
|
||||
return cleaned
|
||||
|
||||
|
||||
def build_week_pdf_rows(week_start: date, *, mode: str) -> tuple[list, list[list[str]]]:
|
||||
days = [week_start + timedelta(days=index) for index in range(7)]
|
||||
grouped_entries = fetch_plan_entries_for_range_export(week_start, week_start + timedelta(days=6), mode=mode)
|
||||
@@ -2098,6 +2115,7 @@ def build_week_plan_pdf(week_start: date, *, mode: str = "mine") -> bytes:
|
||||
pdf.cell(0, 6, f"KW {week_number:02d}", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.ln(3)
|
||||
pdf.set_text_color(20, 20, 20)
|
||||
pdf.set_font("Helvetica", "", 10)
|
||||
|
||||
headings = [" "] + [f"{format_weekday(day)}\n{day.strftime('%d.%m.%Y')}" for day in days]
|
||||
first_column_width = 34
|
||||
@@ -3797,6 +3815,7 @@ def planner():
|
||||
week_hints=build_week_hints(week_start),
|
||||
upcoming_entries=fetch_upcoming_shopping_needs(limit=8),
|
||||
household_settings=get_household_settings(),
|
||||
visibility_options=VISIBILITY_FORM_OPTIONS,
|
||||
)
|
||||
|
||||
|
||||
@@ -3923,6 +3942,7 @@ def planner_generated_meal():
|
||||
@login_required
|
||||
def planner_update(entry_id: int):
|
||||
selected_date = parse_plan_date(request.form.get("plan_date"))
|
||||
return_week = request.form.get("return_week", "").strip()
|
||||
entry = get_db().execute(
|
||||
f"""
|
||||
SELECT plan_entries.*,
|
||||
@@ -3942,19 +3962,24 @@ def planner_update(entry_id: int):
|
||||
ensure_can_edit(describe_record(dict(entry)), "Diesen Planeintrag kannst du gerade nicht bearbeiten.")
|
||||
except PermissionError as exc:
|
||||
flash(str(exc), "error")
|
||||
if return_week:
|
||||
return redirect(url_for("main.planner", week=return_week))
|
||||
return redirect(url_for("main.planner_day", date=selected_date.isoformat()))
|
||||
|
||||
visibility = normalize_visibility(request.form.get("visibility"), entry["visibility"])
|
||||
note = request.form.get("note", "").strip()
|
||||
update_plan_entry(entry_id, visibility=visibility, note=note)
|
||||
flash("Der Planeintrag wurde angepasst.", "success")
|
||||
if return_week:
|
||||
return redirect(url_for("main.planner", week=return_week))
|
||||
return redirect(url_for("main.planner_day", date=selected_date.isoformat(), daypart_id=entry["daypart_id"]))
|
||||
|
||||
|
||||
@main_bp.post("/planner/<int:entry_id>/remove")
|
||||
@login_required
|
||||
def planner_remove(entry_id: int):
|
||||
selected_date = request.args.get("date", "")
|
||||
selected_date = request.args.get("date", "") or request.form.get("plan_date", "").strip()
|
||||
return_week = request.form.get("return_week", "").strip()
|
||||
entry = get_db().execute(
|
||||
f"""
|
||||
SELECT plan_entries.*,
|
||||
@@ -3976,6 +4001,8 @@ def planner_remove(entry_id: int):
|
||||
flash("Der Planeintrag wurde entfernt.", "info")
|
||||
except PermissionError as exc:
|
||||
flash(str(exc), "error")
|
||||
if return_week:
|
||||
return redirect(url_for("main.planner", week=return_week))
|
||||
if selected_date:
|
||||
return redirect(url_for("main.planner_day", date=selected_date))
|
||||
return redirect(url_for("main.planner"))
|
||||
|
||||
@@ -101,7 +101,7 @@ button,
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: transform 160ms ease, background 160ms ease, border-color 160ms ease;
|
||||
transition: transform 160ms ease, background 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
button:focus-visible,
|
||||
@@ -124,19 +124,64 @@ button:hover,
|
||||
.button.secondary,
|
||||
button.secondary,
|
||||
.ghost-button {
|
||||
background: transparent;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--surface-soft) 72%, #fff 28%),
|
||||
color-mix(in srgb, var(--surface-strong) 82%, #fff 18%)
|
||||
);
|
||||
color: var(--text);
|
||||
border-color: var(--line);
|
||||
border-color: color-mix(in srgb, var(--accent) 34%, var(--line) 66%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8),
|
||||
0 8px 20px rgba(225, 181, 138, 0.12);
|
||||
}
|
||||
|
||||
.button.secondary:hover,
|
||||
button.secondary:hover,
|
||||
.ghost-button:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--accent-soft) 58%, #fff 42%),
|
||||
color-mix(in srgb, var(--surface-soft) 88%, #fff 12%)
|
||||
);
|
||||
border-color: color-mix(in srgb, var(--accent) 46%, var(--line) 54%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.84),
|
||||
0 12px 28px rgba(212, 155, 104, 0.16);
|
||||
}
|
||||
|
||||
[data-theme="dark"] button:not(.secondary):not(.ghost-button),
|
||||
[data-theme="dark"] .button:not(.secondary):not(.ghost-button) {
|
||||
background: #d7935f;
|
||||
color: #201a17;
|
||||
border-color: rgba(243, 177, 125, 0.28);
|
||||
}
|
||||
|
||||
[data-theme="dark"] button:not(.secondary):not(.ghost-button):hover,
|
||||
[data-theme="dark"] .button:not(.secondary):not(.ghost-button):hover {
|
||||
background: #e0a270;
|
||||
color: #181311;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .button.secondary,
|
||||
[data-theme="dark"] button.secondary,
|
||||
[data-theme="dark"] .ghost-button {
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border-color: var(--line);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .button.secondary:hover,
|
||||
[data-theme="dark"] button.secondary:hover,
|
||||
[data-theme="dark"] .ghost-button:hover {
|
||||
background: var(--accent-soft);
|
||||
border-color: rgba(243, 177, 125, 0.2);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.page-shell {
|
||||
width: min(1320px, calc(100% - 2rem));
|
||||
width: min(1680px, calc(100% - 2rem));
|
||||
margin: 1rem auto 2rem;
|
||||
}
|
||||
|
||||
@@ -233,6 +278,7 @@ h3,
|
||||
}
|
||||
|
||||
.site-nav a {
|
||||
flex: 0 0 auto;
|
||||
padding: 0.55rem 0.85rem;
|
||||
border-radius: 999px;
|
||||
color: var(--muted);
|
||||
@@ -265,6 +311,8 @@ h3,
|
||||
column-gap: 1.5rem;
|
||||
row-gap: 0.9rem;
|
||||
align-items: center;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.desktop-header-main {
|
||||
@@ -279,6 +327,7 @@ h3,
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
@@ -309,6 +358,10 @@ h3,
|
||||
.desktop-actions > * {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.desktop-nav a {
|
||||
padding: 0.5rem 0.74rem;
|
||||
}
|
||||
}
|
||||
|
||||
.user-chip,
|
||||
@@ -396,6 +449,14 @@ h3,
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--surface) 86%, #fff 14%), color-mix(in srgb, var(--surface) 80%, #ffe5d2 20%));
|
||||
}
|
||||
|
||||
[data-theme="dark"] .hero {
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.06), transparent 42%),
|
||||
linear-gradient(180deg, rgba(64, 55, 52, 0.98), rgba(49, 42, 39, 0.99));
|
||||
border-color: rgba(243, 177, 125, 0.14);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.hero h1,
|
||||
.page-intro h1,
|
||||
.panel h2 {
|
||||
@@ -614,6 +675,11 @@ h3 {
|
||||
background: var(--sky-soft);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .status-idea {
|
||||
background: rgba(126, 143, 160, 0.24);
|
||||
color: #ece8e4;
|
||||
}
|
||||
|
||||
.status-soft {
|
||||
background: var(--lilac-soft);
|
||||
}
|
||||
@@ -702,6 +768,10 @@ h3 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-spaced-panel > .panel-head + * {
|
||||
margin-top: 0.45rem;
|
||||
}
|
||||
|
||||
.template-library-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -910,23 +980,46 @@ legend {
|
||||
}
|
||||
|
||||
.day-tile-icon {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
width: 2.95rem;
|
||||
height: 2.95rem;
|
||||
flex: 0 0 2.95rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 1rem;
|
||||
background: linear-gradient(145deg, rgba(255, 255, 255, 0.92), var(--peach-soft));
|
||||
color: var(--accent-strong);
|
||||
border-radius: 0.9rem;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--surface-soft) 72%, #fff 28%),
|
||||
color-mix(in srgb, var(--surface-soft) 92%, #f7e2cf 8%)
|
||||
);
|
||||
border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--line) 74%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.82),
|
||||
0 10px 24px rgba(223, 177, 134, 0.12);
|
||||
color: #cf8450;
|
||||
}
|
||||
|
||||
.day-tile-icon .ui-icon {
|
||||
width: 1.15rem;
|
||||
height: 1.15rem;
|
||||
width: 1.28rem;
|
||||
height: 1.28rem;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .day-tile-icon {
|
||||
background: linear-gradient(180deg, rgba(86, 74, 69, 0.98), rgba(67, 58, 55, 0.98));
|
||||
border: 1px solid rgba(243, 177, 125, 0.14);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
color: #f3bf90;
|
||||
}
|
||||
|
||||
.day-tile.has-entries .day-tile-icon {
|
||||
background: linear-gradient(145deg, rgba(255, 255, 255, 0.98), color-mix(in srgb, var(--accent-soft) 68%, #fff 32%));
|
||||
box-shadow: 0 10px 22px rgba(94, 68, 49, 0.14);
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--surface-soft) 82%, #fff 18%),
|
||||
color-mix(in srgb, var(--accent-soft) 72%, #fff 28%)
|
||||
);
|
||||
border-color: color-mix(in srgb, var(--accent) 36%, var(--line) 64%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.84),
|
||||
0 12px 26px rgba(94, 68, 49, 0.14);
|
||||
}
|
||||
|
||||
.day-tile-summary-text {
|
||||
@@ -953,8 +1046,9 @@ legend {
|
||||
}
|
||||
|
||||
[data-theme="dark"] .day-tile.has-entries .day-tile-icon {
|
||||
background: linear-gradient(145deg, rgba(255, 255, 255, 0.12), rgba(243, 177, 125, 0.16));
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
background: linear-gradient(180deg, rgba(97, 82, 76, 0.98), rgba(76, 65, 61, 0.98));
|
||||
border-color: rgba(243, 177, 125, 0.18);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .day-tile.has-entries .status-pill {
|
||||
@@ -1078,6 +1172,14 @@ legend {
|
||||
background: color-mix(in srgb, var(--surface-strong) 84%, #fff 16%);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .template-card,
|
||||
[data-theme="dark"] .template-list-card,
|
||||
[data-theme="dark"] .suggestion-card {
|
||||
background: linear-gradient(180deg, rgba(66, 57, 54, 0.98), rgba(54, 47, 44, 0.99));
|
||||
border-color: rgba(243, 177, 125, 0.12);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.selected-quick-action {
|
||||
background: linear-gradient(180deg, color-mix(in srgb, var(--accent-soft) 82%, #fff 18%), color-mix(in srgb, var(--surface-strong) 82%, #fff 18%));
|
||||
border-color: color-mix(in srgb, var(--accent) 36%, var(--line) 64%);
|
||||
@@ -1128,6 +1230,12 @@ legend {
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .hint-chip {
|
||||
background: linear-gradient(180deg, rgba(77, 68, 64, 0.96), rgba(63, 56, 53, 0.98));
|
||||
border-color: rgba(243, 177, 125, 0.12);
|
||||
color: #f0e8e2;
|
||||
}
|
||||
|
||||
.suggestion-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
@@ -1367,6 +1475,20 @@ legend {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.week-slot-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.week-slot-title .ui-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: var(--accent-strong);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.week-slot-head-meta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1461,6 +1583,17 @@ legend {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.plan-chip.is-editable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plan-chip.is-editable:hover {
|
||||
border-color: color-mix(in srgb, var(--accent) 34%, var(--line) 66%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.7),
|
||||
0 10px 22px rgba(94, 68, 49, 0.12);
|
||||
}
|
||||
|
||||
.plan-chip:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
@@ -1480,6 +1613,55 @@ legend {
|
||||
padding: 0.55rem 0.85rem;
|
||||
}
|
||||
|
||||
.week-entry-dialog {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
max-width: min(34rem, calc(100vw - 2rem));
|
||||
width: min(34rem, calc(100vw - 2rem));
|
||||
}
|
||||
|
||||
.week-entry-dialog::backdrop {
|
||||
background: rgba(29, 22, 19, 0.54);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.week-entry-dialog-card {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
padding: 1.1rem;
|
||||
border-radius: 22px;
|
||||
border: 1px solid var(--line);
|
||||
background: color-mix(in srgb, var(--surface) 98%, #fff 2%);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.week-entry-dialog-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.week-entry-dialog-head h3 {
|
||||
margin: 0 0 0.2rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.week-entry-dialog-head p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.week-entry-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.week-entry-remove-form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.plan-chip small,
|
||||
.week-slot-empty {
|
||||
color: var(--muted);
|
||||
@@ -1565,6 +1747,13 @@ legend {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .plan-chip.is-editable:hover {
|
||||
border-color: rgba(243, 177, 125, 0.3);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06),
|
||||
0 12px 26px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .week-slot-copy {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-color: rgba(243, 177, 125, 0.12);
|
||||
@@ -1579,6 +1768,12 @@ legend {
|
||||
border-color: rgba(243, 177, 125, 0.16);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .week-entry-dialog-card {
|
||||
background: rgba(43, 37, 35, 0.98);
|
||||
border-color: rgba(243, 177, 125, 0.14);
|
||||
box-shadow: 0 24px 50px rgba(0, 0, 0, 0.34);
|
||||
}
|
||||
|
||||
.flash-stack {
|
||||
display: grid;
|
||||
gap: 0.7rem;
|
||||
@@ -1607,6 +1802,11 @@ legend {
|
||||
min-width: 5rem;
|
||||
}
|
||||
|
||||
.theme-toggle,
|
||||
.mobile-extra-button[data-theme-toggle] {
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.ui-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
@@ -1651,6 +1851,36 @@ legend {
|
||||
mask-image: url("../icons/fa/calendar-days.svg");
|
||||
}
|
||||
|
||||
.icon-daypart-breakfast {
|
||||
-webkit-mask-image: url("../icons/dayparts/breakfast.svg");
|
||||
mask-image: url("../icons/dayparts/breakfast.svg");
|
||||
}
|
||||
|
||||
.icon-daypart-morning-snack {
|
||||
-webkit-mask-image: url("../icons/dayparts/morning-snack.svg");
|
||||
mask-image: url("../icons/dayparts/morning-snack.svg");
|
||||
}
|
||||
|
||||
.icon-daypart-lunch {
|
||||
-webkit-mask-image: url("../icons/dayparts/lunch.svg");
|
||||
mask-image: url("../icons/dayparts/lunch.svg");
|
||||
}
|
||||
|
||||
.icon-daypart-afternoon-snack {
|
||||
-webkit-mask-image: url("../icons/dayparts/afternoon-snack.svg");
|
||||
mask-image: url("../icons/dayparts/afternoon-snack.svg");
|
||||
}
|
||||
|
||||
.icon-daypart-dinner {
|
||||
-webkit-mask-image: url("../icons/dayparts/dinner.svg");
|
||||
mask-image: url("../icons/dayparts/dinner.svg");
|
||||
}
|
||||
|
||||
.icon-daypart-late-snack {
|
||||
-webkit-mask-image: url("../icons/dayparts/late-snack.svg");
|
||||
mask-image: url("../icons/dayparts/late-snack.svg");
|
||||
}
|
||||
|
||||
.icon-archive {
|
||||
-webkit-mask-image: url("../icons/fa/archive.svg");
|
||||
mask-image: url("../icons/fa/archive.svg");
|
||||
@@ -1696,6 +1926,16 @@ legend {
|
||||
mask-image: url("../icons/fa/mobile-screen-button.svg");
|
||||
}
|
||||
|
||||
.icon-sun-theme {
|
||||
-webkit-mask-image: url("../icons/fa/theme-sun.svg");
|
||||
mask-image: url("../icons/fa/theme-sun.svg");
|
||||
}
|
||||
|
||||
.icon-moon-theme {
|
||||
-webkit-mask-image: url("../icons/fa/theme-moon.svg");
|
||||
mask-image: url("../icons/fa/theme-moon.svg");
|
||||
}
|
||||
|
||||
.icon-apple-whole {
|
||||
-webkit-mask-image: url("../icons/fa/apple-whole.svg");
|
||||
mask-image: url("../icons/fa/apple-whole.svg");
|
||||
@@ -1847,6 +2087,18 @@ legend {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.day-tile-icon {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
flex-basis: 2.8rem;
|
||||
border-radius: 0.85rem;
|
||||
}
|
||||
|
||||
.day-tile-icon .ui-icon {
|
||||
width: 1.12rem;
|
||||
height: 1.12rem;
|
||||
}
|
||||
|
||||
.hero,
|
||||
.page-intro,
|
||||
.panel,
|
||||
@@ -2005,6 +2257,10 @@ legend {
|
||||
.mobile-nav-button {
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.mobile-bottom-nav a.active,
|
||||
@@ -2012,6 +2268,62 @@ legend {
|
||||
.mobile-nav-button.is-open {
|
||||
background: var(--accent-soft);
|
||||
color: var(--text);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mobile-nav-button.is-open {
|
||||
background: var(--accent-soft);
|
||||
color: var(--text);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mobile-nav-stack button.mobile-nav-button:not(.secondary):not(.ghost-button) {
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mobile-nav-stack button.mobile-nav-button:not(.secondary):not(.ghost-button):hover {
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mobile-nav-stack button.mobile-nav-button.is-open:not(.secondary):not(.ghost-button),
|
||||
[data-theme="dark"] .mobile-nav-stack button.mobile-nav-button.is-open:not(.secondary):not(.ghost-button):hover {
|
||||
background: var(--accent-soft);
|
||||
color: var(--text);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mobile-nav-extension .mobile-extra-button,
|
||||
[data-theme="dark"] .mobile-nav-extension .mobile-extra-form .mobile-extra-button {
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mobile-nav-extension .mobile-extra-button[data-theme-toggle] {
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mobile-nav-extension .mobile-extra-button:hover,
|
||||
[data-theme="dark"] .mobile-nav-extension .mobile-extra-button:focus-visible,
|
||||
[data-theme="dark"] .mobile-nav-extension .mobile-extra-form .mobile-extra-button:hover,
|
||||
[data-theme="dark"] .mobile-nav-extension .mobile-extra-form .mobile-extra-button:focus-visible {
|
||||
background: var(--accent-soft);
|
||||
color: var(--text);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.mobile-profile-link {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M362.2 37L213.9 16 81.7 86.7 16 222.1 42 370.4 149.8 475 298.1 496 430.3 425.3 496 289.9 470 141.6 362.2 37zM208 144a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM144 336a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm224-64a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M243.2 .3c4.2-.2 8.5-.3 12.8-.3 62.1 0 118.9 22.1 163.3 58.8L314.6 163.4 243.2 .3zM194 7.6L307.4 266.6 267.3 306.8 12 178.3C38.8 94.2 107.7 29 194 7.6zM1.6 226.8l166 83.6-108.9 108.9C22.1 374.9 0 318.1 0 256 0 246.1 .6 236.4 1.6 226.8zM92.7 453.2l120.1-120.1 11.2 5.6 0 171.3c-49.5-6.2-94.7-26.5-131.3-56.8zM341.2 224l-5.9-13.4 117.9-117.9c30.3 36.6 50.6 81.7 56.8 131.3l-168.8 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 648 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M56 16L40 16 0 152c0 41.5 31.6 75.6 72 79.6l0 264.4 48 0 0-264.4c40.4-4 72-38.1 72-79.6l-40-136-16 0 0 136-16 0-16-136-16 0-16 136-16 0 0-136zm584 0S512 32 512 160l0 160 80 0 0 176 48 0 0-480zM336 32c-43.8 0-84.7 12.6-119.2 34.3l19.1 64.9c27.4-22 62.2-35.2 100.1-35.2 52.3 0 98.8 25.1 128 64 0-29.7 5.5-55.2 14.5-76.9-38.7-31.9-88.3-51.1-142.5-51.1zm0 384c-86.1 0-156.3-68-159.9-153.2-2.7 1.5-5.4 3-8.1 4.3l0 137c41 46.5 101.1 75.8 168 75.8 82.9 0 155.3-45 194-112l-66 0 0-16c-29.2 38.9-75.7 64-128 64zM448 256a112 112 0 1 0 -224 0 112 112 0 1 0 224 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 820 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M359.7 21.9C272.6 43.5 208 122.2 208 216 208 326.4 297.5 416 408 416 426.5 416 444.4 413.5 461.4 408.8 414.8 471.4 340.1 512 256 512 114.6 512 0 397.4 0 256S114.6 0 256 0c36.9 0 72 7.8 103.7 21.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 464 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M0 64l512 0 0 112-512 0 0-112zM320 384l96-48 96 0 0 112-512 0 0-112 224 0 96 48zM144.2 209.1l111.8 29.8 111.8-29.8 8-2.1 8 2c74.8 18.7 117.2 29.3 127 31.8l-15.5 62.1c-11.2-2.8-50.9-12.7-119-29.8l-112 29.9-8.2 2.2-8.2-2.2-112-29.9c-68.1 17-107.8 27-119 29.8L1.2 240.7c9.9-2.5 52.2-13.1 127-31.8l8-2 8 2.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 572 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2025 Fonticons, Inc. --><path fill="currentColor" d="M208 96l0-16c0-44.2 35.8-80 80-80l32 0 0 32c0 44.2-35.8 80-80 80l-32 0 0-16zM0 288c0-76.3 35.7-160 112-160l112 32 112-32c76.3 0 112 83.7 112 160 0 128-80 224-160 224l-64-16-64 16C80 512 0 416 0 288z"/></svg>
|
||||
|
After Width: | Height: | Size: 466 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><path fill="currentColor" d="M99.1 99.8c36.8-31.2 88.7-50.7 158.9-51.8 70.8 1.3 118.7 27.7 149.8 67.9 32.6 42.2 48.7 102.3 48.7 172s-16.1 129.8-48.7 172c-31 40.2-79 66.6-149.8 67.9-70.2-1.1-122.1-20.6-158.9-51.8 14.2 2.5 29.3 3.8 45.4 3.8 4.1 0 8.1-.1 12-.2l0 .2c80.4 0 138.3-19.7 176.2-55.9 38-36.3 51.8-85.6 51.8-136.1s-13.8-99.8-51.8-136.1C294.8 115.7 236.9 96 156.5 96l0 .2c-3.9-.2-7.9-.2-12-.2-16.1 0-31.2 1.3-45.4 3.8zM252.5 .2C125.1 3.6 42.8 62.2 3.5 150.3l38.2 27.5c21.9-20.1 54.9-33.8 102.8-33.8 53.4 0 88.4 16.9 110.2 41.2 22.3 24.8 33.8 60.5 33.8 102.8S277 366 254.7 390.8c-21.9 24.4-56.8 41.2-110.2 41.2-47.9 0-80.9-13.6-102.8-33.8L3.5 425.7c39.3 88.2 121.6 146.7 249 150.1l0 .2c1.9 0 3.8 0 5.6 0 2.1 0 4.2 0 6.4 0 97.8 0 170.8-31.5 219.2-85.3 47.9-53.4 68.8-125.7 68.8-202.7S531.6 138.7 483.6 85.3c-48.3-53.8-121.4-85.3-219.2-85.3-2.1 0-4.3 0-6.4 0-1.9 0-3.7 0-5.6 0l0 .2zM216.5 252c0-26.5-14.4-48-48-48s-48 21.5-48 48 14.4 48 48 48 48-21.5 48-48zm-144 96c25.2 0 36-16.1 36-36s-10.8-36-36-36-36 16.1-36 36 10.8 36 36 36z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 576"><path fill="currentColor" d="M309.6 0L266.4 0 222 47.9c-13.9 15-33.5 23.3-53.9 23L89.2 63 63 89.4 70.9 164.2c.3 19.5-7.3 38.3-21.1 52.1L0 266.1 0 309.6 47.9 354c15 13.9 23.3 33.5 23 53.9l-7.9 78.8 28 26 73.3-12.8c23.9-2.2 47.3 7.7 62.4 26.4l40.2 49.6 42.3 0 40.2-49.6c15.1-18.7 38.5-28.5 62.4-26.4l73.3 12.8 28-26-7.9-78.8c-.3-20.4 8-40 23-53.9l47.9-44.4 0-43.5-49.8-49.8c-13.8-13.8-21.4-32.6-21.1-52.1l7.9-74.8-26.3-26.4-78.8 7.9c-20.4 .3-40-8-53.9-23L309.6 0zM288 64.4l4.8 9.4c26.8 52.1 87.4 77.2 143.2 59.3l10-3.2-3.2 10c-17.9 55.8 7.2 116.4 59.3 143.2l9.4 4.8-9.4 4.8c-52.1 26.8-77.2 87.4-59.3 143.2l3.2 10-10-3.2c-55.8-17.9-116.4 7.2-143.2 59.3l-4.8 9.4-4.8-9.4c-26.8-52.1-87.4-77.2-143.2-59.3l-10 3.2 3.2-10c17.9-55.8-7.2-116.4-59.3-143.2l-9.4-4.8 9.4-4.8c52.1-26.8 77.2-87.4 59.3-143.2l-3.2-10 10 3.2c55.8 17.9 116.4-7.2 143.2-59.3l4.8-9.4zM322.3 224c8.6 14.4 13.7 36.5 13.7 64s-5.1 49.6-13.7 64c-7.8 13.1-18.4 20-34.3 20s-26.5-6.9-34.3-20c-8.6-14.4-13.7-36.5-13.7-64s5.1-49.6 13.7-64c7.8-13.1 18.4-20 34.3-20s26.5 6.9 34.3 20zM288 156c-43.2 0-77.2 14-100.2 39.6-22.6 25.1-31.8 58.5-31.8 92.4s9.2 67.3 31.8 92.4c23 25.6 57 39.6 100.2 39.6s77.2-14 100.2-39.6C410.8 355.3 420 321.9 420 288s-9.2-67.3-31.8-92.4C365.2 170 331.2 156 288 156z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -95,12 +95,14 @@
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
rememberScroll();
|
||||
if (result.redirect_url) {
|
||||
window.location.href = result.redirect_url;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (_error) {
|
||||
rememberScroll();
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
@@ -196,6 +198,72 @@
|
||||
});
|
||||
};
|
||||
|
||||
const initWeekEntryDialogs = () => {
|
||||
const board = document.querySelector(".week-board");
|
||||
if (!board) return;
|
||||
|
||||
const openDialog = (trigger) => {
|
||||
const dialogId = trigger.getAttribute("data-week-entry-dialog-id");
|
||||
if (!dialogId) return;
|
||||
const dialog = document.getElementById(dialogId);
|
||||
if (!(dialog instanceof HTMLDialogElement)) return;
|
||||
if (!dialog.open) {
|
||||
dialog.showModal();
|
||||
}
|
||||
};
|
||||
|
||||
board.querySelectorAll("[data-week-entry-open]").forEach((entry) => {
|
||||
entry.addEventListener("click", (event) => {
|
||||
if (event.target instanceof Element && event.target.closest("button, a, input, select, textarea, label, form")) {
|
||||
return;
|
||||
}
|
||||
openDialog(entry);
|
||||
});
|
||||
|
||||
entry.addEventListener("keydown", (event) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return;
|
||||
event.preventDefault();
|
||||
openDialog(entry);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll(".week-entry-dialog").forEach((dialog) => {
|
||||
if (!(dialog instanceof HTMLDialogElement)) return;
|
||||
|
||||
dialog.addEventListener("click", (event) => {
|
||||
const rect = dialog.getBoundingClientRect();
|
||||
const clickedInside =
|
||||
rect.top <= event.clientY &&
|
||||
event.clientY <= rect.top + rect.height &&
|
||||
rect.left <= event.clientX &&
|
||||
event.clientX <= rect.left + rect.width;
|
||||
if (!clickedInside) {
|
||||
dialog.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-week-entry-close]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const dialog = button.closest(".week-entry-dialog");
|
||||
if (dialog instanceof HTMLDialogElement) {
|
||||
dialog.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll(".js-week-entry-submit").forEach((form) => {
|
||||
form.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
await postAndRefreshInPlace(form);
|
||||
} catch (_error) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const syncActionContainerVisibility = (container) => {
|
||||
if (!(container instanceof HTMLElement)) return;
|
||||
const hasVisibleButtons = Array.from(container.querySelectorAll("button")).some((button) => {
|
||||
@@ -285,6 +353,7 @@
|
||||
initWeekDragAndDrop();
|
||||
initWeekCopyForward();
|
||||
initWeekSlotPicker();
|
||||
initWeekEntryDialogs();
|
||||
initDaySnackReveal();
|
||||
initWeekSnackReveal();
|
||||
});
|
||||
|
||||
@@ -12,7 +12,22 @@
|
||||
root.dataset.theme = finalTheme;
|
||||
|
||||
toggles().forEach((button) => {
|
||||
button.textContent = finalTheme === "dark" ? "Hell" : "Dunkel";
|
||||
const nextModeLabel = finalTheme === "dark" ? "Hell" : "Dunkel";
|
||||
const label = button.querySelector("[data-theme-label]");
|
||||
const icon = button.querySelector("[data-theme-icon]");
|
||||
|
||||
if (label) {
|
||||
label.textContent = nextModeLabel;
|
||||
} else {
|
||||
button.textContent = nextModeLabel;
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
icon.classList.toggle("icon-sun-theme", finalTheme === "dark");
|
||||
icon.classList.toggle("icon-moon-theme", finalTheme !== "dark");
|
||||
}
|
||||
|
||||
button.setAttribute("aria-label", `${nextModeLabel} aktivieren`);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -53,7 +53,10 @@
|
||||
{% if g.user %}
|
||||
<div class="desktop-header-sub">
|
||||
<div class="header-actions desktop-actions">
|
||||
<button class="theme-toggle ghost-button" type="button" data-theme-toggle>Hell</button>
|
||||
<button class="theme-toggle ghost-button" type="button" data-theme-toggle aria-label="Darstellung wechseln">
|
||||
<span class="ui-icon icon-sun-theme" data-theme-icon></span>
|
||||
<span data-theme-label>Hell</span>
|
||||
</button>
|
||||
<a class="ghost-button" href="{{ url_for('main.settings_view') }}">Optionen</a>
|
||||
<a class="user-chip" href="{{ url_for('auth.profile') }}">
|
||||
<span class="user-chip-title">{{ g.user.display_name or g.user.username }}</span>
|
||||
@@ -114,9 +117,9 @@
|
||||
<a class="mobile-extra-link" href="{{ url_for('admin.user_list') }}"><span class="ui-icon icon-sparkles"></span><span>Nutzer</span></a>
|
||||
<a class="mobile-extra-link" href="{{ url_for('admin.category_settings') }}"><span class="ui-icon icon-seedling"></span><span>Kategorien</span></a>
|
||||
{% endif %}
|
||||
<button class="mobile-extra-link mobile-extra-button" type="button" data-theme-toggle>
|
||||
<span class="ui-icon icon-mobile-screen-button"></span>
|
||||
<span>Modus</span>
|
||||
<button class="mobile-extra-link mobile-extra-button" type="button" data-theme-toggle aria-label="Darstellung wechseln">
|
||||
<span class="ui-icon icon-sun-theme" data-theme-icon></span>
|
||||
<span data-theme-label>Hell</span>
|
||||
</button>
|
||||
<form method="post" action="{{ url_for('auth.logout') }}" class="mobile-extra-form">
|
||||
{{ csrf_input() }}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
{% endif %}
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<article class="panel dashboard-spaced-panel">
|
||||
<div class="panel-head">
|
||||
<h2>Kurz griffbereit</h2>
|
||||
<a href="{{ url_for('main.home_view') }}">Alles unter Zuhause</a>
|
||||
@@ -122,7 +122,7 @@
|
||||
</section>
|
||||
|
||||
<section class="two-column">
|
||||
<article class="panel">
|
||||
<article class="panel dashboard-spaced-panel">
|
||||
<div class="panel-head">
|
||||
<h2>Was zuhause gut zusammenpasst</h2>
|
||||
<a href="{{ url_for('main.home_view') }}">Zuhause öffnen</a>
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
>
|
||||
<summary class="day-tile-summary">
|
||||
<div class="day-tile-summary-main">
|
||||
<div class="day-tile-icon"><span class="ui-icon icon-calendar"></span></div>
|
||||
<div class="day-tile-icon"><span class="ui-icon {{ daypart_icon_class(section.daypart.slug) }}"></span></div>
|
||||
<div>
|
||||
<h2>{{ section.daypart.name }}</h2>
|
||||
{% if section.summary_items %}
|
||||
|
||||
@@ -129,7 +129,10 @@
|
||||
{% if slot.is_snack_daypart and not slot.visible_by_default %}hidden data-week-snack-slot{% endif %}
|
||||
>
|
||||
<div class="week-slot-head">
|
||||
<div class="week-slot-title">
|
||||
<span class="ui-icon {{ daypart_icon_class(slot.daypart.slug) }}"></span>
|
||||
<strong>{{ slot.daypart.name }}</strong>
|
||||
</div>
|
||||
<div class="week-slot-head-meta">
|
||||
<span class="week-slot-count{% if slot.entries %} status-home{% endif %}">{{ slot.entries|length }}</span>
|
||||
<button class="week-slot-add" type="button" data-week-slot-picker-open aria-label="{{ slot.daypart.name }} an {{ weekday_name(card.date) }} direkt ergänzen">+</button>
|
||||
@@ -210,10 +213,61 @@
|
||||
{% if slot.entries %}
|
||||
<div class="week-entry-stack">
|
||||
{% for entry in slot.entries %}
|
||||
<article class="plan-chip draggable-plan-entry" draggable="{{ 'true' if entry.can_edit else 'false' }}" data-entry-id="{{ entry.id }}" data-move-url="{{ url_for('main.planner_move', entry_id=entry.id) }}">
|
||||
<article
|
||||
class="plan-chip draggable-plan-entry{% if entry.can_edit %} is-editable{% endif %}"
|
||||
draggable="{{ 'true' if entry.can_edit else 'false' }}"
|
||||
data-entry-id="{{ entry.id }}"
|
||||
data-move-url="{{ url_for('main.planner_move', entry_id=entry.id) }}"
|
||||
{% if entry.can_edit %}
|
||||
data-week-entry-open
|
||||
data-week-entry-dialog-id="week-entry-dialog-{{ entry.id }}"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-label="{{ entry.item_name }} bearbeiten"
|
||||
{% endif %}
|
||||
>
|
||||
<strong>{{ entry.item_name }}</strong>
|
||||
<small>{{ entry.visibility_label }} · {{ entry.for_label }}</small>
|
||||
</article>
|
||||
{% if entry.can_edit %}
|
||||
<dialog class="week-entry-dialog" id="week-entry-dialog-{{ entry.id }}">
|
||||
<div class="week-entry-dialog-card">
|
||||
<div class="week-entry-dialog-head">
|
||||
<div>
|
||||
<h3>{{ entry.item_name }}</h3>
|
||||
<p>{{ slot.daypart.name }} · {{ weekday_name(card.date) }}, {{ card.date.strftime('%d.%m.%Y') }}</p>
|
||||
</div>
|
||||
<button class="ghost-button" type="button" data-week-entry-close>Schließen</button>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('main.planner_update', entry_id=entry.id) }}" class="planner-entry-inline-form js-week-entry-submit">
|
||||
{{ csrf_input() }}
|
||||
<input type="hidden" name="plan_date" value="{{ card.date.isoformat() }}">
|
||||
<input type="hidden" name="return_week" value="{{ week_start.isoformat() }}">
|
||||
<label>
|
||||
Für wen?
|
||||
<select name="visibility">
|
||||
{% for value, label in visibility_options %}
|
||||
<option value="{{ value }}" {% if entry.visibility == value %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="wide">
|
||||
Notiz
|
||||
<input type="text" name="note" value="{{ entry.note or '' }}" placeholder="Optional">
|
||||
</label>
|
||||
<div class="week-entry-dialog-actions">
|
||||
<button type="submit">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
<form method="post" action="{{ url_for('main.planner_remove', entry_id=entry.id) }}" class="week-entry-remove-form js-week-entry-submit">
|
||||
{{ csrf_input() }}
|
||||
<input type="hidden" name="plan_date" value="{{ card.date.isoformat() }}">
|
||||
<input type="hidden" name="return_week" value="{{ week_start.isoformat() }}">
|
||||
<button class="ghost-button" type="submit">Eintrag entfernen</button>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="week-slot-actions">
|
||||
|
||||