Compare commits
4 Commits
538b70d139
...
4ad218cc39
| Author | SHA1 | Date | |
|---|---|---|---|
|
4ad218cc39
|
|||
|
3cd2a678a6
|
|||
|
e5d218820b
|
|||
|
70506e169a
|
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.49.3",
|
"version": "1.52.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Migrate `$app/stores` (deprecated) to `$app/state` (rune-based).
|
||||||
|
*
|
||||||
|
* For each .svelte file:
|
||||||
|
* - Rewrite `from '$app/stores'` → `from '$app/state'`
|
||||||
|
* - For each named import, drop the `$` prefix from auto-subscriptions:
|
||||||
|
* `$page.url.pathname` → `page.url.pathname`
|
||||||
|
* `$navigating` → `navigating`
|
||||||
|
* `$updated` → `updated`
|
||||||
|
* Aliased imports (`page as appPage`) are tracked, so `$appPage` becomes `appPage`.
|
||||||
|
*
|
||||||
|
* Skips:
|
||||||
|
* - Non-.svelte files (server-only code uses getRequestEvent instead).
|
||||||
|
* - Files importing other things from $app/stores that don't have a state equivalent
|
||||||
|
* (none observed in this repo).
|
||||||
|
*
|
||||||
|
* Run: pnpm exec vite-node scripts/codemod-app-stores-to-state.ts [--dry]
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
||||||
|
import { join, extname } from 'node:path';
|
||||||
|
|
||||||
|
const SRC = 'src';
|
||||||
|
const DRY = process.argv.includes('--dry');
|
||||||
|
|
||||||
|
const STORES_IMPORT_RE =
|
||||||
|
/import\s*\{([^}]+)\}\s*from\s*['"]\$app\/stores['"]\s*;?/;
|
||||||
|
|
||||||
|
function walk(dir: string, out: string[] = []): string[] {
|
||||||
|
for (const name of readdirSync(dir)) {
|
||||||
|
const p = join(dir, name);
|
||||||
|
const s = statSync(p);
|
||||||
|
if (s.isDirectory()) walk(p, out);
|
||||||
|
else if (extname(p) === '.svelte') out.push(p);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseImports(inner: string): Array<{ orig: string; local: string }> {
|
||||||
|
return inner
|
||||||
|
.split(',')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((spec) => {
|
||||||
|
const m = spec.match(/^(\w+)(?:\s+as\s+(\w+))?$/);
|
||||||
|
if (!m) return null;
|
||||||
|
return { orig: m[1], local: m[2] ?? m[1] };
|
||||||
|
})
|
||||||
|
.filter((x): x is { orig: string; local: string } => x !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteFile(src: string): { code: string; changed: boolean } {
|
||||||
|
const m = STORES_IMPORT_RE.exec(src);
|
||||||
|
if (!m) return { code: src, changed: false };
|
||||||
|
|
||||||
|
const imports = parseImports(m[1]);
|
||||||
|
if (imports.length === 0) return { code: src, changed: false };
|
||||||
|
|
||||||
|
// Replace the import path; preserve the same import shape.
|
||||||
|
let out = src.replace(STORES_IMPORT_RE, (full) =>
|
||||||
|
full.replace(/['"]\$app\/stores['"]/, "'$app/state'")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Drop `$` prefix from each local name where it appears as a store
|
||||||
|
// auto-subscription (i.e. $name followed by a non-word boundary).
|
||||||
|
for (const { local } of imports) {
|
||||||
|
const re = new RegExp(`\\$${local}\\b`, 'g');
|
||||||
|
out = out.replace(re, local);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { code: out, changed: out !== src };
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = walk(SRC);
|
||||||
|
let changed = 0;
|
||||||
|
for (const f of files) {
|
||||||
|
const orig = readFileSync(f, 'utf8');
|
||||||
|
const { code, changed: didChange } = rewriteFile(orig);
|
||||||
|
if (!didChange) continue;
|
||||||
|
if (!DRY) writeFileSync(f, code);
|
||||||
|
changed++;
|
||||||
|
console.log(` ${f}`);
|
||||||
|
}
|
||||||
|
console.log(`\n${DRY ? '[dry] ' : ''}${changed} files migrated`);
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
/**
|
||||||
|
* Bucket 2 codemod: replace template-literal hrefs that start with `/` and
|
||||||
|
* contain `{expr}` interpolations with `resolve(routeId, { ... })`.
|
||||||
|
*
|
||||||
|
* Skips:
|
||||||
|
* - tags: <link>, <image> (svg), <use>, <textPath>
|
||||||
|
* - hrefs not starting with `/`
|
||||||
|
* - hrefs containing `?` or `#` (query/fragment) — handle manually
|
||||||
|
* - mixed segments like `view-{id}`
|
||||||
|
* - paths matching 0 or >1 routes
|
||||||
|
*
|
||||||
|
* Run: pnpm exec vite-node scripts/codemod-href-resolve-bucket2.ts [--dry] [--verbose]
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
||||||
|
import { join, extname } from 'node:path';
|
||||||
|
|
||||||
|
const SRC = 'src';
|
||||||
|
const ROUTES = 'src/routes';
|
||||||
|
const DRY = process.argv.includes('--dry');
|
||||||
|
|
||||||
|
const SKIP_TAGS = new Set(['link', 'image', 'use', 'textpath']);
|
||||||
|
|
||||||
|
// --- Route tree ---------------------------------------------------------
|
||||||
|
|
||||||
|
type Dir = { name: string; subdirs: Dir[] };
|
||||||
|
|
||||||
|
function loadTree(dir: string, name = ''): Dir {
|
||||||
|
const subdirs: Dir[] = [];
|
||||||
|
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
if (!e.isDirectory()) continue;
|
||||||
|
if (e.name === 'api' || e.name.startsWith('.')) continue;
|
||||||
|
subdirs.push(loadTree(join(dir, e.name), e.name));
|
||||||
|
}
|
||||||
|
return { name, subdirs };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROUTE_TREE = loadTree(ROUTES);
|
||||||
|
|
||||||
|
// --- Path parsing -------------------------------------------------------
|
||||||
|
|
||||||
|
type HrefSeg = { kind: 'literal'; text: string } | { kind: 'param'; expr: string };
|
||||||
|
|
||||||
|
function hasUnbracedChar(path: string, chars: string): boolean {
|
||||||
|
let depth = 0;
|
||||||
|
for (const c of path) {
|
||||||
|
if (c === '{') depth++;
|
||||||
|
else if (c === '}') depth--;
|
||||||
|
else if (depth === 0 && chars.includes(c)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePath(path: string): HrefSeg[] | null {
|
||||||
|
if (!path.startsWith('/')) return null;
|
||||||
|
if (hasUnbracedChar(path, '?#')) return null;
|
||||||
|
if (path.includes('//')) return null;
|
||||||
|
// Split on `/`, but only outside of {...}
|
||||||
|
const parts: string[] = [];
|
||||||
|
let buf = '';
|
||||||
|
let depth = 0;
|
||||||
|
for (const c of path.slice(1)) {
|
||||||
|
if (c === '{') { depth++; buf += c; }
|
||||||
|
else if (c === '}') { depth--; buf += c; }
|
||||||
|
else if (c === '/' && depth === 0) { parts.push(buf); buf = ''; }
|
||||||
|
else buf += c;
|
||||||
|
}
|
||||||
|
parts.push(buf);
|
||||||
|
if (parts.length === 1 && parts[0] === '') return [];
|
||||||
|
const segs: HrefSeg[] = [];
|
||||||
|
for (const p of parts) {
|
||||||
|
if (p === '') return null;
|
||||||
|
const m = p.match(/^\{([^}]+)\}$/);
|
||||||
|
if (m) {
|
||||||
|
segs.push({ kind: 'param', expr: m[1] });
|
||||||
|
} else if (!p.includes('{') && !p.includes('}')) {
|
||||||
|
segs.push({ kind: 'literal', text: p });
|
||||||
|
} else {
|
||||||
|
return null; // mixed segment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return segs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function paramInfo(
|
||||||
|
name: string
|
||||||
|
): { paramName: string; isRest: boolean } | null {
|
||||||
|
let body = name;
|
||||||
|
if (body.startsWith('[[') && body.endsWith(']]')) {
|
||||||
|
body = body.slice(2, -2);
|
||||||
|
} else if (body.startsWith('[') && body.endsWith(']')) {
|
||||||
|
body = body.slice(1, -1);
|
||||||
|
} else return null;
|
||||||
|
const isRest = body.startsWith('...');
|
||||||
|
if (isRest) body = body.slice(3);
|
||||||
|
const eq = body.indexOf('=');
|
||||||
|
const paramName = eq >= 0 ? body.slice(0, eq) : body;
|
||||||
|
return { paramName, isRest };
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tree matching ------------------------------------------------------
|
||||||
|
|
||||||
|
type Match = { routeId: string; params: Array<[string, string]> };
|
||||||
|
|
||||||
|
function matchTree(
|
||||||
|
dir: Dir,
|
||||||
|
segs: HrefSeg[],
|
||||||
|
routePath: string[],
|
||||||
|
params: Array<[string, string]>
|
||||||
|
): Match[] {
|
||||||
|
if (segs.length === 0) {
|
||||||
|
const id = routePath.length === 0 ? '/' : '/' + routePath.join('/');
|
||||||
|
return [{ routeId: id, params }];
|
||||||
|
}
|
||||||
|
const [seg, ...rest] = segs;
|
||||||
|
const out: Match[] = [];
|
||||||
|
for (const sub of dir.subdirs) {
|
||||||
|
// Route groups are transparent — they don't consume a URL segment
|
||||||
|
// but DO appear in the route ID.
|
||||||
|
if (sub.name.startsWith('(') && sub.name.endsWith(')')) {
|
||||||
|
out.push(...matchTree(sub, segs, [...routePath, sub.name], params));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seg.kind === 'literal') {
|
||||||
|
if (sub.name === seg.text) {
|
||||||
|
out.push(
|
||||||
|
...matchTree(sub, rest, [...routePath, sub.name], params)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const info = paramInfo(sub.name);
|
||||||
|
if (info && !info.isRest) {
|
||||||
|
out.push(
|
||||||
|
...matchTree(sub, rest, [...routePath, sub.name], [
|
||||||
|
...params,
|
||||||
|
[info.paramName, seg.expr]
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Output formatting --------------------------------------------------
|
||||||
|
|
||||||
|
function isIdentifier(s: string): boolean {
|
||||||
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatParams(params: Array<[string, string]>): string {
|
||||||
|
if (params.length === 0) return '';
|
||||||
|
const items = params.map(([name, expr]) => {
|
||||||
|
const trimmed = expr.trim();
|
||||||
|
if (isIdentifier(trimmed) && trimmed === name) return name;
|
||||||
|
return `${name}: ${trimmed}`;
|
||||||
|
});
|
||||||
|
return `, { ${items.join(', ')} }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Rewrite ------------------------------------------------------------
|
||||||
|
|
||||||
|
const HREF_RE =
|
||||||
|
/(<([A-Za-z][\w.-]*)\b[^>]*?\s)href="(\/[^"]*\{[^"]*\}[^"]*)"/gs;
|
||||||
|
|
||||||
|
type Skip = { path: string; reason: string };
|
||||||
|
|
||||||
|
function rewriteHrefs(src: string): {
|
||||||
|
code: string;
|
||||||
|
changed: number;
|
||||||
|
skipped: Skip[];
|
||||||
|
} {
|
||||||
|
let changed = 0;
|
||||||
|
const skipped: Skip[] = [];
|
||||||
|
const code = src.replace(HREF_RE, (full, prefix, tag, path) => {
|
||||||
|
if (SKIP_TAGS.has(tag.toLowerCase())) return full;
|
||||||
|
const segs = parsePath(path);
|
||||||
|
if (!segs) {
|
||||||
|
skipped.push({ path, reason: 'unparsable (mixed/query/fragment)' });
|
||||||
|
return full;
|
||||||
|
}
|
||||||
|
const matches = matchTree(ROUTE_TREE, segs, [], []);
|
||||||
|
if (matches.length === 0) {
|
||||||
|
skipped.push({ path, reason: 'no route match' });
|
||||||
|
return full;
|
||||||
|
}
|
||||||
|
if (matches.length > 1) {
|
||||||
|
skipped.push({
|
||||||
|
path,
|
||||||
|
reason: `${matches.length} ambiguous matches: ${matches.map((m) => m.routeId).join(' | ')}`
|
||||||
|
});
|
||||||
|
return full;
|
||||||
|
}
|
||||||
|
const { routeId, params } = matches[0];
|
||||||
|
changed++;
|
||||||
|
return `${prefix}href={resolve('${routeId}'${formatParams(params)})}`;
|
||||||
|
});
|
||||||
|
return { code, changed, skipped };
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Import injection ---------------------------------------------------
|
||||||
|
|
||||||
|
const SCRIPT_RE = /<script\b([^>]*)>([\s\S]*?)<\/script>/;
|
||||||
|
const PATHS_IMPORT_RE =
|
||||||
|
/import\s*\{([^}]*)\}\s*from\s*['"]\$app\/paths['"]\s*;?/;
|
||||||
|
|
||||||
|
function ensureResolveImport(src: string): string {
|
||||||
|
const m = SCRIPT_RE.exec(src);
|
||||||
|
if (!m) {
|
||||||
|
return `<script lang="ts">\n\timport { resolve } from '$app/paths';\n</script>\n\n${src}`;
|
||||||
|
}
|
||||||
|
const [scriptFull, attrs, body] = m;
|
||||||
|
const pm = PATHS_IMPORT_RE.exec(body);
|
||||||
|
if (pm) {
|
||||||
|
const inner = pm[1];
|
||||||
|
if (/\bresolve\b/.test(inner)) return src;
|
||||||
|
const merged = inner.trim().replace(/,?\s*$/, '') + ', resolve';
|
||||||
|
const newImport = `import { ${merged} } from '$app/paths';`;
|
||||||
|
const newBody = body.replace(PATHS_IMPORT_RE, newImport);
|
||||||
|
return src.replace(scriptFull, `<script${attrs}>${newBody}</script>`);
|
||||||
|
}
|
||||||
|
const im = body.match(/^([ \t]*)import\b/m);
|
||||||
|
const indent = im ? im[1] : '\t';
|
||||||
|
const opening = `<script${attrs}>`;
|
||||||
|
return src.replace(
|
||||||
|
scriptFull,
|
||||||
|
`${opening}\n${indent}import { resolve } from '$app/paths';${body}</script>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Driver -------------------------------------------------------------
|
||||||
|
|
||||||
|
function walk(dir: string, out: string[] = []): string[] {
|
||||||
|
for (const name of readdirSync(dir)) {
|
||||||
|
const p = join(dir, name);
|
||||||
|
const s = statSync(p);
|
||||||
|
if (s.isDirectory()) walk(p, out);
|
||||||
|
else if (extname(p) === '.svelte') out.push(p);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = walk(SRC);
|
||||||
|
let totalFiles = 0;
|
||||||
|
let totalReplacements = 0;
|
||||||
|
const allSkipped: Array<{ file: string } & Skip> = [];
|
||||||
|
|
||||||
|
for (const f of files) {
|
||||||
|
const orig = readFileSync(f, 'utf8');
|
||||||
|
const { code, changed, skipped } = rewriteHrefs(orig);
|
||||||
|
for (const s of skipped) allSkipped.push({ file: f, ...s });
|
||||||
|
if (changed === 0) continue;
|
||||||
|
const final = ensureResolveImport(code);
|
||||||
|
if (!DRY) writeFileSync(f, final);
|
||||||
|
totalFiles++;
|
||||||
|
totalReplacements += changed;
|
||||||
|
console.log(`${changed.toString().padStart(3)} ${f}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${DRY ? '[dry] ' : ''}${totalReplacements} replacements across ${totalFiles} files`
|
||||||
|
);
|
||||||
|
if (allSkipped.length > 0) {
|
||||||
|
console.log(`\n--- ${allSkipped.length} skipped hrefs ---`);
|
||||||
|
for (const s of allSkipped) {
|
||||||
|
console.log(` ${s.file}\n ${s.path} [${s.reason}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* Bucket 1 codemod: replace literal href="/path" with href={resolve('/path')}
|
||||||
|
* in .svelte files, and inject `import { resolve } from '$app/paths'`.
|
||||||
|
*
|
||||||
|
* Skips:
|
||||||
|
* - non-anchor tags: <link>, <image> (svg), <use>
|
||||||
|
* - external/protocol URLs: http(s)://, //host, mailto:, tel:
|
||||||
|
* - fragments (#...) and empty values
|
||||||
|
* - existing dynamic hrefs ({...})
|
||||||
|
*
|
||||||
|
* Run: pnpm exec vite-node scripts/codemod-href-resolve.ts [--dry]
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
||||||
|
import { join, extname } from 'node:path';
|
||||||
|
|
||||||
|
const ROOT = 'src';
|
||||||
|
const DRY = process.argv.includes('--dry');
|
||||||
|
|
||||||
|
const SKIP_TAGS = new Set(['link', 'image', 'use']);
|
||||||
|
|
||||||
|
function walk(dir: string, out: string[] = []): string[] {
|
||||||
|
for (const name of readdirSync(dir)) {
|
||||||
|
const p = join(dir, name);
|
||||||
|
const s = statSync(p);
|
||||||
|
if (s.isDirectory()) walk(p, out);
|
||||||
|
else if (extname(p) === '.svelte') out.push(p);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match: opening of element, then its attributes, then href="/...".
|
||||||
|
* Group 1 = full prefix incl. tag-name, Group 2 = tag name, Group 3 = path.
|
||||||
|
*/
|
||||||
|
// Excludes `{` and `}` so Svelte template interpolations inside the
|
||||||
|
// attribute value (e.g. href="/{lang}/foo") are NOT treated as literals.
|
||||||
|
const HREF_RE =
|
||||||
|
/(<([A-Za-z][\w.-]*)\b[^>]*?\s)href="(\/[^"{}]*)"/gs;
|
||||||
|
|
||||||
|
function rewriteHrefs(src: string): { code: string; changed: number } {
|
||||||
|
let changed = 0;
|
||||||
|
const code = src.replace(HREF_RE, (full, prefix, tag, path) => {
|
||||||
|
if (SKIP_TAGS.has(tag.toLowerCase())) return full;
|
||||||
|
// Skip protocol-relative just in case
|
||||||
|
if (path.startsWith('//')) return full;
|
||||||
|
changed++;
|
||||||
|
return `${prefix}href={resolve('${path}')}`;
|
||||||
|
});
|
||||||
|
return { code, changed };
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCRIPT_RE = /<script\b([^>]*)>([\s\S]*?)<\/script>/;
|
||||||
|
const PATHS_IMPORT_RE =
|
||||||
|
/import\s*\{([^}]*)\}\s*from\s*['"]\$app\/paths['"]\s*;?/;
|
||||||
|
|
||||||
|
function ensureResolveImport(src: string): string {
|
||||||
|
const scriptMatch = SCRIPT_RE.exec(src);
|
||||||
|
if (!scriptMatch) {
|
||||||
|
// No script tag — prepend a TS one.
|
||||||
|
return `<script lang="ts">\n\timport { resolve } from '$app/paths';\n</script>\n\n${src}`;
|
||||||
|
}
|
||||||
|
const [scriptFull, attrs, body] = scriptMatch;
|
||||||
|
const pathsMatch = PATHS_IMPORT_RE.exec(body);
|
||||||
|
if (pathsMatch) {
|
||||||
|
const inner = pathsMatch[1];
|
||||||
|
if (/\bresolve\b/.test(inner)) return src; // already imported
|
||||||
|
const merged = inner.trim().replace(/,?\s*$/, '') + ', resolve';
|
||||||
|
const newImport = `import { ${merged} } from '$app/paths';`;
|
||||||
|
const newBody = body.replace(PATHS_IMPORT_RE, newImport);
|
||||||
|
return src.replace(scriptFull, `<script${attrs}>${newBody}</script>`);
|
||||||
|
}
|
||||||
|
// Inject new import line. Detect indent from first import line if present.
|
||||||
|
const importMatch = body.match(/^([ \t]*)import\b/m);
|
||||||
|
const indent = importMatch ? importMatch[1] : '\t';
|
||||||
|
// Insert right after the opening script tag's newline.
|
||||||
|
const opening = `<script${attrs}>`;
|
||||||
|
const insertion = `\n${indent}import { resolve } from '$app/paths';`;
|
||||||
|
const newScript = opening + insertion + body + '</script>';
|
||||||
|
return src.replace(scriptFull, newScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processFile(path: string): { changed: number } {
|
||||||
|
const orig = readFileSync(path, 'utf8');
|
||||||
|
const { code: rewritten, changed } = rewriteHrefs(orig);
|
||||||
|
if (changed === 0) return { changed: 0 };
|
||||||
|
const final = ensureResolveImport(rewritten);
|
||||||
|
if (!DRY) writeFileSync(path, final);
|
||||||
|
return { changed };
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = walk(ROOT);
|
||||||
|
let totalFiles = 0;
|
||||||
|
let totalReplacements = 0;
|
||||||
|
for (const f of files) {
|
||||||
|
const { changed } = processFile(f);
|
||||||
|
if (changed > 0) {
|
||||||
|
totalFiles++;
|
||||||
|
totalReplacements += changed;
|
||||||
|
console.log(`${changed.toString().padStart(3)} ${f}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`\n${DRY ? '[dry] ' : ''}${totalReplacements} replacements across ${totalFiles} files`
|
||||||
|
);
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import Heart from '@lucide/svelte/icons/heart';
|
import Heart from '@lucide/svelte/icons/heart';
|
||||||
|
|
||||||
let { recipeId, isFavorite = $bindable(false), isLoggedIn = false } = $props<{ recipeId: string, isFavorite?: boolean, isLoggedIn?: boolean }>();
|
let { recipeId, isFavorite = $bindable(false), isLoggedIn = false } = $props<{ recipeId: string, isFavorite?: boolean, isLoggedIn?: boolean }>();
|
||||||
|
|
||||||
const recipeLang = $derived($page.url.pathname.split('/')[1] || 'rezepte');
|
const recipeLang = $derived(page.url.pathname.split('/')[1] || 'rezepte');
|
||||||
|
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import Symbol from "./Symbol.svelte"
|
import Symbol from "./Symbol.svelte"
|
||||||
import ThemeToggle from "./ThemeToggle.svelte"
|
import ThemeToggle from "./ThemeToggle.svelte"
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
@@ -329,7 +330,7 @@ nav {
|
|||||||
<div>
|
<div>
|
||||||
|
|
||||||
<nav class:no-links={!links}>
|
<nav class:no-links={!links}>
|
||||||
<a href="/" aria-label="Home" class="home-link" class:full={fullSymbol}><Symbol /></a>
|
<a href={resolve('/')} aria-label="Home" class="home-link" class:full={fullSymbol}><Symbol /></a>
|
||||||
{#if links}
|
{#if links}
|
||||||
<div class="links-wrapper">
|
<div class="links-wrapper">
|
||||||
{@render links()}
|
{@render links()}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
|
import { recipeTranslationStore } from '$lib/stores/recipeTranslation.svelte';
|
||||||
import { languageStore } from '$lib/stores/language';
|
import { languageStore } from '$lib/stores/language.svelte';
|
||||||
import { convertFitnessPath } from '$lib/js/fitnessI18n';
|
import { convertFitnessPath } from '$lib/js/fitnessI18n';
|
||||||
import { convertCospendPath } from '$lib/js/cospendI18n';
|
import { convertCospendPath } from '$lib/js/cospendI18n';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
let { lang = undefined }: { lang?: 'de' | 'en' | 'la' } = $props();
|
let { lang = undefined }: { lang?: 'de' | 'en' | 'la' } = $props();
|
||||||
|
|
||||||
// Use prop for display if provided (SSR-safe), otherwise fall back to store
|
// Use prop for display if provided (SSR-safe), otherwise fall back to store
|
||||||
const displayLang = $derived(lang ?? $languageStore);
|
const displayLang = $derived(lang ?? languageStore.value);
|
||||||
|
|
||||||
let currentPath = $state('');
|
let currentPath = $state('');
|
||||||
let langButton: HTMLButtonElement;
|
let langButton: HTMLButtonElement;
|
||||||
@@ -33,14 +33,14 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Whether the current page is a faith route (show LA option)
|
// Whether the current page is a faith route (show LA option)
|
||||||
const faithPath = $derived(currentPath || $page.url.pathname);
|
const faithPath = $derived(currentPath || page.url.pathname);
|
||||||
const isFaithRoute = $derived(
|
const isFaithRoute = $derived(
|
||||||
faithPath.startsWith('/glaube') || faithPath.startsWith('/faith') || faithPath.startsWith('/fides')
|
faithPath.startsWith('/glaube') || faithPath.startsWith('/faith') || faithPath.startsWith('/fides')
|
||||||
);
|
);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Update current language and path when page changes (reactive to browser navigation)
|
// Update current language and path when page changes (reactive to browser navigation)
|
||||||
const path = $page.url.pathname;
|
const path = page.url.pathname;
|
||||||
currentPath = path;
|
currentPath = path;
|
||||||
|
|
||||||
if (path.startsWith('/recipes') || path.startsWith('/faith')) {
|
if (path.startsWith('/recipes') || path.startsWith('/faith')) {
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
|
|
||||||
// Compute target paths for each language (used as href for no-JS)
|
// Compute target paths for each language (used as href for no-JS)
|
||||||
function computeTargetPath(targetLang: 'de' | 'en' | 'la'): string {
|
function computeTargetPath(targetLang: 'de' | 'en' | 'la'): string {
|
||||||
const path = currentPath || $page.url.pathname;
|
const path = currentPath || page.url.pathname;
|
||||||
|
|
||||||
if (path.startsWith('/glaube') || path.startsWith('/faith') || path.startsWith('/fides')) {
|
if (path.startsWith('/glaube') || path.startsWith('/faith') || path.startsWith('/fides')) {
|
||||||
return convertFaithPath(path, targetLang);
|
return convertFaithPath(path, targetLang);
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use translated recipe slugs from page data when available (works during SSR)
|
// Use translated recipe slugs from page data when available (works during SSR)
|
||||||
const pageData = $page.data;
|
const pageData = page.data;
|
||||||
if (targetLang === 'en' && path.startsWith('/rezepte')) {
|
if (targetLang === 'en' && path.startsWith('/rezepte')) {
|
||||||
if (pageData?.englishShortName) {
|
if (pageData?.englishShortName) {
|
||||||
return `/recipes/${pageData.englishShortName}`;
|
return `/recipes/${pageData.englishShortName}`;
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we have recipe translation data from store, use the correct short names
|
// If we have recipe translation data from store, use the correct short names
|
||||||
const recipeData = $recipeTranslationStore;
|
const recipeData = recipeTranslationStore.value;
|
||||||
if (recipeData) {
|
if (recipeData) {
|
||||||
if (lang === 'en' && recipeData.englishShortName) {
|
if (lang === 'en' && recipeData.englishShortName) {
|
||||||
await goto(`/recipes/${recipeData.englishShortName}`);
|
await goto(`/recipes/${recipeData.englishShortName}`);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import ErrorView from './ErrorView.svelte';
|
import ErrorView from './ErrorView.svelte';
|
||||||
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
|
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
let { sectionHref, sectionLabel, isEnglish: isEnglishProp, extraActions }: Props = $props();
|
let { sectionHref, sectionLabel, isEnglish: isEnglishProp, extraActions }: Props = $props();
|
||||||
|
|
||||||
let status = $derived($page.status);
|
let status = $derived(page.status);
|
||||||
let error = $derived($page.error as any);
|
let error = $derived(page.error as any);
|
||||||
let bibleQuote = $derived(error?.bibleQuote);
|
let bibleQuote = $derived(error?.bibleQuote);
|
||||||
let detectedEnglish = $derived(error?.lang === 'en');
|
let detectedEnglish = $derived(error?.lang === 'en');
|
||||||
let isEnglish = $derived(isEnglishProp ?? detectedEnglish);
|
let isEnglish = $derived(isEnglishProp ?? detectedEnglish);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
import LogIn from '@lucide/svelte/icons/log-in';
|
import LogIn from '@lucide/svelte/icons/log-in';
|
||||||
|
|
||||||
let { user, recipeLang = 'rezepte', lang = 'de' } = $props();
|
let { user, recipeLang = 'rezepte', lang = 'de' } = $props();
|
||||||
@@ -153,10 +155,10 @@
|
|||||||
<p>({user.nickname})</p>
|
<p>({user.nickname})</p>
|
||||||
<ul>
|
<ul>
|
||||||
{#if user.groups?.includes('rezepte_users')}
|
{#if user.groups?.includes('rezepte_users')}
|
||||||
<li><a href="/{recipeLang}/administration">Administration</a></li>
|
<li><a href={resolve('/[recipeLang=recipeLang]/administration', { recipeLang })}>Administration</a></li>
|
||||||
{/if}
|
{/if}
|
||||||
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
|
<li><a href="https://sso.bocken.org/if/user/#/settings" >Einstellungen</a></li>
|
||||||
<li><a href="/logout?callbackUrl={encodeURIComponent(getLogoutCallbackUrl($page.url.pathname))}">Log Out</a></li>
|
<li><a href={`${resolve('/logout')}?callbackUrl=${encodeURIComponent(getLogoutCallbackUrl(page.url.pathname))}`}>Log Out</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -164,7 +166,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
class="entry login-link"
|
class="entry login-link"
|
||||||
href="/login?callbackUrl={encodeURIComponent($page.url.pathname + $page.url.search)}"
|
href={`${resolve('/login')}?callbackUrl=${encodeURIComponent(page.url.pathname + (browser ? page.url.search : ''))}`}
|
||||||
aria-label={lang === 'de' ? 'Anmelden' : 'Login'}
|
aria-label={lang === 'de' ? 'Anmelden' : 'Login'}
|
||||||
title={lang === 'de' ? 'Anmelden' : 'Login'}
|
title={lang === 'de' ? 'Anmelden' : 'Login'}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import ProfilePicture from './ProfilePicture.svelte';
|
import ProfilePicture from './ProfilePicture.svelte';
|
||||||
import { formatCurrency } from '$lib/utils/formatters';
|
import { formatCurrency } from '$lib/utils/formatters';
|
||||||
import { detectCospendLang, locale, t } from '$lib/js/cospendI18n';
|
import { detectCospendLang, locale, t } from '$lib/js/cospendI18n';
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import ProfilePicture from './ProfilePicture.svelte';
|
import ProfilePicture from './ProfilePicture.svelte';
|
||||||
import { formatCurrency as formatCurrencyUtil } from '$lib/utils/formatters';
|
import { formatCurrency as formatCurrencyUtil } from '$lib/utils/formatters';
|
||||||
import { detectCospendLang, locale, t } from '$lib/js/cospendI18n';
|
import { detectCospendLang, locale, t } from '$lib/js/cospendI18n';
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
let { initialBalance = null, initialDebtData = null } = $props<{ initialBalance?: any, initialDebtData?: any }>();
|
let { initialBalance = null, initialDebtData = null } = $props<{ initialBalance?: any, initialDebtData?: any }>();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import ProfilePicture from './ProfilePicture.svelte';
|
import ProfilePicture from './ProfilePicture.svelte';
|
||||||
import EditButton from '$lib/components/EditButton.svelte';
|
import EditButton from '$lib/components/EditButton.svelte';
|
||||||
import { getCategoryEmoji } from '$lib/utils/categories';
|
import { getCategoryEmoji } from '$lib/utils/categories';
|
||||||
@@ -12,9 +13,9 @@
|
|||||||
let { paymentId, onclose, onpaymentDeleted } = $props();
|
let { paymentId, onclose, onpaymentDeleted } = $props();
|
||||||
|
|
||||||
// Get session from page store
|
// Get session from page store
|
||||||
let session = $derived($page.data?.session);
|
let session = $derived(page.data?.session);
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
@@ -243,7 +244,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if payment}
|
{#if payment}
|
||||||
<EditButton href="/{root}/payments/edit/{paymentId}" />
|
<EditButton href={resolve('/[cospendRoot=cospendRoot]/payments/edit/[id]', { cospendRoot: root, id: paymentId })} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import ProfilePicture from './ProfilePicture.svelte';
|
import ProfilePicture from './ProfilePicture.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectCospendLang, t } from '$lib/js/cospendI18n';
|
import { detectCospendLang, t } from '$lib/js/cospendI18n';
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
|
|
||||||
let {
|
let {
|
||||||
splitMethod = $bindable('equal'),
|
splitMethod = $bindable('equal'),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import Shield from '@lucide/svelte/icons/shield';
|
import Shield from '@lucide/svelte/icons/shield';
|
||||||
import Flame from '@lucide/svelte/icons/flame';
|
import Flame from '@lucide/svelte/icons/flame';
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<a
|
<a
|
||||||
class="case-tab"
|
class="case-tab"
|
||||||
class:active={active === 'contra'}
|
class:active={active === 'contra'}
|
||||||
href="/{faithLang}/{slug}/contra"
|
href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/contra', { faithLang, apologetikSlug: slug })}
|
||||||
>
|
>
|
||||||
<Shield class="ct-glyph" size={14} strokeWidth={2} aria-hidden="true" />
|
<Shield class="ct-glyph" size={14} strokeWidth={2} aria-hidden="true" />
|
||||||
<span>{l.contra}</span>
|
<span>{l.contra}</span>
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
<a
|
<a
|
||||||
class="case-tab"
|
class="case-tab"
|
||||||
class:active={active === 'pro'}
|
class:active={active === 'pro'}
|
||||||
href="/{faithLang}/{slug}/pro"
|
href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/pro', { faithLang, apologetikSlug: slug })}
|
||||||
>
|
>
|
||||||
<Flame class="ct-glyph" size={14} strokeWidth={2} aria-hidden="true" />
|
<Flame class="ct-glyph" size={14} strokeWidth={2} aria-hidden="true" />
|
||||||
<span>{l.pro}</span>
|
<span>{l.pro}</span>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import { getEnrichedExerciseById } from '$lib/data/exercisedb';
|
import { getEnrichedExerciseById } from '$lib/data/exercisedb';
|
||||||
import { detectFitnessLang, fitnessSlugs } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, fitnessSlugs } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
let { exerciseId, plain = false } = $props();
|
let { exerciseId, plain = false } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const exercise = $derived(getEnrichedExerciseById(exerciseId, lang));
|
const exercise = $derived(getEnrichedExerciseById(exerciseId, lang));
|
||||||
const sl = $derived(fitnessSlugs(lang));
|
const sl = $derived(fitnessSlugs(lang));
|
||||||
</script>
|
</script>
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
{#if plain}
|
{#if plain}
|
||||||
<span class="exercise-plain">{exercise.localName}</span>
|
<span class="exercise-plain">{exercise.localName}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<a href="/fitness/{sl.exercises}/{exerciseId}" class="exercise-link">{exercise.localName}</a>
|
<a href={resolve('/fitness/[exercises=fitnessExercises]/[id]', { exercises: sl.exercises, id: exerciseId })} class="exercise-link">{exercise.localName}</a>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<span class="exercise-unknown">Unknown Exercise</span>
|
<span class="exercise-unknown">Unknown Exercise</span>
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
import PersonStanding from '@lucide/svelte/icons/person-standing';
|
import PersonStanding from '@lucide/svelte/icons/person-standing';
|
||||||
import Shapes from '@lucide/svelte/icons/shapes';
|
import Shapes from '@lucide/svelte/icons/shapes';
|
||||||
import Weight from '@lucide/svelte/icons/weight';
|
import Weight from '@lucide/svelte/icons/weight';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
import Heart from '@lucide/svelte/icons/heart';
|
import Heart from '@lucide/svelte/icons/heart';
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
initialResults = undefined,
|
initialResults = undefined,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const s = $derived(fitnessSlugs(lang));
|
const s = $derived(fitnessSlugs(lang));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
const btnLabel = $derived(confirmLabel ?? t('log_food', lang));
|
const btnLabel = $derived(confirmLabel ?? t('log_food', lang));
|
||||||
@@ -477,7 +478,7 @@
|
|||||||
<span class="fs-result-cal">{item.calories}<small> kcal</small></span>
|
<span class="fs-result-cal">{item.calories}<small> kcal</small></span>
|
||||||
</button>
|
</button>
|
||||||
{#if showDetailLinks && (item.source === 'bls' || item.source === 'usda' || item.source === 'off')}
|
{#if showDetailLinks && (item.source === 'bls' || item.source === 'usda' || item.source === 'off')}
|
||||||
<a class="fs-detail-link" href="/fitness/{s.nutrition}/food/{item.source}/{item.id}" aria-label="View details">
|
<a class="fs-detail-link" href={resolve('/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]', { nutrition: s.nutrition, source: item.source, id: item.id })} aria-label="View details">
|
||||||
<ExternalLink size={13} />
|
<ExternalLink size={13} />
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { detectFitnessLang } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang } from '$lib/js/fitnessI18n';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import Beef from '@lucide/svelte/icons/beef';
|
import Beef from '@lucide/svelte/icons/beef';
|
||||||
import Droplet from '@lucide/svelte/icons/droplet';
|
import Droplet from '@lucide/svelte/icons/droplet';
|
||||||
import Wheat from '@lucide/svelte/icons/wheat';
|
import Wheat from '@lucide/svelte/icons/wheat';
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
showDetailRows = true,
|
showDetailRows = true,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
|
|
||||||
const macroPercent = $derived.by(() => {
|
const macroPercent = $derived.by(() => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { detectFitnessLang } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang } from '$lib/js/fitnessI18n';
|
||||||
import frontSvgRaw from '$lib/assets/muscle-front.svg?raw';
|
import frontSvgRaw from '$lib/assets/muscle-front.svg?raw';
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
/** @type {{ data?: { totals?: Record<string, MuscleTotals> } | null }} */
|
/** @type {{ data?: { totals?: Record<string, MuscleTotals> } | null }} */
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
/** @type {Record<string, MuscleTotals>} */
|
/** @type {Record<string, MuscleTotals>} */
|
||||||
const totals = $derived(data?.totals ?? {});
|
const totals = $derived(data?.totals ?? {});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import { getExerciseById, getExerciseMetrics } from '$lib/data/exercises';
|
import { getExerciseById, getExerciseMetrics } from '$lib/data/exercises';
|
||||||
import Clock from '@lucide/svelte/icons/clock';
|
import Clock from '@lucide/svelte/icons/clock';
|
||||||
import Weight from '@lucide/svelte/icons/weight';
|
import Weight from '@lucide/svelte/icons/weight';
|
||||||
@@ -9,7 +10,7 @@
|
|||||||
import Flame from '@lucide/svelte/icons/flame';
|
import Flame from '@lucide/svelte/icons/flame';
|
||||||
import { detectFitnessLang, fitnessSlugs } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, fitnessSlugs } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const sl = $derived(fitnessSlugs(lang));
|
const sl = $derived(fitnessSlugs(lang));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,7 +153,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a href="/fitness/{sl.history}/{session._id}" class="session-card">
|
<a href={resolve('/fitness/[history=fitnessHistory]/[id]', { history: sl.history, id: session._id })} class="session-card">
|
||||||
<div class="card-top">
|
<div class="card-top">
|
||||||
<h3 class="session-name">{session.name}</h3>
|
<h3 class="session-name">{session.name}</h3>
|
||||||
<span class="session-date">{formatDate(session.startTime)} · {formatTime(session.startTime)}</span>
|
<span class="session-date">{formatDate(session.startTime)} · {formatTime(session.startTime)}</span>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
import Square from '@lucide/svelte/icons/square';
|
import Square from '@lucide/svelte/icons/square';
|
||||||
import { METRIC_LABELS } from '$lib/data/exercises';
|
import { METRIC_LABELS } from '$lib/data/exercises';
|
||||||
import RestTimer from './RestTimer.svelte';
|
import RestTimer from './RestTimer.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {{
|
* @type {{
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
import { getExerciseById } from '$lib/data/exercises';
|
import { getExerciseById } from '$lib/data/exercises';
|
||||||
import EllipsisVertical from '@lucide/svelte/icons/ellipsis-vertical';
|
import EllipsisVertical from '@lucide/svelte/icons/ellipsis-vertical';
|
||||||
import MapPin from '@lucide/svelte/icons/map-pin';
|
import MapPin from '@lucide/svelte/icons/map-pin';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {{
|
* @type {{
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import Play from '@lucide/svelte/icons/play';
|
|||||||
import Pause from '@lucide/svelte/icons/pause';
|
import Pause from '@lucide/svelte/icons/pause';
|
||||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
import SyncIndicator from '$lib/components/fitness/SyncIndicator.svelte';
|
import SyncIndicator from '$lib/components/fitness/SyncIndicator.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
|
|
||||||
let { href, elapsed = '0:00', paused = false, syncStatus = 'idle', onPauseToggle,
|
let { href, elapsed = '0:00', paused = false, syncStatus = 'idle', onPauseToggle,
|
||||||
restSeconds = 0, restTotal = 0, onRestAdjust = null, onRestSkip = null } = $props();
|
restSeconds = 0, restTotal = 0, onRestAdjust = null, onRestSkip = null } = $props();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
|
|
||||||
let { item, multiplier = 1, yeastId = 0, lang = 'de' } = $props();
|
let { item, multiplier = 1, yeastId = 0, lang = 'de' } = $props();
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
: 'Zwischen Frischhefe und Trockenhefe wechseln');
|
: 'Zwischen Frischhefe und Trockenhefe wechseln');
|
||||||
|
|
||||||
// Get all current URL parameters to preserve state
|
// Get all current URL parameters to preserve state
|
||||||
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : $page.url.searchParams);
|
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : page.url.searchParams);
|
||||||
|
|
||||||
/** @param {Event} event */
|
/** @param {Event} event */
|
||||||
function toggleHefe(event) {
|
function toggleHefe(event) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import "$lib/css/shake.css"
|
import "$lib/css/shake.css"
|
||||||
let { icon, ...restProps } = $props<{ icon: string, [key: string]: any }>();
|
let { icon, ...restProps } = $props<{ icon: string, [key: string]: any }>();
|
||||||
</script>
|
</script>
|
||||||
@@ -26,4 +27,4 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<a href="/rezepte/icon/{icon}" {...restProps} >{icon}</a>
|
<a href={resolve('/[recipeLang=recipeLang]/icon/[icon]', { recipeLang: 'rezepte', icon })} {...restProps} >{icon}</a>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { onNavigate } from "$app/navigation";
|
import { onNavigate } from "$app/navigation";
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import HefeSwapper from './HefeSwapper.svelte';
|
import HefeSwapper from './HefeSwapper.svelte';
|
||||||
import NutritionSummary from './NutritionSummary.svelte';
|
import NutritionSummary from './NutritionSummary.svelte';
|
||||||
import AddToFoodLogButton from './AddToFoodLogButton.svelte';
|
import AddToFoodLogButton from './AddToFoodLogButton.svelte';
|
||||||
@@ -272,7 +272,7 @@ const yeastIds = $derived.by(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Get all current URL parameters to preserve state in multiplier forms
|
// Get all current URL parameters to preserve state in multiplier forms
|
||||||
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : $page.url.searchParams);
|
const currentParams = $derived(browser ? new URLSearchParams(window.location.search) : page.url.searchParams);
|
||||||
|
|
||||||
// Progressive enhancement - use JS if available
|
// Progressive enhancement - use JS if available
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { TranslatedRecipeType } from '$types/types';
|
import type { TranslatedRecipeType } from '$types/types';
|
||||||
import TranslationFieldComparison from './TranslationFieldComparison.svelte';
|
import TranslationFieldComparison from './TranslationFieldComparison.svelte';
|
||||||
import CreateIngredientList from '$lib/components/recipes/CreateIngredientList.svelte';
|
import CreateIngredientList from '$lib/components/recipes/CreateIngredientList.svelte';
|
||||||
@@ -758,7 +759,7 @@ button:disabled {
|
|||||||
{#each untranslatedBaseRecipes as baseRecipe}
|
{#each untranslatedBaseRecipes as baseRecipe}
|
||||||
<li>
|
<li>
|
||||||
<strong>{baseRecipe.name}</strong>
|
<strong>{baseRecipe.name}</strong>
|
||||||
<a href="/de/edit/{baseRecipe.shortName}" target="_blank" rel="noopener noreferrer">
|
<a href={resolve('/[recipeLang=recipeLang]/edit/[name]', { recipeLang: 'de', name: baseRecipe.shortName })} target="_blank" rel="noopener noreferrer">
|
||||||
Open in new tab →
|
Open in new tab →
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1581,6 +1581,32 @@ export function findArgument(id: string): Argument | undefined {
|
|||||||
return ARGUMENTS.find((a) => a.id === id);
|
return ARGUMENTS.find((a) => a.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alex's curated picks per argument (archetype IDs). Same across locales.
|
||||||
|
export const ALEX_PICKS: Record<string, string[]> = {
|
||||||
|
evil: ["aquinas"],
|
||||||
|
evidence: ["logician", "pascal"],
|
||||||
|
science: ["logician", "lewis"],
|
||||||
|
morality: ["logician"],
|
||||||
|
"religion-violence": ["logician"],
|
||||||
|
miracles: ["logician", "pascal"],
|
||||||
|
hiddenness: ["pascal"],
|
||||||
|
hell: ["aquinas", "lewis"],
|
||||||
|
birth: ["lewis", "justin"],
|
||||||
|
bible: ["augustine", "newman"],
|
||||||
|
scale: ["chesterton"],
|
||||||
|
"natural-evil": ["lewis", "catechism"],
|
||||||
|
"many-gods": ["newman"],
|
||||||
|
neuroscience: ["mystic"],
|
||||||
|
prayer: ["lewis", "aquinas"],
|
||||||
|
pleasure: ["catechism"],
|
||||||
|
projection: ["lewis", "chesterton"],
|
||||||
|
"faith-reason": ["aquinas", "lewis"],
|
||||||
|
mythicism: ["historian"],
|
||||||
|
corruption: ["pastor", "chesterton"],
|
||||||
|
intelligence: ["logician"],
|
||||||
|
submission: ["lewis"],
|
||||||
|
};
|
||||||
|
|
||||||
// Locale-aware accessors. DE comes from auto-generated apologetik.de.ts;
|
// Locale-aware accessors. DE comes from auto-generated apologetik.de.ts;
|
||||||
// EN is the source of truth. Latin falls back to EN since DeepL doesn't
|
// EN is the source of truth. Latin falls back to EN since DeepL doesn't
|
||||||
// support it — fill in apologetik.la.ts manually if/when desired.
|
// support it — fill in apologetik.la.ts manually if/when desired.
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export type Language = 'de' | 'en';
|
||||||
|
|
||||||
|
function createLanguage() {
|
||||||
|
let value = $state<Language>('de');
|
||||||
|
|
||||||
|
return {
|
||||||
|
get value() { return value; },
|
||||||
|
set: (v: Language) => { value = v; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const languageStore = createLanguage();
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
type Language = 'de' | 'en';
|
|
||||||
|
|
||||||
function createLanguageStore() {
|
|
||||||
const { subscribe, set } = writable<Language>('de');
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
set,
|
|
||||||
init: () => {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
const path = window.location.pathname;
|
|
||||||
if (path.startsWith('/recipes') || path.startsWith('/faith')) {
|
|
||||||
set('en');
|
|
||||||
} else if (path.startsWith('/rezepte') || path.startsWith('/glaube')) {
|
|
||||||
set('de');
|
|
||||||
} else {
|
|
||||||
const preferredLanguage = localStorage.getItem('preferredLanguage');
|
|
||||||
set(preferredLanguage === 'en' ? 'en' : 'de');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const languageStore = createLanguageStore();
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export interface RecipeTranslationData {
|
||||||
|
germanShortName: string;
|
||||||
|
englishShortName?: string;
|
||||||
|
hasEnglishTranslation: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRecipeTranslation() {
|
||||||
|
let value = $state<RecipeTranslationData | null>(null);
|
||||||
|
|
||||||
|
return {
|
||||||
|
get value() { return value; },
|
||||||
|
set: (v: RecipeTranslationData | null) => { value = v; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const recipeTranslationStore = createRecipeTranslation();
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
interface RecipeTranslationData {
|
|
||||||
germanShortName: string;
|
|
||||||
englishShortName?: string;
|
|
||||||
hasEnglishTranslation: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const recipeTranslationStore = writable<RecipeTranslationData | null>(null);
|
|
||||||
@@ -1,30 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import LinksGrid from "$lib/components/LinksGrid.svelte";
|
import LinksGrid from "$lib/components/LinksGrid.svelte";
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
let lang = $state<'de' | 'en'>('de');
|
let lang = $state<'de' | 'en'>('de');
|
||||||
let recipesUrl = $state('/rezepte');
|
let recipesUrl = $state(resolve('/[recipeLang=recipeLang]', { recipeLang: 'rezepte' }));
|
||||||
let faithUrl = $state('/glaube');
|
let faithUrl = $state(resolve('/[faithLang=faithLang]', { faithLang: 'glaube' }));
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Check localStorage for preferred language
|
// Check localStorage for preferred language
|
||||||
const preferredLanguage = localStorage.getItem('preferredLanguage');
|
const preferredLanguage = localStorage.getItem('preferredLanguage');
|
||||||
if (preferredLanguage === 'en') {
|
if (preferredLanguage === 'en') {
|
||||||
lang = 'en';
|
lang = 'en';
|
||||||
recipesUrl = '/recipes';
|
recipesUrl = resolve('/[recipeLang=recipeLang]', { recipeLang: 'recipes' });
|
||||||
faithUrl = '/faith';
|
faithUrl = resolve('/[faithLang=faithLang]', { faithLang: 'faith' });
|
||||||
} else {
|
} else {
|
||||||
lang = 'de';
|
lang = 'de';
|
||||||
recipesUrl = '/rezepte';
|
recipesUrl = resolve('/[recipeLang=recipeLang]', { recipeLang: 'rezepte' });
|
||||||
faithUrl = '/glaube';
|
faithUrl = resolve('/[faithLang=faithLang]', { faithLang: 'glaube' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for language changes from UserHeader
|
// Listen for language changes from UserHeader
|
||||||
const handleLanguageChange = (e: CustomEvent) => {
|
const handleLanguageChange = (e: CustomEvent) => {
|
||||||
lang = e.detail.lang;
|
lang = e.detail.lang;
|
||||||
recipesUrl = lang === 'en' ? '/recipes' : '/rezepte';
|
recipesUrl = resolve('/[recipeLang=recipeLang]', { recipeLang: lang === 'en' ? 'recipes' : 'rezepte' });
|
||||||
faithUrl = lang === 'en' ? '/faith' : '/glaube';
|
faithUrl = resolve('/[faithLang=faithLang]', { faithLang: lang === 'en' ? 'faith' : 'glaube' });
|
||||||
};
|
};
|
||||||
window.addEventListener('languagechange', handleLanguageChange as EventListener);
|
window.addEventListener('languagechange', handleLanguageChange as EventListener);
|
||||||
|
|
||||||
@@ -151,13 +152,13 @@ section h2{
|
|||||||
<h3>{labels.shopping}</h3>
|
<h3>{labels.shopping}</h3>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/fitness">
|
<a href={resolve('/fitness')}>
|
||||||
<svg class="lock-icon"><use href="#lock-icon"/></svg>
|
<svg class="lock-icon"><use href="#lock-icon"/></svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M96 64c0-17.7 14.3-32 32-32l32 0c17.7 0 32 14.3 32 32l0 160 0 64 0 160c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-64-32 0c-17.7 0-32-14.3-32-32l0-64c-17.7 0-32-14.3-32-32s14.3-32 32-32l0-64c0-17.7 14.3-32 32-32l32 0 0-64zm448 0l0 64 32 0c17.7 0 32 14.3 32 32l0 64c17.7 0 32 14.3 32 32s-14.3 32-32 32l0 64c0 17.7-14.3 32-32 32l-32 0 0 64c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-160 0-64 0-160c0-17.7 14.3-32 32-32l32 0c17.7 0 32 14.3 32 32zM416 224l0 64-192 0 0-64 192 0z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M96 64c0-17.7 14.3-32 32-32l32 0c17.7 0 32 14.3 32 32l0 160 0 64 0 160c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-64-32 0c-17.7 0-32-14.3-32-32l0-64c-17.7 0-32-14.3-32-32s14.3-32 32-32l0-64c0-17.7 14.3-32 32-32l32 0 0-64zm448 0l0 64 32 0c17.7 0 32 14.3 32 32l0 64c17.7 0 32 14.3 32 32s-14.3 32-32 32l0 64c0 17.7-14.3 32-32 32l-32 0 0 64c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-160 0-64 0-160c0-17.7 14.3-32 32-32l32 0c17.7 0 32 14.3 32 32zM416 224l0 64-192 0 0-64 192 0z"/></svg>
|
||||||
<h3>{labels.fitness}</h3>
|
<h3>{labels.fitness}</h3>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/tasks">
|
<a href={resolve('/tasks')}>
|
||||||
<svg class="lock-icon"><use href="#lock-icon"/></svg>
|
<svg class="lock-icon"><use href="#lock-icon"/></svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M152.1 38.2c9.9 8.9 10.7 24 1.8 33.9l-72 80c-4.4 4.9-10.6 7.8-17.2 7.9s-12.9-2.4-17.6-7L7 113c-9.3-9.4-9.3-24.6 0-34C16.3 69.5 31.5 69.5 40.7 79l21.9 22.3 53.5-59.4c8.9-9.9 24-10.7 33.9-1.8zm0 160c9.9 8.9 10.7 24 1.8 33.9l-72 80c-4.4 4.9-10.6 7.8-17.2 7.9s-12.9-2.4-17.6-7L7 273c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l22 22.3 53.5-59.4c8.9-9.9 24-10.7 33.9-1.8zM224 96c0-17.7 14.3-32 32-32H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H256c-17.7 0-32-14.3-32-32zm0 160c0-17.7 14.3-32 32-32H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H256c-17.7 0-32-14.3-32-32zM160 416c0-17.7 14.3-32 32-32H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H192c-17.7 0-32-14.3-32-32zM48 368a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M152.1 38.2c9.9 8.9 10.7 24 1.8 33.9l-72 80c-4.4 4.9-10.6 7.8-17.2 7.9s-12.9-2.4-17.6-7L7 113c-9.3-9.4-9.3-24.6 0-34C16.3 69.5 31.5 69.5 40.7 79l21.9 22.3 53.5-59.4c8.9-9.9 24-10.7 33.9-1.8zm0 160c9.9 8.9 10.7 24 1.8 33.9l-72 80c-4.4 4.9-10.6 7.8-17.2 7.9s-12.9-2.4-17.6-7L7 273c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l22 22.3 53.5-59.4c8.9-9.9 24-10.7 33.9-1.8zM224 96c0-17.7 14.3-32 32-32H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H256c-17.7 0-32-14.3-32-32zm0 160c0-17.7 14.3-32 32-32H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H256c-17.7 0-32-14.3-32-32zM160 416c0-17.7 14.3-32 32-32H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H192c-17.7 0-32-14.3-32-32zM48 368a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/></svg>
|
||||||
<h3>{labels.tasks}</h3>
|
<h3>{labels.tasks}</h3>
|
||||||
@@ -169,7 +170,7 @@ section h2{
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
<a href="/fitness/nutrition">
|
<a href={resolve('/fitness/nutrition')}>
|
||||||
<svg class="lock-icon"><use href="#lock-icon"/></svg>
|
<svg class="lock-icon"><use href="#lock-icon"/></svg>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -226 532 468"><path d="m256-140-15-21c-25-34-65-55-108-55C60-216 0-156 0-83v3C0-57 6-32 17-8h106c3 0 6-2 7-5l32-76c4-9 12-15 22-15 9 0 18 5 22 14l51 114 42-83c4-8 12-13 21-13s17 5 22 13l23 47c1 2 4 4 7 4h124c10-24 16-49 16-72v-3c0-73-60-133-133-133-43 0-83 21-108 55l-15 21zM470 40h-98c-21 0-41-12-50-31l-2-3-42 85c-5 8-13 13-22 13-10 0-18-6-22-14L185-20 174 6c-8 21-29 34-51 34H42c48 74 123 142 171 178 12 9 27 14 43 14 15 0 31-4 43-14 48-36 123-104 171-178z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -226 532 468"><path d="m256-140-15-21c-25-34-65-55-108-55C60-216 0-156 0-83v3C0-57 6-32 17-8h106c3 0 6-2 7-5l32-76c4-9 12-15 22-15 9 0 18 5 22 14l51 114 42-83c4-8 12-13 21-13s17 5 22 13l23 47c1 2 4 4 7 4h124c10-24 16-49 16-72v-3c0-73-60-133-133-133-43 0-83 21-108 55l-15 21zM470 40h-98c-21 0-41-12-50-31l-2-3-42 85c-5 8-13 13-22 13-10 0-18-6-22-14L185-20 174 6c-8 21-29 34-51 34H42c48 74 123 142 171 178 12 9 27 14 43 14 15 0 31-4 43-14 48-36 123-104 171-178z"/></svg>
|
||||||
<h3>{labels.nutrition}</h3>
|
<h3>{labels.nutrition}</h3>
|
||||||
@@ -222,7 +223,7 @@ section h2{
|
|||||||
|
|
||||||
<!-- instead of redirect_to_docs(), use a normal link with internal checks for data.session -->
|
<!-- instead of redirect_to_docs(), use a normal link with internal checks for data.session -->
|
||||||
{#if !data.session}
|
{#if !data.session}
|
||||||
<a href="/auth/signin">
|
<a href={resolve('/auth/signin')}>
|
||||||
<svg class="lock-icon"><use href="#lock-icon"/></svg>
|
<svg class="lock-icon"><use href="#lock-icon"/></svg>
|
||||||
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m106 512h300c24.814 0 45-20.186 45-45v-317h-105c-24.814 0-45-20.186-45-45v-105h-195c-24.814 0-45 20.186-45 45v422c0 24.814 20.186 45 45 45zm60-301h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h120c8.291 0 15 6.709 15 15s-6.709 15-15 15h-120c-8.291 0-15-6.709-15-15s6.709-15 15-15z"/><path d="m346 120h96.211l-111.211-111.211v96.211c0 8.276 6.724 15 15 15z"/></svg>
|
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="m106 512h300c24.814 0 45-20.186 45-45v-317h-105c-24.814 0-45-20.186-45-45v-105h-195c-24.814 0-45 20.186-45 45v422c0 24.814 20.186 45 45 45zm60-301h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h180c8.291 0 15 6.709 15 15s-6.709 15-15 15h-180c-8.291 0-15-6.709-15-15s6.709-15 15-15zm0 60h120c8.291 0 15 6.709 15 15s-6.709 15-15 15h-120c-8.291 0-15-6.709-15-15s6.709-15 15-15z"/><path d="m346 120h96.211l-111.211-111.211v96.211c0 8.276 6.724 15 15 15z"/></svg>
|
||||||
<h3>{labels.documents}</h3>
|
<h3>{labels.documents}</h3>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
import ErrorView from '$lib/components/ErrorView.svelte';
|
import ErrorView from '$lib/components/ErrorView.svelte';
|
||||||
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
|
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
|
||||||
|
|
||||||
let status = $derived($page.status);
|
let status = $derived(page.status);
|
||||||
let error = $derived($page.error as any);
|
let error = $derived(page.error as any);
|
||||||
|
|
||||||
let bibleQuote = $derived(error?.bibleQuote);
|
let bibleQuote = $derived(error?.bibleQuote);
|
||||||
let isEnglish = $derived(error?.lang === 'en');
|
let isEnglish = $derived(error?.lang === 'en');
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import SectionError from '$lib/components/SectionError.svelte';
|
import SectionError from '$lib/components/SectionError.svelte';
|
||||||
import { detectCospendLang, cospendRoot } from '$lib/js/cospendI18n';
|
import { detectCospendLang, cospendRoot } from '$lib/js/cospendI18n';
|
||||||
|
|
||||||
let lang = $derived(detectCospendLang($page.url.pathname));
|
let lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
let isEnglish = $derived(lang === 'en');
|
let isEnglish = $derived(lang === 'en');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SectionError
|
<SectionError
|
||||||
sectionHref={cospendRoot(lang)}
|
sectionHref={resolve('/[cospendRoot=cospendRoot]', { cospendRoot: cospendRoot(lang) })}
|
||||||
sectionLabel={{ en: 'Expenses', de: 'Kosten' }}
|
sectionLabel={{ en: 'Expenses', de: 'Kosten' }}
|
||||||
{isEnglish}
|
{isEnglish}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
|
|
||||||
let { data, children } = $props();
|
let { data, children } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const labels = $derived(cospendLabels(lang));
|
const labels = $derived(cospendLabels(lang));
|
||||||
|
|
||||||
@@ -28,9 +29,9 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Check if URL contains payment view route OR if we have paymentId in state
|
// Check if URL contains payment view route OR if we have paymentId in state
|
||||||
const match = $page.url.pathname.match(/\/(cospend|expenses)\/payments\/view\/([^\/]+)/);
|
const match = page.url.pathname.match(/\/(cospend|expenses)\/payments\/view\/([^\/]+)/);
|
||||||
const statePaymentId = $page.state?.paymentId;
|
const statePaymentId = page.state?.paymentId;
|
||||||
const isOnDashboard = $page.route.id === '/[cospendRoot=cospendRoot]/dash';
|
const isOnDashboard = page.route.id === '/[cospendRoot=cospendRoot]/dash';
|
||||||
|
|
||||||
// Only show modal if we're on the dashboard AND have a payment to show
|
// Only show modal if we're on the dashboard AND have a payment to show
|
||||||
if (isOnDashboard && (match || statePaymentId)) {
|
if (isOnDashboard && (match || statePaymentId)) {
|
||||||
@@ -48,14 +49,14 @@
|
|||||||
paymentId = null;
|
paymentId = null;
|
||||||
|
|
||||||
// Dispatch a custom event to trigger dashboard refresh
|
// Dispatch a custom event to trigger dashboard refresh
|
||||||
if ($page.route.id === '/[cospendRoot=cospendRoot]/dash') {
|
if (page.route.id === '/[cospendRoot=cospendRoot]/dash') {
|
||||||
window.dispatchEvent(new CustomEvent('dashboardRefresh'));
|
window.dispatchEvent(new CustomEvent('dashboardRefresh'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {string} path */
|
/** @param {string} path */
|
||||||
function isActive(path) {
|
function isActive(path) {
|
||||||
const currentPath = $page.url.pathname;
|
const currentPath = page.url.pathname;
|
||||||
// Exact match for dash
|
// Exact match for dash
|
||||||
if (path.endsWith('/dash')) {
|
if (path.endsWith('/dash')) {
|
||||||
return currentPath === path || currentPath === path + '/';
|
return currentPath === path || currentPath === path + '/';
|
||||||
@@ -69,12 +70,12 @@
|
|||||||
{#snippet links()}
|
{#snippet links()}
|
||||||
<ul class="site_header">
|
<ul class="site_header">
|
||||||
{#if !isGuest}
|
{#if !isGuest}
|
||||||
<li style="--active-fill: var(--nord9)"><a href="/{root}/dash" class:active={isActive(`/${root}/dash`)}><LayoutDashboard size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.dash}</span></a></li>
|
<li style="--active-fill: var(--nord9)"><a href={resolve('/[cospendRoot=cospendRoot]/dash', { cospendRoot: root })} class:active={isActive(`/${root}/dash`)}><LayoutDashboard size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.dash}</span></a></li>
|
||||||
{/if}
|
{/if}
|
||||||
<li style="--active-fill: var(--nord13)"><a href="/{root}/list" class:active={isActive(`/${root}/list`)}><ShoppingCart size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.list}</span></a></li>
|
<li style="--active-fill: var(--nord13)"><a href={resolve('/[cospendRoot=cospendRoot]/list', { cospendRoot: root })} class:active={isActive(`/${root}/list`)}><ShoppingCart size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.list}</span></a></li>
|
||||||
{#if !isGuest}
|
{#if !isGuest}
|
||||||
<li style="--active-fill: var(--nord14)"><a href="/{root}/payments" class:active={isActive(`/${root}/payments`)}><span class="nav-icon-wrap nav-icon-wallet"><Wallet size={16} strokeWidth={1.5} class="nav-icon" /></span><span class="nav-label">{labels.payments}</span></a></li>
|
<li style="--active-fill: var(--nord14)"><a href={resolve('/[cospendRoot=cospendRoot]/payments', { cospendRoot: root })} class:active={isActive(`/${root}/payments`)}><span class="nav-icon-wrap nav-icon-wallet"><Wallet size={16} strokeWidth={1.5} class="nav-icon" /></span><span class="nav-label">{labels.payments}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord12); --active-shape: circle(50%)"><a href="/{root}/recurring" class:active={isActive(`/${root}/recurring`)}><span class="nav-icon-wrap"><RefreshCw size={16} strokeWidth={1.5} class="nav-icon" /></span><span class="nav-label">{labels.recurring}</span></a></li>
|
<li style="--active-fill: var(--nord12); --active-shape: circle(50%)"><a href={resolve('/[cospendRoot=cospendRoot]/recurring', { cospendRoot: root })} class:active={isActive(`/${root}/recurring`)}><span class="nav-icon-wrap"><RefreshCw size={16} strokeWidth={1.5} class="nav-icon" /></span><span class="nav-label">{labels.recurring}</span></a></li>
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { invalidateAll } from '$app/navigation';
|
import { invalidateAll } from '$app/navigation';
|
||||||
import { pushState } from '$app/navigation';
|
import { pushState } from '$app/navigation';
|
||||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
import { detectCospendLang, cospendRoot, t, locale, paymentCategoryName } from '$lib/js/cospendI18n';
|
import { detectCospendLang, cospendRoot, t, locale, paymentCategoryName } from '$lib/js/cospendI18n';
|
||||||
|
|
||||||
let { data } = $props(); // Contains session data and balance from server
|
let { data } = $props(); // Contains session data and balance from server
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
{#if balance.netBalance !== 0}
|
{#if balance.netBalance !== 0}
|
||||||
<a href="/{root}/settle" class="btn btn-settlement">{t('settle_debts', lang)}</a>
|
<a href={resolve('/[cospendRoot=cospendRoot]/settle', { cospendRoot: root })} class="btn btn-settlement">{t('settle_debts', lang)}</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -186,7 +187,7 @@
|
|||||||
{#if isSettlementPayment(split.paymentId)}
|
{#if isSettlementPayment(split.paymentId)}
|
||||||
<!-- Settlement Payment Display - User -> User Flow -->
|
<!-- Settlement Payment Display - User -> User Flow -->
|
||||||
<a
|
<a
|
||||||
href="/{root}/payments/view/{split.paymentId?._id}"
|
href={resolve('/[cospendRoot=cospendRoot]/payments/view/[id]', { cospendRoot: root, id: split.paymentId?._id })}
|
||||||
class="settlement-flow-activity"
|
class="settlement-flow-activity"
|
||||||
onclick={(e) => handlePaymentClick(split.paymentId?._id, e)}
|
onclick={(e) => handlePaymentClick(split.paymentId?._id, e)}
|
||||||
>
|
>
|
||||||
@@ -217,7 +218,7 @@
|
|||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<ProfilePicture username={split.paymentId?.paidBy || 'Unknown'} size={36} />
|
<ProfilePicture username={split.paymentId?.paidBy || 'Unknown'} size={36} />
|
||||||
<a
|
<a
|
||||||
href="/{root}/payments/view/{split.paymentId?._id}"
|
href={resolve('/[cospendRoot=cospendRoot]/payments/view/[id]', { cospendRoot: root, id: split.paymentId?._id })}
|
||||||
class="activity-bubble"
|
class="activity-bubble"
|
||||||
onclick={(e) => handlePaymentClick(split.paymentId?._id, e)}
|
onclick={(e) => handlePaymentClick(split.paymentId?._id, e)}
|
||||||
>
|
>
|
||||||
@@ -262,7 +263,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<AddButton href="/{root}/payments/add" />
|
<AddButton href={resolve('/[cospendRoot=cospendRoot]/payments/add', { cospendRoot: root })} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.cospend-main {
|
.cospend-main {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
import Copy from '@lucide/svelte/icons/copy';
|
import Copy from '@lucide/svelte/icons/copy';
|
||||||
|
|
||||||
import Check from '@lucide/svelte/icons/check';
|
import Check from '@lucide/svelte/icons/check';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectCospendLang, t, locale, categoryName, formatTTL as formatTTLi18n, ttlOptions } from '$lib/js/cospendI18n';
|
import { detectCospendLang, t, locale, categoryName, formatTTL as formatTTLi18n, ttlOptions } from '$lib/js/cospendI18n';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
/** @type {Record<string, { icon: typeof Plus, color: string }>} */
|
/** @type {Record<string, { icon: typeof Plus, color: string }>} */
|
||||||
@@ -355,7 +355,7 @@
|
|||||||
|
|
||||||
/** @param {{ id: string, token: string }} tok */
|
/** @param {{ id: string, token: string }} tok */
|
||||||
async function copyTokenLink(tok) {
|
async function copyTokenLink(tok) {
|
||||||
const root = $page.url.pathname.split('/')[1];
|
const root = page.url.pathname.split('/')[1];
|
||||||
const url = new URL(`/${root}/list`, window.location.origin);
|
const url = new URL(`/${root}/list`, window.location.origin);
|
||||||
url.searchParams.set('token', tok.token);
|
url.searchParams.set('token', tok.token);
|
||||||
await navigator.clipboard.writeText(url.toString());
|
await navigator.clipboard.writeText(url.toString());
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||||
import { getCategoryEmoji } from '$lib/utils/categories';
|
import { getCategoryEmoji } from '$lib/utils/categories';
|
||||||
import { toast } from '$lib/js/toast.svelte';
|
import { toast } from '$lib/js/toast.svelte';
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
import { formatCurrency } from '$lib/utils/formatters';
|
import { formatCurrency } from '$lib/utils/formatters';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<h2>{t('no_payments_yet', lang)}</h2>
|
<h2>{t('no_payments_yet', lang)}</h2>
|
||||||
<p>{t('start_first_expense', lang)}</p>
|
<p>{t('start_first_expense', lang)}</p>
|
||||||
<a href="/{root}/payments/add" class="btn btn-primary">{t('add_first_payment', lang)}</a>
|
<a href={resolve('/[cospendRoot=cospendRoot]/payments/add', { cospendRoot: root })} class="btn btn-primary">{t('add_first_payment', lang)}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -156,7 +157,7 @@
|
|||||||
{#each payments as payment}
|
{#each payments as payment}
|
||||||
{#if isSettlementPayment(payment)}
|
{#if isSettlementPayment(payment)}
|
||||||
<!-- Settlement Card - Distinct Layout -->
|
<!-- Settlement Card - Distinct Layout -->
|
||||||
<a href="/{root}/payments/view/{payment._id}" class="payment-card settlement-card">
|
<a href={resolve('/[cospendRoot=cospendRoot]/payments/view/[id]', { cospendRoot: root, id: payment._id })} class="payment-card settlement-card">
|
||||||
<div class="settlement-header">
|
<div class="settlement-header">
|
||||||
<div class="settlement-badge">
|
<div class="settlement-badge">
|
||||||
<span class="settlement-icon">💸</span>
|
<span class="settlement-icon">💸</span>
|
||||||
@@ -190,7 +191,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Regular Payment Card -->
|
<!-- Regular Payment Card -->
|
||||||
<a href="/{root}/payments/view/{payment._id}" class="payment-card">
|
<a href={resolve('/[cospendRoot=cospendRoot]/payments/view/[id]', { cospendRoot: root, id: payment._id })} class="payment-card">
|
||||||
<div class="payment-header">
|
<div class="payment-header">
|
||||||
<div class="payment-title-section">
|
<div class="payment-title-section">
|
||||||
<ProfilePicture username={payment.paidBy} size={40} />
|
<ProfilePicture username={payment.paidBy} size={40} />
|
||||||
@@ -279,7 +280,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<AddButton href="/{root}/payments/add" />
|
<AddButton href={resolve('/[cospendRoot=cospendRoot]/payments/add', { cospendRoot: root })} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.payments-list {
|
.payments-list {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n, frequencyDescription } from '$lib/js/cospendI18n';
|
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n, frequencyDescription } from '$lib/js/cospendI18n';
|
||||||
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
|
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
let { data, form } = $props();
|
let { data, form } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n } from '$lib/js/cospendI18n';
|
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n } from '$lib/js/cospendI18n';
|
||||||
import { confirm } from '$lib/js/confirmDialog.svelte';
|
import { confirm } from '$lib/js/confirmDialog.svelte';
|
||||||
import FormSection from '$lib/components/FormSection.svelte';
|
import FormSection from '$lib/components/FormSection.svelte';
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto, invalidateAll } from '$app/navigation';
|
import { goto, invalidateAll } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||||
import { getCategoryEmoji } from '$lib/utils/categories';
|
import { getCategoryEmoji } from '$lib/utils/categories';
|
||||||
import EditButton from '$lib/components/EditButton.svelte';
|
import EditButton from '$lib/components/EditButton.svelte';
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
@@ -152,7 +153,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
{#if payment}
|
{#if payment}
|
||||||
<EditButton href="/{root}/payments/edit/{data.paymentId}" />
|
<EditButton href={resolve('/[cospendRoot=cospendRoot]/payments/edit/[id]', { cospendRoot: root, id: data.paymentId ?? '' })} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { getCategoryEmoji } from '$lib/utils/categories';
|
import { getCategoryEmoji } from '$lib/utils/categories';
|
||||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||||
@@ -7,12 +8,12 @@
|
|||||||
import AddButton from '$lib/components/AddButton.svelte';
|
import AddButton from '$lib/components/AddButton.svelte';
|
||||||
import { formatCurrency } from '$lib/utils/formatters';
|
import { formatCurrency } from '$lib/utils/formatters';
|
||||||
import Toggle from '$lib/components/Toggle.svelte';
|
import Toggle from '$lib/components/Toggle.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectCospendLang, cospendRoot, t, locale, paymentCategoryName, frequencyDescription, formatNextExecutionI18n } from '$lib/js/cospendI18n';
|
import { detectCospendLang, cospendRoot, t, locale, paymentCategoryName, frequencyDescription, formatNextExecutionI18n } from '$lib/js/cospendI18n';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@
|
|||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<h2>{t('no_recurring', lang)}</h2>
|
<h2>{t('no_recurring', lang)}</h2>
|
||||||
<p>{t('no_recurring_desc', lang)}</p>
|
<p>{t('no_recurring_desc', lang)}</p>
|
||||||
<a href="/{root}/payments/add" class="btn btn-primary">{t('add_first_payment', lang)}</a>
|
<a href={resolve('/[cospendRoot=cospendRoot]/payments/add', { cospendRoot: root })} class="btn btn-primary">{t('add_first_payment', lang)}</a>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="payments-grid">
|
<div class="payments-grid">
|
||||||
@@ -208,7 +209,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<a href="/{root}/recurring/edit/{payment._id}" class="btn btn-secondary btn-small">
|
<a href={resolve('/[cospendRoot=cospendRoot]/recurring/edit/[id]', { cospendRoot: root, id: payment._id })} class="btn btn-secondary btn-small">
|
||||||
{t('edit', lang)}
|
{t('edit', lang)}
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
@@ -232,7 +233,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<AddButton href="/{root}/payments/add" />
|
<AddButton href={resolve('/[cospendRoot=cospendRoot]/payments/add', { cospendRoot: root })} />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.recurring-payments {
|
.recurring-payments {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n, frequencyDescription } from '$lib/js/cospendI18n';
|
import { detectCospendLang, cospendRoot, locale, t, getCategoryOptionsI18n, frequencyDescription } from '$lib/js/cospendI18n';
|
||||||
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
|
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
|
||||||
import { validateCronExpression, calculateNextExecutionDate } from '$lib/utils/recurring';
|
import { validateCronExpression, calculateNextExecutionDate } from '$lib/utils/recurring';
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||||
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
|
import { PREDEFINED_USERS, isPredefinedUsersMode } from '$lib/config/users';
|
||||||
import { detectCospendLang, cospendRoot, t, locale } from '$lib/js/cospendI18n';
|
import { detectCospendLang, cospendRoot, t, locale } from '$lib/js/cospendI18n';
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
|
|
||||||
let { data, form } = $props();
|
let { data, form } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectCospendLang($page.url.pathname));
|
const lang = $derived(detectCospendLang(page.url.pathname));
|
||||||
const root = $derived(cospendRoot(lang));
|
const root = $derived(cospendRoot(lang));
|
||||||
const loc = $derived(locale(lang));
|
const loc = $derived(locale(lang));
|
||||||
|
|
||||||
@@ -165,7 +166,7 @@
|
|||||||
<h2>🎉 {t('all_settled', lang)}</h2>
|
<h2>🎉 {t('all_settled', lang)}</h2>
|
||||||
<p>{t('no_debts_msg', lang)}</p>
|
<p>{t('no_debts_msg', lang)}</p>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<a href="/{root}/dash" class="btn btn-primary">{t('back_to_dashboard', lang)}</a>
|
<a href={resolve('/[cospendRoot=cospendRoot]/dash', { cospendRoot: root })} class="btn btn-primary">{t('back_to_dashboard', lang)}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -349,7 +350,7 @@
|
|||||||
<button type="submit" class="btn btn-settlement">
|
<button type="submit" class="btn btn-settlement">
|
||||||
{t('record_settlement', lang)}
|
{t('record_settlement', lang)}
|
||||||
</button>
|
</button>
|
||||||
<a href="/{root}/dash" class="btn btn-secondary">
|
<a href={resolve('/[cospendRoot=cospendRoot]/dash', { cospendRoot: root })} class="btn btn-secondary">
|
||||||
{t('cancel', lang)}
|
{t('cancel', lang)}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import SectionError from '$lib/components/SectionError.svelte';
|
import SectionError from '$lib/components/SectionError.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
|
|
||||||
let faithLang = $derived($page.params.faithLang);
|
let faithLang = $derived(page.params.faithLang!);
|
||||||
let isEnglish = $derived(faithLang === 'faith');
|
let isEnglish = $derived(faithLang === 'faith');
|
||||||
let sectionLabel = $derived(
|
let sectionLabel = $derived(
|
||||||
faithLang === 'fides'
|
faithLang === 'fides'
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SectionError
|
<SectionError
|
||||||
sectionHref="/{faithLang}"
|
sectionHref={resolve('/[faithLang=faithLang]', { faithLang })}
|
||||||
{sectionLabel}
|
{sectionLabel}
|
||||||
{isEnglish}
|
{isEnglish}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { asset, resolve } from '$app/paths';
|
||||||
import '$lib/css/christ.css';
|
import '$lib/css/christ.css';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import Header from '$lib/components/Header.svelte'
|
import Header from '$lib/components/Header.svelte'
|
||||||
import UserHeader from '$lib/components/UserHeader.svelte';
|
import UserHeader from '$lib/components/UserHeader.svelte';
|
||||||
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
|
import LanguageSelector from '$lib/components/LanguageSelector.svelte';
|
||||||
@@ -10,13 +11,20 @@ let { data, children } = $props();
|
|||||||
const isEnglish = $derived(data.lang === 'en');
|
const isEnglish = $derived(data.lang === 'en');
|
||||||
const isLatin = $derived(data.lang === 'la');
|
const isLatin = $derived(data.lang === 'la');
|
||||||
const eastertide = isEastertide();
|
const eastertide = isEastertide();
|
||||||
const prayersHref = $derived(isLatin ? '/fides/orationes' : `/${data.faithLang}/${isEnglish ? 'prayers' : 'gebete'}`);
|
const prayersSlug = $derived(isLatin ? 'orationes' : isEnglish ? 'prayers' : 'gebete');
|
||||||
const rosaryHref = $derived(`/${data.faithLang}/${isLatin ? 'rosarium' : isEnglish ? 'rosary' : 'rosenkranz'}`);
|
const prayersHref = $derived(resolve('/[faithLang=faithLang]/[prayers=prayersLang]', { faithLang: data.faithLang, prayers: prayersSlug }));
|
||||||
const calendarHref = $derived(`/${data.faithLang}/${isLatin ? 'calendarium' : isEnglish ? 'calendar' : 'kalender'}`);
|
const rosaryHref = $derived(resolve('/[faithLang=faithLang]/[rosary=rosaryLang]', { faithLang: data.faithLang, rosary: isLatin ? 'rosarium' : isEnglish ? 'rosary' : 'rosenkranz' }));
|
||||||
const apologetikHref = $derived(isLatin ? '/faith/apologetics' : `/${data.faithLang}/${isEnglish ? 'apologetics' : 'apologetik'}`);
|
const calendarHref = $derived(resolve('/[faithLang=faithLang]/[calendar=calendarLang]', { faithLang: data.faithLang, calendar: isLatin ? 'calendarium' : isEnglish ? 'calendar' : 'kalender' }));
|
||||||
const angelusHref = $derived(eastertide
|
const apologetikHref = $derived(
|
||||||
? `${prayersHref}/regina-caeli`
|
isLatin
|
||||||
: `${prayersHref}/angelus`);
|
? resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]', { faithLang: 'faith', apologetikSlug: 'apologetics' })
|
||||||
|
: resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]', { faithLang: data.faithLang, apologetikSlug: isEnglish ? 'apologetics' : 'apologetik' })
|
||||||
|
);
|
||||||
|
const angelusHref = $derived(resolve('/[faithLang=faithLang]/[prayers=prayersLang]/[prayer]', {
|
||||||
|
faithLang: data.faithLang,
|
||||||
|
prayers: prayersSlug,
|
||||||
|
prayer: eastertide ? 'regina-caeli' : 'angelus'
|
||||||
|
}));
|
||||||
const angelusLabel = $derived(eastertide ? 'Regína Cæli' : 'Angelus');
|
const angelusLabel = $derived(eastertide ? 'Regína Cæli' : 'Angelus');
|
||||||
|
|
||||||
const labels = $derived({
|
const labels = $derived({
|
||||||
@@ -31,14 +39,14 @@ const typedLang = $derived(/** @type {'de' | 'en'} */ (data.lang));
|
|||||||
|
|
||||||
/** @param {string} path */
|
/** @param {string} path */
|
||||||
function isActive(path) {
|
function isActive(path) {
|
||||||
const currentPath = $page.url.pathname;
|
const currentPath = page.url.pathname;
|
||||||
return currentPath.startsWith(path);
|
return currentPath.startsWith(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prayersActive = $derived(isActive(prayersHref) && !isActive(angelusHref));
|
const prayersActive = $derived(isActive(prayersHref) && !isActive(angelusHref));
|
||||||
</script>
|
</script>
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<link rel="preload" href="/fonts/crosses.woff2" as="font" type="font/woff2" crossorigin="anonymous">
|
<link rel="preload" href={asset('/fonts/crosses.woff2')} as="font" type="font/woff2" crossorigin="anonymous">
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<Header>
|
<Header>
|
||||||
{#snippet links()}
|
{#snippet links()}
|
||||||
@@ -50,7 +58,7 @@ const prayersActive = $derived(isActive(prayersHref) && !isActive(angelusHref));
|
|||||||
{:else}
|
{:else}
|
||||||
<li style="--active-fill: var(--nord14)"><a href={angelusHref} class:active={isActive(angelusHref)} title={angelusLabel}><svg class="nav-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="6 -274 564 548" fill="currentColor"><path d="M392-162c-4-10-9-18-15-26 5-4 7-8 7-12 0-18-43-32-96-32s-96 14-96 32c0 4 3 8 7 12-6 8-11 16-15 26-15-11-24-24-24-38 0-35 57-64 128-64s128 29 128 64c0 14-9 27-24 38zm-104-22c35 0 64 29 64 64s-29 64-64 64-64-29-64-64 29-64 64-64zM82 159c3-22-3-48-20-64C34 68 16 30 16-12v-64c0-42 34-76 76-76 23 0 44 10 59 27l65 78c-21 16-37 40-43 67l-43 195c-4 17-2 34 5 49h-21c-26 0-46-24-42-50l10-55zm364 56L403 20c-6-27-21-51-42-67l64-77c15-18 36-28 59-28 42 0 76 34 76 76v64c0 42-18 80-46 107-17 16-23 42-20 64l10 56c4 26-16 49-42 49h-20c6-15 8-32 4-49zM220 31c7-32 35-55 68-55s61 23 68 55l43 194c5 20-11 39-31 39H208c-21 0-36-19-31-39l43-194z"/></svg><span class="nav-label">{angelusLabel}</span></a></li>
|
<li style="--active-fill: var(--nord14)"><a href={angelusHref} class:active={isActive(angelusHref)} title={angelusLabel}><svg class="nav-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="6 -274 564 548" fill="currentColor"><path d="M392-162c-4-10-9-18-15-26 5-4 7-8 7-12 0-18-43-32-96-32s-96 14-96 32c0 4 3 8 7 12-6 8-11 16-15 26-15-11-24-24-24-38 0-35 57-64 128-64s128 29 128 64c0 14-9 27-24 38zm-104-22c35 0 64 29 64 64s-29 64-64 64-64-29-64-64 29-64 64-64zM82 159c3-22-3-48-20-64C34 68 16 30 16-12v-64c0-42 34-76 76-76 23 0 44 10 59 27l65 78c-21 16-37 40-43 67l-43 195c-4 17-2 34 5 49h-21c-26 0-46-24-42-50l10-55zm364 56L403 20c-6-27-21-51-42-67l64-77c15-18 36-28 59-28 42 0 76 34 76 76v64c0 42-18 80-46 107-17 16-23 42-20 64l10 56c4 26-16 49-42 49h-20c6-15 8-32 4-49zM220 31c7-32 35-55 68-55s61 23 68 55l43 194c5 20-11 39-31 39H208c-21 0-36-19-31-39l43-194z"/></svg><span class="nav-label">{angelusLabel}</span></a></li>
|
||||||
{/if}
|
{/if}
|
||||||
<li style="--active-fill: var(--nord13)"><a href="/{data.faithLang}/katechese" class:active={isActive(`/${data.faithLang}/katechese`)} title={labels.catechesis}><svg class="nav-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M16 12h2"/><path d="M16 8h2"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/><path d="M6 12h2"/><path d="M6 8h2"/></svg><span class="nav-label">{labels.catechesis}</span></a></li>
|
<li style="--active-fill: var(--nord13)"><a href={resolve('/[faithLang=faithLang]/katechese', { faithLang: data.faithLang })} class:active={isActive(`/${data.faithLang}/katechese`)} title={labels.catechesis}><svg class="nav-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M16 12h2"/><path d="M16 8h2"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/><path d="M6 12h2"/><path d="M6 8h2"/></svg><span class="nav-label">{labels.catechesis}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord10)"><a href={apologetikHref} class:active={isActive(apologetikHref)} title={labels.apologetics}><svg class="nav-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="M7 21h10"/><path d="M12 3v18"/><path d="M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2"/></svg><span class="nav-label">{labels.apologetics}</span></a></li>
|
<li style="--active-fill: var(--nord10)"><a href={apologetikHref} class:active={isActive(apologetikHref)} title={labels.apologetics}><svg class="nav-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="M7 21h10"/><path d="M12 3v18"/><path d="M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2"/></svg><span class="nav-label">{labels.apologetics}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord15)"><a href={calendarHref} class:active={isActive(calendarHref)} title={labels.calendar}><svg class="nav-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg><span class="nav-label">{labels.calendar}</span></a></li>
|
<li style="--active-fill: var(--nord15)"><a href={calendarHref} class:active={isActive(calendarHref)} title={labels.calendar}><svg class="nav-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg><span class="nav-label">{labels.calendar}</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import LinksGrid from '$lib/components/LinksGrid.svelte';
|
import LinksGrid from '$lib/components/LinksGrid.svelte';
|
||||||
import { isEastertide } from '$lib/js/easter.svelte';
|
import { isEastertide } from '$lib/js/easter.svelte';
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -8,7 +9,11 @@
|
|||||||
const prayersPath = $derived(isLatin ? 'orationes' : isEnglish ? 'prayers' : 'gebete');
|
const prayersPath = $derived(isLatin ? 'orationes' : isEnglish ? 'prayers' : 'gebete');
|
||||||
const rosaryPath = $derived(isLatin ? 'rosarium' : isEnglish ? 'rosary' : 'rosenkranz');
|
const rosaryPath = $derived(isLatin ? 'rosarium' : isEnglish ? 'rosary' : 'rosenkranz');
|
||||||
const calendarPath = $derived(isLatin ? 'calendarium' : isEnglish ? 'calendar' : 'kalender');
|
const calendarPath = $derived(isLatin ? 'calendarium' : isEnglish ? 'calendar' : 'kalender');
|
||||||
const apologetikHref = $derived(isLatin ? '/faith/apologetics' : `/${data.faithLang}/${isEnglish ? 'apologetics' : 'apologetik'}`);
|
const apologetikHref = $derived(
|
||||||
|
isLatin
|
||||||
|
? resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]', { faithLang: 'faith', apologetikSlug: 'apologetics' })
|
||||||
|
: resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]', { faithLang: data.faithLang, apologetikSlug: isEnglish ? 'apologetics' : 'apologetik' })
|
||||||
|
);
|
||||||
const eastertide = isEastertide();
|
const eastertide = isEastertide();
|
||||||
|
|
||||||
const labels = $derived({
|
const labels = $derived({
|
||||||
@@ -81,11 +86,11 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<LinksGrid>
|
<LinksGrid>
|
||||||
<a href="/{data.faithLang}/{prayersPath}">
|
<a href={resolve('/[faithLang=faithLang]/[prayers=prayersLang]', { faithLang: data.faithLang, prayers: prayersPath })}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M351.2 4.8c3.2-2 6.6-3.3 10-4.1c4.7-1 9.6-.9 14.1 .1c7.7 1.8 14.8 6.5 19.4 13.6L514.6 194.2c8.8 13.1 13.4 28.6 13.4 44.4v73.5c0 6.9 4.4 13 10.9 15.2l79.2 26.4C631.2 358 640 370.2 640 384v96c0 9.9-4.6 19.3-12.5 25.4s-18.1 8.1-27.7 5.5L431 465.9c-56-14.9-95-65.7-95-123.7V224c0-17.7 14.3-32 32-32s32 14.3 32 32v80c0 8.8 7.2 16 16 16s16-7.2 16-16V219.1c0-7-1.8-13.8-5.3-19.8L340.3 48.1c-1.7-3-2.9-6.1-3.6-9.3c-1-4.7-1-9.6 .1-14.1c1.9-8 6.8-15.2 14.3-19.9zm-62.4 0c7.5 4.6 12.4 11.9 14.3 19.9c1.1 4.6 1.2 9.4 .1 14.1c-.7 3.2-1.9 6.3-3.6 9.3L213.3 199.3c-3.5 6-5.3 12.9-5.3 19.8V304c0 8.8 7.2 16 16 16s16-7.2 16-16V224c0-17.7 14.3-32 32-32s32 14.3 32 32V342.3c0 58-39 108.7-95 123.7l-168.7 45c-9.6 2.6-19.9 .5-27.7-5.5S0 490 0 480V384c0-13.8 8.8-26 21.9-30.4l79.2-26.4c6.5-2.2 10.9-8.3 10.9-15.2V238.5c0-15.8 4.7-31.2 13.4-44.4L245.2 14.5c4.6-7.1 11.7-11.8 19.4-13.6c4.6-1.1 9.4-1.2 14.1-.1c3.5 .8 6.9 2.1 10 4.1z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M351.2 4.8c3.2-2 6.6-3.3 10-4.1c4.7-1 9.6-.9 14.1 .1c7.7 1.8 14.8 6.5 19.4 13.6L514.6 194.2c8.8 13.1 13.4 28.6 13.4 44.4v73.5c0 6.9 4.4 13 10.9 15.2l79.2 26.4C631.2 358 640 370.2 640 384v96c0 9.9-4.6 19.3-12.5 25.4s-18.1 8.1-27.7 5.5L431 465.9c-56-14.9-95-65.7-95-123.7V224c0-17.7 14.3-32 32-32s32 14.3 32 32v80c0 8.8 7.2 16 16 16s16-7.2 16-16V219.1c0-7-1.8-13.8-5.3-19.8L340.3 48.1c-1.7-3-2.9-6.1-3.6-9.3c-1-4.7-1-9.6 .1-14.1c1.9-8 6.8-15.2 14.3-19.9zm-62.4 0c7.5 4.6 12.4 11.9 14.3 19.9c1.1 4.6 1.2 9.4 .1 14.1c-.7 3.2-1.9 6.3-3.6 9.3L213.3 199.3c-3.5 6-5.3 12.9-5.3 19.8V304c0 8.8 7.2 16 16 16s16-7.2 16-16V224c0-17.7 14.3-32 32-32s32 14.3 32 32V342.3c0 58-39 108.7-95 123.7l-168.7 45c-9.6 2.6-19.9 .5-27.7-5.5S0 490 0 480V384c0-13.8 8.8-26 21.9-30.4l79.2-26.4c6.5-2.2 10.9-8.3 10.9-15.2V238.5c0-15.8 4.7-31.2 13.4-44.4L245.2 14.5c4.6-7.1 11.7-11.8 19.4-13.6c4.6-1.1 9.4-1.2 14.1-.1c3.5 .8 6.9 2.1 10 4.1z"/></svg>
|
||||||
<h3>{labels.prayers}</h3>
|
<h3>{labels.prayers}</h3>
|
||||||
</a>
|
</a>
|
||||||
<a href="/{data.faithLang}/{rosaryPath}">
|
<a href={resolve('/[faithLang=faithLang]/[rosary=rosaryLang]', { faithLang: data.faithLang, rosary: rosaryPath })}>
|
||||||
<svg viewBox="0 0 512 512">
|
<svg viewBox="0 0 512 512">
|
||||||
<g>
|
<g>
|
||||||
<path class="st0" d="M241.251,145.056c-39.203-17.423-91.472,17.423-104.54,60.982c-13.068,43.558,8.712,117.608,65.337,143.742
|
<path class="st0" d="M241.251,145.056c-39.203-17.423-91.472,17.423-104.54,60.982c-13.068,43.558,8.712,117.608,65.337,143.742
|
||||||
@@ -114,18 +119,18 @@
|
|||||||
<h3>{labels.rosary}</h3>
|
<h3>{labels.rosary}</h3>
|
||||||
</a>
|
</a>
|
||||||
{#if eastertide}
|
{#if eastertide}
|
||||||
<a href="/{data.faithLang}/{prayersPath}/regina-caeli" class="regina-link">
|
<a href={resolve('/[faithLang=faithLang]/[prayers=prayersLang]/[prayer]', { faithLang: data.faithLang, prayers: prayersPath, prayer: 'regina-caeli' })} class="regina-link">
|
||||||
<span class="easter-badge">{isLatin ? 'Tempore' : isEnglish ? 'In season' : 'Zur Zeit'}</span>
|
<span class="easter-badge">{isLatin ? 'Tempore' : isEnglish ? 'In season' : 'Zur Zeit'}</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -274 532 548"><path d="M256-168c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zM6-63l122 199-56 70c-5 7-8 14-8 23 0 19 16 35 36 35h312c20 0 36-16 36-35 0-9-3-16-8-23l-56-70L507-63c3-6 5-13 5-20 0-20-16-37-37-37-7 0-14 2-20 6l-17 12c-13 8-30 6-40-4l-35-35c-7-7-17-11-27-11s-20 4-27 11l-30 30c-13 13-33 13-46 0l-30-30c-7-7-17-11-27-11s-20 4-27 11l-34 34c-11 11-28 13-41 4l-17-11c-6-4-13-6-20-6-20 0-37 17-37 37 0 7 2 14 6 20z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -274 532 548"><path d="M256-168c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zM6-63l122 199-56 70c-5 7-8 14-8 23 0 19 16 35 36 35h312c20 0 36-16 36-35 0-9-3-16-8-23l-56-70L507-63c3-6 5-13 5-20 0-20-16-37-37-37-7 0-14 2-20 6l-17 12c-13 8-30 6-40-4l-35-35c-7-7-17-11-27-11s-20 4-27 11l-30 30c-13 13-33 13-46 0l-30-30c-7-7-17-11-27-11s-20 4-27 11l-34 34c-11 11-28 13-41 4l-17-11c-6-4-13-6-20-6-20 0-37 17-37 37 0 7 2 14 6 20z"/></svg>
|
||||||
<h3>Regína Cæli</h3>
|
<h3>Regína Cæli</h3>
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a href="/{data.faithLang}/{prayersPath}/angelus">
|
<a href={resolve('/[faithLang=faithLang]/[prayers=prayersLang]/[prayer]', { faithLang: data.faithLang, prayers: prayersPath, prayer: 'angelus' })}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"viewBox="6 -274 564 548"><path d="M392-162c-4-10-9-18-15-26 5-4 7-8 7-12 0-18-43-32-96-32s-96 14-96 32c0 4 3 8 7 12-6 8-11 16-15 26-15-11-24-24-24-38 0-35 57-64 128-64s128 29 128 64c0 14-9 27-24 38zm-104-22c35 0 64 29 64 64s-29 64-64 64-64-29-64-64 29-64 64-64zM82 159c3-22-3-48-20-64C34 68 16 30 16-12v-64c0-42 34-76 76-76 23 0 44 10 59 27l65 78c-21 16-37 40-43 67l-43 195c-4 17-2 34 5 49h-21c-26 0-46-24-42-50l10-55zm364 56L403 20c-6-27-21-51-42-67l64-77c15-18 36-28 59-28 42 0 76 34 76 76v64c0 42-18 80-46 107-17 16-23 42-20 64l10 56c4 26-16 49-42 49h-20c6-15 8-32 4-49zM220 31c7-32 35-55 68-55s61 23 68 55l43 194c5 20-11 39-31 39H208c-21 0-36-19-31-39l43-194z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg"viewBox="6 -274 564 548"><path d="M392-162c-4-10-9-18-15-26 5-4 7-8 7-12 0-18-43-32-96-32s-96 14-96 32c0 4 3 8 7 12-6 8-11 16-15 26-15-11-24-24-24-38 0-35 57-64 128-64s128 29 128 64c0 14-9 27-24 38zm-104-22c35 0 64 29 64 64s-29 64-64 64-64-29-64-64 29-64 64-64zM82 159c3-22-3-48-20-64C34 68 16 30 16-12v-64c0-42 34-76 76-76 23 0 44 10 59 27l65 78c-21 16-37 40-43 67l-43 195c-4 17-2 34 5 49h-21c-26 0-46-24-42-50l10-55zm364 56L403 20c-6-27-21-51-42-67l64-77c15-18 36-28 59-28 42 0 76 34 76 76v64c0 42-18 80-46 107-17 16-23 42-20 64l10 56c4 26-16 49-42 49h-20c6-15 8-32 4-49zM220 31c7-32 35-55 68-55s61 23 68 55l43 194c5 20-11 39-31 39H208c-21 0-36-19-31-39l43-194z"/></svg>
|
||||||
<h3>Angelus</h3>
|
<h3>Angelus</h3>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<a href="/{data.faithLang}/katechese" class="katechese-link">
|
<a href={resolve('/[faithLang=faithLang]/katechese', { faithLang: data.faithLang })} class="katechese-link">
|
||||||
{#if isEnglish || isLatin}<span class="lang-badge">DE</span>{/if}
|
{#if isEnglish || isLatin}<span class="lang-badge">DE</span>{/if}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -226 532 506"><path d="M256-107v310l1-1c54-22 113-34 172-34h19v-320h-19c-42 0-84 8-123 25-17 7-34 14-50 20zm-25-79 25 10 25-10c47-20 97-30 148-30h35c27 0 48 22 48 48v352c0 27-21 48-48 48h-35c-51 0-101 10-148 30l-13 5c-8 3-16 3-24 0l-13-5c-47-20-97-30-148-30H48c-26 0-48-21-48-48v-352c0-26 22-48 48-48h35c51 0 101 10 148 30z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -226 532 506"><path d="M256-107v310l1-1c54-22 113-34 172-34h19v-320h-19c-42 0-84 8-123 25-17 7-34 14-50 20zm-25-79 25 10 25-10c47-20 97-30 148-30h35c27 0 48 22 48 48v352c0 27-21 48-48 48h-35c-51 0-101 10-148 30l-13 5c-8 3-16 3-24 0l-13-5c-47-20-97-30-148-30H48c-26 0-48-21-48-48v-352c0-26 22-48 48-48h35c51 0 101 10 148 30z"/></svg>
|
||||||
<h3>Katechese</h3>
|
<h3>Katechese</h3>
|
||||||
@@ -134,7 +139,7 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M384 32H512c17.7 0 32 14.3 32 32s-14.3 32-32 32H398.4c-5.2 25.8-22.9 47.1-46.4 57.3V448H544c17.7 0 32 14.3 32 32s-14.3 32-32 32H320 96c-17.7 0-32-14.3-32-32s14.3-32 32-32H288V153.3c-23.5-10.3-41.2-31.6-46.4-57.3H128c-17.7 0-32-14.3-32-32s14.3-32 32-32H256c14.6-19.4 37.8-32 64-32s49.4 12.6 64 32zm55.6 288H584.4L512 195.8 439.6 320zM512 416c-62.9 0-115.2-34-126-78.9c-2.6-11 1-22.3 6.7-32.1l95.2-163.2c5-8.6 14.2-13.8 24.1-13.8s19.1 5.3 24.1 13.8l95.2 163.2c5.7 9.8 9.3 21.1 6.7 32.1C627.2 382 574.9 416 512 416zM126.8 195.8L54.4 320H199.3L126.8 195.8zM.9 337.1c-2.6-11 1-22.3 6.7-32.1l95.2-163.2c5-8.6 14.2-13.8 24.1-13.8s19.1 5.3 24.1 13.8l95.2 163.2c5.7 9.8 9.3 21.1 6.7 32.1C242 382 189.7 416 126.8 416S11.7 382 .9 337.1z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M384 32H512c17.7 0 32 14.3 32 32s-14.3 32-32 32H398.4c-5.2 25.8-22.9 47.1-46.4 57.3V448H544c17.7 0 32 14.3 32 32s-14.3 32-32 32H320 96c-17.7 0-32-14.3-32-32s14.3-32 32-32H288V153.3c-23.5-10.3-41.2-31.6-46.4-57.3H128c-17.7 0-32-14.3-32-32s14.3-32 32-32H256c14.6-19.4 37.8-32 64-32s49.4 12.6 64 32zm55.6 288H584.4L512 195.8 439.6 320zM512 416c-62.9 0-115.2-34-126-78.9c-2.6-11 1-22.3 6.7-32.1l95.2-163.2c5-8.6 14.2-13.8 24.1-13.8s19.1 5.3 24.1 13.8l95.2 163.2c5.7 9.8 9.3 21.1 6.7 32.1C627.2 382 574.9 416 512 416zM126.8 195.8L54.4 320H199.3L126.8 195.8zM.9 337.1c-2.6-11 1-22.3 6.7-32.1l95.2-163.2c5-8.6 14.2-13.8 24.1-13.8s19.1 5.3 24.1 13.8l95.2 163.2c5.7 9.8 9.3 21.1 6.7 32.1C242 382 189.7 416 126.8 416S11.7 382 .9 337.1z"/></svg>
|
||||||
<h3>{labels.apologetics}</h3>
|
<h3>{labels.apologetics}</h3>
|
||||||
</a>
|
</a>
|
||||||
<a href="/{data.faithLang}/{calendarPath}">
|
<a href={resolve('/[faithLang=faithLang]/[calendar=calendarLang]', { faithLang: data.faithLang, calendar: calendarPath })}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192zm64 80v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V272c0-8.8-7.2-16-16-16H80c-8.8 0-16 7.2-16 16zm128 0v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V272c0-8.8-7.2-16-16-16H208c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V272c0-8.8-7.2-16-16-16H336zM64 400v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V400c0-8.8-7.2-16-16-16H80c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V400c0-8.8-7.2-16-16-16H208zm112 16v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V400c0-8.8-7.2-16-16-16H336c-8.8 0-16 7.2-16 16z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M128 0c17.7 0 32 14.3 32 32V64H288V32c0-17.7 14.3-32 32-32s32 14.3 32 32V64h48c26.5 0 48 21.5 48 48v48H0V112C0 85.5 21.5 64 48 64H96V32c0-17.7 14.3-32 32-32zM0 192H448V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V192zm64 80v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V272c0-8.8-7.2-16-16-16H80c-8.8 0-16 7.2-16 16zm128 0v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V272c0-8.8-7.2-16-16-16H208c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V272c0-8.8-7.2-16-16-16H336zM64 400v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V400c0-8.8-7.2-16-16-16H80c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V400c0-8.8-7.2-16-16-16H208zm112 16v32c0 8.8 7.2 16 16 16h32c8.8 0 16-7.2 16-16V400c0-8.8-7.2-16-16-16H336c-8.8 0-16 7.2-16 16z"/></svg>
|
||||||
<h3>{labels.calendar}</h3>
|
<h3>{labels.calendar}</h3>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import Shield from '@lucide/svelte/icons/shield';
|
import Shield from '@lucide/svelte/icons/shield';
|
||||||
import Flame from '@lucide/svelte/icons/flame';
|
import Flame from '@lucide/svelte/icons/flame';
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="cards" aria-label={t.title}>
|
<section class="cards" aria-label={t.title}>
|
||||||
<a class="case-card contra" href="/{faithLang}/{slug}/contra">
|
<a class="case-card contra" href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/contra', { faithLang, apologetikSlug: slug })}>
|
||||||
<div class="card-glyph" aria-hidden="true"><Shield size={28} strokeWidth={2} /></div>
|
<div class="card-glyph" aria-hidden="true"><Shield size={28} strokeWidth={2} /></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="card-sub">{t.contraSub}</div>
|
<div class="card-sub">{t.contraSub}</div>
|
||||||
@@ -65,7 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="case-card pro" href="/{faithLang}/{slug}/pro">
|
<a class="case-card pro" href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/pro', { faithLang, apologetikSlug: slug })}>
|
||||||
<div class="card-glyph" aria-hidden="true"><Flame size={28} strokeWidth={2} /></div>
|
<div class="card-glyph" aria-hidden="true"><Flame size={28} strokeWidth={2} /></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="card-sub">{t.proSub}</div>
|
<div class="card-sub">{t.proSub}</div>
|
||||||
|
|||||||
+12
-2
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import CaseTabs from '$lib/components/faith/CaseTabs.svelte';
|
import CaseTabs from '$lib/components/faith/CaseTabs.svelte';
|
||||||
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
|
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
|
||||||
@@ -181,7 +182,7 @@
|
|||||||
<article class="arg-row" id="arg-{arg.id}">
|
<article class="arg-row" id="arg-{arg.id}">
|
||||||
<a
|
<a
|
||||||
class="card-link"
|
class="card-link"
|
||||||
href="/{faithLang}/{slug}/contra/{arg.id}"
|
href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/contra/[argId]', { faithLang, apologetikSlug: slug, argId: arg.id })}
|
||||||
aria-label={arg.title}
|
aria-label={arg.title}
|
||||||
></a>
|
></a>
|
||||||
<div class="arg-num">
|
<div class="arg-num">
|
||||||
@@ -201,7 +202,7 @@
|
|||||||
{@const a = ARCHETYPES[archId]}
|
{@const a = ARCHETYPES[archId]}
|
||||||
<a
|
<a
|
||||||
class="archetype-badge"
|
class="archetype-badge"
|
||||||
href="/{faithLang}/{slug}/contra/{arg.id}#voice-{archId}"
|
href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/contra/[argId]/[[archId]]', { faithLang, apologetikSlug: slug, argId: arg.id, archId })}
|
||||||
title="{a.name} — {a.sub}"
|
title="{a.name} — {a.sub}"
|
||||||
>
|
>
|
||||||
<span class="glyph" aria-hidden="true" style="background:{a.color};">
|
<span class="glyph" aria-hidden="true" style="background:{a.color};">
|
||||||
@@ -496,6 +497,15 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 760px) {
|
||||||
|
.answer-rail {
|
||||||
|
/* Extend past the 760px content column into the right gutter when space allows.
|
||||||
|
arg-body left = 50vw - 278px; available right width capped 24px from viewport edge. */
|
||||||
|
max-width: min(calc(100vw - 126px), calc(50vw + 254px));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.answer-rail .label {
|
.answer-rail .label {
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
|
|||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
import { error } from '@sveltejs/kit';
|
|
||||||
import { findArgumentLang, getArchetypes, getArguments } from '$lib/data/apologetik';
|
|
||||||
import type { PageLoad } from './$types';
|
|
||||||
|
|
||||||
export const load: PageLoad = async ({ params, parent }) => {
|
|
||||||
const { lang } = await parent();
|
|
||||||
const [arg, archetypes, args] = await Promise.all([
|
|
||||||
findArgumentLang(params.argId, lang),
|
|
||||||
getArchetypes(lang),
|
|
||||||
getArguments(lang)
|
|
||||||
]);
|
|
||||||
if (!arg) {
|
|
||||||
error(404, 'Argument not found');
|
|
||||||
}
|
|
||||||
return { argument: arg, archetypes, args };
|
|
||||||
};
|
|
||||||
+86
-19
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { resolve } from '$app/paths';
|
||||||
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
|
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -9,6 +9,10 @@
|
|||||||
const isGerman = $derived(data?.lang === 'de');
|
const isGerman = $derived(data?.lang === 'de');
|
||||||
const arg = $derived(data.argument);
|
const arg = $derived(data.argument);
|
||||||
const ARCHETYPES = $derived(data.archetypes);
|
const ARCHETYPES = $derived(data.archetypes);
|
||||||
|
const alexPicks = $derived<string[]>(data.alexPicks ?? []);
|
||||||
|
const alexPickLabel = $derived(
|
||||||
|
isLatin ? 'Alexandri delectus' : isGerman ? "Alex' Wahl" : "Alex's pick"
|
||||||
|
);
|
||||||
|
|
||||||
const tocLabel = $derived(
|
const tocLabel = $derived(
|
||||||
isLatin ? 'Obiectiones' : isGerman ? 'Einwände' : 'Objections'
|
isLatin ? 'Obiectiones' : isGerman ? 'Einwände' : 'Objections'
|
||||||
@@ -24,10 +28,13 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
const archIds = $derived(Object.keys(arg.counters));
|
const archIds = $derived(Object.keys(arg.counters));
|
||||||
let userSelected = $state<string | null>(null);
|
let selectedByArg = $state<Record<string, string>>({});
|
||||||
const activeId = $derived(
|
const activeId = $derived.by(() => {
|
||||||
userSelected && archIds.includes(userSelected) ? userSelected : (archIds[0] ?? '')
|
const sel = selectedByArg[arg.id];
|
||||||
);
|
if (sel && archIds.includes(sel)) return sel;
|
||||||
|
if (data.initialArchId && archIds.includes(data.initialArchId)) return data.initialArchId;
|
||||||
|
return archIds[0] ?? '';
|
||||||
|
});
|
||||||
const arch = $derived(ARCHETYPES[activeId]);
|
const arch = $derived(ARCHETYPES[activeId]);
|
||||||
const counter = $derived(arg.counters[activeId]);
|
const counter = $derived(arg.counters[activeId]);
|
||||||
|
|
||||||
@@ -72,21 +79,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectArch(id: string) {
|
function selectArch(id: string) {
|
||||||
userSelected = id;
|
selectedByArg = { ...selectedByArg, [arg.id]: id };
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
history.replaceState(null, '', `#voice-${id}`);
|
history.replaceState(null, '', `/${faithLang}/${slug}/contra/${arg.id}/${id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const hash = window.location.hash;
|
|
||||||
if (hash.startsWith('#voice-')) {
|
|
||||||
const id = hash.slice('#voice-'.length);
|
|
||||||
if (archIds.includes(id)) {
|
|
||||||
userSelected = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -103,7 +101,7 @@
|
|||||||
<ApologetikToc title={tocLabel} items={tocItems} activeId={arg.id} />
|
<ApologetikToc title={tocLabel} items={tocItems} activeId={arg.id} />
|
||||||
|
|
||||||
<main class="detail">
|
<main class="detail">
|
||||||
<a class="back-link" href="/{faithLang}/{slug}/contra">{labels.back}</a>
|
<a class="back-link" href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/contra', { faithLang, apologetikSlug: slug })}>{labels.back}</a>
|
||||||
|
|
||||||
<div class="detail-eyebrow">
|
<div class="detail-eyebrow">
|
||||||
{labels.eyebrowPrefix}
|
{labels.eyebrowPrefix}
|
||||||
@@ -123,12 +121,14 @@
|
|||||||
{#each archIds as id (id)}
|
{#each archIds as id (id)}
|
||||||
{@const a = ARCHETYPES[id]}
|
{@const a = ARCHETYPES[id]}
|
||||||
{@const isActive = id === activeId}
|
{@const isActive = id === activeId}
|
||||||
|
{@const isPick = alexPicks.includes(id)}
|
||||||
<a
|
<a
|
||||||
href="#voice-{id}"
|
href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/contra/[argId]/[[archId]]', { faithLang, apologetikSlug: slug, argId: arg.id, archId: id })}
|
||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={isActive}
|
aria-selected={isActive}
|
||||||
class="tab"
|
class="tab"
|
||||||
class:active={isActive}
|
class:active={isActive}
|
||||||
|
class:has-pick={isPick}
|
||||||
style:border-bottom-color={isActive ? a.colorHex : 'transparent'}
|
style:border-bottom-color={isActive ? a.colorHex : 'transparent'}
|
||||||
onclick={(e) => {
|
onclick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -137,6 +137,20 @@
|
|||||||
>
|
>
|
||||||
<span class="glyph" aria-hidden="true" style="background:{a.color};">{a.glyph}</span>
|
<span class="glyph" aria-hidden="true" style="background:{a.color};">{a.glyph}</span>
|
||||||
<span>{a.name}</span>
|
<span>{a.name}</span>
|
||||||
|
{#if isPick}
|
||||||
|
<span class="alex-mark" aria-label={alexPickLabel}>
|
||||||
|
<img
|
||||||
|
class="alex-pfp"
|
||||||
|
src="https://bocken.org/static/user/thumb/alexander.webp"
|
||||||
|
alt=""
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
/>
|
||||||
|
<span class="alex-label">{alexPickLabel}</span>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -185,7 +199,7 @@
|
|||||||
{#each arg.related as rid (rid)}
|
{#each arg.related as rid (rid)}
|
||||||
{@const r = data.args.find((x) => x.id === rid)}
|
{@const r = data.args.find((x) => x.id === rid)}
|
||||||
{#if r}
|
{#if r}
|
||||||
<a class="related-item" href="/{faithLang}/{slug}/contra/{r.id}">
|
<a class="related-item" href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/contra/[argId]', { faithLang, apologetikSlug: slug, argId: r.id })}>
|
||||||
<span class="num">{String(r.n).padStart(2, '0')}</span>
|
<span class="num">{String(r.n).padStart(2, '0')}</span>
|
||||||
{r.title}
|
{r.title}
|
||||||
</a>
|
</a>
|
||||||
@@ -345,6 +359,59 @@
|
|||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alex-mark {
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
right: -6px;
|
||||||
|
z-index: 2;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
background: var(--color-bg-elevated);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
padding: 1px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateY(0);
|
||||||
|
transition:
|
||||||
|
padding var(--transition-normal),
|
||||||
|
transform var(--transition-normal);
|
||||||
|
}
|
||||||
|
.alex-pfp {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.alex-label {
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.62rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
max-width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
transition:
|
||||||
|
max-width var(--transition-normal),
|
||||||
|
opacity var(--transition-fast),
|
||||||
|
margin-left var(--transition-normal);
|
||||||
|
}
|
||||||
|
.tab.active.has-pick .alex-mark {
|
||||||
|
padding: 1px 8px 1px 1px;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
.tab.active.has-pick .alex-label {
|
||||||
|
max-width: 140px;
|
||||||
|
opacity: 1;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.glyph {
|
.glyph {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import { ALEX_PICKS, findArgumentLang, getArchetypes, getArguments } from '$lib/data/apologetik';
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ params, parent }) => {
|
||||||
|
const { lang } = await parent();
|
||||||
|
const [arg, archetypes, args] = await Promise.all([
|
||||||
|
findArgumentLang(params.argId, lang),
|
||||||
|
getArchetypes(lang),
|
||||||
|
getArguments(lang)
|
||||||
|
]);
|
||||||
|
if (!arg) {
|
||||||
|
error(404, 'Argument not found');
|
||||||
|
}
|
||||||
|
const archIds = Object.keys(arg.counters);
|
||||||
|
if (params.archId && !archIds.includes(params.archId)) {
|
||||||
|
error(404, 'Voice not found');
|
||||||
|
}
|
||||||
|
const initialArchId = params.archId ?? null;
|
||||||
|
return {
|
||||||
|
argument: arg,
|
||||||
|
archetypes,
|
||||||
|
args,
|
||||||
|
alexPicks: ALEX_PICKS[params.argId] ?? [],
|
||||||
|
initialArchId
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { POS_LAYER_COLORS, type PosArgument } from '$lib/data/apologetik';
|
import { POS_LAYER_COLORS, type PosArgument } from '$lib/data/apologetik';
|
||||||
import CaseTabs from '$lib/components/faith/CaseTabs.svelte';
|
import CaseTabs from '$lib/components/faith/CaseTabs.svelte';
|
||||||
@@ -174,7 +175,7 @@
|
|||||||
{@const stroke = POS_LAYER_COLORS[it.layer]}
|
{@const stroke = POS_LAYER_COLORS[it.layer]}
|
||||||
{@const opacity = 0.25 + (it.strength / 5) * 0.55}
|
{@const opacity = 0.25 + (it.strength / 5) * 0.55}
|
||||||
{@const sw = 1.6 + it.strength * 1.0}
|
{@const sw = 1.6 + it.strength * 1.0}
|
||||||
<a href="/{faithLang}/{slug}/pro/{it.id}" aria-label={it.title}>
|
<a href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/pro/[posArgId]', { faithLang, apologetikSlug: slug, posArgId: it.id })} aria-label={it.title}>
|
||||||
<path
|
<path
|
||||||
d="M 38 {it.y} C {W * 0.45} {it.y}, {W * 0.55} {targetY}, {targetX} {targetY}"
|
d="M 38 {it.y} C {W * 0.45} {it.y}, {W * 0.55} {targetY}, {targetX} {targetY}"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -253,7 +254,7 @@
|
|||||||
<article class="pos-row" id="pos-{arg.id}">
|
<article class="pos-row" id="pos-{arg.id}">
|
||||||
<a
|
<a
|
||||||
class="card-link"
|
class="card-link"
|
||||||
href="/{faithLang}/{slug}/pro/{arg.id}"
|
href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/pro/[posArgId]', { faithLang, apologetikSlug: slug, posArgId: arg.id })}
|
||||||
aria-label={arg.title}
|
aria-label={arg.title}
|
||||||
></a>
|
></a>
|
||||||
<div class="pos-num">
|
<div class="pos-num">
|
||||||
@@ -286,7 +287,7 @@
|
|||||||
{@const v = POS_VOICES[vid]}
|
{@const v = POS_VOICES[vid]}
|
||||||
<a
|
<a
|
||||||
class="archetype-badge"
|
class="archetype-badge"
|
||||||
href="/{faithLang}/{slug}/pro/{arg.id}#voice-{vid}"
|
href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/pro/[posArgId]/[[voiceId]]', { faithLang, apologetikSlug: slug, posArgId: arg.id, voiceId: vid })}
|
||||||
title="{v.name} — {v.sub}"
|
title="{v.name} — {v.sub}"
|
||||||
>
|
>
|
||||||
<span class="glyph" aria-hidden="true" style="background:{v.color};"
|
<span class="glyph" aria-hidden="true" style="background:{v.color};"
|
||||||
|
|||||||
+7
-1
@@ -21,6 +21,12 @@ export const load: PageServerLoad = async ({ params, parent }) => {
|
|||||||
error(404, 'Argument not found');
|
error(404, 'Argument not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const voiceIds = Object.keys(arg.voices);
|
||||||
|
if (params.voiceId && !voiceIds.includes(params.voiceId)) {
|
||||||
|
error(404, 'Voice not found');
|
||||||
|
}
|
||||||
|
const initialVoiceId = params.voiceId ?? null;
|
||||||
|
|
||||||
const lng: 'en' | 'de' = lang === 'de' ? 'de' : 'en';
|
const lng: 'en' | 'de' = lang === 'de' ? 'de' : 'en';
|
||||||
const enArg = EN_POS_ARGUMENTS.find((x) => x.id === arg.id);
|
const enArg = EN_POS_ARGUMENTS.find((x) => x.id === arg.id);
|
||||||
const argument = enArg
|
const argument = enArg
|
||||||
@@ -40,5 +46,5 @@ export const load: PageServerLoad = async ({ params, parent }) => {
|
|||||||
return { ...a, scripture: resolved.text ? resolved : a.scripture };
|
return { ...a, scripture: resolved.text ? resolved : a.scripture };
|
||||||
});
|
});
|
||||||
|
|
||||||
return { argument, voices, layers, args: argsWithScripture };
|
return { argument, voices, layers, args: argsWithScripture, initialVoiceId };
|
||||||
};
|
};
|
||||||
+13
-20
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { resolve } from '$app/paths';
|
||||||
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
|
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -35,10 +35,13 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
const voiceIds = $derived(Object.keys(arg.voices));
|
const voiceIds = $derived(Object.keys(arg.voices));
|
||||||
let userSelected = $state<string | null>(null);
|
let selectedByArg = $state<Record<string, string>>({});
|
||||||
const activeId = $derived(
|
const activeId = $derived.by(() => {
|
||||||
userSelected && voiceIds.includes(userSelected) ? userSelected : (voiceIds[0] ?? '')
|
const sel = selectedByArg[arg.id];
|
||||||
);
|
if (sel && voiceIds.includes(sel)) return sel;
|
||||||
|
if (data.initialVoiceId && voiceIds.includes(data.initialVoiceId)) return data.initialVoiceId;
|
||||||
|
return voiceIds[0] ?? '';
|
||||||
|
});
|
||||||
const voice = $derived(POS_VOICES[activeId]);
|
const voice = $derived(POS_VOICES[activeId]);
|
||||||
const counter = $derived(arg.voices[activeId]);
|
const counter = $derived(arg.voices[activeId]);
|
||||||
|
|
||||||
@@ -83,21 +86,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectVoice(id: string) {
|
function selectVoice(id: string) {
|
||||||
userSelected = id;
|
selectedByArg = { ...selectedByArg, [arg.id]: id };
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
history.replaceState(null, '', `#voice-${id}`);
|
history.replaceState(null, '', `/${faithLang}/${slug}/pro/${arg.id}/${id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const hash = window.location.hash;
|
|
||||||
if (hash.startsWith('#voice-')) {
|
|
||||||
const id = hash.slice('#voice-'.length);
|
|
||||||
if (voiceIds.includes(id)) {
|
|
||||||
userSelected = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -114,7 +107,7 @@
|
|||||||
<ApologetikToc title={tocLabel} items={tocItems} activeId={arg.id} />
|
<ApologetikToc title={tocLabel} items={tocItems} activeId={arg.id} />
|
||||||
|
|
||||||
<main class="detail">
|
<main class="detail">
|
||||||
<a class="back-link" href="/{faithLang}/{slug}/pro">{labels.back}</a>
|
<a class="back-link" href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/pro', { faithLang, apologetikSlug: slug })}>{labels.back}</a>
|
||||||
|
|
||||||
{#if layer}
|
{#if layer}
|
||||||
<div class="layer-tag">{layer.sub}</div>
|
<div class="layer-tag">{layer.sub}</div>
|
||||||
@@ -150,7 +143,7 @@
|
|||||||
{@const v = POS_VOICES[id]}
|
{@const v = POS_VOICES[id]}
|
||||||
{@const isActive = id === activeId}
|
{@const isActive = id === activeId}
|
||||||
<a
|
<a
|
||||||
href="#voice-{id}"
|
href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/pro/[posArgId]/[[voiceId]]', { faithLang, apologetikSlug: slug, posArgId: arg.id, voiceId: id })}
|
||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={isActive}
|
aria-selected={isActive}
|
||||||
class="tab"
|
class="tab"
|
||||||
@@ -211,7 +204,7 @@
|
|||||||
{#each arg.related as rid (rid)}
|
{#each arg.related as rid (rid)}
|
||||||
{@const r = POS_ARGUMENTS.find((x) => x.id === rid)}
|
{@const r = POS_ARGUMENTS.find((x) => x.id === rid)}
|
||||||
{#if r}
|
{#if r}
|
||||||
<a class="related-item" href="/{faithLang}/{slug}/pro/{r.id}">
|
<a class="related-item" href={resolve('/[faithLang=faithLang]/[apologetikSlug=apologetikSlug]/pro/[posArgId]', { faithLang, apologetikSlug: slug, posArgId: r.id })}>
|
||||||
<span class="num">{String(r.n).padStart(2, '0')}</span>
|
<span class="num">{String(r.n).padStart(2, '0')}</span>
|
||||||
{r.title}
|
{r.title}
|
||||||
</a>
|
</a>
|
||||||
+32
-8
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
@@ -103,17 +104,24 @@
|
|||||||
|
|
||||||
// URL: /{faithLang}/{calendar}/{rite}/{yyyy}/{mm}/{dd} — rite is a required
|
// URL: /{faithLang}/{calendar}/{rite}/{yyyy}/{mm}/{dd} — rite is a required
|
||||||
// path segment so day/month nav stays inside the active rite.
|
// path segment so day/month nav stays inside the active rite.
|
||||||
const riteBase = $derived(`/${page.params.faithLang}/${page.params.calendar}/${rite}`);
|
const riteParams = $derived({
|
||||||
const calendarBase = $derived(`/${page.params.faithLang}/${page.params.calendar}`);
|
faithLang: page.params.faithLang!,
|
||||||
|
calendar: page.params.calendar!,
|
||||||
|
rite
|
||||||
|
});
|
||||||
|
|
||||||
function dayHref(iso: string) {
|
function dayHref(iso: string) {
|
||||||
const [yy, mm, dd] = iso.split('-');
|
const [yy, mm, dd] = iso.split('-');
|
||||||
return `${riteBase}/${yy}/${mm}/${dd}${dioceseQuery}`;
|
return resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/[[yyyy=calendarYear]]/[[mm=calendarMonth]]/[[dd=calendarDay]]', {
|
||||||
|
...riteParams, yyyy: yy, mm, dd
|
||||||
|
}) + dioceseQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
function detailHref(iso: string) {
|
function detailHref(iso: string) {
|
||||||
const [yy, mm, dd] = iso.split('-');
|
const [yy, mm, dd] = iso.split('-');
|
||||||
return `${riteBase}/detail/${yy}/${mm}/${dd}${dioceseQuery}`;
|
return resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/detail/[yyyy=calendarYear]/[mm=calendarMonth]/[dd=calendarDay]', {
|
||||||
|
...riteParams, yyyy: yy, mm, dd
|
||||||
|
}) + dioceseQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hero card: prefer the currently-selected day; fall back to today when
|
// Hero card: prefer the currently-selected day; fall back to today when
|
||||||
@@ -121,12 +129,19 @@
|
|||||||
const hero = $derived(selected ?? today);
|
const hero = $derived(selected ?? today);
|
||||||
|
|
||||||
function monthHref(y: number, m: number) {
|
function monthHref(y: number, m: number) {
|
||||||
return `${riteBase}/${y}/${pad(m + 1)}${dioceseQuery}`;
|
return resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/[[yyyy=calendarYear]]/[[mm=calendarMonth]]', {
|
||||||
|
...riteParams, yyyy: String(y), mm: pad(m + 1)
|
||||||
|
}) + dioceseQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
const todayHref = $derived.by(() => {
|
const todayHref = $derived.by(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
return `${riteBase}/${now.getFullYear()}/${pad(now.getMonth() + 1)}/${pad(now.getDate())}${dioceseQuery}`;
|
return resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/[[yyyy=calendarYear]]/[[mm=calendarMonth]]/[[dd=calendarDay]]', {
|
||||||
|
...riteParams,
|
||||||
|
yyyy: String(now.getFullYear()),
|
||||||
|
mm: pad(now.getMonth() + 1),
|
||||||
|
dd: pad(now.getDate())
|
||||||
|
}) + dioceseQuery;
|
||||||
});
|
});
|
||||||
|
|
||||||
const pageTitle = $derived(t('calendar', lang));
|
const pageTitle = $derived(t('calendar', lang));
|
||||||
@@ -136,14 +151,23 @@
|
|||||||
// re-applies each rite's default if none is given.
|
// re-applies each rite's default if none is given.
|
||||||
function riteHref(r: 'novus' | 'vetus') {
|
function riteHref(r: 'novus' | 'vetus') {
|
||||||
const dd = selectedIso.slice(8, 10);
|
const dd = selectedIso.slice(8, 10);
|
||||||
return `${calendarBase}/${r}/${year}/${pad(month + 1)}/${dd}`;
|
return resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/[[yyyy=calendarYear]]/[[mm=calendarMonth]]/[[dd=calendarDay]]', {
|
||||||
|
faithLang: page.params.faithLang!,
|
||||||
|
calendar: page.params.calendar!,
|
||||||
|
rite: r,
|
||||||
|
yyyy: String(year),
|
||||||
|
mm: pad(month + 1),
|
||||||
|
dd
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDioceseChange(e: Event) {
|
function onDioceseChange(e: Event) {
|
||||||
const next = (e.currentTarget as HTMLSelectElement).value;
|
const next = (e.currentTarget as HTMLSelectElement).value;
|
||||||
const def = rite === 'vetus' ? DEFAULT_DIOCESE_1962 : DEFAULT_DIOCESE_1969;
|
const def = rite === 'vetus' ? DEFAULT_DIOCESE_1962 : DEFAULT_DIOCESE_1969;
|
||||||
const dd = selectedIso.slice(8, 10);
|
const dd = selectedIso.slice(8, 10);
|
||||||
const path = `${riteBase}/${year}/${pad(month + 1)}/${dd}`;
|
const path = resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/[[yyyy=calendarYear]]/[[mm=calendarMonth]]/[[dd=calendarDay]]', {
|
||||||
|
...riteParams, yyyy: String(year), mm: pad(month + 1), dd
|
||||||
|
});
|
||||||
goto(next === def ? path : `${path}?diocese=${next}`, { noScroll: true });
|
goto(next === def ? path : `${path}?diocese=${next}`, { noScroll: true });
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+28
-4
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
@@ -37,23 +38,46 @@
|
|||||||
return String(n).padStart(2, '0');
|
return String(n).padStart(2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
const riteBase = $derived(`/${page.params.faithLang}/${page.params.calendar}/${rite}`);
|
|
||||||
const dioceseQuery = $derived.by(() => {
|
const dioceseQuery = $derived.by(() => {
|
||||||
const q = page.url.searchParams.get('diocese');
|
const q = page.url.searchParams.get('diocese');
|
||||||
return q ? `?diocese=${q}` : '';
|
return q ? `?diocese=${q}` : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const riteParams = $derived({
|
||||||
|
faithLang: page.params.faithLang!,
|
||||||
|
calendar: page.params.calendar!,
|
||||||
|
rite
|
||||||
|
});
|
||||||
|
|
||||||
// Back link: return to the month view for the day's month
|
// Back link: return to the month view for the day's month
|
||||||
const backHref = $derived(`${riteBase}/${year}/${pad(month + 1)}${dioceseQuery}`);
|
const backHref = $derived(
|
||||||
|
resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/[[yyyy=calendarYear]]/[[mm=calendarMonth]]', {
|
||||||
|
...riteParams,
|
||||||
|
yyyy: String(year),
|
||||||
|
mm: pad(month + 1)
|
||||||
|
}) + dioceseQuery
|
||||||
|
);
|
||||||
// Day cell in the month grid is the same URL, kept the selection by including dd
|
// Day cell in the month grid is the same URL, kept the selection by including dd
|
||||||
const dayInMonthHref = $derived(`${riteBase}/${year}/${pad(month + 1)}/${pad(data.day)}${dioceseQuery}`);
|
const dayInMonthHref = $derived(
|
||||||
|
resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/[[yyyy=calendarYear]]/[[mm=calendarMonth]]/[[dd=calendarDay]]', {
|
||||||
|
...riteParams,
|
||||||
|
yyyy: String(year),
|
||||||
|
mm: pad(month + 1),
|
||||||
|
dd: pad(data.day)
|
||||||
|
}) + dioceseQuery
|
||||||
|
);
|
||||||
|
|
||||||
// Next/prev day navigation inside the detail view
|
// Next/prev day navigation inside the detail view
|
||||||
function shiftDay(days: number): string {
|
function shiftDay(days: number): string {
|
||||||
const [y, m, d] = iso.split('-').map(Number);
|
const [y, m, d] = iso.split('-').map(Number);
|
||||||
const next = new Date(y, m - 1, d);
|
const next = new Date(y, m - 1, d);
|
||||||
next.setDate(next.getDate() + days);
|
next.setDate(next.getDate() + days);
|
||||||
return `${riteBase}/detail/${next.getFullYear()}/${pad(next.getMonth() + 1)}/${pad(next.getDate())}${dioceseQuery}`;
|
return resolve('/[faithLang=faithLang]/[calendar=calendarLang]/[rite=calendarRite]/detail/[yyyy=calendarYear]/[mm=calendarMonth]/[dd=calendarDay]', {
|
||||||
|
...riteParams,
|
||||||
|
yyyy: String(next.getFullYear()),
|
||||||
|
mm: pad(next.getMonth() + 1),
|
||||||
|
dd: pad(next.getDate())
|
||||||
|
}) + dioceseQuery;
|
||||||
}
|
}
|
||||||
const prevHref = $derived(shiftDay(-1));
|
const prevHref = $derived(shiftDay(-1));
|
||||||
const nextHref = $derived(shiftDay(1));
|
const nextHref = $derived(shiftDay(1));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { createLanguageContext } from "$lib/contexts/languageContext.js";
|
import { createLanguageContext } from "$lib/contexts/languageContext.js";
|
||||||
@@ -171,7 +172,10 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Base URL for prayer links
|
// Base URL for prayer links
|
||||||
const baseUrl = $derived(isLatin ? '/fides/orationes' : isEnglish ? '/faith/prayers' : '/glaube/gebete');
|
const baseUrl = $derived(resolve('/[faithLang=faithLang]/[prayers=prayersLang]', {
|
||||||
|
faithLang: isLatin ? 'fides' : isEnglish ? 'faith' : 'glaube',
|
||||||
|
prayers: isLatin ? 'orationes' : isEnglish ? 'prayers' : 'gebete'
|
||||||
|
}));
|
||||||
|
|
||||||
// Get prayer name by ID (reactive based on language)
|
// Get prayer name by ID (reactive based on language)
|
||||||
/** @param {string} id */
|
/** @param {string} id */
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { asset } from '$app/paths';
|
||||||
let { pos, BEAD_SPACING, DECADE_OFFSET, activeSection, decadeCounters } = $props();
|
let { pos, BEAD_SPACING, DECADE_OFFSET, activeSection, decadeCounters } = $props();
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
<circle cx="25" cy={pos.lbead2} r="15" class="large-bead" class:active-large-bead={activeSection === 'lbead2'} data-section="lbead2" />
|
<circle cx="25" cy={pos.lbead2} r="15" class="large-bead" class:active-large-bead={activeSection === 'lbead2'} data-section="lbead2" />
|
||||||
|
|
||||||
<!-- Benedictus Medal -->
|
<!-- Benedictus Medal -->
|
||||||
<image class="medal" href="/glaube/benedictus.svg" x="5" y={pos.lbead2 + 25} width="40" height="40" />
|
<image class="medal" href={asset('/glaube/benedictus.svg')} x="5" y={pos.lbead2 + 25} width="40" height="40" />
|
||||||
|
|
||||||
<!-- 5 Decades -->
|
<!-- 5 Decades -->
|
||||||
{#each [1, 2, 3, 4, 5] as d (d)}
|
{#each [1, 2, 3, 4, 5] as d (d)}
|
||||||
@@ -125,7 +126,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<image class="medal" href="/glaube/benedictus.svg" x="5" y={pos.secret5 + DECADE_OFFSET + 9 * BEAD_SPACING + 15} width="40" height="40" />
|
<image class="medal" href={asset('/glaube/benedictus.svg')} x="5" y={pos.secret5 + DECADE_OFFSET + 9 * BEAD_SPACING + 15} width="40" height="40" />
|
||||||
<!-- Final transition: Gloria + Fatima -->
|
<!-- Final transition: Gloria + Fatima -->
|
||||||
<circle cx="25" cy={pos.final_transition} r="15" class="large-bead" class:active-large-bead={activeSection === 'final_transition'} data-section="final_transition" />
|
<circle cx="25" cy={pos.final_transition} r="15" class="large-bead" class:active-large-bead={activeSection === 'final_transition'} data-section="final_transition" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import LinksGrid from '$lib/components/LinksGrid.svelte';
|
import LinksGrid from '$lib/components/LinksGrid.svelte';
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
const isGerman = $derived(data.lang === 'de');
|
const isGerman = $derived(data.lang === 'de');
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
|
|
||||||
<h1>Katechese</h1>
|
<h1>Katechese</h1>
|
||||||
{#if !isGerman}
|
{#if !isGerman}
|
||||||
<p class="lang-notice">{isLatin ? 'Haec catechesis tantum in ' : 'This catechesis is only available in '}<a href="/glaube/katechese">{isLatin ? 'lingua Germanica' : 'German'}</a>{isLatin ? ' praesto est.' : '.'}</p>
|
<p class="lang-notice">{isLatin ? 'Haec catechesis tantum in ' : 'This catechesis is only available in '}<a href={resolve('/glaube/katechese')}>{isLatin ? 'lingua Germanica' : 'German'}</a>{isLatin ? ' praesto est.' : '.'}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<p>
|
<p>
|
||||||
Aufgearbeitete Lehrinhalte aus dem Glaubenskurs von P. Martin Ramm FSSP.
|
Aufgearbeitete Lehrinhalte aus dem Glaubenskurs von P. Martin Ramm FSSP.
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
<p class="disclaimer">Diese Seiten stellen eine freie Aufbereitung der erhaltenen Unterlagen dar und sind kein offizielles Angebot von P. Martin Ramm oder der FSSP. Etwaige Fehler oder Missverständnisse sind dem Verfasser dieser Seiten anzulasten.</p>
|
<p class="disclaimer">Diese Seiten stellen eine freie Aufbereitung der erhaltenen Unterlagen dar und sind kein offizielles Angebot von P. Martin Ramm oder der FSSP. Etwaige Fehler oder Missverständnisse sind dem Verfasser dieser Seiten anzulasten.</p>
|
||||||
|
|
||||||
<LinksGrid>
|
<LinksGrid>
|
||||||
<a href="/{data.faithLang}/katechese/zehn-gebote">
|
<a href={resolve('/[faithLang=faithLang]/katechese/zehn-gebote', { faithLang: data.faithLang })}>
|
||||||
<svg viewBox="2 14 96 68" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="2 14 96 68" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect x="8" y="20" width="38" height="55" rx="12" ry="12" stroke="currentColor" stroke-width="3" fill="none"/>
|
<rect x="8" y="20" width="38" height="55" rx="12" ry="12" stroke="currentColor" stroke-width="3" fill="none"/>
|
||||||
<rect x="54" y="20" width="38" height="55" rx="12" ry="12" stroke="currentColor" stroke-width="3" fill="none"/>
|
<rect x="54" y="20" width="38" height="55" rx="12" ry="12" stroke="currentColor" stroke-width="3" fill="none"/>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import ArrowDown from '@lucide/svelte/icons/arrow-down';
|
import ArrowDown from '@lucide/svelte/icons/arrow-down';
|
||||||
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
|
import ApologetikToc from '$lib/components/faith/ApologetikToc.svelte';
|
||||||
/** @type {number | string | null} */
|
/** @type {number | string | null} */
|
||||||
let expanded = $state(null);
|
let expanded = $state(null);
|
||||||
const isGerman = $derived($page.url.pathname.startsWith('/glaube'));
|
const isGerman = $derived(page.url.pathname.startsWith('/glaube'));
|
||||||
const isLatin = $derived($page.url.pathname.startsWith('/fides'));
|
const isLatin = $derived(page.url.pathname.startsWith('/fides'));
|
||||||
|
|
||||||
/** @param {number | string} id */
|
/** @param {number | string} id */
|
||||||
function toggle(id) {
|
function toggle(id) {
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if !isGerman}
|
{#if !isGerman}
|
||||||
<p class="lang-notice">{isLatin ? 'Haec catechesis tantum in ' : 'This catechesis is only available in '}<a href="/glaube/katechese/zehn-gebote">{isLatin ? 'lingua Germanica' : 'German'}</a>{isLatin ? ' praesto est.' : '.'}</p>
|
<p class="lang-notice">{isLatin ? 'Haec catechesis tantum in ' : 'This catechesis is only available in '}<a href={resolve('/glaube/katechese/zehn-gebote')}>{isLatin ? 'lingua Germanica' : 'German'}</a>{isLatin ? ' praesto est.' : '.'}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<section id="ursprung">
|
<section id="ursprung">
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import SectionError from '$lib/components/SectionError.svelte';
|
import SectionError from '$lib/components/SectionError.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
|
|
||||||
let recipeLang = $derived($page.params.recipeLang);
|
let recipeLang = $derived(page.params.recipeLang!);
|
||||||
let isEnglish = $derived(recipeLang === 'recipes');
|
let isEnglish = $derived(recipeLang === 'recipes');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SectionError
|
<SectionError
|
||||||
sectionHref="/{recipeLang}"
|
sectionHref={resolve('/[recipeLang=recipeLang]', { recipeLang })}
|
||||||
sectionLabel={{ en: 'Recipes', de: 'Rezepte' }}
|
sectionLabel={{ en: 'Recipes', de: 'Rezepte' }}
|
||||||
{isEnglish}
|
{isEnglish}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import '$lib/css/recipe-links.css';
|
import '$lib/css/recipe-links.css';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { onNavigate } from '$app/navigation';
|
import { onNavigate } from '$app/navigation';
|
||||||
import Header from '$lib/components/Header.svelte'
|
import Header from '$lib/components/Header.svelte'
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ const labels = $derived({
|
|||||||
|
|
||||||
/** @param {string} path */
|
/** @param {string} path */
|
||||||
function isActive(path) {
|
function isActive(path) {
|
||||||
const currentPath = $page.url.pathname;
|
const currentPath = page.url.pathname;
|
||||||
// Exact match for recipe lang root
|
// Exact match for recipe lang root
|
||||||
if (path === `/${data.recipeLang}`) {
|
if (path === `/${data.recipeLang}`) {
|
||||||
return currentPath === `/${data.recipeLang}` || currentPath === `/${data.recipeLang}/`;
|
return currentPath === `/${data.recipeLang}` || currentPath === `/${data.recipeLang}/`;
|
||||||
@@ -80,14 +81,14 @@ function isActive(path) {
|
|||||||
<Header>
|
<Header>
|
||||||
{#snippet links()}
|
{#snippet links()}
|
||||||
<ul class=site_header>
|
<ul class=site_header>
|
||||||
<li style="--active-fill: var(--nord9)"><a href="/{data.recipeLang}" class:active={isActive(`/${data.recipeLang}`)} title={labels.allRecipes}><BookOpen size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.allRecipes}</span></a></li>
|
<li style="--active-fill: var(--nord9)"><a href={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} class:active={isActive(`/${data.recipeLang}`)} title={labels.allRecipes}><BookOpen size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.allRecipes}</span></a></li>
|
||||||
{#if user}
|
{#if user}
|
||||||
<li style="--active-fill: var(--nord11)"><a href="/{data.recipeLang}/favorites" class:active={isActive(`/${data.recipeLang}/favorites`)} title={labels.favorites}><Heart size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.favorites}</span></a></li>
|
<li style="--active-fill: var(--nord11)"><a href={resolve('/[recipeLang=recipeLang]/favorites', { recipeLang: data.recipeLang })} class:active={isActive(`/${data.recipeLang}/favorites`)} title={labels.favorites}><Heart size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.favorites}</span></a></li>
|
||||||
{/if}
|
{/if}
|
||||||
<li style="--active-fill: var(--nord14)"><a href="/{data.recipeLang}/season" class:active={isActive(`/${data.recipeLang}/season`)} title={labels.inSeason}><Leaf size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.inSeason}</span></a></li>
|
<li style="--active-fill: var(--nord14)"><a href={resolve('/[recipeLang=recipeLang]/season', { recipeLang: data.recipeLang })} class:active={isActive(`/${data.recipeLang}/season`)} title={labels.inSeason}><Leaf size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.inSeason}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord9)"><a href="/{data.recipeLang}/category" class:active={isActive(`/${data.recipeLang}/category`)} title={labels.category}><LayoutGrid size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.category}</span></a></li>
|
<li style="--active-fill: var(--nord9)"><a href={resolve('/[recipeLang=recipeLang]/category', { recipeLang: data.recipeLang })} class:active={isActive(`/${data.recipeLang}/category`)} title={labels.category}><LayoutGrid size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.category}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord15)"><a href="/{data.recipeLang}/icon" class:active={isActive(`/${data.recipeLang}/icon`)} title={labels.icon}><Palette size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.icon}</span></a></li>
|
<li style="--active-fill: var(--nord15)"><a href={resolve('/[recipeLang=recipeLang]/icon', { recipeLang: data.recipeLang })} class:active={isActive(`/${data.recipeLang}/icon`)} title={labels.icon}><Palette size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.icon}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord13)"><a href="/{data.recipeLang}/tag" class:active={isActive(`/${data.recipeLang}/tag`)} title={labels.keywords}><Tag size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.keywords}</span></a></li>
|
<li style="--active-fill: var(--nord13)"><a href={resolve('/[recipeLang=recipeLang]/tag', { recipeLang: data.recipeLang })} class:active={isActive(`/${data.recipeLang}/tag`)} title={labels.keywords}><Tag size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.keywords}</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import AddButton from '$lib/components/AddButton.svelte';
|
import AddButton from '$lib/components/AddButton.svelte';
|
||||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||||
@@ -367,7 +368,7 @@
|
|||||||
<div class="hero-text">
|
<div class="hero-text">
|
||||||
<h1>{labels.title}</h1>
|
<h1>{labels.title}</h1>
|
||||||
<p class="subheading">{labels.subheading}</p>
|
<p class="subheading">{labels.subheading}</p>
|
||||||
<a href="/{data.recipeLang}/{heroRecipe.short_name}" class="hero-featured"
|
<a href={resolve('/[recipeLang=recipeLang]/[name]', { recipeLang: data.recipeLang, name: heroRecipe.short_name })} class="hero-featured"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
const img = document.querySelector('.hero-img') as HTMLElement | null;
|
const img = document.querySelector('.hero-img') as HTMLElement | null;
|
||||||
if (img) (img.style as any).viewTransitionName = `recipe-${heroRecipe.short_name}-img`;
|
if (img) (img.style as any).viewTransitionName = `recipe-${heroRecipe.short_name}-img`;
|
||||||
@@ -435,7 +436,7 @@
|
|||||||
isFavorite={recipe.isFavorite}
|
isFavorite={recipe.isFavorite}
|
||||||
showFavoriteIndicator={!!data.session?.user}
|
showFavoriteIndicator={!!data.session?.user}
|
||||||
loading_strat={i < 12 ? "eager" : "lazy"}
|
loading_strat={i < 12 ? "eager" : "lazy"}
|
||||||
routePrefix="/{data.recipeLang}"
|
routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -448,7 +449,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{#if !isEnglish}
|
{#if !isEnglish}
|
||||||
<AddButton href="/rezepte/add"></AddButton>
|
<AddButton href={resolve('/rezepte/add')}></AddButton>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="hero-fallback">
|
<div class="hero-fallback">
|
||||||
@@ -465,7 +466,7 @@
|
|||||||
isFavorite={recipe.isFavorite}
|
isFavorite={recipe.isFavorite}
|
||||||
showFavoriteIndicator={!!data.session?.user}
|
showFavoriteIndicator={!!data.session?.user}
|
||||||
loading_strat={i < 12 ? "eager" : "lazy"}
|
loading_strat={i < 12 ? "eager" : "lazy"}
|
||||||
routePrefix="/{data.recipeLang}"
|
routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -475,6 +476,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !isEnglish}
|
{#if !isEnglish}
|
||||||
<AddButton href="/rezepte/add"></AddButton>
|
<AddButton href={resolve('/rezepte/add')}></AddButton>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import ErrorView from '$lib/components/ErrorView.svelte';
|
import ErrorView from '$lib/components/ErrorView.svelte';
|
||||||
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
|
import { getErrorTitle, getErrorDescription, errorLabels, pick } from '$lib/js/errorStrings';
|
||||||
|
|
||||||
let status = $derived($page.status);
|
let status = $derived(page.status);
|
||||||
let error = $derived($page.error as any);
|
let error = $derived(page.error as any);
|
||||||
let recipeLang = $derived($page.params.recipeLang);
|
let recipeLang = $derived(page.params.recipeLang);
|
||||||
let recipeName = $derived($page.params.name);
|
let recipeName = $derived(page.params.name);
|
||||||
let user = $derived($page.data?.session?.user);
|
let user = $derived(page.data?.session?.user);
|
||||||
|
|
||||||
let isEnglishRoute = $derived(recipeLang === 'recipes');
|
let isEnglishRoute = $derived(recipeLang === 'recipes');
|
||||||
let isEnglish = $derived(error?.lang === 'en' || isEnglishRoute);
|
let isEnglish = $derived(error?.lang === 'en' || isEnglishRoute);
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
: error?.details
|
: error?.details
|
||||||
);
|
);
|
||||||
|
|
||||||
let recipesHref = $derived(isEnglishRoute ? '/recipes' : '/rezepte');
|
let recipesHref = $derived(resolve('/[recipeLang=recipeLang]', { recipeLang: isEnglishRoute ? 'recipes' : 'rezepte' }));
|
||||||
|
|
||||||
function viewGermanRecipe() { goto(`/rezepte/${recipeName}`); }
|
function viewGermanRecipe() { goto(`/rezepte/${recipeName}`); }
|
||||||
function editToTranslate() { goto(`/rezepte/edit/${recipeName}`); }
|
function editToTranslate() { goto(`/rezepte/edit/${recipeName}`); }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
export const multiplier = writable(0);
|
export const multiplier = writable(0);
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
import RecipeNote from '$lib/components/recipes/RecipeNote.svelte';
|
import RecipeNote from '$lib/components/recipes/RecipeNote.svelte';
|
||||||
import FavoriteButton from '$lib/components/FavoriteButton.svelte';
|
import FavoriteButton from '$lib/components/FavoriteButton.svelte';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
|
import { recipeTranslationStore } from '$lib/stores/recipeTranslation.svelte';
|
||||||
|
|
||||||
let { data } = $props<{ data: PageData }>();
|
let { data } = $props<{ data: PageData }>();
|
||||||
|
|
||||||
@@ -318,7 +319,7 @@ h2{
|
|||||||
<div class=tags>
|
<div class=tags>
|
||||||
<h2>{labels.season}</h2>
|
<h2>{labels.season}</h2>
|
||||||
{#each season_iv as season}
|
{#each season_iv as season}
|
||||||
<a class="g-tag" href="/{data.recipeLang}/season/{season[0]}">
|
<a class="g-tag" href={resolve('/[recipeLang=recipeLang]/season/[month]', { recipeLang: data.recipeLang, month: season[0] })}>
|
||||||
{#if season[0]}
|
{#if season[0]}
|
||||||
{months[season[0] - 1]}
|
{months[season[0] - 1]}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -333,7 +334,7 @@ h2{
|
|||||||
<h2 class="section-label">{labels.keywords}</h2>
|
<h2 class="section-label">{labels.keywords}</h2>
|
||||||
<div class="tags center">
|
<div class="tags center">
|
||||||
{#each data.tags as tag}
|
{#each data.tags as tag}
|
||||||
<a class="g-tag" href="/{data.recipeLang}/tag/{tag}">{tag}</a>
|
<a class="g-tag" href={resolve('/[recipeLang=recipeLang]/tag/[tag]', { recipeLang: data.recipeLang, tag })}>{tag}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -361,4 +362,4 @@ h2{
|
|||||||
</div>
|
</div>
|
||||||
</TitleImgParallax>
|
</TitleImgParallax>
|
||||||
|
|
||||||
<EditButton href="/rezepte/edit/{data.germanShortName}"></EditButton>
|
<EditButton href={resolve('/[recipeLang=recipeLang]/edit/[name]', { recipeLang: 'rezepte', name: data.germanShortName })}></EditButton>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||||
|
|
||||||
@@ -161,7 +162,7 @@ h1 {
|
|||||||
<CompactCard
|
<CompactCard
|
||||||
{recipe}
|
{recipe}
|
||||||
{current_month}
|
{current_month}
|
||||||
routePrefix="/{data.recipeLang}"
|
routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })}
|
||||||
/>
|
/>
|
||||||
<div class="translation-badge {recipe.translationStatus || 'none'}">
|
<div class="translation-badge {recipe.translationStatus || 'none'}">
|
||||||
{#if recipe.translationStatus === 'pending'}
|
{#if recipe.translationStatus === 'pending'}
|
||||||
@@ -179,7 +180,7 @@ h1 {
|
|||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>Alle Rezepte sind übersetzt!</p>
|
<p>Alle Rezepte sind übersetzt!</p>
|
||||||
<p style="font-size: 1rem; margin-top: 1rem;">
|
<p style="font-size: 1rem; margin-top: 1rem;">
|
||||||
<a href="/{data.recipeLang}">Zurück zu den Rezepten</a>
|
<a href={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })}>Zurück zu den Rezepten</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
let { data } = $props<{ data: PageData }>();
|
let { data } = $props<{ data: PageData }>();
|
||||||
import TagCloud from '$lib/components/TagCloud.svelte';
|
import TagCloud from '$lib/components/TagCloud.svelte';
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<TagCloud>
|
<TagCloud>
|
||||||
{#each data.categories as tag}
|
{#each data.categories as tag}
|
||||||
<TagBall {tag} ref="/{data.recipeLang}/category">
|
<TagBall {tag} ref={resolve('/[recipeLang=recipeLang]/category', { recipeLang: data.recipeLang })}>
|
||||||
</TagBall>
|
</TagBall>
|
||||||
{/each}
|
{/each}
|
||||||
</TagCloud>
|
</TagCloud>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import type { BriefRecipeType } from '$types/types';
|
import type { BriefRecipeType } from '$types/types';
|
||||||
import Search from '$lib/components/recipes/Search.svelte';
|
import Search from '$lib/components/recipes/Search.svelte';
|
||||||
@@ -44,6 +45,6 @@
|
|||||||
<Search category={data.category} lang={data.lang} recipes={data.allRecipes} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}></Search>
|
<Search category={data.category} lang={data.lang} recipes={data.allRecipes} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}></Search>
|
||||||
<div class="recipe-grid">
|
<div class="recipe-grid">
|
||||||
{#each rand_array(displayRecipes) as recipe (recipe._id)}
|
{#each rand_array(displayRecipes) as recipe (recipe._id)}
|
||||||
<CompactCard {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}" />
|
<CompactCard {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||||
import Search from '$lib/components/recipes/Search.svelte';
|
import Search from '$lib/components/recipes/Search.svelte';
|
||||||
@@ -91,7 +92,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="to-try-link"><a href="/{data.recipeLang}/to-try">{labels.toTry} →</a></p>
|
<p class="to-try-link"><a href={resolve('/[recipeLang=recipeLang]/to-try', { recipeLang: data.recipeLang })}>{labels.toTry} →</a></p>
|
||||||
|
|
||||||
<Search favoritesOnly={true} lang={data.lang} recipes={data.favorites} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}></Search>
|
<Search favoritesOnly={true} lang={data.lang} recipes={data.favorites} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}></Search>
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@
|
|||||||
{:else if filteredFavorites.length > 0}
|
{:else if filteredFavorites.length > 0}
|
||||||
<div class="recipe-grid">
|
<div class="recipe-grid">
|
||||||
{#each filteredFavorites as recipe (recipe._id)}
|
{#each filteredFavorites as recipe (recipe._id)}
|
||||||
<CompactCard {recipe} {current_month} isFavorite={true} showFavoriteIndicator={true} routePrefix="/{data.recipeLang}" />
|
<CompactCard {recipe} {current_month} isFavorite={true} showFavoriteIndicator={true} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else if data.favorites.length > 0}
|
{:else if data.favorites.length > 0}
|
||||||
@@ -110,6 +111,6 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>{labels.emptyState1}</p>
|
<p>{labels.emptyState1}</p>
|
||||||
<p><a href="/{data.recipeLang}">{labels.emptyState2}</a></p>
|
<p><a href={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })}>{labels.emptyState2}</a></p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
let { data } = $props<{ data: PageData }>();
|
let { data } = $props<{ data: PageData }>();
|
||||||
|
|
||||||
@@ -79,6 +80,6 @@
|
|||||||
</style>
|
</style>
|
||||||
<div class=flex>
|
<div class=flex>
|
||||||
{#each data.icons as icon}
|
{#each data.icons as icon}
|
||||||
<a href="/{data.recipeLang}/icon/{icon}">{icon}</a>
|
<a href={resolve('/[recipeLang=recipeLang]/icon/[icon]', { recipeLang: data.recipeLang, icon })}>{icon}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import IconLayout from '$lib/components/recipes/IconLayout.svelte';
|
import IconLayout from '$lib/components/recipes/IconLayout.svelte';
|
||||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||||
@@ -31,11 +32,11 @@
|
|||||||
<title>{data.icon} - {siteTitle}</title>
|
<title>{data.icon} - {siteTitle}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<IconLayout icons={data.icons} active_icon={data.icon} routePrefix="/{data.recipeLang}" lang={data.lang} recipes={data.season} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}>
|
<IconLayout icons={data.icons} active_icon={data.icon} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} lang={data.lang} recipes={data.season} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}>
|
||||||
{#snippet recipesSlot()}
|
{#snippet recipesSlot()}
|
||||||
<div class="recipe-grid">
|
<div class="recipe-grid">
|
||||||
{#each rand_array(filteredRecipes) as recipe (recipe._id)}
|
{#each rand_array(filteredRecipes) as recipe (recipe._id)}
|
||||||
<CompactCard {recipe} icon_override={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}" />
|
<CompactCard {recipe} icon_override={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ let { data } = $props();
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Only proceed if we're actually offline or have a redirect target
|
// Only proceed if we're actually offline or have a redirect target
|
||||||
// This prevents issues if someone navigates here directly while online
|
// This prevents issues if someone navigates here directly while online
|
||||||
const targetUrl = $page.url.searchParams.get('redirect');
|
const targetUrl = page.url.searchParams.get('redirect');
|
||||||
|
|
||||||
if (!targetUrl) {
|
if (!targetUrl) {
|
||||||
// No redirect target - just go to main recipe list
|
// No redirect target - just go to main recipe list
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import Search from '$lib/components/recipes/Search.svelte';
|
import Search from '$lib/components/recipes/Search.svelte';
|
||||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||||
@@ -112,7 +113,7 @@
|
|||||||
{#if displayedRecipes.length > 0}
|
{#if displayedRecipes.length > 0}
|
||||||
<div class="recipe-grid">
|
<div class="recipe-grid">
|
||||||
{#each displayedRecipes as recipe (recipe._id)}
|
{#each displayedRecipes as recipe (recipe._id)}
|
||||||
<CompactCard {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={true} routePrefix="/{data.recipeLang}" />
|
<CompactCard {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={true} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else if (data.query || hasActiveSearch) && !data.error}
|
{:else if (data.query || hasActiveSearch) && !data.error}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import SeasonLayout from '$lib/components/recipes/SeasonLayout.svelte'
|
import SeasonLayout from '$lib/components/recipes/SeasonLayout.svelte'
|
||||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||||
@@ -38,11 +39,11 @@
|
|||||||
<title>{labels.title} - {labels.siteTitle}</title>
|
<title>{labels.title} - {labels.siteTitle}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<SeasonLayout active_index={current_month-1} {months} routePrefix="/{data.recipeLang}" lang={data.lang} recipes={data.season} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}>
|
<SeasonLayout active_index={current_month-1} {months} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} lang={data.lang} recipes={data.season} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}>
|
||||||
{#snippet recipesSlot()}
|
{#snippet recipesSlot()}
|
||||||
<div class="recipe-grid">
|
<div class="recipe-grid">
|
||||||
{#each rand_array(filteredRecipes) as recipe (recipe._id)}
|
{#each rand_array(filteredRecipes) as recipe (recipe._id)}
|
||||||
<CompactCard {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}" />
|
<CompactCard {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import SeasonLayout from '$lib/components/recipes/SeasonLayout.svelte';
|
import SeasonLayout from '$lib/components/recipes/SeasonLayout.svelte';
|
||||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||||
@@ -36,11 +37,11 @@
|
|||||||
<title>{currentMonth} - {siteTitle}</title>
|
<title>{currentMonth} - {siteTitle}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<SeasonLayout active_index={data.month -1} {months} routePrefix="/{data.recipeLang}" lang={data.lang} recipes={data.season} onSearchResults={handleSearchResults}>
|
<SeasonLayout active_index={data.month -1} {months} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} lang={data.lang} recipes={data.season} onSearchResults={handleSearchResults}>
|
||||||
{#snippet recipesSlot()}
|
{#snippet recipesSlot()}
|
||||||
<div class="recipe-grid">
|
<div class="recipe-grid">
|
||||||
{#each rand_array(filteredRecipes) as recipe (recipe._id)}
|
{#each rand_array(filteredRecipes) as recipe (recipe._id)}
|
||||||
<CompactCard {recipe} icon_override={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}" />
|
<CompactCard {recipe} icon_override={true} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
let { data } = $props<{ data: PageData }>();
|
let { data } = $props<{ data: PageData }>();
|
||||||
import TagCloud from '$lib/components/TagCloud.svelte';
|
import TagCloud from '$lib/components/TagCloud.svelte';
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<TagCloud>
|
<TagCloud>
|
||||||
{#each filteredTags as tag}
|
{#each filteredTags as tag}
|
||||||
<TagBall {tag} ref="/{data.recipeLang}/tag">
|
<TagBall {tag} ref={resolve('/[recipeLang=recipeLang]/tag', { recipeLang: data.recipeLang })}>
|
||||||
</TagBall>
|
</TagBall>
|
||||||
{/each}
|
{/each}
|
||||||
</TagCloud>
|
</TagCloud>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import type { BriefRecipeType } from '$types/types';
|
import type { BriefRecipeType } from '$types/types';
|
||||||
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
import CompactCard from '$lib/components/recipes/CompactCard.svelte';
|
||||||
@@ -44,6 +45,6 @@
|
|||||||
<Search tag={data.tag} lang={data.lang} recipes={data.allRecipes} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}></Search>
|
<Search tag={data.tag} lang={data.lang} recipes={data.allRecipes} isLoggedIn={!!data.session?.user} onSearchResults={handleSearchResults}></Search>
|
||||||
<div class="recipe-grid">
|
<div class="recipe-grid">
|
||||||
{#each rand_array(displayRecipes) as recipe (recipe._id)}
|
{#each rand_array(displayRecipes) as recipe (recipe._id)}
|
||||||
<CompactCard {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix="/{data.recipeLang}" />
|
<CompactCard {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={!!data.session?.user} routePrefix={resolve('/[recipeLang=recipeLang]', { recipeLang: data.recipeLang })} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import AddButton from '$lib/components/AddButton.svelte';
|
import AddButton from '$lib/components/AddButton.svelte';
|
||||||
import Converter from './Converter.svelte';
|
import Converter from './Converter.svelte';
|
||||||
@@ -64,4 +65,4 @@ h1{
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AddButton href="/rezepte/add"></AddButton>
|
<AddButton href={resolve('/rezepte/add')}></AddButton>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import SectionError from '$lib/components/SectionError.svelte';
|
import SectionError from '$lib/components/SectionError.svelte';
|
||||||
import { detectFitnessLang } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
let lang = $derived(detectFitnessLang($page.url.pathname));
|
let lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
let isEnglish = $derived(lang === 'en');
|
let isEnglish = $derived(lang === 'en');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SectionError
|
<SectionError
|
||||||
sectionHref={isEnglish ? '/fitness/workout' : '/fitness/training'}
|
sectionHref={resolve('/fitness/[workout=fitnessWorkout]', { workout: isEnglish ? 'workout' : 'training' })}
|
||||||
sectionLabel={{ en: 'Fitness', de: 'Fitness' }}
|
sectionLabel={{ en: 'Fitness', de: 'Fitness' }}
|
||||||
{isEnglish}
|
{isEnglish}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
import UserHeader from '$lib/components/UserHeader.svelte';
|
import UserHeader from '$lib/components/UserHeader.svelte';
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
const workout = getWorkout();
|
const workout = getWorkout();
|
||||||
const sync = getWorkoutSync();
|
const sync = getWorkoutSync();
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const s = $derived(fitnessSlugs(lang));
|
const s = $derived(fitnessSlugs(lang));
|
||||||
const labels = $derived(fitnessLabels(lang));
|
const labels = $derived(fitnessLabels(lang));
|
||||||
|
|
||||||
@@ -62,22 +63,22 @@
|
|||||||
|
|
||||||
/** @param {string} path */
|
/** @param {string} path */
|
||||||
function isActive(path) {
|
function isActive(path) {
|
||||||
const currentPath = $page.url.pathname;
|
const currentPath = page.url.pathname;
|
||||||
return currentPath.startsWith(path);
|
return currentPath.startsWith(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const activePath = $derived(`/fitness/${s.workout}/${s.active}`);
|
const activePath = $derived(`/fitness/${s.workout}/${s.active}`);
|
||||||
const isOnActivePage = $derived($page.url.pathname === activePath);
|
const isOnActivePage = $derived(page.url.pathname === activePath);
|
||||||
const isNutritionPage = $derived(
|
const isNutritionPage = $derived(
|
||||||
$page.url.pathname.startsWith(`/fitness/${s.nutrition}`) &&
|
page.url.pathname.startsWith(`/fitness/${s.nutrition}`) &&
|
||||||
!$page.url.pathname.startsWith(`/fitness/${s.nutrition}/food`) &&
|
!page.url.pathname.startsWith(`/fitness/${s.nutrition}/food`) &&
|
||||||
!$page.url.pathname.startsWith(`/fitness/${s.nutrition}/meals`)
|
!page.url.pathname.startsWith(`/fitness/${s.nutrition}/meals`)
|
||||||
);
|
);
|
||||||
const isMeasureIndex = $derived(
|
const isMeasureIndex = $derived(
|
||||||
/^\/fitness\/(check-in|erfassung)\/?$/.test($page.url.pathname)
|
/^\/fitness\/(check-in|erfassung)\/?$/.test(page.url.pathname)
|
||||||
);
|
);
|
||||||
const isExercisesIndex = $derived(
|
const isExercisesIndex = $derived(
|
||||||
/^\/fitness\/(exercises|uebungen)\/?$/.test($page.url.pathname)
|
/^\/fitness\/(exercises|uebungen)\/?$/.test(page.url.pathname)
|
||||||
);
|
);
|
||||||
/** @param {number} secs */
|
/** @param {number} secs */
|
||||||
function formatElapsed(secs) {
|
function formatElapsed(secs) {
|
||||||
@@ -90,12 +91,12 @@
|
|||||||
<Header>
|
<Header>
|
||||||
{#snippet links()}
|
{#snippet links()}
|
||||||
<ul class="site_header">
|
<ul class="site_header">
|
||||||
<li><a href="/fitness/{s.stats}" class:active={isActive(`/fitness/${s.stats}`)}><BarChart3 size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.stats}</span></a></li>
|
<li><a href={resolve('/fitness/[stats=fitnessStats]', { stats: s.stats })} class:active={isActive(`/fitness/${s.stats}`)}><BarChart3 size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.stats}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord13)"><a href="/fitness/{s.history}" class:active={isActive(`/fitness/${s.history}`)}><Clock size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.history}</span></a></li>
|
<li style="--active-fill: var(--nord13)"><a href={resolve('/fitness/[history=fitnessHistory]', { history: s.history })} class:active={isActive(`/fitness/${s.history}`)}><Clock size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.history}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord8)"><a href="/fitness/{s.workout}" class:active={isActive(`/fitness/${s.workout}`)}><Dumbbell size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.workout}</span></a></li>
|
<li style="--active-fill: var(--nord8)"><a href={resolve('/fitness/[workout=fitnessWorkout]', { workout: s.workout })} class:active={isActive(`/fitness/${s.workout}`)}><Dumbbell size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.workout}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord14)"><a href="/fitness/{s.exercises}" class:active={isActive(`/fitness/${s.exercises}`)}><ListChecks size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.exercises}</span></a></li>
|
<li style="--active-fill: var(--nord14)"><a href={resolve('/fitness/[exercises=fitnessExercises]', { exercises: s.exercises })} class:active={isActive(`/fitness/${s.exercises}`)}><ListChecks size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.exercises}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord12)"><a href="/fitness/{s.measure}" class:active={isActive(`/fitness/${s.measure}`)}><NotebookPen size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.measure}</span></a></li>
|
<li style="--active-fill: var(--nord12)"><a href={resolve('/fitness/[checkin=fitnessCheckIn]', { checkin: s.measure })} class:active={isActive(`/fitness/${s.measure}`)}><NotebookPen size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.measure}</span></a></li>
|
||||||
<li style="--active-fill: var(--nord15)"><a href="/fitness/{s.nutrition}" class:active={isActive(`/fitness/${s.nutrition}`)}><UtensilsCrossed size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.nutrition}</span></a></li>
|
<li style="--active-fill: var(--nord15)"><a href={resolve('/fitness/[nutrition=fitnessNutrition]', { nutrition: s.nutrition })} class:active={isActive(`/fitness/${s.nutrition}`)}><UtensilsCrossed size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">{labels.nutrition}</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import Pencil from '@lucide/svelte/icons/pencil';
|
import Pencil from '@lucide/svelte/icons/pencil';
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
`viewBox="0 ${BP_VIEW_TOP} 660.46 ${BP_VIEW_H}"`
|
`viewBox="0 ${BP_VIEW_TOP} 660.46 ${BP_VIEW_H}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
|
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
|
||||||
import { getWorkout } from '$lib/js/workout.svelte';
|
import { getWorkout } from '$lib/js/workout.svelte';
|
||||||
import PeriodTracker from '$lib/components/fitness/PeriodTracker.svelte';
|
import PeriodTracker from '$lib/components/fitness/PeriodTracker.svelte';
|
||||||
@@ -541,7 +542,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a class="bp-card" href="/fitness/{checkinSlug}/body-parts">
|
<a class="bp-card" href={resolve('/fitness/[checkin=fitnessCheckIn]/body-parts', { checkin: checkinSlug })}>
|
||||||
<div class="bp-figure" aria-hidden="true">
|
<div class="bp-figure" aria-hidden="true">
|
||||||
<div class="muscle-base">{@html bpFrontSvg}</div>
|
<div class="muscle-base">{@html bpFrontSvg}</div>
|
||||||
<svg class="dot-overlay" viewBox="0 {BP_VIEW_TOP} 660.46 {BP_VIEW_H}" preserveAspectRatio="xMidYMid meet">
|
<svg class="dot-overlay" viewBox="0 {BP_VIEW_TOP} 660.46 {BP_VIEW_H}" preserveAspectRatio="xMidYMid meet">
|
||||||
@@ -633,7 +634,7 @@
|
|||||||
<span class="edit-unit">%</span>
|
<span class="edit-unit">%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="edit-actions">
|
<div class="edit-actions">
|
||||||
<a class="edit-more" href="/fitness/{checkinSlug}/edit/{m._id}" aria-label={t('edit_measurement', lang)}>
|
<a class="edit-more" href={resolve('/fitness/[checkin=fitnessCheckIn]/edit/[id]', { checkin: checkinSlug, id: m._id })} aria-label={t('edit_measurement', lang)}>
|
||||||
<Pencil size={11} />
|
<Pencil size={11} />
|
||||||
<span class="edit-more-label">{lang === 'en' ? 'Edit all fields' : 'Alle Felder bearbeiten'}</span>
|
<span class="edit-more-label">{lang === 'en' ? 'Edit all fields' : 'Alle Felder bearbeiten'}</span>
|
||||||
<ChevronRight size={11} />
|
<ChevronRight size={11} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import Minus from '@lucide/svelte/icons/minus';
|
import Minus from '@lucide/svelte/icons/minus';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
|
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
|
||||||
|
|
||||||
/** @typedef {{ key: string, labelKey: string, img: string | null, paired: boolean, tipKey: string, dbSingle?: string, dbLeft?: string, dbRight?: string }} Step */
|
/** @typedef {{ key: string, labelKey: string, img: string | null, paired: boolean, tipKey: string, dbSingle?: string, dbLeft?: string, dbRight?: string }} Step */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||||
import { toast } from '$lib/js/toast.svelte';
|
import { toast } from '$lib/js/toast.svelte';
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||||
import DatePicker from '$lib/components/DatePicker.svelte';
|
import DatePicker from '$lib/components/DatePicker.svelte';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
|
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import Search from '@lucide/svelte/icons/search';
|
import Search from '@lucide/svelte/icons/search';
|
||||||
import Cable from '@lucide/svelte/icons/cable';
|
import Cable from '@lucide/svelte/icons/cable';
|
||||||
import Cog from '@lucide/svelte/icons/cog';
|
import Cog from '@lucide/svelte/icons/cog';
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
import { MUSCLE_GROUPS, MUSCLE_GROUP_DE } from '$lib/data/muscleMap';
|
import { MUSCLE_GROUPS, MUSCLE_GROUP_DE } from '$lib/data/muscleMap';
|
||||||
import MuscleFilter from '$lib/components/fitness/MuscleFilter.svelte';
|
import MuscleFilter from '$lib/components/fitness/MuscleFilter.svelte';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
const sl = $derived(fitnessSlugs(lang));
|
const sl = $derived(fitnessSlugs(lang));
|
||||||
|
|
||||||
@@ -212,7 +213,7 @@
|
|||||||
<ul class="exercise-list">
|
<ul class="exercise-list">
|
||||||
{#each filtered as exercise (exercise.id)}
|
{#each filtered as exercise (exercise.id)}
|
||||||
<li>
|
<li>
|
||||||
<a href="/fitness/{sl.exercises}/{exercise.id}" class="exercise-row">
|
<a href={resolve('/fitness/[exercises=fitnessExercises]/[id]', { exercises: sl.exercises, id: exercise.id })} class="exercise-row">
|
||||||
<div class="exercise-info">
|
<div class="exercise-info">
|
||||||
<span class="exercise-name">
|
<span class="exercise-name">
|
||||||
{exercise.localName}
|
{exercise.localName}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
|
||||||
/** @param {string | undefined | null} type @param {'en'|'de'} lang */
|
/** @param {string | undefined | null} type @param {'en'|'de'} lang */
|
||||||
function exerciseTypeInfo(type, lang) {
|
function exerciseTypeInfo(type, lang) {
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
|
||||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const s = $derived(fitnessSlugs(lang));
|
const s = $derived(fitnessSlugs(lang));
|
||||||
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
||||||
import MuscleMap from '$lib/components/fitness/MuscleMap.svelte';
|
import MuscleMap from '$lib/components/fitness/MuscleMap.svelte';
|
||||||
@@ -198,7 +199,7 @@
|
|||||||
<h3>{lang === 'en' ? 'Similar Exercises' : 'Ähnliche Übungen'}</h3>
|
<h3>{lang === 'en' ? 'Similar Exercises' : 'Ähnliche Übungen'}</h3>
|
||||||
<div class="similar-scroll">
|
<div class="similar-scroll">
|
||||||
{#each similar as sim}
|
{#each similar as sim}
|
||||||
<a class="similar-card" href="/fitness/{s.exercises}/{sim.id}">
|
<a class="similar-card" href={resolve('/fitness/[exercises=fitnessExercises]/[id]', { exercises: s.exercises, id: sim.id })}>
|
||||||
<div class="similar-info">
|
<div class="similar-info">
|
||||||
<span class="similar-name">{sim.localName}</span>
|
<span class="similar-name">{sim.localName}</span>
|
||||||
<span class="similar-meta">{sim.localBodyPart} · {sim.localEquipment}</span>
|
<span class="similar-meta">{sim.localBodyPart} · {sim.localEquipment}</span>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page as appPage } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page as appPage } from '$app/state';
|
||||||
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
import SessionCard from '$lib/components/fitness/SessionCard.svelte';
|
import SessionCard from '$lib/components/fitness/SessionCard.svelte';
|
||||||
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($appPage.url.pathname));
|
const lang = $derived(detectFitnessLang(appPage.url.pathname));
|
||||||
const s = $derived(fitnessSlugs(lang));
|
const s = $derived(fitnessSlugs(lang));
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -48,9 +49,15 @@
|
|||||||
return d.toLocaleString(lang === 'de' ? 'de-DE' : 'en-US', { month: 'long', year: 'numeric' });
|
return d.toLocaleString(lang === 'de' ? 'de-DE' : 'en-US', { month: 'long', year: 'numeric' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevHref = $derived(`/fitness/${s.history}/${prevMonth}`);
|
const prevHref = $derived(resolve('/fitness/[history=fitnessHistory]/[[month=fitnessMonth]]', { history: s.history, month: prevMonth }));
|
||||||
const nextHref = $derived(nextMonth && nextMonth === currentYM ? `/fitness/${s.history}` : nextMonth ? `/fitness/${s.history}/${nextMonth}` : null);
|
const nextHref = $derived(
|
||||||
const recentHref = $derived(`/fitness/${s.history}`);
|
nextMonth && nextMonth === currentYM
|
||||||
|
? resolve('/fitness/[history=fitnessHistory]', { history: s.history })
|
||||||
|
: nextMonth
|
||||||
|
? resolve('/fitness/[history=fitnessHistory]/[[month=fitnessMonth]]', { history: s.history, month: nextMonth })
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
const recentHref = $derived(resolve('/fitness/[history=fitnessHistory]', { history: s.history }));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head><title>{t('history_title', lang)} - Bocken</title></svelte:head>
|
<svelte:head><title>{t('history_title', lang)} - Bocken</title></svelte:head>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { goto, invalidateAll } from '$app/navigation';
|
import { goto, invalidateAll } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import Clock from '@lucide/svelte/icons/clock';
|
import Clock from '@lucide/svelte/icons/clock';
|
||||||
import Weight from '@lucide/svelte/icons/weight';
|
import Weight from '@lucide/svelte/icons/weight';
|
||||||
import Trophy from '@lucide/svelte/icons/trophy';
|
import Trophy from '@lucide/svelte/icons/trophy';
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
import { confirm } from '$lib/js/confirmDialog.svelte';
|
import { confirm } from '$lib/js/confirmDialog.svelte';
|
||||||
import { toast } from '$lib/js/toast.svelte';
|
import { toast } from '$lib/js/toast.svelte';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const sl = $derived(fitnessSlugs(lang));
|
const sl = $derived(fitnessSlugs(lang));
|
||||||
import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises';
|
import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises';
|
||||||
import { formatPaceRangeLabel, formatPaceValue } from '$lib/data/cardioPrRanges';
|
import { formatPaceRangeLabel, formatPaceValue } from '$lib/data/cardioPrRanges';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import { goto, invalidateAll } from '$app/navigation';
|
import { goto, invalidateAll } from '$app/navigation';
|
||||||
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
* }} FoodSelection
|
* }} FoodSelection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const s = $derived(fitnessSlugs(lang));
|
const s = $derived(fitnessSlugs(lang));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
|
|
||||||
@@ -95,9 +96,17 @@
|
|||||||
|
|
||||||
const prevDate = $derived(dateOffset(-1));
|
const prevDate = $derived(dateOffset(-1));
|
||||||
const nextDate = $derived(dateOffset(1));
|
const nextDate = $derived(dateOffset(1));
|
||||||
const prevHref = $derived(prevDate === todayStr ? `/fitness/${s.nutrition}` : `/fitness/${s.nutrition}/${prevDate}`);
|
const prevHref = $derived(
|
||||||
const nextHref = $derived(nextDate === todayStr ? `/fitness/${s.nutrition}` : `/fitness/${s.nutrition}/${nextDate}`);
|
prevDate === todayStr
|
||||||
const todayHref = $derived(`/fitness/${s.nutrition}`);
|
? resolve('/fitness/[nutrition=fitnessNutrition]', { nutrition: s.nutrition })
|
||||||
|
: resolve('/fitness/[nutrition=fitnessNutrition]/[[date=fitnessDate]]', { nutrition: s.nutrition, date: prevDate })
|
||||||
|
);
|
||||||
|
const nextHref = $derived(
|
||||||
|
nextDate === todayStr
|
||||||
|
? resolve('/fitness/[nutrition=fitnessNutrition]', { nutrition: s.nutrition })
|
||||||
|
: resolve('/fitness/[nutrition=fitnessNutrition]/[[date=fitnessDate]]', { nutrition: s.nutrition, date: nextDate })
|
||||||
|
);
|
||||||
|
const todayHref = $derived(resolve('/fitness/[nutrition=fitnessNutrition]', { nutrition: s.nutrition }));
|
||||||
|
|
||||||
// --- Entries ---
|
// --- Entries ---
|
||||||
// svelte-ignore state_referenced_locally
|
// svelte-ignore state_referenced_locally
|
||||||
@@ -632,10 +641,10 @@
|
|||||||
let inlineTab = $state('search'); // 'search' | 'favorites' | 'meals'
|
let inlineTab = $state('search'); // 'search' | 'favorites' | 'meals'
|
||||||
|
|
||||||
// --- FAB modal (route-based via ?add param) ---
|
// --- FAB modal (route-based via ?add param) ---
|
||||||
const showFabModal = $derived($page.url.searchParams.has('add'));
|
const showFabModal = $derived(page.url.searchParams.has('add'));
|
||||||
let fabMealType = $state('lunch');
|
let fabMealType = $state('lunch');
|
||||||
|
|
||||||
const fabHref = $derived(`/fitness/${s.nutrition}?add`);
|
const fabHref = $derived(`${resolve('/fitness/[nutrition=fitnessNutrition]', { nutrition: s.nutrition })}?add`);
|
||||||
|
|
||||||
function defaultMealType() {
|
function defaultMealType() {
|
||||||
const h = new Date().getHours();
|
const h = new Date().getHours();
|
||||||
@@ -1343,7 +1352,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<a class="manage-meals-link" href="/fitness/{s.nutrition}/meals">
|
<a class="manage-meals-link" href={resolve('/fitness/[nutrition=fitnessNutrition]/meals', { nutrition: s.nutrition })}>
|
||||||
<Settings size={13} />
|
<Settings size={13} />
|
||||||
{isEn ? 'Manage meals' : 'Mahlzeiten verwalten'}
|
{isEn ? 'Manage meals' : 'Mahlzeiten verwalten'}
|
||||||
</a>
|
</a>
|
||||||
@@ -1473,7 +1482,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if !hasBmrData}
|
{#if !hasBmrData}
|
||||||
<div class="bmr-hint">{isEn ? 'Set profile in' : 'Profil unter'} <a href="/fitness/{s.measure}">{t('measure_title', lang)}</a></div>
|
<div class="bmr-hint">{isEn ? 'Set profile in' : 'Profil unter'} <a href={resolve('/fitness/[checkin=fitnessCheckIn]', { checkin: s.measure })}>{t('measure_title', lang)}</a></div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1601,7 +1610,7 @@
|
|||||||
<span>{isEn
|
<span>{isEn
|
||||||
? 'Your TDEE (Total Daily Energy Expenditure) is the calories you burn per day. Set weight, height, and birth year under'
|
? 'Your TDEE (Total Daily Energy Expenditure) is the calories you burn per day. Set weight, height, and birth year under'
|
||||||
: 'Dein TDEE (Gesamtenergieumsatz) sind die Kalorien, die du pro Tag verbrauchst. Gewicht, Größe und Geburtsjahr einstellen unter'}
|
: 'Dein TDEE (Gesamtenergieumsatz) sind die Kalorien, die du pro Tag verbrauchst. Gewicht, Größe und Geburtsjahr einstellen unter'}
|
||||||
<a href="/fitness/{s.measure}">{t('measure_title', lang)}</a>
|
<a href={resolve('/fitness/[checkin=fitnessCheckIn]', { checkin: s.measure })}>{t('measure_title', lang)}</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1907,9 +1916,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="food-card-body">
|
<div class="food-card-body">
|
||||||
{#if entry.source === 'bls' || entry.source === 'usda' || entry.source === 'off'}
|
{#if entry.source === 'bls' || entry.source === 'usda' || entry.source === 'off'}
|
||||||
<a class="food-card-name food-card-link" draggable="false" href="/fitness/{s.nutrition}/food/{entry.source}/{entry.sourceId}">{entry.name}</a>
|
<a class="food-card-name food-card-link" draggable="false" href={resolve('/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]', { nutrition: s.nutrition, source: entry.source, id: entry.sourceId })}>{entry.name}</a>
|
||||||
{:else if (entry.source === 'recipe' || entry.source === 'custom') && entry.sourceId}
|
{:else if (entry.source === 'recipe' || entry.source === 'custom') && entry.sourceId}
|
||||||
<a class="food-card-name food-card-link" draggable="false" href="/fitness/{s.nutrition}/food/{entry.source}/{entry.sourceId}?logEntry={entry._id}">{entry.name}</a>
|
<a class="food-card-name food-card-link" draggable="false" href={`${resolve('/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]', { nutrition: s.nutrition, source: entry.source, id: entry.sourceId })}?logEntry=${entry._id}`}>{entry.name}</a>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="food-card-name">{entry.name}</span>
|
<span class="food-card-name">{entry.name}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
||||||
import ExternalLink from '@lucide/svelte/icons/external-link';
|
import ExternalLink from '@lucide/svelte/icons/external-link';
|
||||||
import Heart from '@lucide/svelte/icons/heart';
|
import Heart from '@lucide/svelte/icons/heart';
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const s = $derived(fitnessSlugs(lang));
|
const s = $derived(fitnessSlugs(lang));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@
|
|||||||
<span class="badge badge-nutriscore" data-score={food.nutriscore.toLowerCase()}>Nutri-Score {food.nutriscore.toUpperCase()}</span>
|
<span class="badge badge-nutriscore" data-score={food.nutriscore.toLowerCase()}>Nutri-Score {food.nutriscore.toUpperCase()}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if food.recipeSlug}
|
{#if food.recipeSlug}
|
||||||
<a class="badge badge-recipe-link" href="/{isEn ? 'recipes' : 'rezepte'}/{isEn && food.recipeSlugEn ? food.recipeSlugEn : food.recipeSlug}">
|
<a class="badge badge-recipe-link" href={resolve('/[recipeLang=recipeLang]/[name]', { recipeLang: isEn ? 'recipes' : 'rezepte', name: isEn && food.recipeSlugEn ? food.recipeSlugEn : food.recipeSlug })}>
|
||||||
{isEn ? 'View recipe' : 'Zum Rezept'} <ExternalLink size={12} />
|
{isEn ? 'View recipe' : 'Zum Rezept'} <ExternalLink size={12} />
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -289,7 +290,7 @@
|
|||||||
<div class="ingredient-row">
|
<div class="ingredient-row">
|
||||||
<div class="ingredient-info">
|
<div class="ingredient-info">
|
||||||
{#if ing.sourceId && (ing.source === 'bls' || ing.source === 'usda' || ing.source === 'off')}
|
{#if ing.sourceId && (ing.source === 'bls' || ing.source === 'usda' || ing.source === 'off')}
|
||||||
<a class="ingredient-name" href="/fitness/{s.nutrition}/food/{ing.source}/{ing.sourceId}">{ing.name}</a>
|
<a class="ingredient-name" href={resolve('/fitness/[nutrition=fitnessNutrition]/food/[source]/[id]', { nutrition: s.nutrition, source: ing.source, id: ing.sourceId })}>{ing.name}</a>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="ingredient-name">{ing.name}</span>
|
<span class="ingredient-name">{ing.name}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
/** @typedef {import('$models/CustomMeal').ICustomMeal & { _id?: string }} Meal */
|
/** @typedef {import('$models/CustomMeal').ICustomMeal & { _id?: string }} Meal */
|
||||||
/** @typedef {import('$models/CustomMeal').ICustomMealIngredient} MealIngredient */
|
/** @typedef {import('$models/CustomMeal').ICustomMealIngredient} MealIngredient */
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const s = $derived(fitnessSlugs(lang));
|
const s = $derived(fitnessSlugs(lang));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { invalidateAll } from '$app/navigation';
|
import { invalidateAll } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
||||||
import MuscleHeatmap from '$lib/components/fitness/MuscleHeatmap.svelte';
|
import MuscleHeatmap from '$lib/components/fitness/MuscleHeatmap.svelte';
|
||||||
import Dumbbell from '@lucide/svelte/icons/dumbbell';
|
import Dumbbell from '@lucide/svelte/icons/dumbbell';
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
import StatsRingGraph from '$lib/components/fitness/StatsRingGraph.svelte';
|
import StatsRingGraph from '$lib/components/fitness/StatsRingGraph.svelte';
|
||||||
import { BODY_PART_CARDS, bodyPartSlug, bodyPartAccent } from '$lib/js/fitnessBodyParts';
|
import { BODY_PART_CARDS, bodyPartSlug, bodyPartAccent } from '$lib/js/fitnessBodyParts';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik');
|
const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik');
|
||||||
const historySlug = $derived(lang === 'en' ? 'history' : 'verlauf');
|
const historySlug = $derived(lang === 'en' ? 'history' : 'verlauf');
|
||||||
|
|
||||||
@@ -293,7 +294,7 @@
|
|||||||
<div class="card-value">~{stats.kcalEstimate.kcal.toLocaleString()}<span class="card-unit">kcal</span></div>
|
<div class="card-value">~{stats.kcalEstimate.kcal.toLocaleString()}<span class="card-unit">kcal</span></div>
|
||||||
<div class="card-label">{t('burned', lang)}</div>
|
<div class="card-label">{t('burned', lang)}</div>
|
||||||
{#if !hasDemographics}
|
{#if !hasDemographics}
|
||||||
<div class="card-hint">{t('kcal_set_profile', lang)} <a href="/fitness/{fitnessSlugs(lang).measure}">{t('measure_title', lang)}</a></div>
|
<div class="card-hint">{t('kcal_set_profile', lang)} <a href={resolve('/fitness/[checkin=fitnessCheckIn]', { checkin: fitnessSlugs(lang).measure })}>{t('measure_title', lang)}</a></div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -512,7 +513,7 @@
|
|||||||
<a
|
<a
|
||||||
class="bp-card"
|
class="bp-card"
|
||||||
style="--accent: {bodyPartAccent(card.key)}"
|
style="--accent: {bodyPartAccent(card.key)}"
|
||||||
href="/fitness/{statsSlug}/{historySlug}/{bodyPartSlug(card, lang)}"
|
href={resolve('/fitness/[stats=fitnessStats]/[history=fitnessHistory]/[part]', { stats: statsSlug, history: historySlug, part: bodyPartSlug(card, lang) })}
|
||||||
>
|
>
|
||||||
<div class="bp-img-wrap" aria-hidden="true">
|
<div class="bp-img-wrap" aria-hidden="true">
|
||||||
{#if card.img}
|
{#if card.img}
|
||||||
|
|||||||
+5
-4
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
||||||
import Ruler from '@lucide/svelte/icons/ruler';
|
import Ruler from '@lucide/svelte/icons/ruler';
|
||||||
import TrendingUp from '@lucide/svelte/icons/trending-up';
|
import TrendingUp from '@lucide/svelte/icons/trending-up';
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik');
|
const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik');
|
||||||
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
|
const checkinSlug = $derived(lang === 'en' ? 'check-in' : 'erfassung');
|
||||||
const card = $derived(data.card);
|
const card = $derived(data.card);
|
||||||
@@ -151,7 +152,7 @@
|
|||||||
|
|
||||||
<div class="detail-page">
|
<div class="detail-page">
|
||||||
<header class="detail-header" style="--accent: {bodyPartAccent(card.key)}">
|
<header class="detail-header" style="--accent: {bodyPartAccent(card.key)}">
|
||||||
<a class="back-link" href="/fitness/{statsSlug}" aria-label={t('back', lang)}>
|
<a class="back-link" href={resolve('/fitness/[stats=fitnessStats]', { stats: statsSlug })} aria-label={t('back', lang)}>
|
||||||
<ArrowLeft size={18} />
|
<ArrowLeft size={18} />
|
||||||
</a>
|
</a>
|
||||||
<div class="head-text">
|
<div class="head-text">
|
||||||
@@ -173,7 +174,7 @@
|
|||||||
{#if !hasData}
|
{#if !hasData}
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<p>{t('no_measurements_yet', lang)}</p>
|
<p>{t('no_measurements_yet', lang)}</p>
|
||||||
<a class="cta" href="/fitness/{checkinSlug}/body-parts">
|
<a class="cta" href={resolve('/fitness/[checkin=fitnessCheckIn]/body-parts', { checkin: checkinSlug })}>
|
||||||
<Ruler size={16} /> {t('measure_body_parts', lang)}
|
<Ruler size={16} /> {t('measure_body_parts', lang)}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
|
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
|
||||||
import { toast } from '$lib/js/toast.svelte';
|
import { toast } from '$lib/js/toast.svelte';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const sl = $derived(fitnessSlugs(lang));
|
const sl = $derived(fitnessSlugs(lang));
|
||||||
import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises';
|
import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises';
|
||||||
import TemplateCard from '$lib/components/fitness/TemplateCard.svelte';
|
import TemplateCard from '$lib/components/fitness/TemplateCard.svelte';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
import Play from '@lucide/svelte/icons/play';
|
import Play from '@lucide/svelte/icons/play';
|
||||||
import Pause from '@lucide/svelte/icons/pause';
|
import Pause from '@lucide/svelte/icons/pause';
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
import { confirm } from '$lib/js/confirmDialog.svelte';
|
import { confirm } from '$lib/js/confirmDialog.svelte';
|
||||||
import { toast } from '$lib/js/toast.svelte';
|
import { toast } from '$lib/js/toast.svelte';
|
||||||
|
|
||||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
const lang = $derived(detectFitnessLang(page.url.pathname));
|
||||||
const isEn = $derived(lang === 'en');
|
const isEn = $derived(lang === 'en');
|
||||||
const sl = $derived(fitnessSlugs(lang));
|
const sl = $derived(fitnessSlugs(lang));
|
||||||
import { getWorkout } from '$lib/js/workout.svelte';
|
import { getWorkout } from '$lib/js/workout.svelte';
|
||||||
@@ -1667,7 +1668,7 @@
|
|||||||
exerciseId={activeExercise.exerciseId}
|
exerciseId={activeExercise.exerciseId}
|
||||||
bodyPart={activeExerciseMeta?.localBodyPart ?? null}
|
bodyPart={activeExerciseMeta?.localBodyPart ?? null}
|
||||||
equipment={activeExerciseMeta?.localEquipment ?? null}
|
equipment={activeExerciseMeta?.localEquipment ?? null}
|
||||||
detailsHref={`/fitness/${sl.exercises}/${activeExercise.exerciseId}`}
|
detailsHref={resolve('/fitness/[exercises=fitnessExercises]/[id]', { exercises: sl.exercises, id: activeExercise.exerciseId })}
|
||||||
detailsLabel={isEn ? 'Exercise details' : 'Übungsdetails'}
|
detailsLabel={isEn ? 'Exercise details' : 'Übungsdetails'}
|
||||||
exerciseIndex={activeIdx}
|
exerciseIndex={activeIdx}
|
||||||
totalExercises={workout.exercises.length}
|
totalExercises={workout.exercises.length}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import SectionError from '$lib/components/SectionError.svelte';
|
import SectionError from '$lib/components/SectionError.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SectionError
|
<SectionError
|
||||||
sectionHref="/tasks"
|
sectionHref={resolve('/tasks')}
|
||||||
sectionLabel={{ en: 'Tasks', de: 'Aufgaben' }}
|
sectionLabel={{ en: 'Tasks', de: 'Aufgaben' }}
|
||||||
isEnglish={false}
|
isEnglish={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { page } from '$app/stores';
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
import UserHeader from '$lib/components/UserHeader.svelte';
|
import UserHeader from '$lib/components/UserHeader.svelte';
|
||||||
import ClipboardList from '@lucide/svelte/icons/clipboard-list';
|
import ClipboardList from '@lucide/svelte/icons/clipboard-list';
|
||||||
@@ -9,7 +10,7 @@
|
|||||||
|
|
||||||
/** @param {string} path */
|
/** @param {string} path */
|
||||||
function isActive(path) {
|
function isActive(path) {
|
||||||
const currentPath = $page.url.pathname;
|
const currentPath = page.url.pathname;
|
||||||
if (path === '/tasks') {
|
if (path === '/tasks') {
|
||||||
return currentPath === '/tasks' || currentPath === '/tasks/';
|
return currentPath === '/tasks' || currentPath === '/tasks/';
|
||||||
}
|
}
|
||||||
@@ -20,8 +21,8 @@
|
|||||||
<Header>
|
<Header>
|
||||||
{#snippet links()}
|
{#snippet links()}
|
||||||
<ul class="site_header">
|
<ul class="site_header">
|
||||||
<li style="--active-fill: var(--nord10)"><a href="/tasks" class:active={isActive('/tasks')}><ClipboardList size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">Aufgaben</span></a></li>
|
<li style="--active-fill: var(--nord10)"><a href={resolve('/tasks')} class:active={isActive('/tasks')}><ClipboardList size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">Aufgaben</span></a></li>
|
||||||
<li style="--active-fill: var(--nord13)"><a href="/tasks/rewards" class:active={isActive('/tasks/rewards')}><Trophy size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">Sticker</span></a></li>
|
<li style="--active-fill: var(--nord13)"><a href={resolve('/tasks/rewards')} class:active={isActive('/tasks/rewards')}><Trophy size={16} strokeWidth={1.5} class="nav-icon" /><span class="nav-label">Sticker</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user