1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-11-09 20:00:12 +00:00

setup: flex-ify, light/dark, keep page position on reload

got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.

The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).

`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.

Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
This commit is contained in:
Harvey Tindall 2024-08-21 16:13:17 +01:00
parent e5f79c60ae
commit 2057823b7a
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
6 changed files with 577 additions and 485 deletions

View File

@ -18,6 +18,8 @@
--bg-light: #fff; --bg-light: #fff;
--bg-dark: #101010; --bg-dark: #101010;
color-scheme: light;
} }
.light { .light {
@ -26,6 +28,7 @@
.dark { .dark {
--settings-section-button-filter: 80%; --settings-section-button-filter: 80%;
color-scheme: dark !important;
} }
.dark body { .dark body {
@ -456,3 +459,14 @@ input[type="checkbox" i], [class^="ri-"], [class*=" ri-"], .ri-refresh-line:befo
margin-bottom: -0.5rem; margin-bottom: -0.5rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
section.section:not(.\~neutral) {
background-color: inherit;
}
@layer components {
.switch input {
@apply mr-1;
}
}

View File

@ -7,7 +7,9 @@
</head> </head>
<body class="max-w-full overflow-x-hidden section"> <body class="max-w-full overflow-x-hidden section">
<div id="notification-box"></div> <div id="notification-box"></div>
<div class="top-2 left-2 absolute"> <div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4">
<div class="top-2 inset-x-2 lg:absolute flex flex-row justify-between">
<div class="flex flex-row gap-2">
<span class="dropdown" tabindex="0" id="lang-dropdown"> <span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button"> <span class="button ~urge dropdown-button">
<i class="ri-global-line"></i> <i class="ri-global-line"></i>
@ -19,79 +21,83 @@
</div> </div>
</span> </span>
</div> </div>
<div class="page-container m-2 lg:my-20 lg:mx-64" id="page-container"> <span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
<div class="card ~neutral @low mb-2">
<div class="row">
<img class="banner header" src="banner.svg" alt="jfa-go" />
</div> </div>
<div class="row col flex center"> <div class="card sectioned ~neutral @low flex flex-col gap-4 justify-between items-center">
<img class="w-[105%] max-w-none" src="banner.svg" alt="jfa-go" />
<span class="heading welcome">{{ .lang.StartPage.welcome }}</span> <span class="heading welcome">{{ .lang.StartPage.welcome }}</span>
</div> <p class="content text-center">{{ .lang.StartPage.pressStart }}</p>
<div class="row col flex center"> <section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<p class="content my-2">{{ .lang.StartPage.pressStart }}</p>
</div>
<section class="section ~neutral banner footer flex flex-row justify-between middle">
<span class="support">{{ .lang.StartPage.httpsNotice }}</span> <span class="support">{{ .lang.StartPage.httpsNotice }}</span>
<span class="button ~urge @low next">{{ .lang.StartPage.start }}</span> <span class="button ~urge @low next">{{ .lang.StartPage.start }}</span>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.Language.title }}</span> <span class="heading">{{ .lang.Language.title }}</span>
<p class="content my-2" id="language-description"></p> <p class="content" id="language-description"></p>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Language.defaultAdminLang }}</span> <span>{{ .lang.Language.defaultAdminLang }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="ui-language-admin"> <select id="ui-language-admin">
</select> </select>
</div> </div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Language.defaultFormLang }}</span> <span>{{ .lang.Language.defaultFormLang }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="ui-language-form"> <select id="ui-language-form">
</select> </select>
</div> </div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Language.defaultEmailLang }}</span> <span>{{ .lang.Language.defaultEmailLang }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="email-language"> <select id="email-language">
</select> </select>
</div> </div>
</label> </label>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.General.title }}</span> <span class="heading">{{ .lang.General.title }}</span>
<div class="row"> <div class="flex flex-row gap-2 justify-between">
<div class="col"> <div class="flex flex-col gap-2">
<label class="label"> <div class="flex flex-row gap-2 justify-between">
<span class="mt-4">{{ .lang.General.listenAddress }}</span> <label class="label flex flex-col gap-2 grow">
<input type="url" class="input ~neutral @low mt-4 mb-2" id="ui-host" value="0.0.0.0"> <span>{{ .lang.General.listenAddress }}</span>
<input type="url" class="input ~neutral @low" id="ui-host" value="0.0.0.0">
</label> </label>
<label class="row switch"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="advanced-tls"><span>{{ .lang.General.useHTTPS }}</span> <span>{{ .lang.Strings.port }}</span>
<input type="number" class="input ~neutral @low" id="ui-port" value="8056">
</label> </label>
<p class="support mb-2 mt-1">{{ .lang.General.useHTTPSNotice }}</p> </div>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.General.pathToCertificate }}</span> <div class="switch"><input type="checkbox" id="advanced-tls"><span>{{ .lang.General.useHTTPS }}</span></div>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_cert"> <p class="support">{{ .lang.General.useHTTPSNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.General.pathToKeyFile }}</span> <span>{{ .lang.General.pathToCertificate }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_key"> <input type="text" class="input ~neutral @low" id="advanced-tls_cert">
</label>
<label class="label flex flex-col gap-2">
<span>{{ .lang.General.pathToKeyFile }}</span>
<input type="text" class="input ~neutral @low" id="advanced-tls_key">
</label> </label>
<span class="heading">{{ .lang.Updates.title }}</span> <span class="heading">{{ .lang.Updates.title }}</span>
<p class="content my-2" id="updates-description"></p> <p class="content" id="updates-description"></p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="updates-enabled" checked><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="updates-enabled" checked><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span>{{ .lang.Updates.updateChannel }}</span> <span>{{ .lang.Updates.updateChannel }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="updates-channel"> <select id="updates-channel">
<option value="stable">{{ .lang.Updates.stable }}</option> <option value="stable">{{ .lang.Updates.stable }}</option>
<option value="unstable">{{ .lang.Updates.unstable }}</option> <option value="unstable">{{ .lang.Updates.unstable }}</option>
@ -99,28 +105,24 @@
</div> </div>
</label> </label>
</div> </div>
<div class="col"> <div class="flex flex-col gap-2">
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.port }}</span> <span>{{ .lang.General.httpsPort }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="ui-port" value="8056"> <input type="number" class="input ~neutral @low" id="advanced-tls_port" value="8057">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.General.httpsPort }}</span> <span>{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_port" value="8057"> <input type="text" class="input ~neutral @low" id="ui-url_base" placeholder="/mysubfolder">
<p class="support">{{ .lang.General.urlBaseNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span> <span>{{ .lang.General.externalURL }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ui-url_base" placeholder="/mysubfolder"> <input type="text" class="input ~neutral @low" id="ui-jfa_url" placeholder="https://jellyf.in/mysubfolder">
<p class="support mb-2 mt-1">{{ .lang.General.urlBaseNotice }}</p> <p class="support">{{ .lang.General.externalURLNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.General.externalURL }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ui-jfa_url" placeholder="https://jellyf.in/mysubfolder">
<p class="support mb-2 mt-1">{{ .lang.General.externalURLNotice }}</p>
</label>
<label class="label">
<span>{{ .lang.Strings.theme }}</span> <span>{{ .lang.Strings.theme }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="ui-theme"> <select id="ui-theme">
<option value="Jellyfin (Dark)">{{ .lang.General.darkTheme }}</option> <option value="Jellyfin (Dark)">{{ .lang.General.darkTheme }}</option>
<option value="Default (Light)">{{ .lang.General.lightTheme }}</option> <option value="Default (Light)">{{ .lang.General.lightTheme }}</option>
@ -128,208 +130,221 @@
</div> </div>
</label> </label>
<span class="heading">{{ .lang.Proxy.title }}</span> <span class="heading">{{ .lang.Proxy.title }}</span>
<p class="content my-2" id="proxy-description">{{ .lang.Proxy.description }}</p> <p class="content" id="proxy-description">{{ .lang.Proxy.description }}</p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="advanced-proxy"><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="advanced-proxy"><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span>{{ .lang.Proxy.protocol }}</span> <span>{{ .lang.Proxy.protocol }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="advanced-proxy_protocol"> <select id="advanced-proxy_protocol">
<option value="http">HTTP</option> <option value="http">HTTP</option>
<option value="socks">SOCKS5</option> <option value="socks">SOCKS5</option>
</select> </select>
</div> </div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Proxy.address }}</span> <span>{{ .lang.Proxy.address }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-proxy_address"> <input type="text" class="input ~neutral @low" id="advanced-proxy_address">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.username }}</span> <span>{{ .lang.Strings.username }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-proxy_user"> <input type="text" class="input ~neutral @low" id="advanced-proxy_user">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.password }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-proxy_password">
</label>
</div>
</div>
<section class="section ~neutral banner footer flex flex-row justify-between middle">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section>
</div>
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.Login.title }}</span>
<p class="content my-2">{{ .lang.Login.description }}</p>
<div class="pl-4">
<label class="row switch pb-4">
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="true" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
</label>
<label class="row switch pl-4 pb-4">
<input type="checkbox" class="mr-2" id="ui-admin_only" checked><span>{{ .lang.Login.adminOnly }}</span>
</label>
<label class="row switch pl-4 pb-2">
<input type="checkbox" class="mr-2" id="ui-allow_all"><span>{{ .lang.Login.allowAll }}</span>
</label>
<p class="support pb-4 pl-4 mt-1" id="description-ui-allow_all">{{ .lang.Login.allowAllDescription }}</p>
<label class="row switch pb-4">
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
</label>
<p class="support pb-4 pl-4 mt-1">{{ .lang.Login.authorizeManualUserPageNotice }}</p>
</div>
<div id="login-manual">
<label class="label">
<span class="mt-4">{{ .lang.Strings.username }}</span>
<input type="text" id="ui-username" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.username }}">
</label>
<label class="label">
<span>{{ .lang.Strings.password }}</span> <span>{{ .lang.Strings.password }}</span>
<input type="password" id="ui-password" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.password }}"> <input type="text" class="input ~neutral @low" id="advanced-proxy_password">
</label>
<label class="label">
<span>{{ .lang.Strings.emailAddress }} ({{ .lang.Strings.optional }})</span>
<input type="email" id="ui-email" class="input ~neutral @low mt-4" placeholder="email@address">
<span class="support mb-2 mt-1">{{ .lang.Login.emailNotice }}</span>
</label> </label>
</div> </div>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </div>
</section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.Login.title }}</span>
<p class="content">{{ .lang.Login.description }}</p>
<div class="flex flex-col gap-2">
<label class="label flex flex-col gap-2">
<div class="switch"><input type="radio" name="ui-jellyfin_login" value="true" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span></div>
</label>
<div class="pl-4 flex flex-col gap-2">
<label class="label flex flex-col gap-2">
<div class="switch"><input type="checkbox" id="ui-admin_only" checked><span>{{ .lang.Login.adminOnly }}</span></div>
</label>
<label class="label flex flex-col gap-2">
<div class="switch"><input type="checkbox" id="ui-allow_all"><span>{{ .lang.Login.allowAll }}</span></div>
<p class="support" id="description-ui-allow_all">{{ .lang.Login.allowAllDescription }}</p>
</label>
</div>
</div>
<div class="flex flex-col gap-2">
<label class="label flex flex-col gap-2">
<div class="switch"><input type="radio" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span></div>
</label>
<p class="support">{{ .lang.Login.authorizeManualUserPageNotice }}</p>
</div>
<div class ="flex flex-col gap-2" id="login-manual">
<label class="label flex flex-col gap-2">
<span>{{ .lang.Strings.username }}</span>
<input type="text" id="ui-username" class="input ~neutral @low" placeholder="{{ .lang.Strings.username }}">
</label>
<label class="label flex flex-col gap-2">
<span>{{ .lang.Strings.password }}</span>
<input type="password" id="ui-password" class="input ~neutral @low" placeholder="{{ .lang.Strings.password }}">
</label>
<label class="label flex flex-col gap-2">
<span>{{ .lang.Strings.emailAddress }} ({{ .lang.Strings.optional }})</span>
<input type="email" id="ui-email" class="input ~neutral @low" placeholder="email@address">
<span class="support">{{ .lang.Login.emailNotice }}</span>
</label>
</div>
</section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section>
</div>
<div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.JellyfinEmby.title }}</span> <span class="heading">{{ .lang.JellyfinEmby.title }}</span>
<p class="content my-2">{{ .lang.JellyfinEmby.description }}</p> <p class="content">{{ .lang.JellyfinEmby.description }}</p>
<div class="row"> <div class="flex flex-row gap-2 justify-between">
<div class="col"> <div class="flex flex-col gap-2 grow">
<label class="label"> <label class="label flex flex-col gap-2">
<span>{{ .lang.Strings.serverType }}</span> <span>{{ .lang.Strings.serverType }}</span>
<div class="select ~neutral @low mt-4"> <div class="select ~neutral @low">
<select id="jellyfin-type"> <select id="jellyfin-type">
<option value="jellyfin">Jellyfin</option> <option value="jellyfin">Jellyfin</option>
<option value="emby">Emby</option> <option value="emby">Emby</option>
</select> </select>
</div> </div>
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.embyNotice }}</p> <p class="support">{{ .lang.JellyfinEmby.embyNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.JellyfinEmby.replaceJellyfin }} ({{ .lang.Strings.optional }})</span> <span>{{ .lang.JellyfinEmby.replaceJellyfin }} ({{ .lang.Strings.optional }})</span>
<input type="text" class="input ~neutral @low mt-4" id="jellyfin-substitute_jellyfin_strings"> <input type="text" class="input ~neutral @low" id="jellyfin-substitute_jellyfin_strings">
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.replaceJellyfinNotice }}</p> <p class="support">{{ .lang.JellyfinEmby.replaceJellyfinNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.username }}</span> <span>{{ .lang.Strings.username }}</span>
<input type="text" id="jellyfin-username" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.username }}"> <input type="text" id="jellyfin-username" class="input ~neutral @low" placeholder="{{ .lang.Strings.username }}">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span>{{ .lang.Strings.password }}</span> <span>{{ .lang.Strings.password }}</span>
<input type="password" id="jellyfin-password" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.password }}"> <input type="password" id="jellyfin-password" class="input ~neutral @low" placeholder="{{ .lang.Strings.password }}">
</label> </label>
</div> </div>
<div class="col"> <div class="flex flex-col gap-2 grow ">
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span> <span>{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="jellyfin-server" placeholder="http://jellyf.in:80"> <input type="url" class="input ~neutral @low" id="jellyfin-server" placeholder="http://jellyf.in:80">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span> <span>{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span>
<input type="url" class="input ~neutral @low mt-4" id="jellyfin-public_server" placeholder="https://jellyf.in"> <input type="url" class="input ~neutral @low" id="jellyfin-public_server" placeholder="https://jellyf.in">
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.addressExternalNotice }}</p> <p class="support">{{ .lang.JellyfinEmby.addressExternalNotice }}</p>
</label> </label>
</div> </div>
</div> </div>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div class="flex flex-row gap-2">
<span class="button ~urge @low" id="jellyfin-test-connection">{{ .lang.JellyfinEmby.testConnection }}</span> <span class="button ~urge @low" id="jellyfin-test-connection">{{ .lang.JellyfinEmby.testConnection }}</span>
<span class="button ~urge @low next" disabled>{{ .lang.Strings.next }}</span> <span class="button ~urge @low next" disabled>{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.Ombi.title }}</span> <span class="heading">{{ .lang.Ombi.title }}</span>
<p class="content my-2">{{ .lang.Ombi.description }}</p> <p class="content">{{ .lang.Ombi.description }}</p>
<aside class="aside ~warning my-2" id="ombi-stability-warning">{{ .lang.Ombi.stabilityWarning }}</aside> <aside class="aside ~warning" id="ombi-stability-warning">{{ .lang.Ombi.stabilityWarning }}</aside>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="ombi-enabled"><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="ombi-enabled"><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.serverAddress }}</span> <span>{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="ombi-server" placeholder="ombi.jellyf.in"> <input type="url" class="input ~neutral @low" id="ombi-server" placeholder="ombi.jellyf.in">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.apiKey }}</span> <span>{{ .lang.Strings.apiKey }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ombi-api_key"> <input type="text" class="input ~neutral @low" id="ombi-api_key">
<p class="support mb-2 mt-1">{{ .lang.Ombi.apiKeyNotice }}</p> <p class="support">{{ .lang.Ombi.apiKeyNotice }}</p>
</label> </label>
<span class="heading">{{ .lang.Jellyseerr.title }}</span> <span class="heading">{{ .lang.Jellyseerr.title }}</span>
<p class="content my-2">{{ .lang.Jellyseerr.description }}</p> <p class="content">{{ .lang.Jellyseerr.description }}</p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="jellyseerr-enabled"><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="jellyseerr-enabled"><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.serverAddress }}</span> <span>{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="jellyseerr-server" placeholder="https://jellyseerr.jellyf.in:5055"> <input type="url" class="input ~neutral @low" id="jellyseerr-server" placeholder="https://jellyseerr.jellyf.in:5055">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.apiKey }}</span> <span>{{ .lang.Strings.apiKey }}</span>
<input type="text" class="input ~neutral @low mt-4" id="jellyseerr-api_key"> <input type="text" class="input ~neutral @low" id="jellyseerr-api_key">
</label> </label>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="jellyseerr-import_existing"><span>{{ .lang.Jellyseerr.importExisting }}</span> <div class="switch"><input type="checkbox" id="jellyseerr-import_existing" checked><span>{{ .lang.Jellyseerr.importExisting }}</span></div>
<p class="support mb-2 mt-1">{{ .lang.Jellyseerr.importExistingDescription }}</p> <p class="support">{{ .lang.Jellyseerr.importExistingDescription }}</p>
</label> </label>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.UserPage.title }}</span> <span class="heading">{{ .lang.UserPage.title }}</span>
<p class="content my-2">{{ .lang.UserPage.description }}</p> <p class="content">{{ .lang.UserPage.description }}</p>
<p class="content my-2">{{ .lang.UserPage.customizeMessages }}</p> <p class="content">{{ .lang.UserPage.customizeMessages }}</p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="userpage-enabled"><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="userpage-enabled"><span>{{ .lang.Strings.enabled }}</span></div>
<p class="support">{{ .lang.UserPage.requiredSettings }}</p>
</label> </label>
<p class="support mb-1 mt-1">{{ .lang.UserPage.requiredSettings }}</p> </section>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> <section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.Messages.title }}</span> <span class="heading">{{ .lang.Messages.title }}</span>
<p class="content my-2" id="messages-description"></p> <p class="content" id="messages-description"></p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="messages-enabled" checked><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="messages-enabled" checked><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Email.dateFormat }}</span> <span>{{ .lang.Email.dateFormat }}</span>
<input type="text" class="input ~neutral @low mt-4" id="email-date_format" value="%d/%m/%y"> <input type="text" class="input ~neutral @low" id="email-date_format" value="%d/%m/%y">
<p class="support mb-2 mt-1" id="email-dateformat-notice"></p> <p class="support" id="email-dateformat-notice"></p>
</label> </label>
<div> <div class="flex flex-col gap-2">
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="radio" class="mr-2" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span> <div class="switch"><input type="radio" class="mr-2" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span></div>
</label> </label>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="radio" class="mr-2" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span> <div class="switch"><input type="radio" class="mr-2" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span></div>
</label> </label>
</div> </div>
<div id="email-sect"> <div id="email-sect" class="flex flex-row gap-2 justify-between">
<div class="flex flex-col gap-2">
<span class="heading">{{ .lang.Email.title }}</span> <span class="heading">{{ .lang.Email.title }}</span>
<p class="content my-2" id="email-description"></p> <p class="content" id="email-description"></p>
<div class="row"> <label class="label flex flex-col gap-2">
<div class="col">
<label class="label">
<span>{{ .lang.Email.method }}</span> <span>{{ .lang.Email.method }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="email-method"> <select id="email-method">
<option value="">{{ .lang.Strings.disabled }}</option> <option value="">{{ .lang.Strings.disabled }}</option>
<option value="smtp">SMTP</option> <option value="smtp">SMTP</option>
@ -337,218 +352,225 @@
</select> </select>
</div> </div>
</label> </label>
<label class="row switch"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span> <div class="switch"><input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span></div>
<p class="support mb-2 mt-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p> <p class="support">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Email.fromAddress }}</span> <span>{{ .lang.Email.fromAddress }}</span>
<input type="email" class="input ~neutral @low mt-4 mb-2" id="email-address" placeholder="mail@jellyf.in"> <input type="email" class="input ~neutral @low" id="email-address" placeholder="mail@jellyf.in">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Email.senderName }}</span> <span>{{ .lang.Email.senderName }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="email-from" value="Jellyfin"> <input type="text" class="input ~neutral @low" id="email-from" value="Jellyfin">
</label> </label>
</div> </div>
<div class="col"> <div id="email-smtp" class="flex flex-col gap-2 min-w-[40%]">
<div id="email-smtp"> <p class="text-2xl font-semibold">SMTP</p>
<p class="text-2xl font-semibold mb-2">SMTP</p> <label class="label flex flex-col gap-2">
<label class="label">
<span>{{ .lang.Email.encryption }}</span> <span>{{ .lang.Email.encryption }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="smtp-encryption"> <select id="smtp-encryption">
<option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option> <option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option>
<option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option> <option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option>
</select> </select>
</div> </div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.serverAddress }}</span> <span>{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="smtp-server" placeholder="smtp.jellyf.in"> <input type="url" class="input ~neutral @low" id="smtp-server" placeholder="smtp.jellyf.in">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.port }}</span> <span>{{ .lang.Strings.port }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="smtp-port" placeholder="587"> <input type="number" class="input ~neutral @low" id="smtp-port" placeholder="587">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.username }}</span> <span>{{ .lang.Strings.username }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="smtp-username"> <input type="text" class="input ~neutral @low" id="smtp-username">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.password }}</span> <span>{{ .lang.Strings.password }}</span>
<input type="password" class="input ~neutral @low mt-4 mb-2" id="smtp-password"> <input type="password" class="input ~neutral @low" id="smtp-password">
</label> </label>
</div> </div>
<div id="email-mailgun"> <div id="email-mailgun" class="flex flex-col gap-2 min-w-[40%]">
<p class="text-2xl font-semibold mb-2">Mailgun</p> <p class="text-2xl font-semibold">Mailgun</p>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Email.mailgunApiURL }}</span> <span>{{ .lang.Email.mailgunApiURL }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages"> <input type="url" class="input ~neutral @low" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.apiKey }}</span> <span>{{ .lang.Strings.apiKey }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="mailgun-api_key"> <input type="text" class="input ~neutral @low" id="mailgun-api_key">
</label> </label>
</div> </div>
</div> </div>
</div> </section>
</div> <section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<section class="section ~neutral banner footer flex flex-row justify-between middle">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused related-to-email"> <div class="card sectioned ~neutral @low unfocused related-to-email">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.Notifications.title }}</span> <span class="heading">{{ .lang.Notifications.title }}</span>
<p class="content my-2">{{ .lang.Notifications.description }}</p> <p class="content">{{ .lang.Notifications.description }}</p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<span class="heading">{{ .lang.WelcomeEmails.title }}</span> <span class="heading">{{ .lang.WelcomeEmails.title }}</span>
<p class="content my-2">{{ .lang.WelcomeEmails.description }}</p> <p class="content">{{ .lang.WelcomeEmails.description }}</p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="welcome_email-enabled"><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="welcome_email-enabled"><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span> <span>{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="welcome_email-subject" placeholder="{{ .emailLang.WelcomeEmail.title }}"> <input type="text" class="input ~neutral @low" id="welcome_email-subject" placeholder="{{ .emailLang.WelcomeEmail.title }}">
</label> </label>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused related-to-email"> <div class="card sectioned ~neutral @low unfocused related-to-email">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.InviteEmails.title }}</span> <span class="heading">{{ .lang.InviteEmails.title }}</span>
<p class="content my-2">{{ .lang.InviteEmails.description }}</p> <p class="content">{{ .lang.InviteEmails.description }}</p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span> <span>{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}"> <input type="text" class="input ~neutral @low" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}">
</label> </label>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div id="password-resets" class="card ~neutral @low mb-2 unfocused related-to-email"> <div id="password-resets" class="card sectioned ~neutral @low unfocused related-to-email">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.PasswordResets.title }}</span> <span class="heading">{{ .lang.PasswordResets.title }}</span>
<p class="content my-2">{{ .lang.PasswordResets.description }}</p> <p class="content">{{ .lang.PasswordResets.description }}</p>
<p class="content my-2" id="password_resets-more-info">{{ .lang.PasswordResets.moreInfo }}</p> <p class="content" id="password_resets-more-info">{{ .lang.PasswordResets.moreInfo }}</p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="password_resets-enabled"><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="password_resets-enabled"><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.PasswordResets.pathToJellyfin }}</span> <span>{{ .lang.PasswordResets.pathToJellyfin }}</span>
<input type="text" class="input ~neutral @low mt-4" id="password_resets-watch_directory" placeholder="/config/jellyfin"> <input type="text" class="input ~neutral @low" id="password_resets-watch_directory" placeholder="/config/jellyfin">
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.pathToJellyfinNotice }}</p> <p class="support">{{ .lang.PasswordResets.pathToJellyfinNotice }}</p>
</label> </label>
<label class="switch"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span> <div class="switch"><input type="checkbox" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span></div>
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.resetLinksNotice }} {{ .lang.PasswordResets.resetLinksRequiredForUserPage }}</p> <p class="support">{{ .lang.PasswordResets.resetLinksNotice }} {{ .lang.PasswordResets.resetLinksRequiredForUserPage }}</p>
</label> </label>
<label class="switch"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span> <div class="switch"><input type="checkbox" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span></div>
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.setPasswordNotice }}</p> <p class="support">{{ .lang.PasswordResets.setPasswordNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<p class="mt-4">{{ .lang.PasswordResets.resetLinksLanguage }}</p> <span>{{ .lang.PasswordResets.resetLinksLanguage }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low">
<select id="password_resets-language"> <select id="password_resets-language">
</select> </select>
</div> </div>
</label> </label>
<label class="row label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span> <span>{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="password_resets-subject" placeholder="{{ .emailLang.PasswordReset.title }}"> <input type="text" class="input ~neutral @low" id="password_resets-subject" placeholder="{{ .emailLang.PasswordReset.title }}">
</label> </label>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.PasswordValidation.title }}</span> <span class="heading">{{ .lang.PasswordValidation.title }}</span>
<p class="content my-2">{{ .lang.PasswordValidation.description }}</p> <p class="content">{{ .lang.PasswordValidation.description }}</p>
<label class="row switch pb-4"> <label class="label flex flex-col gap-2">
<input type="checkbox" class="mr-2" id="password_validation-enabled" checked><span>{{ .lang.Strings.enabled }}</span> <div class="switch"><input type="checkbox" id="password_validation-enabled" checked><span>{{ .lang.Strings.enabled }}</span></div>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.PasswordValidation.length }}</span> <span>{{ .lang.PasswordValidation.length }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-min_length" value="8"> <input type="number" class="input ~neutral @low" id="password_validation-min_length" value="8">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.PasswordValidation.uppercase }}</span> <span>{{ .lang.PasswordValidation.uppercase }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-upper" value="1"> <input type="number" class="input ~neutral @low" id="password_validation-upper" value="1">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.PasswordValidation.lowercase }}</span> <span>{{ .lang.PasswordValidation.lowercase }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-lower" value="0"> <input type="number" class="input ~neutral @low" id="password_validation-lower" value="0">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.PasswordValidation.numbers }}</span> <span>{{ .lang.PasswordValidation.numbers }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-number" value="0"> <input type="number" class="input ~neutral @low" id="password_validation-number" value="0">
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.PasswordValidation.special }}</span> <span>{{ .lang.PasswordValidation.special }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-special" value="0"> <input type="number" class="input ~neutral @low" id="password_validation-special" value="0">
</label> </label>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<section class="section flex flex-col gap-2 justify-between">
<span class="heading">{{ .lang.HelpMessages.title }}</span> <span class="heading">{{ .lang.HelpMessages.title }}</span>
<p class="content my-2">{{ .lang.HelpMessages.description }}</p> <p class="content">{{ .lang.HelpMessages.description }}</p>
<label class="label"> <p class="content">{{ .lang.HelpMessages.markdownMessageNotice }}</p>
<span class="mt-4">{{ .lang.HelpMessages.contactMessage }}</span> <label class="label flex flex-col gap-2">
<input type="text" class="input ~neutral @low mt-4" id="ui-contact_message"> <span>{{ .lang.HelpMessages.contactMessage }}</span>
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.contactMessageNotice }}</p> <input type="text" class="input ~neutral @low" id="ui-contact_message">
<p class="support">{{ .lang.HelpMessages.contactMessageNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.HelpMessages.helpMessage }}</span> <span>{{ .lang.HelpMessages.helpMessage }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ui-help_message"> <input type="text" class="input ~neutral @low" id="ui-help_message">
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.helpMessageNotice }}</p> <p class="support">{{ .lang.HelpMessages.helpMessageNotice }}</p>
</label> </label>
<label class="label"> <label class="label flex flex-col gap-2">
<span class="mt-4">{{ .lang.HelpMessages.successMessage }}</span> <span>{{ .lang.HelpMessages.successMessage }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ui-success_message"> <input type="text" class="input ~neutral @low" id="ui-success_message">
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.successMessageNotice }}</p> <p class="support">{{ .lang.HelpMessages.successMessageNotice }}</p>
</label> </label>
<label class="label related-to-email"> <label class="label related-to-email">
<span class="mt-4">{{ .lang.HelpMessages.emailMessage }}</span> <span>{{ .lang.HelpMessages.emailMessage }}</span>
<input type="text" class="input ~neutral @low mt-4" id="email-message"> <input type="text" class="input ~neutral @low" id="email-message">
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.emailMessageNotice }}</p> <p class="support">{{ .lang.HelpMessages.emailMessageNotice }}</p>
</label> </label>
<section class="section ~neutral banner footer flex flex-row justify-between middle"> </section>
<section class="section w-full ~neutral footer flex flex-row justify-between items-center">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div> <div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span> <span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div> </div>
</section> </section>
</div> </div>
<div class="card ~neutral @low mb-2 unfocused"> <div class="card sectioned ~neutral @low unfocused">
<div class="row col flex center"> <section class="section flex flex-col gap-2 justify-center items-center">
<span class="heading">{{ .lang.EndPage.finished }}</span> <span class="heading">{{ .lang.EndPage.finished }}</span>
</div> <p class="content">{{ .lang.EndPage.restartMessage }}</p>
<div class="row col flex center"> </section>
<p class="content my-2">{{ .lang.EndPage.restartMessage }}</p> <section class="section w-full ~neutral footer flex flex-row justify-center items-center gap-2">
</div> <span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div class="row col flex center">
<span class="button ~neutral @low back mr-4">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low" id="restart">{{ .lang.Strings.submit }}</span> <span class="button ~urge @low" id="restart">{{ .lang.Strings.submit }}</span>
<span class="button ~urge @low unfocused" id="refresh">{{ .lang.EndPage.refreshPage }}</span> <span class="button ~urge @low unfocused" id="refresh">{{ .lang.EndPage.refreshPage }}</span>
</div> </div>

View File

@ -102,6 +102,7 @@
"jellyseerr": { "jellyseerr": {
"title": "Jellyseerr", "title": "Jellyseerr",
"description": "Jellyseerr is an alternative to Ombi, and integrates with jfa-go slightly better. Again, after setup is finished, go to Settings to create a profile and add a template for new Jellyseerr accounts.", "description": "Jellyseerr is an alternative to Ombi, and integrates with jfa-go slightly better. Again, after setup is finished, go to Settings to create a profile and add a template for new Jellyseerr accounts.",
"importExisting": "Import existing users",
"importExistingDescription": "If enabled, your existing users will have contact details and preferences from jfa-go synchronized." "importExistingDescription": "If enabled, your existing users will have contact details and preferences from jfa-go synchronized."
}, },
"messages": { "messages": {
@ -164,6 +165,7 @@
"helpMessages": { "helpMessages": {
"title": "Help Messages", "title": "Help Messages",
"description": "These messages will display in the account creation page and in some emails.", "description": "These messages will display in the account creation page and in some emails.",
"markdownMessageNotice": "Contents of some emails, pages and messages can be customized further with markdown in Settings.",
"contactMessage": "Contact Message", "contactMessage": "Contact Message",
"contactMessageNotice": "Displays at the bottom of all pages except admin.", "contactMessageNotice": "Displays at the bottom of all pages except admin.",
"helpMessage": "Help Message", "helpMessage": "Help Message",

View File

@ -33,7 +33,7 @@ function fixHTML(infile, outfile) {
} }
} }
let doc = new parser.load(f); let doc = new parser.load(f);
for (let item of ["badge", "chip", "shield", "input", "table", "button", "portal", "select", "aside", "card", "field", "textarea"]) { for (let item of ["badge", "chip", "shield", "input", "table", "button", "portal", "select", "aside", "card", "field", "textarea", "section"]) {
let items = doc("."+item); let items = doc("."+item);
items.each((i, elem) => { items.each((i, elem) => {
let hasColor = false; let hasColor = false;
@ -50,8 +50,8 @@ function fixHTML(infile, outfile) {
} }
if (!hasColor) { if (!hasColor) {
if (!hasDark(doc(elem))) { if (!hasDark(doc(elem))) {
// card without ~neutral look different than with. // card (and sections in sectioned cards) without ~neutral look different than with.
if (item != "card") doc(elem).addClass("~neutral"); if (item != "card" && item != "section") doc(elem).addClass("~neutral");
doc(elem).addClass("dark:~d_neutral"); doc(elem).addClass("dark:~d_neutral");
} }
} }

View File

@ -1,6 +1,7 @@
export class ThemeManager { export class ThemeManager {
private _themeButton: HTMLElement = null; private _themeButton: HTMLElement = null;
private _metaTag: HTMLMetaElement;
private _beforeTransition = () => { private _beforeTransition = () => {
const doc = document.documentElement; const doc = document.documentElement;
@ -45,6 +46,7 @@ export class ThemeManager {
} }
constructor(button?: HTMLElement) { constructor(button?: HTMLElement) {
this._metaTag = document.querySelector("meta[name=color-scheme]") as HTMLMetaElement;
const theme = localStorage.getItem("theme"); const theme = localStorage.getItem("theme");
if (theme == "dark") { if (theme == "dark") {
this._enable(true); this._enable(true);
@ -54,18 +56,22 @@ export class ThemeManager {
this._enable(true); this._enable(true);
} }
if (arguments.length == 1) if (button)
this.bindButton(button); this.bindButton(button);
} }
private _toggle = () => { private _toggle = () => {
let metaValue = "light dark";
this._beforeTransition(); this._beforeTransition();
if (!document.documentElement.classList.contains('dark')) { if (!document.documentElement.classList.contains('dark')) {
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
metaValue = "dark light";
} else { } else {
document.documentElement.classList.remove('dark'); document.documentElement.classList.remove('dark');
} }
localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? "dark" : "light"); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? "dark" : "light");
// this._metaTag.setAttribute("content", metaValue);
}; };
private _enable = (dark: boolean) => { private _enable = (dark: boolean) => {
@ -80,6 +86,8 @@ export class ThemeManager {
document.documentElement.classList.remove(opposite); document.documentElement.classList.remove(opposite);
} }
document.documentElement.classList.add(mode); document.documentElement.classList.add(mode);
// this._metaTag.setAttribute("content", `${mode} ${opposite}`);
}; };
enable = (dark: boolean) => { enable = (dark: boolean) => {

View File

@ -1,5 +1,6 @@
import { _get, _post, toggleLoader, notificationBox } from "./modules/common.js"; import { _get, _post, toggleLoader, notificationBox } from "./modules/common.js";
import { lang, LangFile, loadLangSelector } from "./modules/lang.js"; import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
import { ThemeManager } from "./modules/theme.js";
interface sWindow extends Window { interface sWindow extends Window {
messages: {}; messages: {};
@ -8,6 +9,7 @@ interface sWindow extends Window {
declare var window: sWindow; declare var window: sWindow;
window.URLBase = ""; window.URLBase = "";
const theme = new ThemeManager(document.getElementById("button-theme"));
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5); window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
@ -68,7 +70,16 @@ class Checkbox {
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) { constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
this._el = el as HTMLInputElement; this._el = el as HTMLInputElement;
this._hideEl = this._el as HTMLElement; this._hideEl = this._el as HTMLElement;
if (this._hideEl.parentElement.tagName == "LABEL") { this._hideEl = this._hideEl.parentElement; } if (this._hideEl.parentElement.tagName == "LABEL") {
this._hideEl = this._hideEl.parentElement;
} else if (this._hideEl.parentElement.classList.contains("switch")) {
if (this._hideEl.parentElement.parentElement.tagName == "LABEL") {
this._hideEl = this._hideEl.parentElement.parentElement;
} else {
this._hideEl = this._hideEl.parentElement;
}
}
if (section && setting) { if (section && setting) {
this._section = section; this._section = section;
this._setting = setting; this._setting = setting;
@ -86,11 +97,11 @@ class Checkbox {
}); });
} }
if (this._el.hasAttribute("checked")) { /* if (this._el.hasAttribute("checked")) {
this._el.checked = true; this._el.checked = true;
} else { } else {
this._el.checked = false; this._el.checked = false;
} } */
this.broadcast(); this.broadcast();
} }
} }
@ -217,7 +228,7 @@ class LangSelect extends Select {
} }
this.value = "en-us"; this.value = "en-us";
} }
}); }, true);
} }
} }
@ -448,23 +459,32 @@ settings["email"]["method"].onchange = emailMethodChange;
settings["messages"]["enabled"].onchange = emailMethodChange; settings["messages"]["enabled"].onchange = emailMethodChange;
emailMethodChange(); emailMethodChange();
const getParentCard = (el: HTMLElement): HTMLDivElement => {
let pEl = el.parentElement;
while (pEl.tagName != "html") {
if (pEl.classList.contains("card")) return pEl as HTMLDivElement;
pEl = pEl.parentElement;
}
return pEl as HTMLDivElement;
};
const jellyfinLoginAccessChange = () => { const jellyfinLoginAccessChange = () => {
const adminOnly = settings["ui"]["admin_only"].value == "true"; const adminOnly = settings["ui"]["admin_only"].value == "true";
const allowAll = settings["ui"]["allow_all"].value == "true"; const allowAll = settings["ui"]["allow_all"].value == "true";
const adminOnlyEl = document.getElementById("ui-admin_only") as HTMLInputElement; const adminOnlyEl = document.getElementById("ui-admin_only") as HTMLInputElement;
const allowAllEls = [document.getElementById("ui-allow_all"), document.getElementById("description-ui-allow_all")]; const allowAllEl = document.getElementById("ui-allow_all") as HTMLInputElement;
const nextButton = adminOnlyEl.parentElement.parentElement.parentElement.querySelector("span.next") as HTMLSpanElement; const nextButton = getParentCard(adminOnlyEl).querySelector("span.next") as HTMLSpanElement;
if (adminOnly && !allowAll) { if (adminOnly && !allowAll) {
(allowAllEls[0] as HTMLInputElement).disabled = true; allowAllEl.disabled = true;
adminOnlyEl.disabled = false; adminOnlyEl.disabled = false;
nextButton.removeAttribute("disabled"); nextButton.removeAttribute("disabled");
} else if (!adminOnly && allowAll) { } else if (!adminOnly && allowAll) {
adminOnlyEl.disabled = true; adminOnlyEl.disabled = true;
(allowAllEls[0] as HTMLInputElement).disabled = false; allowAllEl.disabled = false;
nextButton.removeAttribute("disabled"); nextButton.removeAttribute("disabled");
} else { } else {
adminOnlyEl.disabled = false; adminOnlyEl.disabled = false;
(allowAllEls[0] as HTMLInputElement).disabled = false; allowAllEl.disabled = false;
nextButton.setAttribute("disabled", "true") nextButton.setAttribute("disabled", "true")
} }
}; };
@ -495,18 +515,17 @@ for (let section in settings) {
const pageNames: string[][] = []; const pageNames: string[][] = [];
window.history.replaceState("welcome", "Setup - jfa-go"); (() => {
const pushState = window.history.pushState;
window.history.pushState = function (data: any, __: string, _: string | URL) {
pushState.apply(window.history, arguments);
let ev = { state: data as string } as PopStateEvent;
window.onpopstate(ev);
};
})();
const changePage = (title: string, pageTitle: string) => {
const urlParams = new URLSearchParams(window.location.search);
const lang = urlParams.get("lang");
let page = "/#" + title;
if (lang) { page += "?lang=" + lang; }
window.history.pushState(title || "welcome", pageTitle, page);
};
const cards = Array.from(document.getElementById("page-container").getElementsByClassName("card")) as Array<HTMLDivElement>;
(window as any).cards = cards;
window.onpopstate = (event: PopStateEvent) => { window.onpopstate = (event: PopStateEvent) => {
console.log("CALLLLLLLL", event);
if (event.state === "welcome") { if (event.state === "welcome") {
cards[0].classList.remove("unfocused"); cards[0].classList.remove("unfocused");
for (let i = 1; i < cards.length; i++) { cards[i].classList.add("unfocused"); } for (let i = 1; i < cards.length; i++) { cards[i].classList.add("unfocused"); }
@ -521,6 +540,35 @@ window.onpopstate = (event: PopStateEvent) => {
} }
}; };
const cards = Array.from(document.getElementsByClassName("page-container")[0].querySelectorAll(".card.sectioned")) as Array<HTMLDivElement>;
(window as any).cards = cards;
const changePageToIndex = (title: string, pageTitle: string, i: number) => {
cards[i].classList.remove("unfocused");
const urlParams = new URLSearchParams(window.location.search);
const lang = urlParams.get("lang");
let page = "/#" + title;
if (lang) { page += "?lang=" + lang; }
console.log("pushing", title, pageTitle, page);
window.history.pushState(title || "welcome", pageTitle, page);
};
const changePage = (title: string, pageTitle: string) => {
let found = false;
for (let i = 0; i < cards.length; i++) {
if (!found && pageNames[i][0] == title && !(cards[i].classList.contains("hidden"))) {
found = true;
changePageToIndex(title, pageTitle, i);
} else {
cards[i].classList.add("unfocused");
}
}
if (!found) {
changePageToIndex(title, pageTitle, 0);
}
window.scrollTo(0, 0);
};
(() => { (() => {
for (let i = 0; i < cards.length; i++) { for (let i = 0; i < cards.length; i++) {
const card = cards[i]; const card = cards[i];
@ -534,36 +582,34 @@ window.onpopstate = (event: PopStateEvent) => {
let pageTitle = titleEl.textContent + " - jfa-go"; let pageTitle = titleEl.textContent + " - jfa-go";
pageNames.push([title, pageTitle]); pageNames.push([title, pageTitle]);
if (back) { back.addEventListener("click", () => { if (back) { back.addEventListener("click", () => {
let found = false;
for (let ind = cards.length - 1; ind >= 0; ind--) { for (let ind = cards.length - 1; ind >= 0; ind--) {
cards[ind].classList.add("unfocused"); if (ind < i && !(cards[ind].classList.contains("hidden"))) {
if (ind < i && !(cards[ind].classList.contains("hidden")) && !found) {
cards[ind].classList.remove("unfocused");
changePage(pageNames[ind][0], pageNames[ind][1]); changePage(pageNames[ind][0], pageNames[ind][1]);
found = true; break;
} }
} }
window.scrollTo(0, 0);
}); } }); }
if (next) { if (next) {
const func = () => { const func = () => {
if (next.hasAttribute("disabled")) return; if (next.hasAttribute("disabled")) return;
let found = false;
for (let ind = 0; ind < cards.length; ind++) { for (let ind = 0; ind < cards.length; ind++) {
cards[ind].classList.add("unfocused"); if (ind > i && !(cards[ind].classList.contains("hidden"))) {
if (ind > i && !(cards[ind].classList.contains("hidden")) && !found) {
cards[ind].classList.remove("unfocused");
changePage(pageNames[ind][0], pageNames[ind][1]); changePage(pageNames[ind][0], pageNames[ind][1]);
found = true; break;
} }
} }
window.scrollTo(0, 0);
}; };
next.addEventListener("click", func) next.addEventListener("click", func)
} }
} }
})(); })();
(() => {
let initialLocation = window.location.hash.replace("#", "") || "welcome";
changePage(initialLocation, "Setup - jfa-go");
})();
// window.history.replaceState("welcome", "Setup - jfa-go",);
(() => { (() => {
const button = document.getElementById("jellyfin-test-connection") as HTMLSpanElement; const button = document.getElementById("jellyfin-test-connection") as HTMLSpanElement;
const ogText = button.textContent; const ogText = button.textContent;