simplified and removed terminal stuff
This commit is contained in:
78
assets/i18n/en/main.ftl
Normal file
78
assets/i18n/en/main.ftl
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
brand = Universal Web
|
||||||
|
hello-world = Hello world!
|
||||||
|
meta-description = A guitar player's personal site. News, blog posts, albums, and songs in one place.
|
||||||
|
nav-home = Home
|
||||||
|
nav-about = About
|
||||||
|
nav-blog = Blog
|
||||||
|
nav-audio = Audio
|
||||||
|
nav-songs = Songs
|
||||||
|
nav-admin = Admin login
|
||||||
|
admin-title = Admin
|
||||||
|
admin-dashboard = Dashboard
|
||||||
|
admin-blog = Blog
|
||||||
|
admin-audio = Audio
|
||||||
|
admin-images = Images
|
||||||
|
admin-about = About
|
||||||
|
admin-exit = Exit
|
||||||
|
view-site = View site
|
||||||
|
admin-blog-desc = create and update blog articles.
|
||||||
|
admin-about-desc = edit the public about page content.
|
||||||
|
admin-audio-desc = upload songs, then group them into albums.
|
||||||
|
admin-images-desc = upload images for covers and articles.
|
||||||
|
logout = Log out
|
||||||
|
settings = Settings
|
||||||
|
settings-language = Language
|
||||||
|
settings-theme = Theme
|
||||||
|
menu = Menu
|
||||||
|
theme-system = System
|
||||||
|
theme-light = Light
|
||||||
|
theme-dark = Dark
|
||||||
|
home-title = Home
|
||||||
|
home-sub = news and updates.
|
||||||
|
home-all-posts = All posts
|
||||||
|
home-recent = Recent posts
|
||||||
|
home-tagline = guitar player - original songs, albums, and notes
|
||||||
|
home-sections = about/ blog/ audio/ songs/
|
||||||
|
home-no-posts = no published posts yet
|
||||||
|
blog-title = Blog
|
||||||
|
blog-sub = published article(s)
|
||||||
|
blog-manage = Manage
|
||||||
|
blog-read = Read
|
||||||
|
blog-no-posts = no published posts yet
|
||||||
|
blog-views = views logged
|
||||||
|
cd-up = cd ..
|
||||||
|
about-sub = about this site.
|
||||||
|
about-readonly = readonly
|
||||||
|
audio-title = Audio
|
||||||
|
audio-sub = published album(s)
|
||||||
|
audio-all-songs = All songs
|
||||||
|
audio-open = Open
|
||||||
|
audio-play = Play
|
||||||
|
audio-no-albums = no published albums yet
|
||||||
|
songs-title = Songs
|
||||||
|
songs-sub = track(s) across every album.
|
||||||
|
songs-play-all = Play all
|
||||||
|
songs-albums = Albums
|
||||||
|
songs-no-tracks = no tracks yet
|
||||||
|
album-by = by
|
||||||
|
album-play-full = Play full album
|
||||||
|
album-queue-all = queue all tracks in order
|
||||||
|
album-no-tracks = no tracks yet
|
||||||
|
login-title = Admin login
|
||||||
|
login-error = Access denied - invalid email or password.
|
||||||
|
login-root = root
|
||||||
|
login-auth = Authenticate
|
||||||
|
login-email = Email
|
||||||
|
login-password = Password
|
||||||
|
readonly = readonly
|
||||||
|
post = post
|
||||||
|
album = album
|
||||||
|
published = published
|
||||||
|
draft = draft
|
||||||
|
single = single
|
||||||
|
manage = Manage
|
||||||
|
open = Open
|
||||||
|
play = Play
|
||||||
|
new-article = New article
|
||||||
|
edit = Edit
|
||||||
|
delete = Delete
|
||||||
78
assets/i18n/sk/main.ftl
Normal file
78
assets/i18n/sk/main.ftl
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
brand = Universal Web
|
||||||
|
hello-world = Ahoj svet!
|
||||||
|
meta-description = Osobná stránka gitaristu. Novinky, blog, albumy a skladby na jednom mieste.
|
||||||
|
nav-home = Domov
|
||||||
|
nav-about = O mne
|
||||||
|
nav-blog = Blog
|
||||||
|
nav-audio = Audio
|
||||||
|
nav-songs = Skladby
|
||||||
|
nav-admin = Prihlásenie admina
|
||||||
|
admin-title = Admin
|
||||||
|
admin-dashboard = Prehľad
|
||||||
|
admin-blog = Blog
|
||||||
|
admin-audio = Audio
|
||||||
|
admin-images = Obrázky
|
||||||
|
admin-about = O mne
|
||||||
|
admin-exit = Späť na web
|
||||||
|
view-site = Zobraziť web
|
||||||
|
admin-blog-desc = vytvoriť a upravovať blogové články.
|
||||||
|
admin-about-desc = upraviť obsah verejnej stránky o mne.
|
||||||
|
admin-audio-desc = nahrať skladby a potom ich zoskupiť do albumov.
|
||||||
|
admin-images-desc = nahrať obrázky pre obaly a články.
|
||||||
|
logout = Odhlásiť sa
|
||||||
|
settings = Nastavenia
|
||||||
|
settings-language = Jazyk
|
||||||
|
settings-theme = Téma
|
||||||
|
menu = Menu
|
||||||
|
theme-system = Systém
|
||||||
|
theme-light = Svetlý
|
||||||
|
theme-dark = Tmavý
|
||||||
|
home-title = Domov
|
||||||
|
home-sub = novinky a aktuality.
|
||||||
|
home-all-posts = Všetky príspevky
|
||||||
|
home-recent = Posledné príspevky
|
||||||
|
home-tagline = gitarista - autorské skladby, albumy a poznámky
|
||||||
|
home-sections = about/ blog/ audio/ songs/
|
||||||
|
home-no-posts = zatiaľ žiadne zverejnené príspevky
|
||||||
|
blog-title = Blog
|
||||||
|
blog-sub = zverejnené články
|
||||||
|
blog-manage = Spravovať
|
||||||
|
blog-read = Čítať
|
||||||
|
blog-no-posts = zatiaľ žiadne zverejnené príspevky
|
||||||
|
blog-views = zobrazení
|
||||||
|
cd-up = cd ..
|
||||||
|
about-sub = o tejto stránke.
|
||||||
|
about-readonly = iba na čítanie
|
||||||
|
audio-title = Audio
|
||||||
|
audio-sub = zverejnené albumy
|
||||||
|
audio-all-songs = Všetky skladby
|
||||||
|
audio-open = Otvoriť
|
||||||
|
audio-play = Prehrať
|
||||||
|
audio-no-albums = zatiaľ žiadne zverejnené albumy
|
||||||
|
songs-title = Skladby
|
||||||
|
songs-sub = skladieb naprieč všetkými albumami.
|
||||||
|
songs-play-all = Prehrať všetko
|
||||||
|
songs-albums = Albumy
|
||||||
|
songs-no-tracks = zatiaľ žiadne skladby
|
||||||
|
album-by = od
|
||||||
|
album-play-full = Prehrať celý album
|
||||||
|
album-queue-all = zoradiť všetky skladby v poradí
|
||||||
|
album-no-tracks = zatiaľ žiadne skladby
|
||||||
|
login-title = Prihlásenie admina
|
||||||
|
login-error = Prístup odmietnutý - nesprávny e-mail alebo heslo.
|
||||||
|
login-root = root
|
||||||
|
login-auth = Prihlásiť sa
|
||||||
|
login-email = E-mail
|
||||||
|
login-password = Heslo
|
||||||
|
readonly = iba na čítanie
|
||||||
|
post = príspevok
|
||||||
|
album = album
|
||||||
|
published = zverejnené
|
||||||
|
draft = koncept
|
||||||
|
single = samostatne
|
||||||
|
manage = Spravovať
|
||||||
|
open = Otvoriť
|
||||||
|
play = Prehrať
|
||||||
|
new-article = Nový článok
|
||||||
|
edit = Upraviť
|
||||||
|
delete = Zmazať
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-theme="dark">
|
<html lang="{{ lang | default(value='sk') }}" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}Admin{% endblock title %}</title>
|
<title>{% block title %}{{ t(key="admin-title", lang=lang | default(value='sk')) }}{% endblock title %}</title>
|
||||||
<script>
|
<script>
|
||||||
function applyTheme(t) {
|
function applyTheme(t) {
|
||||||
var dark = t === 'dark'
|
var dark = t === 'dark'
|
||||||
@@ -73,21 +73,21 @@
|
|||||||
<span class="t-red">root</span><span class="t-dim">@universal-web</span><span class="t-dim">:</span><span class="t-yellow">/admin</span><span class="t-dim">#</span>
|
<span class="t-red">root</span><span class="t-dim">@universal-web</span><span class="t-dim">:</span><span class="t-yellow">/admin</span><span class="t-dim">#</span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="nav-menu term-navlinks menu menu-sm hidden items-center md:flex">
|
<ul class="nav-menu term-navlinks menu menu-sm hidden items-center md:flex">
|
||||||
<li><a href="/admin/dashboard" data-nav="/admin/dashboard">dashboard</a></li>
|
<li><a href="/admin/dashboard" data-nav="/admin/dashboard">{{ t(key="admin-dashboard", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/admin/blog/articles" data-nav="/admin/blog">blog</a></li>
|
<li><a href="/admin/blog/articles" data-nav="/admin/blog">{{ t(key="admin-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/admin/audio/albums" data-nav="/admin/audio">audio</a></li>
|
<li><a href="/admin/audio/albums" data-nav="/admin/audio">{{ t(key="admin-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/admin/images" data-nav="/admin/images">images</a></li>
|
<li><a href="/admin/images" data-nav="/admin/images">{{ t(key="admin-images", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/admin/about" data-nav="/admin/about">about</a></li>
|
<li><a href="/admin/about" data-nav="/admin/about">{{ t(key="admin-about", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/" class="t-blue">exit</a></li>
|
<li><a href="/" class="t-blue">{{ t(key="admin-exit", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li>
|
<li>
|
||||||
<form method="post" action="/admin/logout">
|
<form method="post" action="/admin/logout">
|
||||||
<button type="submit" class="t-red w-full">logout</button>
|
<button type="submit" class="t-red w-full">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="term-nav-right">
|
<div class="term-nav-right">
|
||||||
<div class="dropdown dropdown-end md:hidden">
|
<div class="dropdown dropdown-end md:hidden">
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="Menu">
|
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" class="h-5 w-5">
|
stroke="currentColor" class="h-5 w-5">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||||
@@ -95,21 +95,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul tabindex="0"
|
<ul tabindex="0"
|
||||||
class="menu dropdown-content z-50 mt-3 w-52 border border-base-300 bg-base-200 p-2 shadow-lg">
|
class="menu dropdown-content z-50 mt-3 w-52 border border-base-300 bg-base-200 p-2 shadow-lg">
|
||||||
<li><a href="/admin/dashboard">dashboard</a></li>
|
<li><a href="/admin/dashboard">{{ t(key="admin-dashboard", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/admin/blog/articles">blog</a></li>
|
<li><a href="/admin/blog/articles">{{ t(key="admin-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/admin/audio/albums">audio</a></li>
|
<li><a href="/admin/audio/albums">{{ t(key="admin-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/admin/images">images</a></li>
|
<li><a href="/admin/images">{{ t(key="admin-images", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/admin/about">about</a></li>
|
<li><a href="/admin/about">{{ t(key="admin-about", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/" class="t-blue">exit</a></li>
|
<li><a href="/" class="t-blue">{{ t(key="admin-exit", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li>
|
<li>
|
||||||
<form method="post" action="/admin/logout">
|
<form method="post" action="/admin/logout">
|
||||||
<button type="submit" class="t-red w-full">logout</button>
|
<button type="submit" class="t-red w-full">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="Settings" title="Settings">
|
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}" title="{{ t(key='settings', lang=lang | default(value='sk')) }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" class="h-5 w-5">
|
stroke="currentColor" class="h-5 w-5">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
@@ -118,6 +118,27 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 border border-base-300 bg-base-200 p-2 shadow-lg">
|
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 border border-base-300 bg-base-200 p-2 shadow-lg">
|
||||||
|
<li class="menu-title">{{ t(key="settings-language", lang=lang | default(value='sk')) }}</li>
|
||||||
|
<li>
|
||||||
|
<form method="post" action="/lang" hx-boost="false">
|
||||||
|
<button type="submit" name="lang" value="en" class="btn btn-ghost btn-sm w-full justify-start">
|
||||||
|
<span>English</span>
|
||||||
|
{% if lang | default(value='sk') == 'en' %}
|
||||||
|
<span class="ml-auto">✓</span>
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<form method="post" action="/lang" hx-boost="false">
|
||||||
|
<button type="submit" name="lang" value="sk" class="btn btn-ghost btn-sm w-full justify-start">
|
||||||
|
<span>Slovenčina</span>
|
||||||
|
{% if lang | default(value='sk') == 'sk' %}
|
||||||
|
<span class="ml-auto">✓</span>
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
<li class="menu-title">:set theme</li>
|
<li class="menu-title">:set theme</li>
|
||||||
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">system <span class="opt-check ml-auto hidden">✓</span></button></li>
|
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">system <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||||
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">light <span class="opt-check ml-auto hidden">✓</span></button></li>
|
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">light <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "admin/base.html" %}
|
{% extends "admin/base.html" %}
|
||||||
|
|
||||||
{% block title %}Admin{% endblock title %}
|
{% block title %}{{ t(key="admin-title", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||||
{% block crumb %}dashboard{% endblock crumb %}
|
{% block crumb %}dashboard{% endblock crumb %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -10,11 +10,11 @@
|
|||||||
<span class="t-red">root@universal-web</span><span class="t-dim">:</span><span class="t-yellow">/admin</span><span class="t-dim">#</span>
|
<span class="t-red">root@universal-web</span><span class="t-dim">:</span><span class="t-yellow">/admin</span><span class="t-dim">#</span>
|
||||||
ls -la
|
ls -la
|
||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">dashboard</h1>
|
<h1 class="term-title">{{ t(key="admin-dashboard", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">// session: {{ admin.email }}</p>
|
<p class="term-sub">// session: {{ admin.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/" class="btn btn-outline btn-sm">[ view site ]</a>
|
<a href="/" class="btn btn-outline btn-sm">[ {{ t(key="view-site", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -30,13 +30,13 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">/admin/blog</span>
|
<span class="term-head-name">/admin/blog</span>
|
||||||
<span class="term-head-meta term-tag">content</span>
|
<span class="term-head-meta term-tag">{{ t(key="manage", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">blog</h2>
|
<h2 class="card-title text-base">{{ t(key="blog-title", lang=lang | default(value='sk')) }}</h2>
|
||||||
<p class="text-sm opacity-70">create and update blog articles.</p>
|
<p class="text-sm opacity-70">{{ t(key="admin-blog-desc", lang=lang | default(value='sk')) }}</p>
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<a href="/admin/blog/articles" class="btn btn-primary btn-sm">[ manage → ]</a>
|
<a href="/admin/blog/articles" class="btn btn-primary btn-sm">[ {{ t(key="blog-manage", lang=lang | default(value='sk')) }} → ]</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -47,13 +47,13 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">/admin/about</span>
|
<span class="term-head-name">/admin/about</span>
|
||||||
<span class="term-head-meta term-tag is-blue">page</span>
|
<span class="term-head-meta term-tag is-blue">{{ t(key="single", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">about page</h2>
|
<h2 class="card-title text-base">{{ t(key="about-sub", lang=lang | default(value='sk')) }}</h2>
|
||||||
<p class="text-sm opacity-70">edit the public about page content.</p>
|
<p class="text-sm opacity-70">{{ t(key="admin-about-desc", lang=lang | default(value='sk')) }}</p>
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<a href="/admin/about" class="btn btn-primary btn-sm">[ edit → ]</a>
|
<a href="/admin/about" class="btn btn-primary btn-sm">[ {{ t(key="edit", lang=lang | default(value='sk')) }} → ]</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -64,13 +64,13 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">/admin/audio</span>
|
<span class="term-head-name">/admin/audio</span>
|
||||||
<span class="term-head-meta term-tag is-purple">media</span>
|
<span class="term-head-meta term-tag is-purple">{{ t(key="album", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">audio</h2>
|
<h2 class="card-title text-base">{{ t(key="audio-title", lang=lang | default(value='sk')) }}</h2>
|
||||||
<p class="text-sm opacity-70">upload songs, then group them into albums.</p>
|
<p class="text-sm opacity-70">{{ t(key="admin-audio-desc", lang=lang | default(value='sk')) }}</p>
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<a href="/admin/audio/albums" class="btn btn-primary btn-sm">[ manage → ]</a>
|
<a href="/admin/audio/albums" class="btn btn-primary btn-sm">[ {{ t(key="manage", lang=lang | default(value='sk')) }} → ]</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -81,13 +81,13 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">/admin/images</span>
|
<span class="term-head-name">/admin/images</span>
|
||||||
<span class="term-head-meta term-tag is-green">uploads</span>
|
<span class="term-head-meta term-tag is-green">{{ t(key="admin-images", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">images</h2>
|
<h2 class="card-title text-base">{{ t(key="admin-images", lang=lang | default(value='sk')) }}</h2>
|
||||||
<p class="text-sm opacity-70">upload images for covers and articles.</p>
|
<p class="text-sm opacity-70">{{ t(key="admin-images-desc", lang=lang | default(value='sk')) }}</p>
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<a href="/admin/images" class="btn btn-primary btn-sm">[ upload → ]</a>
|
<a href="/admin/images" class="btn btn-primary btn-sm">[ {{ t(key="open", lang=lang | default(value='sk')) }} → ]</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Admin login{% endblock title %}
|
{% block title %}{{ t(key="login-title", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||||
{% block crumb %}admin/login{% endblock crumb %}
|
{% block crumb %}admin/login{% endblock crumb %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -15,24 +15,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="term-cmd-line">
|
<p class="term-cmd-line">
|
||||||
<span class="t-dim">universal-web login:</span> <span class="t-red">root</span>
|
<span class="t-dim">universal-web login:</span> <span class="t-red">{{ t(key="login-root", lang=lang | default(value='sk')) }}</span>
|
||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">authenticate</h1>
|
<h1 class="term-title">{{ t(key="login-auth", lang=lang | default(value='sk')) }}</h1>
|
||||||
{% if error %}
|
{% if error %}
|
||||||
<div class="alert alert-error mt-2">
|
<div class="alert alert-error mt-2">
|
||||||
<span>✗ access denied — invalid email or password.</span>
|
<span>✗ {{ t(key="login-error", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="post" action="/admin/login" hx-boost="false" class="space-y-2">
|
<form method="post" action="/admin/login" hx-boost="false" class="space-y-2">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label"><span class="label-text t-green">email:</span></label>
|
<label class="label"><span class="label-text t-green">{{ t(key="login-email", lang=lang | default(value='sk')) }}:</span></label>
|
||||||
<input type="email" name="email" required autofocus class="input input-bordered w-full">
|
<input type="email" name="email" required autofocus class="input input-bordered w-full">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label"><span class="label-text t-green">password:</span></label>
|
<label class="label"><span class="label-text t-green">{{ t(key="login-password", lang=lang | default(value='sk')) }}:</span></label>
|
||||||
<input type="password" name="password" required class="input input-bordered w-full">
|
<input type="password" name="password" required class="input input-bordered w-full">
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary mt-2 w-full">[ authenticate ]</button>
|
<button class="btn btn-primary mt-2 w-full">[ {{ t(key="login-auth", lang=lang | default(value='sk')) }} ]</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,15 +13,15 @@
|
|||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">{{ album.title }}</h1>
|
<h1 class="term-title">{{ album.title }}</h1>
|
||||||
{% if album.artist %}
|
{% if album.artist %}
|
||||||
<p class="term-sub">// by {{ album.artist }}</p>
|
<p class="term-sub">// {{ t(key="album-by", lang=lang | default(value='sk')) }} {{ album.artist }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
{% if tracks | length > 0 %}
|
{% if tracks | length > 0 %}
|
||||||
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
||||||
data-tracks-from="#uw-album-tracks">▶ play album</button>
|
data-tracks-from="#uw-album-tracks">{{ t(key="album-play-full", lang=lang | default(value='sk')) }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/audio/albums" class="btn btn-outline btn-sm">[ cd .. ]</a>
|
<a href="/audio/albums" class="btn btn-outline btn-sm">[ {{ t(key="cd-up", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
{% if tracks | length > 0 %}
|
{% if tracks | length > 0 %}
|
||||||
<div class="term-track-bar">
|
<div class="term-track-bar">
|
||||||
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
||||||
data-tracks-from="#uw-album-tracks">▶ play album</button>
|
data-tracks-from="#uw-album-tracks">{{ t(key="album-play-full", lang=lang | default(value='sk')) }}</button>
|
||||||
<span class="term-track-name t-dim">// queue all {{ tracks | length }} tracks</span>
|
<span class="term-track-name t-dim">// {{ t(key="album-queue-all", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% for track in tracks %}
|
{% for track in tracks %}
|
||||||
<div class="term-track">
|
<div class="term-track">
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="term-empty-cmd">$ ls → no tracks yet</p>
|
<p class="term-empty-cmd">{{ t(key="album-no-tracks", lang=lang | default(value='sk')) }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,15 +88,15 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="term-title">{{ album.title }}</h1>
|
<h1 class="term-title">{{ album.title }}</h1>
|
||||||
{% if album.artist %}
|
{% if album.artist %}
|
||||||
<p class="term-sub">by {{ album.artist }}</p>
|
<p class="term-sub">{{ t(key="album-by", lang=lang | default(value='sk')) }} {{ album.artist }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
{% if tracks | length > 0 %}
|
{% if tracks | length > 0 %}
|
||||||
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
||||||
data-tracks-from="#uw-album-tracks">play album</button>
|
data-tracks-from="#uw-album-tracks">{{ t(key="album-play-full", lang=lang | default(value='sk')) }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/audio/albums" class="btn btn-outline btn-sm">[ cd .. ]</a>
|
<a href="/audio/albums" class="btn btn-outline btn-sm">[ {{ t(key="cd-up", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -131,13 +131,13 @@
|
|||||||
{% if tracks | length > 0 %}
|
{% if tracks | length > 0 %}
|
||||||
<div class="term-track-bar">
|
<div class="term-track-bar">
|
||||||
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
||||||
data-tracks-from="#uw-album-tracks">play full album</button>
|
data-tracks-from="#uw-album-tracks">{{ t(key="album-play-full", lang=lang | default(value='sk')) }}</button>
|
||||||
<span class="term-track-name t-dim">// play every track in order</span>
|
<span class="term-track-name t-dim">// {{ t(key="album-queue-all", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% for track in tracks %}
|
{% for track in tracks %}
|
||||||
<div class="term-track">
|
<div class="term-track">
|
||||||
<button type="button" class="uw-play btn btn-primary btn-sm"
|
<button type="button" class="uw-play btn btn-primary btn-sm"
|
||||||
data-src="/audio/tracks/{{ track.id }}/stream" data-title="{{ track.title }}">play</button>
|
data-src="/audio/tracks/{{ track.id }}/stream" data-title="{{ track.title }}">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
<span class="term-track-name">
|
<span class="term-track-name">
|
||||||
<span class="t-dim">{% if track.track_number %}{{ track.track_number }}{% else %}-{% endif %}</span>
|
<span class="t-dim">{% if track.track_number %}{{ track.track_number }}{% else %}-{% endif %}</span>
|
||||||
<span class="t-green">▸</span> {{ track.title }}
|
<span class="t-green">▸</span> {{ track.title }}
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="term-empty-cmd">$ ls → no tracks yet</p>
|
<p class="term-empty-cmd">{{ t(key="album-no-tracks", lang=lang | default(value='sk')) }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Audio{% endblock title %}
|
{% block title %}{{ t(key="audio-title", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||||
{% block crumb %}audio{% endblock crumb %}
|
{% block crumb %}audio{% endblock crumb %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -11,11 +11,11 @@
|
|||||||
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/audio</span><span class="t-dim">$</span>
|
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/audio</span><span class="t-dim">$</span>
|
||||||
ls -d */
|
ls -d */
|
||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">audio</h1>
|
<h1 class="term-title">{{ t(key="audio-title", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">// {{ albums | length }} published album(s).</p>
|
<p class="term-sub">// {{ albums | length }} {{ t(key="audio-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/audio/tracks" class="btn btn-outline btn-sm">[ all songs ]</a>
|
<a href="/audio/tracks" class="btn btn-outline btn-sm">[ {{ t(key="audio-all-songs", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">~/audio/{{ album.slug }}/</span>
|
<span class="term-head-name">~/audio/{{ album.slug }}/</span>
|
||||||
<span class="term-head-meta term-tag is-purple">album</span>
|
<span class="term-head-meta term-tag is-purple">{{ t(key="album", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if album.cover_image_id %}
|
{% if album.cover_image_id %}
|
||||||
@@ -43,8 +43,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex flex-wrap gap-2 pt-2">
|
<div class="flex flex-wrap gap-2 pt-2">
|
||||||
<button type="button" class="uw-play-album-remote btn btn-primary btn-sm"
|
<button type="button" class="uw-play-album-remote btn btn-primary btn-sm"
|
||||||
data-album-tracks-url="/audio/albums/{{ album.slug }}/tracks">▶ play</button>
|
data-album-tracks-url="/audio/albums/{{ album.slug }}/tracks">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
<a href="/audio/albums/{{ album.slug }}" class="btn btn-outline btn-sm">[ open → ]</a>
|
<a href="/audio/albums/{{ album.slug }}" class="btn btn-outline btn-sm">{{ t(key="audio-open", lang=lang | default(value='sk')) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -52,18 +52,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="term-empty">
|
<div class="term-empty">
|
||||||
<p class="font-medium">no published albums yet</p>
|
<p class="font-medium">{{ t(key="audio-no-albums", lang=lang | default(value='sk')) }}</p>
|
||||||
<p class="term-empty-cmd">$ ls ~/audio → 0 results</p>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<header class="term-cmd">
|
<header class="term-cmd">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="term-title">audio</h1>
|
<h1 class="term-title">{{ t(key="audio-title", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">{{ albums | length }} published album(s).</p>
|
<p class="term-sub">{{ albums | length }} {{ t(key="audio-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/audio/tracks" class="btn btn-outline btn-sm">[ all songs ]</a>
|
<a href="/audio/tracks" class="btn btn-outline btn-sm">[ {{ t(key="audio-all-songs", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -73,7 +72,7 @@
|
|||||||
<article class="card">
|
<article class="card">
|
||||||
<div class="term-head">
|
<div class="term-head">
|
||||||
<span class="term-head-name">~/audio/{{ album.slug }}/</span>
|
<span class="term-head-name">~/audio/{{ album.slug }}/</span>
|
||||||
<span class="term-head-meta term-tag is-purple">album</span>
|
<span class="term-head-meta term-tag is-purple">{{ t(key="album", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if album.cover_image_id %}
|
{% if album.cover_image_id %}
|
||||||
@@ -88,8 +87,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex flex-wrap gap-2 pt-2">
|
<div class="flex flex-wrap gap-2 pt-2">
|
||||||
<button type="button" class="uw-play-album-remote btn btn-primary btn-sm"
|
<button type="button" class="uw-play-album-remote btn btn-primary btn-sm"
|
||||||
data-album-tracks-url="/audio/albums/{{ album.slug }}/tracks">play</button>
|
data-album-tracks-url="/audio/albums/{{ album.slug }}/tracks">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
<a href="/audio/albums/{{ album.slug }}" class="btn btn-outline btn-sm">open</a>
|
<a href="/audio/albums/{{ album.slug }}" class="btn btn-outline btn-sm">{{ t(key="audio-open", lang=lang | default(value='sk')) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -97,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="term-empty">
|
<div class="term-empty">
|
||||||
<p class="font-medium">no published albums yet</p>
|
<p class="font-medium">{{ t(key="audio-no-albums", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Songs{% endblock title %}
|
{% block title %}{{ t(key="songs-title", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||||
{% block crumb %}audio/tracks{% endblock crumb %}
|
{% block crumb %}audio/tracks{% endblock crumb %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -11,15 +11,15 @@
|
|||||||
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/audio</span><span class="t-dim">$</span>
|
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/audio</span><span class="t-dim">$</span>
|
||||||
find . -name '*.mp3'
|
find . -name '*.mp3'
|
||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">songs</h1>
|
<h1 class="term-title">{{ t(key="songs-title", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">// {{ tracks | length }} track(s) across every album.</p>
|
<p class="term-sub">// {{ tracks | length }} {{ t(key="songs-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
{% if tracks | length > 0 %}
|
{% if tracks | length > 0 %}
|
||||||
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
||||||
data-tracks-from="#uw-songs-list">▶ play all</button>
|
data-tracks-from="#uw-songs-list">{{ t(key="songs-play-all", lang=lang | default(value='sk')) }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/audio/albums" class="btn btn-outline btn-sm">[ albums ]</a>
|
<a href="/audio/albums" class="btn btn-outline btn-sm">[ {{ t(key="songs-albums", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -36,27 +36,27 @@
|
|||||||
{% for track in tracks %}
|
{% for track in tracks %}
|
||||||
<div class="term-track">
|
<div class="term-track">
|
||||||
<button type="button" class="uw-play btn btn-primary btn-sm"
|
<button type="button" class="uw-play btn btn-primary btn-sm"
|
||||||
data-src="/audio/tracks/{{ track.id }}/stream" data-title="{{ track.title }}">▶ play</button>
|
data-src="/audio/tracks/{{ track.id }}/stream" data-title="{{ track.title }}">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
<span class="term-track-name"><span class="t-green">▸</span> {{ track.title }}</span>
|
<span class="term-track-name"><span class="t-green">▸</span> {{ track.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="term-empty-cmd">$ find ~/audio -name '*.mp3' → 0 results</p>
|
<p class="term-empty-cmd">{{ t(key="songs-no-tracks", lang=lang | default(value='sk')) }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<header class="term-cmd">
|
<header class="term-cmd">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="term-title">songs</h1>
|
<h1 class="term-title">{{ t(key="songs-title", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">{{ tracks | length }} track(s) across every album.</p>
|
<p class="term-sub">{{ tracks | length }} {{ t(key="songs-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
{% if tracks | length > 0 %}
|
{% if tracks | length > 0 %}
|
||||||
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
<button type="button" class="uw-play-album btn btn-primary btn-sm"
|
||||||
data-tracks-from="#uw-songs-list">play all</button>
|
data-tracks-from="#uw-songs-list">{{ t(key="songs-play-all", lang=lang | default(value='sk')) }}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/audio/albums" class="btn btn-outline btn-sm">[ albums ]</a>
|
<a href="/audio/albums" class="btn btn-outline btn-sm">[ {{ t(key="songs-albums", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -70,12 +70,12 @@
|
|||||||
{% for track in tracks %}
|
{% for track in tracks %}
|
||||||
<div class="term-track">
|
<div class="term-track">
|
||||||
<button type="button" class="uw-play btn btn-primary btn-sm"
|
<button type="button" class="uw-play btn btn-primary btn-sm"
|
||||||
data-src="/audio/tracks/{{ track.id }}/stream" data-title="{{ track.title }}">play</button>
|
data-src="/audio/tracks/{{ track.id }}/stream" data-title="{{ track.title }}">{{ t(key="audio-play", lang=lang | default(value='sk')) }}</button>
|
||||||
<span class="term-track-name">{{ track.title }}</span>
|
<span class="term-track-name">{{ track.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="term-empty-cmd">no tracks yet</p>
|
<p class="term-empty-cmd">{{ t(key="songs-no-tracks", lang=lang | default(value='sk')) }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-theme="dark">
|
<html lang="{{ lang | default(value='sk') }}" data-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}Universal Web{% endblock title %}</title>
|
<title>{% block title %}{{ t(key="brand", lang=lang | default(value='sk')) }}{% endblock title %}</title>
|
||||||
<script>
|
<script>
|
||||||
function applyTheme(t) {
|
function applyTheme(t) {
|
||||||
var dark = t === 'dark'
|
var dark = t === 'dark'
|
||||||
@@ -258,28 +258,28 @@
|
|||||||
<span class="t-green">root</span><span class="t-dim">@universal-web</span><span class="t-dim">:~$</span>
|
<span class="t-green">root</span><span class="t-dim">@universal-web</span><span class="t-dim">:~$</span>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/" class="term-brand"><span class="t-green">universal-web</span></a>
|
<a href="/" class="term-brand">{{ t(key="brand", lang=lang | default(value='sk')) }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul class="nav-menu term-navlinks menu menu-sm hidden items-center md:flex">
|
<ul class="nav-menu term-navlinks menu menu-sm hidden items-center md:flex">
|
||||||
<li><a href="/" data-nav="/">home</a></li>
|
<li><a href="/" data-nav="/">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/about" data-nav="/about">about</a></li>
|
<li><a href="/about" data-nav="/about">{{ t(key="nav-about", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/blog" data-nav="/blog">blog</a></li>
|
<li><a href="/blog" data-nav="/blog">{{ t(key="nav-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/audio/albums" data-nav="/audio/albums">audio</a></li>
|
<li><a href="/audio/albums" data-nav="/audio/albums">{{ t(key="nav-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/audio/tracks" data-nav="/audio/tracks">songs</a></li>
|
<li><a href="/audio/tracks" data-nav="/audio/tracks">{{ t(key="nav-songs", lang=lang | default(value='sk')) }}</a></li>
|
||||||
{% if logged_in_admin %}
|
{% if logged_in_admin %}
|
||||||
<li><a href="/admin/dashboard" class="t-yellow" data-nav="/admin">admin</a></li>
|
<li><a href="/admin/dashboard" class="t-yellow" data-nav="/admin">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li>
|
<li>
|
||||||
<form method="post" action="/admin/logout" hx-boost="false">
|
<form method="post" action="/admin/logout" hx-boost="false">
|
||||||
<button type="submit" class="t-red w-full">logout</button>
|
<button type="submit" class="t-red w-full">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="/admin/login" data-nav="/admin/login">login</a></li>
|
<li><a href="/admin/login" data-nav="/admin/login">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="term-nav-right">
|
<div class="term-nav-right">
|
||||||
<div class="dropdown dropdown-end md:hidden">
|
<div class="dropdown dropdown-end md:hidden">
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="Menu">
|
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" class="h-5 w-5">
|
stroke="currentColor" class="h-5 w-5">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||||
@@ -287,25 +287,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul tabindex="0"
|
<ul tabindex="0"
|
||||||
class="menu dropdown-content z-50 mt-3 w-52 border border-base-300 bg-base-200 p-2 shadow-lg">
|
class="menu dropdown-content z-50 mt-3 w-52 border border-base-300 bg-base-200 p-2 shadow-lg">
|
||||||
<li><a href="/">home</a></li>
|
<li><a href="/">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/about">about</a></li>
|
<li><a href="/about">{{ t(key="nav-about", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/blog">blog</a></li>
|
<li><a href="/blog">{{ t(key="nav-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/audio/albums">audio</a></li>
|
<li><a href="/audio/albums">{{ t(key="nav-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li><a href="/audio/tracks">songs</a></li>
|
<li><a href="/audio/tracks">{{ t(key="nav-songs", lang=lang | default(value='sk')) }}</a></li>
|
||||||
{% if logged_in_admin %}
|
{% if logged_in_admin %}
|
||||||
<li><a href="/admin/dashboard" class="t-yellow">admin</a></li>
|
<li><a href="/admin/dashboard" class="t-yellow">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||||
<li>
|
<li>
|
||||||
<form method="post" action="/admin/logout">
|
<form method="post" action="/admin/logout">
|
||||||
<button type="submit" class="t-red w-full">logout</button>
|
<button type="submit" class="t-red w-full">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="/admin/login">login</a></li>
|
<li><a href="/admin/login">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="Settings" title="Settings">
|
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}" title="{{ t(key='settings', lang=lang | default(value='sk')) }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" class="h-5 w-5">
|
stroke="currentColor" class="h-5 w-5">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
@@ -314,10 +314,31 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 border border-base-300 bg-base-200 p-2 shadow-lg">
|
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 border border-base-300 bg-base-200 p-2 shadow-lg">
|
||||||
<li class="menu-title">:set theme</li>
|
<li class="menu-title">{{ t(key="settings-language", lang=lang | default(value='sk')) }}</li>
|
||||||
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">system <span class="opt-check ml-auto hidden">✓</span></button></li>
|
<li>
|
||||||
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">light <span class="opt-check ml-auto hidden">✓</span></button></li>
|
<form method="post" action="/lang" hx-boost="false">
|
||||||
<li><button type="button" data-theme-opt="dark" onclick="setTheme('dark')">dark <span class="opt-check ml-auto hidden">✓</span></button></li>
|
<button type="submit" name="lang" value="en" class="btn btn-ghost btn-sm w-full justify-start">
|
||||||
|
<span>English</span>
|
||||||
|
{% if lang | default(value='sk') == 'en' %}
|
||||||
|
<span class="ml-auto">✓</span>
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<form method="post" action="/lang" hx-boost="false">
|
||||||
|
<button type="submit" name="lang" value="sk" class="btn btn-ghost btn-sm w-full justify-start">
|
||||||
|
<span>Slovenčina</span>
|
||||||
|
{% if lang | default(value='sk') == 'sk' %}
|
||||||
|
<span class="ml-auto">✓</span>
|
||||||
|
{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<li class="menu-title">{{ t(key="settings-theme", lang=lang | default(value='sk')) }}</li>
|
||||||
|
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">{{ t(key="theme-system", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||||
|
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">{{ t(key="theme-light", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||||
|
<li><button type="button" data-theme-opt="dark" onclick="setTheme('dark')">{{ t(key="theme-dark", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -329,7 +350,7 @@
|
|||||||
</main>
|
</main>
|
||||||
{% if logged_in_admin %}
|
{% if logged_in_admin %}
|
||||||
<footer class="term-statusline">
|
<footer class="term-statusline">
|
||||||
<span class="term-seg is-mode">ADMIN</span>
|
<span class="term-seg is-mode">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</span>
|
||||||
<span class="term-seg">universal-web</span>
|
<span class="term-seg">universal-web</span>
|
||||||
<span class="term-seg is-fill">~/{% block crumb %}{% endblock crumb %}</span>
|
<span class="term-seg is-fill">~/{% block crumb %}{% endblock crumb %}</span>
|
||||||
<span class="term-seg">utf-8</span>
|
<span class="term-seg">utf-8</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Blog{% endblock title %}
|
{% block title %}{{ t(key="blog-title", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||||
{% block crumb %}blog{% endblock crumb %}
|
{% block crumb %}blog{% endblock crumb %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -11,11 +11,11 @@
|
|||||||
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/blog</span><span class="t-dim">$</span>
|
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/blog</span><span class="t-dim">$</span>
|
||||||
ls -la
|
ls -la
|
||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">blog</h1>
|
<h1 class="term-title">{{ t(key="blog-title", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">// {{ articles | length }} published article(s).</p>
|
<p class="term-sub">// {{ articles | length }} {{ t(key="blog-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/admin/blog/articles" class="btn btn-outline btn-sm">[ manage ]</a>
|
<a href="/admin/blog/articles" class="btn btn-outline btn-sm">[ {{ t(key="blog-manage", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
||||||
<span class="term-head-meta term-tag">post</span>
|
<span class="term-head-meta term-tag">{{ t(key="post", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">
|
<h2 class="card-title text-base">
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<p class="term-prose text-sm opacity-80">{{ article.excerpt }}</p>
|
<p class="term-prose text-sm opacity-80">{{ article.excerpt }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">[ cat → ]</a>
|
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">{{ t(key="blog-read", lang=lang | default(value='sk')) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -46,15 +46,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="term-empty">
|
<div class="term-empty">
|
||||||
<p class="font-medium">no published posts yet</p>
|
<p class="font-medium">{{ t(key="blog-no-posts", lang=lang | default(value='sk')) }}</p>
|
||||||
<p class="term-empty-cmd">$ ls ~/blog → 0 results</p>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<header class="term-cmd">
|
<header class="term-cmd">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="term-title">blog</h1>
|
<h1 class="term-title">{{ t(key="blog-title", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">{{ articles | length }} published article(s).</p>
|
<p class="term-sub">{{ articles | length }} {{ t(key="blog-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@
|
|||||||
<article class="card">
|
<article class="card">
|
||||||
<div class="term-head">
|
<div class="term-head">
|
||||||
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
||||||
<span class="term-head-meta term-tag">post</span>
|
<span class="term-head-meta term-tag">{{ t(key="post", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">
|
<h2 class="card-title text-base">
|
||||||
@@ -74,7 +73,7 @@
|
|||||||
<p class="term-prose text-sm opacity-80">{{ article.excerpt }}</p>
|
<p class="term-prose text-sm opacity-80">{{ article.excerpt }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">read</a>
|
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">{{ t(key="blog-read", lang=lang | default(value='sk')) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -82,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="term-empty">
|
<div class="term-empty">
|
||||||
<p class="font-medium">no published posts yet</p>
|
<p class="font-medium">{{ t(key="blog-no-posts", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
cat {{ article.slug }}.txt
|
cat {{ article.slug }}.txt
|
||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">{{ article.title }}</h1>
|
<h1 class="term-title">{{ article.title }}</h1>
|
||||||
<p class="term-sub">// {{ article.view_count }} view(s) logged.</p>
|
<p class="term-sub">// {{ article.view_count }} {{ t(key="blog-views", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/blog" class="btn btn-outline btn-sm">[ cd .. ]</a>
|
<a href="/blog" class="btn btn-outline btn-sm">[ {{ t(key="cd-up", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
||||||
<span class="term-head-meta term-tag is-blue">readonly</span>
|
<span class="term-head-meta term-tag is-blue">{{ t(key="readonly", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if article.excerpt %}
|
{% if article.excerpt %}
|
||||||
@@ -39,17 +39,17 @@
|
|||||||
<header class="term-cmd">
|
<header class="term-cmd">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="term-title">{{ article.title }}</h1>
|
<h1 class="term-title">{{ article.title }}</h1>
|
||||||
<p class="term-sub">{{ article.view_count }} view(s) logged.</p>
|
<p class="term-sub">{{ article.view_count }} {{ t(key="blog-views", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/blog" class="btn btn-outline btn-sm">[ cd .. ]</a>
|
<a href="/blog" class="btn btn-outline btn-sm">[ {{ t(key="cd-up", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<div class="term-head">
|
<div class="term-head">
|
||||||
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
||||||
<span class="term-head-meta term-tag is-blue">readonly</span>
|
<span class="term-head-meta term-tag is-blue">{{ t(key="readonly", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if article.excerpt %}
|
{% if article.excerpt %}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
find this tera template at <code>assets/views/home/hello.html</code>:
|
find this tera template at <code>assets/views/home/hello.html</code>:
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
{{ t(key="hello-world", lang="en-US") }},
|
{{ t(key="hello-world", lang="sk") }},
|
||||||
<br/>
|
<br/>
|
||||||
{{ t(key="hello-world", lang="de-DE") }}
|
{{ t(key="hello-world", lang="en") }}
|
||||||
|
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Home{% endblock title %}
|
{% block title %}{{ t(key="home-title", lang=lang | default(value='sk')) }}{% endblock title %}
|
||||||
{% block crumb %}{% endblock crumb %}
|
{% block crumb %}{% endblock crumb %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -11,23 +11,23 @@
|
|||||||
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~</span><span class="t-dim">$</span>
|
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~</span><span class="t-dim">$</span>
|
||||||
ls -la
|
ls -la
|
||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">home</h1>
|
<h1 class="term-title">{{ t(key="home-title", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">// latest news and updates.</p>
|
<p class="term-sub">// {{ t(key="home-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/blog" class="btn btn-outline btn-sm">[ all posts ]</a>
|
<a href="/blog" class="btn btn-outline btn-sm">[ {{ t(key="home-all-posts", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="term-screen mb-6">
|
<div class="term-screen mb-6">
|
||||||
<p class="line" data-p="visitor@universal-web:~$">whoami</p>
|
<p class="line" data-p="visitor@universal-web:~$">whoami</p>
|
||||||
<p class="line out">→ guitar player - original songs, albums and notes</p>
|
<p class="line out">→ {{ t(key="home-tagline", lang=lang | default(value='sk')) }}</p>
|
||||||
<p class="line" data-p="visitor@universal-web:~$">ls ~/sections</p>
|
<p class="line" data-p="visitor@universal-web:~$">ls ~/sections</p>
|
||||||
<p class="line out">about/ blog/ audio/ songs/</p>
|
<p class="line out">{{ t(key="home-sections", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>recent posts <span class="t-dim">({{ articles | length }})</span></p>
|
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>{{ t(key="home-recent", lang=lang | default(value='sk')) }} <span class="t-dim">({{ articles | length }})</span></p>
|
||||||
{% if articles | length > 0 %}
|
{% if articles | length > 0 %}
|
||||||
<div class="term-stack">
|
<div class="term-stack">
|
||||||
{% for article in articles %}
|
{% for article in articles %}
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
||||||
<span class="term-head-meta term-tag">post</span>
|
<span class="term-head-meta term-tag">{{ t(key="post", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">
|
<h2 class="card-title text-base">
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<p class="term-prose text-sm opacity-80">{{ article.excerpt }}</p>
|
<p class="term-prose text-sm opacity-80">{{ article.excerpt }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">[ cat → ]</a>
|
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">{{ t(key="blog-read", lang=lang | default(value='sk')) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -55,36 +55,35 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="term-empty">
|
<div class="term-empty">
|
||||||
<p class="font-medium">no published posts yet</p>
|
<p class="font-medium">{{ t(key="home-no-posts", lang=lang | default(value='sk')) }}</p>
|
||||||
<p class="term-empty-cmd">$ ls ~/blog → 0 results</p>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
{% else %}
|
{% else %}
|
||||||
<header class="term-cmd">
|
<header class="term-cmd">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="term-title">home</h1>
|
<h1 class="term-title">{{ t(key="home-title", lang=lang | default(value='sk')) }}</h1>
|
||||||
<p class="term-sub">latest news and updates.</p>
|
<p class="term-sub">{{ t(key="home-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/blog" class="btn btn-outline btn-sm">all posts</a>
|
<a href="/blog" class="btn btn-outline btn-sm">{{ t(key="home-all-posts", lang=lang | default(value='sk')) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="term-screen mb-6">
|
<div class="term-screen mb-6">
|
||||||
<p class="line">guitar player - original songs, albums and notes</p>
|
<p class="line">{{ t(key="home-tagline", lang=lang | default(value='sk')) }}</p>
|
||||||
<p class="line out">about/ blog/ audio/ songs/</p>
|
<p class="line out">{{ t(key="home-sections", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>recent posts <span class="t-dim">({{ articles | length }})</span></p>
|
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>{{ t(key="home-recent", lang=lang | default(value='sk')) }} <span class="t-dim">({{ articles | length }})</span></p>
|
||||||
{% if articles | length > 0 %}
|
{% if articles | length > 0 %}
|
||||||
<div class="term-stack">
|
<div class="term-stack">
|
||||||
{% for article in articles %}
|
{% for article in articles %}
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<div class="term-head">
|
<div class="term-head">
|
||||||
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
|
||||||
<span class="term-head-meta term-tag">post</span>
|
<span class="term-head-meta term-tag">{{ t(key="post", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">
|
<h2 class="card-title text-base">
|
||||||
@@ -94,7 +93,7 @@
|
|||||||
<p class="text-sm opacity-80">{{ article.excerpt }}</p>
|
<p class="text-sm opacity-80">{{ article.excerpt }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">cat</a>
|
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">{{ t(key="blog-read", lang=lang | default(value='sk')) }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -102,7 +101,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="term-empty">
|
<div class="term-empty">
|
||||||
<p class="font-medium">no published posts yet</p>
|
<p class="font-medium">{{ t(key="home-no-posts", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
cat about.txt
|
cat about.txt
|
||||||
</p>
|
</p>
|
||||||
<h1 class="term-title">{{ page.title }}</h1>
|
<h1 class="term-title">{{ page.title }}</h1>
|
||||||
<p class="term-sub">// about this site.</p>
|
<p class="term-sub">// {{ t(key="about-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="term-cmd-actions">
|
<div class="term-cmd-actions">
|
||||||
<a href="/admin/about" class="btn btn-outline btn-sm">[ edit ]</a>
|
<a href="/admin/about" class="btn btn-outline btn-sm">[ {{ t(key="edit", lang=lang | default(value='sk')) }} ]</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="term-head-name">~/about.txt</span>
|
<span class="term-head-name">~/about.txt</span>
|
||||||
<span class="term-head-meta term-tag is-blue">readonly</span>
|
<span class="term-head-meta term-tag is-blue">{{ t(key="readonly", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="term-prose whitespace-pre-line">{{ page.content }}</div>
|
<div class="term-prose whitespace-pre-line">{{ page.content }}</div>
|
||||||
@@ -35,14 +35,14 @@
|
|||||||
<header class="term-cmd">
|
<header class="term-cmd">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="term-title">{{ page.title }}</h1>
|
<h1 class="term-title">{{ page.title }}</h1>
|
||||||
<p class="term-sub">about this site.</p>
|
<p class="term-sub">{{ t(key="about-sub", lang=lang | default(value='sk')) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<div class="term-head">
|
<div class="term-head">
|
||||||
<span class="term-head-name">~/about.txt</span>
|
<span class="term-head-name">~/about.txt</span>
|
||||||
<span class="term-head-meta term-tag is-blue">readonly</span>
|
<span class="term-head-meta term-tag is-blue">{{ t(key="readonly", lang=lang | default(value='sk')) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="term-prose whitespace-pre-line">{{ page.content }}</div>
|
<div class="term-prose whitespace-pre-line">{{ page.content }}</div>
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ impl Hooks for App {
|
|||||||
.add_route(controllers::auth::routes())
|
.add_route(controllers::auth::routes())
|
||||||
.add_route(controllers::admin::routes())
|
.add_route(controllers::admin::routes())
|
||||||
.add_route(controllers::blog::routes())
|
.add_route(controllers::blog::routes())
|
||||||
|
.add_route(controllers::i18n::routes())
|
||||||
.add_route(controllers::media::routes())
|
.add_route(controllers::media::routes())
|
||||||
.add_route(controllers::pages::routes())
|
.add_route(controllers::pages::routes())
|
||||||
.add_route(controllers::frontend::routes())
|
.add_route(controllers::frontend::routes())
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
controllers::{admin, auth as auth_controller},
|
controllers::{admin, auth as auth_controller, i18n::current_lang},
|
||||||
models::{
|
models::{
|
||||||
_entities::{blog_articles, site_pages},
|
_entities::{blog_articles, site_pages},
|
||||||
users::{self, LoginParams},
|
users::{self, LoginParams},
|
||||||
@@ -122,7 +122,11 @@ async fn home(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"home/index.html",
|
"home/index.html",
|
||||||
json!({ "articles": articles, "logged_in_admin": logged_in_admin(&ctx, &jar).await }),
|
json!({
|
||||||
|
"articles": articles,
|
||||||
|
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +142,7 @@ async fn about(
|
|||||||
json!({
|
json!({
|
||||||
"page": about_page(&ctx).await?,
|
"page": about_page(&ctx).await?,
|
||||||
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -157,7 +162,11 @@ async fn blog_index(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"blog/index.html",
|
"blog/index.html",
|
||||||
json!({ "articles": articles, "logged_in_admin": logged_in_admin(&ctx, &jar).await }),
|
json!({
|
||||||
|
"articles": articles,
|
||||||
|
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +192,11 @@ async fn blog_show(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"blog/show.html",
|
"blog/show.html",
|
||||||
json!({ "article": article, "logged_in_admin": logged_in_admin(&ctx, &jar).await }),
|
json!({
|
||||||
|
"article": article,
|
||||||
|
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,12 +213,17 @@ async fn admin_login_page(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"admin/login.html",
|
"admin/login.html",
|
||||||
json!({ "error": null, "logged_in_admin": false }),
|
json!({
|
||||||
|
"error": null,
|
||||||
|
"logged_in_admin": false,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_login(
|
async fn admin_login(
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
Form(params): Form<LoginParams>,
|
Form(params): Form<LoginParams>,
|
||||||
@@ -214,7 +232,11 @@ async fn admin_login(
|
|||||||
return format::view(
|
return format::view(
|
||||||
&v,
|
&v,
|
||||||
"admin/login.html",
|
"admin/login.html",
|
||||||
json!({ "error": "Invalid credentials", "logged_in_admin": false }),
|
json!({
|
||||||
|
"error": "Invalid credentials",
|
||||||
|
"logged_in_admin": false,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -222,7 +244,11 @@ async fn admin_login(
|
|||||||
return format::view(
|
return format::view(
|
||||||
&v,
|
&v,
|
||||||
"admin/login.html",
|
"admin/login.html",
|
||||||
json!({ "error": "Invalid credentials", "logged_in_admin": false }),
|
json!({
|
||||||
|
"error": "Invalid credentials",
|
||||||
|
"logged_in_admin": false,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,21 +272,31 @@ async fn admin_logout() -> Result<Response> {
|
|||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_home(
|
async fn admin_home(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let admin_user = admin::current_admin(auth, &ctx).await?;
|
let admin_user = admin::current_admin(auth, &ctx).await?;
|
||||||
format::view(&v, "admin/index.html", json!({ "admin": admin_user }))
|
format::view(
|
||||||
|
&v,
|
||||||
|
"admin/index.html",
|
||||||
|
json!({ "admin": admin_user, "lang": current_lang(&jar) }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_about(
|
async fn admin_about(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
format::view(&v, "admin/about.html", json!({ "page": about_page(&ctx).await? }))
|
format::view(
|
||||||
|
&v,
|
||||||
|
"admin/about.html",
|
||||||
|
json!({ "page": about_page(&ctx).await?, "lang": current_lang(&jar) }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
@@ -280,6 +316,7 @@ async fn admin_about_update(
|
|||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_articles(
|
async fn admin_articles(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
@@ -288,17 +325,22 @@ async fn admin_articles(
|
|||||||
.order_by_desc(blog_articles::Column::CreatedAt)
|
.order_by_desc(blog_articles::Column::CreatedAt)
|
||||||
.all(&ctx.db)
|
.all(&ctx.db)
|
||||||
.await?;
|
.await?;
|
||||||
format::view(&v, "admin/blog/index.html", json!({ "articles": articles }))
|
format::view(
|
||||||
|
&v,
|
||||||
|
"admin/blog/index.html",
|
||||||
|
json!({ "articles": articles, "lang": current_lang(&jar) }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_article_new(
|
async fn admin_article_new(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
format::view(&v, "admin/blog/new.html", json!({}))
|
format::view(&v, "admin/blog/new.html", json!({ "lang": current_lang(&jar) }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
@@ -332,6 +374,7 @@ async fn admin_article_create(
|
|||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_article_edit(
|
async fn admin_article_edit(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
@@ -340,7 +383,7 @@ async fn admin_article_edit(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"admin/blog/edit.html",
|
"admin/blog/edit.html",
|
||||||
json!({ "article": article_by_id(&ctx, id).await? }),
|
json!({ "article": article_by_id(&ctx, id).await?, "lang": current_lang(&jar) }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
60
src/controllers/i18n.rs
Normal file
60
src/controllers/i18n.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use axum::{
|
||||||
|
http::{header, HeaderMap},
|
||||||
|
response::Redirect,
|
||||||
|
};
|
||||||
|
use loco_rs::prelude::*;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
pub const LANG_COOKIE: &str = "lang";
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct LangForm {
|
||||||
|
pub lang: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_lang(jar: &axum_extra::extract::cookie::CookieJar) -> String {
|
||||||
|
match jar.get(LANG_COOKIE).map(|cookie| cookie.value().to_string()) {
|
||||||
|
Some(ref lang) if lang == "en" => "en".to_string(),
|
||||||
|
_ => "sk".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn set_lang(headers: HeaderMap, Form(form): Form<LangForm>) -> Result<Response> {
|
||||||
|
let lang = if form.lang == "en" { "en" } else { "sk" };
|
||||||
|
let cookie = format!("{LANG_COOKIE}={lang}; Path=/; Max-Age=31536000; SameSite=Lax");
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
[(header::SET_COOKIE, cookie)],
|
||||||
|
Redirect::to(&back_path(&headers)),
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn back_path(headers: &HeaderMap) -> String {
|
||||||
|
let raw = headers
|
||||||
|
.get(header::REFERER)
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
.unwrap_or("/");
|
||||||
|
|
||||||
|
if raw.starts_with('/') {
|
||||||
|
return raw.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(after_scheme) = raw.split_once("://").map(|(_, rest)| rest) {
|
||||||
|
if let Some(path_start) = after_scheme.find('/') {
|
||||||
|
let path = &after_scheme[path_start..];
|
||||||
|
return if path.starts_with('/') {
|
||||||
|
path.to_string()
|
||||||
|
} else {
|
||||||
|
"/".to_string()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"/".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes() -> Routes {
|
||||||
|
Routes::new().add("/lang", post(set_lang))
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
controllers::{admin, auth as auth_controller},
|
controllers::{admin, auth as auth_controller, i18n::current_lang},
|
||||||
models::{
|
models::{
|
||||||
_entities::{audio_albums, audio_tracks},
|
_entities::{audio_albums, audio_tracks},
|
||||||
users,
|
users,
|
||||||
@@ -439,6 +439,7 @@ async fn image_upload(auth: auth::JWT, State(ctx): State<AppContext>, multipart:
|
|||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_images(
|
async fn admin_images(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
Query(query): Query<HashMap<String, String>>,
|
Query(query): Query<HashMap<String, String>>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
@@ -451,6 +452,7 @@ async fn admin_images(
|
|||||||
json!({
|
json!({
|
||||||
"uploaded": uploaded,
|
"uploaded": uploaded,
|
||||||
"uploaded_url": uploaded.map(|filename| format!("/images/{filename}")),
|
"uploaded_url": uploaded.map(|filename| format!("/images/{filename}")),
|
||||||
|
"lang": current_lang(&jar),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -507,7 +509,11 @@ async fn public_albums(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"audio/albums.html",
|
"audio/albums.html",
|
||||||
json!({ "albums": albums, "logged_in_admin": logged_in_admin(&ctx, &jar).await }),
|
json!({
|
||||||
|
"albums": albums,
|
||||||
|
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,6 +550,7 @@ async fn public_album(
|
|||||||
"album": album,
|
"album": album,
|
||||||
"tracks": tracks,
|
"tracks": tracks,
|
||||||
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -599,13 +606,18 @@ async fn public_tracks(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"audio/tracks.html",
|
"audio/tracks.html",
|
||||||
json!({ "tracks": tracks, "logged_in_admin": logged_in_admin(&ctx, &jar).await }),
|
json!({
|
||||||
|
"tracks": tracks,
|
||||||
|
"logged_in_admin": logged_in_admin(&ctx, &jar).await,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_albums(
|
async fn admin_albums(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
@@ -623,12 +635,17 @@ async fn admin_albums(
|
|||||||
rows.push(json!({ "album": album, "track_count": track_count }));
|
rows.push(json!({ "album": album, "track_count": track_count }));
|
||||||
}
|
}
|
||||||
|
|
||||||
format::view(&v, "admin/audio/albums.html", json!({ "albums": rows }))
|
format::view(
|
||||||
|
&v,
|
||||||
|
"admin/audio/albums.html",
|
||||||
|
json!({ "albums": rows, "lang": current_lang(&jar) }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_tracks(
|
async fn admin_tracks(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
@@ -638,18 +655,34 @@ async fn admin_tracks(
|
|||||||
.all(&ctx.db)
|
.all(&ctx.db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
format::view(&v, "admin/audio/songs.html", json!({ "tracks": tracks }))
|
format::view(
|
||||||
|
&v,
|
||||||
|
"admin/audio/songs.html",
|
||||||
|
json!({ "tracks": tracks, "lang": current_lang(&jar) }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_album_new(auth: auth::JWT, ViewEngine(v): ViewEngine<TeraView>, State(ctx): State<AppContext>) -> Result<Response> {
|
async fn admin_album_new(
|
||||||
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
let available_tracks = audio_tracks::Entity::find()
|
let available_tracks = audio_tracks::Entity::find()
|
||||||
.filter(audio_tracks::Column::AlbumId.is_null())
|
.filter(audio_tracks::Column::AlbumId.is_null())
|
||||||
.order_by_asc(audio_tracks::Column::Title)
|
.order_by_asc(audio_tracks::Column::Title)
|
||||||
.all(&ctx.db)
|
.all(&ctx.db)
|
||||||
.await?;
|
.await?;
|
||||||
format::view(&v, "admin/audio/new_album.html", json!({ "available_tracks": available_tracks }))
|
format::view(
|
||||||
|
&v,
|
||||||
|
"admin/audio/new_album.html",
|
||||||
|
json!({
|
||||||
|
"available_tracks": available_tracks,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
@@ -713,6 +746,7 @@ async fn admin_album_create(
|
|||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_album_tracks(
|
async fn admin_album_tracks(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
Path(album_id): Path<Uuid>,
|
Path(album_id): Path<Uuid>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
@@ -734,7 +768,12 @@ async fn admin_album_tracks(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"admin/audio/tracks.html",
|
"admin/audio/tracks.html",
|
||||||
json!({ "album": album, "tracks": tracks, "available_tracks": available_tracks }),
|
json!({
|
||||||
|
"album": album,
|
||||||
|
"tracks": tracks,
|
||||||
|
"available_tracks": available_tracks,
|
||||||
|
"lang": current_lang(&jar),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,6 +823,7 @@ async fn admin_track_remove_from_album(
|
|||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_track_upload_form(
|
async fn admin_track_upload_form(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
Path(album_id): Path<Uuid>,
|
Path(album_id): Path<Uuid>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
@@ -792,18 +832,23 @@ async fn admin_track_upload_form(
|
|||||||
format::view(
|
format::view(
|
||||||
&v,
|
&v,
|
||||||
"admin/audio/upload_track.html",
|
"admin/audio/upload_track.html",
|
||||||
json!({ "album": album_by_id(&ctx, album_id).await? }),
|
json!({ "album": album_by_id(&ctx, album_id).await?, "lang": current_lang(&jar) }),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn admin_song_upload_form(
|
async fn admin_song_upload_form(
|
||||||
auth: auth::JWT,
|
auth: auth::JWT,
|
||||||
|
jar: CookieJar,
|
||||||
ViewEngine(v): ViewEngine<TeraView>,
|
ViewEngine(v): ViewEngine<TeraView>,
|
||||||
State(ctx): State<AppContext>,
|
State(ctx): State<AppContext>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
admin::current_admin(auth, &ctx).await?;
|
admin::current_admin(auth, &ctx).await?;
|
||||||
format::view(&v, "admin/audio/upload_track.html", json!({ "album": null }))
|
format::view(
|
||||||
|
&v,
|
||||||
|
"admin/audio/upload_track.html",
|
||||||
|
json!({ "album": null, "lang": current_lang(&jar) }),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_uploaded_track(
|
async fn create_uploaded_track(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod blog;
|
pub mod blog;
|
||||||
|
pub mod i18n;
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
pub mod pages;
|
pub mod pages;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ impl Initializer for ViewEngineInitializer {
|
|||||||
async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
|
async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
|
||||||
let tera_engine = if std::path::Path::new(I18N_DIR).exists() {
|
let tera_engine = if std::path::Path::new(I18N_DIR).exists() {
|
||||||
let arc = std::sync::Arc::new(
|
let arc = std::sync::Arc::new(
|
||||||
ArcLoader::builder(&I18N_DIR, unic_langid::langid!("en-US"))
|
ArcLoader::builder(&I18N_DIR, unic_langid::langid!("sk"))
|
||||||
.shared_resources(Some(&[I18N_SHARED.into()]))
|
.shared_resources(Some(&[I18N_SHARED.into()]))
|
||||||
.customize(|bundle| bundle.set_use_isolating(false))
|
.customize(|bundle| bundle.set_use_isolating(false))
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
Reference in New Issue
Block a user