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

Compare commits

..

31 Commits

Author SHA1 Message Date
dependabot[bot]
a3b69d6565
Merge 7db12fdbe7 into 43e36ee6fc 2023-10-22 15:35:30 -06:00
43e36ee6fc
setup: Include proxy, test JF with it
Found on the 2nd page.
2023-10-19 17:19:52 +01:00
53c9569a37
build: add notray windows build
better for daemonization with stuff like nssm.
2023-10-19 16:25:05 +01:00
c39a9e80e7
daemon: ensure correct error before wiping user data
ensure the error is specifically "User not found", rather than a
connection error or such. For #303.
2023-10-19 15:04:31 +01:00
3d0f756264
Merge SMTP Auth Option from @SquaredPotato
feat: Add SMTP authentication types to settings
2023-10-14 13:43:37 +01:00
Stefan Schokker
85de1c97ff feat: Add SMTP authentication types to settings 2023-10-14 14:29:34 +02:00
2c8afecfbb
lowercase lang 2023-10-14 13:19:05 +01:00
4924700c52
Merge settings-search
Adds searchbox to settings
2023-10-14 13:17:50 +01:00
e2c24a2593
accounts: add "not results found" screen 2023-10-14 13:07:30 +01:00
31b7ede665
accounts: fix search button (again) 2023-10-14 12:52:10 +01:00
dba7d0bd4e
admin: improve searchboxes appearance
"Clear search" button is now fully over the search box, so the
focus/click effects fully wrap round it. Rounded edges of the button are
now only on the right edge.
2023-10-14 12:46:39 +01:00
73cfa5bef2
settings: "no results found", section matching
No results found screen added, nd when a section name matches the
search, all settings in the section are shown normally.
2023-10-14 12:33:48 +01:00
6909477f45
settings: hidden items in search explained
if a matched setting is hidden, an aside card will show explaining why,
    eitherbecause advanced settings is not enabled or because it depends
    on another setting.
2023-10-13 19:07:41 +01:00
701d1305d3
settings: non-match search result have transparency
Matches have 100% opacity, non-matches have 50. Looks better than the
aside thing, doesn't break anything.
2023-10-13 15:52:53 +01:00
08498074ed
settings: funtioning search functionality
Search box and clear button work, curently matching settings are changed
to "aside"s for the border effect. Not super happy with how it looks
yet, and it messes up tooltips slightly.
2023-10-13 14:51:42 +01:00
brixik1
28d321986a Translated using Weblate (Czech)
Currently translated at 100.0% (120 of 120 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/cs/
2023-10-13 15:23:45 +02:00
brixik1
943d523f3f Translated using Weblate (Czech)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/cs/
2023-10-13 15:23:45 +02:00
brixik1
8f88b6aaa2 Translated using Weblate (Czech)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/cs/
2023-10-13 15:23:45 +02:00
brixik1
7f60598d4a Translated using Weblate (Czech)
Currently translated at 99.1% (119 of 120 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/cs/
2023-10-13 15:23:45 +02:00
brixik1
18e82fd04b translation from Weblate (Czech)
Currently translated at 100.0% (189 of 189 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/cs/
2023-10-13 15:23:45 +02:00
brixik1
d7d7146e12 Translated using Weblate (Czech)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/cs/
2023-10-13 15:23:45 +02:00
brixik1
aaa5217398 translation from Weblate (Czech)
Currently translated at 100.0% (62 of 62 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/cs/
2023-10-13 15:23:45 +02:00
brixik1
9610b89fa5 Translated using Weblate (Czech)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/cs/
2023-10-13 15:23:45 +02:00
brixik1
9809611d0d add translation from Weblate (Czech) 2023-10-13 15:23:45 +02:00
brixik1
b1e38ba15d Added translation using Weblate (Czech) 2023-10-13 15:23:45 +02:00
brixik1
35a765aa01 Added translation using Weblate (Czech) 2023-10-13 15:23:45 +02:00
brixik1
82411f1868 Added translation using Weblate (Czech) 2023-10-13 15:23:45 +02:00
brixik1
b0e01144f4 Added translation using Weblate (Czech) 2023-10-13 15:23:45 +02:00
brixik1
04f354b3d1 Added translation using Weblate (Czech) 2023-10-13 15:23:45 +02:00
brixik1
918f3ad588 add translation from Weblate (Czech) 2023-10-13 15:23:45 +02:00
635c2be32c
settings: initial search function
not really plugged into anything yet.
2023-10-13 10:30:59 +01:00
21 changed files with 992 additions and 46 deletions

View File

@ -54,6 +54,7 @@ builds:
goos: goos:
- linux - linux
- darwin - darwin
- windows
goarch: goarch:
- arm - arm
- arm64 - arm64

View File

@ -76,6 +76,7 @@ func (app *appContext) loadConfig() error {
app.MustSetValue("smtp", "hello_hostname", "localhost") app.MustSetValue("smtp", "hello_hostname", "localhost")
app.MustSetValue("smtp", "cert_validation", "true") app.MustSetValue("smtp", "cert_validation", "true")
app.MustSetValue("smtp", "auth_type", "4")
sc := app.config.Section("discord").Key("start_command").MustString("start") sc := app.config.Section("discord").Key("start_command").MustString("start")
app.config.Section("discord").Key("start_command").SetValue(strings.TrimPrefix(strings.TrimPrefix(sc, "/"), "!")) app.config.Section("discord").Key("start_command").SetValue(strings.TrimPrefix(strings.TrimPrefix(sc, "/"), "!"))

View File

@ -918,6 +918,22 @@
"type": "bool", "type": "bool",
"value": true, "value": true,
"description": "Warning, disabling this makes you much more vulnerable to man-in-the-middle attacks" "description": "Warning, disabling this makes you much more vulnerable to man-in-the-middle attacks"
},
"auth_type": {
"name": "Authentication type",
"required": false,
"requires_restart": false,
"advanced": false,
"type": "select",
"options": [
["0", "Plain"],
["1", "Login"],
["2", "CRAM-MD5"],
["3", "None"],
["4", "Auto"]
],
"value": 4,
"description": "SMTP authentication method"
} }
} }
}, },

View File

@ -1,6 +1,10 @@
package main package main
import "time" import (
"time"
"github.com/hrfee/mediabrowser"
)
// clearEmails removes stored emails for users which no longer exist. // clearEmails removes stored emails for users which no longer exist.
// meant to be called with other such housekeeping functions, so assumes // meant to be called with other such housekeeping functions, so assumes
@ -9,11 +13,14 @@ func (app *appContext) clearEmails() {
app.debug.Println("Housekeeping: removing unused email addresses") app.debug.Println("Housekeeping: removing unused email addresses")
emails := app.storage.GetEmails() emails := app.storage.GetEmails()
for _, email := range emails { for _, email := range emails {
_, status, err := app.jf.UserByID(email.JellyfinID, false) _, _, err := app.jf.UserByID(email.JellyfinID, false)
if status == 200 && err == nil { // Make sure the user doesn't exist, and no other error has occured
switch err.(type) {
case mediabrowser.ErrUserNotFound:
app.storage.DeleteEmailsKey(email.JellyfinID)
default:
continue continue
} }
app.storage.DeleteEmailsKey(email.JellyfinID)
} }
} }
@ -22,11 +29,14 @@ func (app *appContext) clearDiscord() {
app.debug.Println("Housekeeping: removing unused Discord IDs") app.debug.Println("Housekeeping: removing unused Discord IDs")
discordUsers := app.storage.GetDiscord() discordUsers := app.storage.GetDiscord()
for _, discordUser := range discordUsers { for _, discordUser := range discordUsers {
_, status, err := app.jf.UserByID(discordUser.JellyfinID, false) _, _, err := app.jf.UserByID(discordUser.JellyfinID, false)
if status == 200 && err == nil { // Make sure the user doesn't exist, and no other error has occured
switch err.(type) {
case mediabrowser.ErrUserNotFound:
app.storage.DeleteDiscordKey(discordUser.JellyfinID)
default:
continue continue
} }
app.storage.DeleteDiscordKey(discordUser.JellyfinID)
} }
} }
@ -35,11 +45,14 @@ func (app *appContext) clearMatrix() {
app.debug.Println("Housekeeping: removing unused Matrix IDs") app.debug.Println("Housekeeping: removing unused Matrix IDs")
matrixUsers := app.storage.GetMatrix() matrixUsers := app.storage.GetMatrix()
for _, matrixUser := range matrixUsers { for _, matrixUser := range matrixUsers {
_, status, err := app.jf.UserByID(matrixUser.JellyfinID, false) _, _, err := app.jf.UserByID(matrixUser.JellyfinID, false)
if status == 200 && err == nil { // Make sure the user doesn't exist, and no other error has occured
switch err.(type) {
case mediabrowser.ErrUserNotFound:
app.storage.DeleteMatrixKey(matrixUser.JellyfinID)
default:
continue continue
} }
app.storage.DeleteMatrixKey(matrixUser.JellyfinID)
} }
} }
@ -48,11 +61,14 @@ func (app *appContext) clearTelegram() {
app.debug.Println("Housekeeping: removing unused Telegram IDs") app.debug.Println("Housekeeping: removing unused Telegram IDs")
telegramUsers := app.storage.GetTelegram() telegramUsers := app.storage.GetTelegram()
for _, telegramUser := range telegramUsers { for _, telegramUser := range telegramUsers {
_, status, err := app.jf.UserByID(telegramUser.JellyfinID, false) _, _, err := app.jf.UserByID(telegramUser.JellyfinID, false)
if status == 200 && err == nil { // Make sure the user doesn't exist, and no other error has occured
switch err.(type) {
case mediabrowser.ErrUserNotFound:
app.storage.DeleteTelegramKey(telegramUser.JellyfinID)
default:
continue continue
} }
app.storage.DeleteTelegramKey(telegramUser.JellyfinID)
} }
} }

View File

@ -92,7 +92,8 @@ func NewEmailer(app *appContext) *Emailer {
if app.proxyEnabled { if app.proxyEnabled {
proxyConf = &app.proxyConfig proxyConf = &app.proxyConfig
} }
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, password, sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""), app.config.Section("smtp").Key("hello_hostname").String(), app.config.Section("smtp").Key("cert_validation").MustBool(true), proxyConf) authType := sMail.AuthType(app.config.Section("smtp").Key("auth_type").MustInt(4))
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, password, sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""), app.config.Section("smtp").Key("hello_hostname").String(), app.config.Section("smtp").Key("cert_validation").MustBool(true), authType, proxyConf)
if err != nil { if err != nil {
app.err.Printf("Error while initiating SMTP mailer: %v", err) app.err.Printf("Error while initiating SMTP mailer: %v", err)
} }
@ -118,7 +119,7 @@ type SMTP struct {
} }
// NewSMTP returns an SMTP emailClient. // NewSMTP returns an SMTP emailClient.
func (emailer *Emailer) NewSMTP(server string, port int, username, password string, sslTLS bool, certPath string, helloHostname string, validateCertificate bool, proxy *easyproxy.ProxyConfig) (err error) { func (emailer *Emailer) NewSMTP(server string, port int, username, password string, sslTLS bool, certPath string, helloHostname string, validateCertificate bool, authType sMail.AuthType, proxy *easyproxy.ProxyConfig) (err error) {
sender := &SMTP{} sender := &SMTP{}
sender.Client = sMail.NewSMTPClient() sender.Client = sMail.NewSMTPClient()
if sslTLS { if sslTLS {
@ -127,7 +128,7 @@ func (emailer *Emailer) NewSMTP(server string, port int, username, password stri
sender.Client.Encryption = sMail.EncryptionSTARTTLS sender.Client.Encryption = sMail.EncryptionSTARTTLS
} }
if username != "" || password != "" { if username != "" || password != "" {
sender.Client.Authentication = sMail.AuthLogin sender.Client.Authentication = authType
sender.Client.Username = username sender.Client.Username = username
sender.Client.Password = password sender.Client.Password = password
} }

View File

@ -645,7 +645,7 @@
</div> </div>
</div> </div>
<input type="search" class="field ~neutral @low input search ml-2 mr-2" id="accounts-search" placeholder="{{ .strings.search }}"> <input type="search" class="field ~neutral @low input search ml-2 mr-2" id="accounts-search" placeholder="{{ .strings.search }}">
<span class="button ~neutral @low center -ml-8" id="accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span> <span class="button ~neutral @low center ml-[-2.64rem] rounded-s-none accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
</div> </div>
<div class="supra py-1 sm hidden" id="accounts-search-options-header">{{ .strings.searchOptions }}</div> <div class="supra py-1 sm hidden" id="accounts-search-options-header">{{ .strings.searchOptions }}</div>
<div class="row -mx-2 mb-2"> <div class="row -mx-2 mb-2">
@ -708,6 +708,14 @@
</thead> </thead>
<tbody id="accounts-list"></tbody> <tbody id="accounts-list"></tbody>
</table> </table>
<div class="unfocused h-[100%] my-3" id="accounts-not-found">
<div class="flex flex-col h-[100%] justify-center items-center">
<span class="text-2xl font-medium italic mb-3">{{ .strings.noResultsFound }}</span>
<button class="button ~neutral @low accounts-search-clear">
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -729,11 +737,25 @@
</div> </div>
<div class="flex flex-col md:flex-row gap-3"> <div class="flex flex-col md:flex-row gap-3">
<div class="card @low dark:~d_neutral col" id="settings-sidebar"> <div class="card @low dark:~d_neutral col" id="settings-sidebar">
<div class="flex-expand">
<input type="search" class="field ~neutral @low input settings-section-button justify-between mb-2" id="settings-search" placeholder="{{ .strings.search }}">
<button class="button ~neutral @low center -ml-10 rounded-s-none mb-2 settings-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></button>
</div>
<aside class="aside sm ~urge dark:~d_info mb-2 @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside> <aside class="aside sm ~urge dark:~d_info mb-2 @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside>
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span> <span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span> <span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
</div> </div>
<div class="card ~neutral @low col overflow" id="settings-panel"></div> <div class="card ~neutral @low col overflow" id="settings-panel">
<div class="settings-section unfocused h-[100%]" id="settings-not-found">
<div class="flex flex-col h-[100%] justify-center items-center">
<span class="text-2xl font-medium italic mb-2">{{ .strings.noResultsFound }}</span>
<span class="mb-2 px-12 text-center">{{ .strings.settingsMaybeUnderAdvanced }}</span>
<button class="button ~neutral @low settings-search-clear">
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -122,6 +122,32 @@
</select> </select>
</div> </div>
</label> </label>
<span class="heading">{{ .lang.Proxy.title }}</span>
<p class="content my-2" id="proxy-description">{{ .lang.Proxy.description }}</p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="advanced-proxy"><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span>{{ .lang.Proxy.protocol }}</span>
<div class="select ~neutral @low mt-4 mb-2">
<select id="advanced-proxy_protocol">
<option value="http">HTTP</option>
<option value="socks">SOCKS5</option>
</select>
</div>
</label>
<label class="label">
<span class="mt-4">{{ .lang.Proxy.address }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-proxy_address">
</label>
<label class="label">
<span class="mt-4">{{ .lang.Strings.username }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-proxy_user">
</label>
<label class="label">
<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>
</div> </div>
<section class="section ~neutral banner footer flex-expand middle"> <section class="section ~neutral banner footer flex-expand middle">

View File

@ -116,6 +116,7 @@ type setupLang struct {
EndPage langSection `json:"endPage"` EndPage langSection `json:"endPage"`
General langSection `json:"general"` General langSection `json:"general"`
Updates langSection `json:"updates"` Updates langSection `json:"updates"`
Proxy langSection `json:"proxy"`
Language langSection `json:"language"` Language langSection `json:"language"`
Login langSection `json:"login"` Login langSection `json:"login"`
JellyfinEmby langSection `json:"jellyfinEmby"` JellyfinEmby langSection `json:"jellyfinEmby"`

229
lang/admin/cs-cz.json Normal file
View File

@ -0,0 +1,229 @@
{
"meta": {
"name": "Čeština (CZ)"
},
"strings": {
"invites": "Pozvánky",
"invite": "Pozvat",
"accounts": "Účty",
"settings": "Nastavení",
"inviteMonths": "Měsíce",
"inviteDays": "Dny",
"inviteHours": "Hodiny",
"inviteMinutes": "Minut",
"inviteNumberOfUses": "Počet použití",
"inviteDuration": "Doba trvání pozvánky",
"warning": "Varování",
"inviteInfiniteUsesWarning": "pozvánky s nekonečným využitím mohou být zneužity",
"inviteSendToEmail": "Poslat komu",
"create": "Vytvořit",
"apply": "Aplikovat",
"select": "Vybrat",
"name": "Název",
"date": "Datum",
"setExpiry": "Nastavit expiraci",
"updates": "Aktualizace",
"update": "Aktualizace",
"download": "Stažení",
"search": "Vyhledávání",
"advancedSettings": "Pokročilé nastavení",
"lastActiveTime": "Naposled aktivní",
"from": "Z",
"after": "Po",
"before": "Před",
"user": "Uživatel",
"userExpiry": "Vypršení platnosti",
"userExpiryDescription": "Zadanou dobu po každé registraci jfa-go smaže/zakáže účet. Toto chování můžete změnit v nastavení.",
"aboutProgram": "O",
"version": "Verze",
"commitNoun": "Zavázat se",
"newUser": "Nový uživatel",
"profile": "Profil",
"unknown": "Neznámý",
"label": "Štítek",
"userLabel": "Uživatelský štítek",
"userLabelDescription": "Štítek, který se použije pro uživatele vytvořené pomocí této pozvánky.",
"logs": "Protokoly",
"announce": "Oznámit",
"templates": "Šablony",
"subject": "Předmět",
"message": "Zpráva",
"variables": "Proměnné",
"conditionals": "Podmínky",
"preview": "Náhled",
"reset": "Resetovat",
"donate": "Darovat",
"unlink": "Odpojit účet",
"sendPWR": "Odeslat resetování hesla",
"contactThrough": "Kontakt přes:",
"extendExpiry": "Prodloužit platnost",
"sendPWRManual": "Uživatel {n} nemá žádný způsob kontaktu, stisknutím tlačítka Kopírovat získáte odkaz, který mu chcete poslat.",
"sendPWRSuccess": "Odkaz pro resetování hesla byl odeslán.",
"sendPWRSuccessManual": "Pokud jej uživatel neobdržel, stisknutím tlačítka Kopírovat získáte odkaz, který mu můžete ručně odeslat.",
"sendPWRValidFor": "Odkaz je platný 30m.",
"customizeMessages": "Přizpůsobit zprávy",
"customizeMessagesDescription": "Pokud nechcete používat šablony zpráv jfa-go, můžete si vytvořit vlastní pomocí Markdown.",
"markdownSupported": "Markdown je podporován.",
"modifySettings": "Upravit nastavení",
"modifySettingsDescription": "Použít nastavení ze stávajícího profilu nebo je získat přímo od uživatele.",
"enableReferrals": "Povolit doporučení",
"disableReferrals": "Zakázat doporučení",
"enableReferralsDescription": "Poskytněte uživatelům osobní doporučující odkaz podobný pozvánce, kterou můžete poslat přátelům/rodině. Lze je získat ze šablony doporučení v profilu nebo z existující pozvánky.",
"enableReferralsProfileDescription": "Poskytněte uživatelům vytvořeným pomocí tohoto profilu osobní doporučující odkaz podobný pozvánce, aby jej poslali přátelům/rodině. Vytvořte pozvánku s požadovaným nastavením a poté ji vyberte zde. Každé doporučení pak bude založeno na této pozvánce. Po dokončení můžete pozvánku smazat.",
"applyHomescreenLayout": "Použít rozložení domovské obrazovky",
"sendDeleteNotificationEmail": "Odeslat zprávu s upozorněním",
"sendDeleteNotifiationExample": "Váš účet byl smazán.",
"settingsRestart": "Restartovat",
"settingsRestarting": "Restartování…",
"settingsRestartRequired": "Je potřeba restart",
"settingsRestartRequiredDescription": "K použití některých změn, které jste změnili, je nutný restart. Restartovat hned nebo později?",
"settingsApplyRestartLater": "Použít, restartovat později",
"settingsApplyRestartNow": "Použít a restartovat",
"settingsApplied": "Nastavení byla použita.",
"settingsRefreshPage": "Obnovte stránku během několika sekund.",
"settingsRequiredOrRestartMessage": "Poznámka: {n} označuje povinné pole, {n} označuje, že změny vyžadují restart.",
"settingsSave": "Uložit",
"ombiProfile": "Ombi uživatelský profil",
"ombiUserDefaultsDescription": "Vytvořte uživatele Ombi a nakonfigurujte jej, poté jej vyberte níže. Když je tento profil vybrán, jeho nastavení/oprávnění budou uložena a použita pro nové uživatele Ombi vytvořené jfa-go.",
"userProfiles": "Uživatelské profily",
"userProfilesDescription": "Profily se použijí pro uživatele, když si vytvoří účet. Profil zahrnuje přístupová práva ke knihovně a rozvržení domovské obrazovky.",
"userProfilesIsDefault": "Výchozí",
"userProfilesLibraries": "Knihovny",
"addProfile": "Přidat profil",
"addProfileDescription": "Vytvořte uživatele Jellyfin a nakonfigurujte jej, poté jej vyberte níže. Když se tento profil použije na pozvánku, vytvoří se noví uživatelé s nastavením.",
"addProfileNameOf": "Jméno profilu",
"addProfileStoreHomescreenLayout": "Uložit rozložení domovské obrazovky",
"inviteNoUsersCreated": "Ještě žádný!",
"inviteUsersCreated": "Vytvoření uživatelé",
"inviteNoProfile": "Žádný profil",
"inviteDateCreated": "Vytvořeno",
"inviteNoInvites": "Žádný",
"inviteExpiresInTime": "Platnost vyprší za {n}",
"notifyEvent": "Upozornit na:",
"notifyInviteExpiry": "Při vypršení platnosti",
"notifyUserCreation": "Při vytvoření uživatele",
"sendPIN": "Požádejte uživatele, aby robotovi zaslal níže uvedený PIN.",
"searchDiscordUser": "Začněte psát uživatelské jméno Discord a vyhledejte uživatele.",
"findDiscordUser": "Najít uživatele Discordu",
"linkMatrixDescription": "Zadejte uživatelské jméno a heslo uživatele, který chcete použít jako robot. Po odeslání se aplikace restartuje.",
"matrixHomeServer": "Adresa domovského serveru",
"saveAsTemplate": "Uložit jako šablonu",
"deleteTemplate": "Smazat šablonu",
"templateEnterName": "Zadejte název pro uložení této šablony.",
"accessJFA": "Přístup k jfa-go",
"accessJFASettings": "Nelze změnit, protože v Nastavení > Obecné bylo nastaveno \"Pouze správce\" nebo \"Povolit vše\".",
"sortingBy": "Řazení podle",
"filters": "Filtry",
"clickToRemoveFilter": "Kliknutím tento filtr odstraníte.",
"clearSearch": "Vymazat vyhledávání",
"actions": "Akce",
"searchOptions": "Možnosti hledání",
"matchText": "Shoda textu",
"jellyfinID": "Jellyfin ID",
"userPageLogin": "Uživatelská stránka: Přihlášení",
"userPagePage": "Uživatelská stránka: Stránka",
"buildTime": "Čas sestavení",
"builtBy": "Postaven",
"loginNotAdmin": "Nejste správce?"
},
"notifications": {
"changedEmailAddress": "Změněna e-mailová adresa uživatele {n}.",
"userCreated": "Uživatel {n} byl vytvořen.",
"createProfile": "Vytvořen profil {n}.",
"saveSettings": "Nastavení byla uložena",
"saveEmail": "Email byl uložen.",
"sentAnnouncement": "Oznámení odesláno.",
"savedAnnouncement": "Oznámení uloženo.",
"setOmbiProfile": "Uložený ombi profil.",
"updateApplied": "Aktualizace byla použita, restartujte prosím.",
"updateAppliedRefresh": "Aktualizace byla použita, obnovte ji.",
"telegramVerified": "Účet telegramu ověřen.",
"accountConnected": "Účet připojen.",
"referralsEnabled": "Doporučení povolena.",
"errorSettingsAppliedNoHomescreenLayout": "Nastavení byla použita, ale použití rozvržení domovské obrazovky mohlo selhat.",
"errorHomescreenAppliedNoSettings": "Bylo použito rozvržení domovské obrazovky, ale použití nastavení mohlo selhat.",
"errorSettingsFailed": "Aplikace se nezdařila.",
"errorSaveEmail": "Uložení e-mailu se nezdařilo.",
"errorBlankFields": "Pole zůstala prázdná",
"errorDeleteProfile": "Smazání profilu {n} se nezdařilo",
"errorLoadProfiles": "Načtení profilů se nezdařilo.",
"errorCreateProfile": "Nepodařilo se vytvořit profil {n}",
"errorSetDefaultProfile": "Nepodařilo se nastavit výchozí profil.",
"errorLoadUsers": "Uživatele se nepodařilo načíst.",
"errorLoadSettings": "Nastavení se nepodařilo načíst.",
"errorSetOmbiProfile": "Uložení profilu ombi se nezdařilo.",
"errorLoadOmbiUsers": "Uživatele ombi se nepodařilo načíst.",
"errorChangedEmailAddress": "E-mailovou adresu uživatele {n} se nepodařilo změnit.",
"errorFailureCheckLogs": "Selhalo (zkontrolujte konzolu/protokoly)",
"errorPartialFailureCheckLogs": "Částečná chyba (zkontrolujte konzolu/protokoly)",
"errorUserCreated": "Nepodařilo se vytvořit uživatele {n}.",
"errorSendWelcomeEmail": "Nepodařilo se odeslat uvítací zprávu (zkontrolujte konzolu/protokoly)",
"errorApplyUpdate": "Aktualizaci se nepodařilo použít, zkuste to ručně.",
"errorCheckUpdate": "Kontrola aktualizace se nezdařila.",
"errorNoReferralTemplate": "Profil neobsahuje šablonu doporučení, přidejte si ji v nastavení.",
"updateAvailable": "Je k dispozici nová aktualizace, zkontrolujte nastavení.",
"noUpdatesAvailable": "Nejsou k dispozici žádné nové aktualizace."
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Upravit nastavení pro {n} uživatele",
"plural": "Upravit nastavení pro {n} uživatelů"
},
"enableReferralsFor": {
"singular": "Povolit doporučení pro {n} uživatele",
"plural": "Povolit doporučení pro {n} uživatelů"
},
"deleteNUsers": {
"singular": "Smazat {n} uživatele",
"plural": "Smazat {n} uživatelů"
},
"disableUsers": {
"singular": "Zakázat {n} uživatele",
"plural": "Zakázat {n} uživatelů"
},
"reEnableUsers": {
"singular": "Znovu povolte {n} uživatele",
"plural": "Znovu povolit {n} uživatelů"
},
"addUser": {
"singular": "Přidat uživatele",
"plural": "Přidat uživatele"
},
"deleteUser": {
"singular": "Smazat uživatele",
"plural": "Smazat uživatele"
},
"deletedUser": {
"singular": "Smazán {n} uživatel.",
"plural": "Smazaní {n} uživatelé."
},
"disabledUser": {
"singular": "Deaktivován {n} uživatel.",
"plural": "Zakázaných {n} uživatelů."
},
"enabledUser": {
"singular": "Povoleno {n} uživatele.",
"plural": "Povolených {n} uživatelů."
},
"announceTo": {
"singular": "Oznámeno {n} uživateli",
"plural": "Oznámit {n} uživatelům"
},
"appliedSettings": {
"singular": "Nastavení byla použita na {n} uživatele.",
"plural": "Nastavení byla použita na {n} uživatelů."
},
"extendExpiry": {
"singular": "Prodloužit platnost pro {n} uživatele",
"plural": "Prodloužit platnost pro {n} uživatelů"
},
"setExpiry": {
"singular": "Nastavit vypršení platnosti pro {n} uživatele",
"plural": "Nastavit vypršení platnosti pro {n} uživatelů"
},
"extendedExpiry": {
"singular": "Prodloužená platnost pro {n} uživatele.",
"plural": "Prodloužená platnost pro {n} uživatelů."
}
}
}

View File

@ -55,6 +55,7 @@
"donate": "Donate", "donate": "Donate",
"unlink": "Unlink Account", "unlink": "Unlink Account",
"sendPWR": "Send Password Reset", "sendPWR": "Send Password Reset",
"noResultsFound": "No Results Found",
"contactThrough": "Contact through:", "contactThrough": "Contact through:",
"extendExpiry": "Extend expiry", "extendExpiry": "Extend expiry",
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.", "sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
@ -83,6 +84,10 @@
"settingsRefreshPage": "Refresh the page in a few seconds.", "settingsRefreshPage": "Refresh the page in a few seconds.",
"settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.", "settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.",
"settingsSave": "Save", "settingsSave": "Save",
"settingsHiddenDependency": "Matching settings are hidden because they depend on the value of another setting:",
"settingsDependsOn": "{setting}: Depends on {dependency}",
"settingsAdvancedMode": "{setting}: Advanced Settings must be enabled",
"settingsMaybeUnderAdvanced": "Tip: You might find what you're looking for by enabling Advanced Settings.",
"ombiProfile": "Ombi user profile", "ombiProfile": "Ombi user profile",
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go when this profile is selected.", "ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go when this profile is selected.",
"userProfiles": "User Profiles", "userProfiles": "User Profiles",

67
lang/common/cs-cz.json Normal file
View File

@ -0,0 +1,67 @@
{
"meta": {
"name": "Čeština (CZ)"
},
"strings": {
"username": "Uživatelské jméno",
"password": "Heslo",
"emailAddress": "Emailová adresa",
"name": "Název",
"submit": "Odeslat",
"send": "Poslat",
"success": "Hotovo",
"continue": "Pokračovat",
"error": "Chyba",
"copy": "Kopírovat",
"copied": "Zkopírováno",
"time24h": "Čas 24 hodin",
"time12h": "Čas 12 hodin",
"linkTelegram": "Link Telegram",
"contactEmail": "Kontakt přes Email",
"contactTelegram": "Kontakt přes Telegram",
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Kontakt přes Discord",
"theme": "Téma",
"refresh": "Obnovit",
"required": "Požadované",
"login": "Přihlásit se",
"logout": "Odhlásit se",
"admin": "Admin",
"enabled": "Povoleno",
"disabled": "Zakázáno",
"reEnable": "Znovu povolit",
"disable": "Zakázat",
"contactMethods": "Kontaktní metody",
"accountStatus": "Stav účtu",
"notSet": "Nenastaveno",
"expiry": "Uplynutí",
"add": "Přidat",
"edit": "Upravit",
"delete": "Vymazat",
"myAccount": "Můj účet",
"referrals": "Doporučení",
"inviteRemainingUses": "Zbývající použití"
},
"notifications": {
"errorLoginBlank": "Uživatelské jméno a/nebo heslo zůstalo prázdné.",
"errorConnection": "Nelze se připojit k jfa-go.",
"errorUnknown": "Neznámá chyba.",
"error401Unauthorized": "Neoprávněný. Zkuste stránku obnovit.",
"errorSaveSettings": "Nastavení se nepodařilo uložit."
},
"quantityStrings": {
"year": {
"singular": "{n} rok",
"plural": "{n} let"
},
"month": {
"singular": "{n} měsíc",
"plural": "{n} měsíců"
},
"day": {
"singular": "{n} den",
"plural": "{n} dní"
}
}
}

77
lang/email/cs-cz.json Normal file
View File

@ -0,0 +1,77 @@
{
"meta": {
"name": "Čeština (CZ)"
},
"strings": {
"ifItWasNotYou": "Pokud jste to nebyl vy, ignorujte to.",
"helloUser": "Ahoj {username},",
"reason": "Důvod"
},
"userCreated": {
"name": "Vytvoření uživatele",
"title": "Upozornění: Uživatel vytvořen",
"aUserWasCreated": "Uživatel byl vytvořen pomocí kódu {code}.",
"time": "Čas",
"notificationNotice": "Poznámka: Zprávy s upozorněním lze přepínat na řídicím panelu správce."
},
"inviteExpiry": {
"name": "Platnost pozvánky",
"title": "Upozornění: Platnost pozvánky vypršela",
"inviteExpired": "Platnost pozvánky vypršela.",
"expiredAt": "Platnost kódu {code} vypršela v {time}.",
"notificationNotice": "Poznámka: Zprávy s upozorněním lze přepínat na řídicím panelu správce."
},
"passwordReset": {
"name": "Resetovat heslo",
"title": "Požadováno resetování hesla - Jellyfin",
"someoneHasRequestedReset": "Někdo nedávno požádal o reset hesla na Jellyfin.",
"ifItWasYou": "Pokud jste to byli vy, zadejte do výzvy níže uvedený kód PIN.",
"ifItWasYouLink": "Pokud jste to byli vy, klikněte na odkaz níže.",
"codeExpiry": "Platnost kódu vyprší {date} v {time} UTC, což je za {expiresInMinutes}.",
"pin": "PIN"
},
"userDeleted": {
"name": "Smazání uživatele",
"title": "Váš účet byl smazán - Jellyfin",
"yourAccountWasDeleted": "Váš účet Jellyfin byl smazán."
},
"userDisabled": {
"name": "Uživatel zakázán",
"title": "Váš účet byl deaktivován - Jellyfin",
"yourAccountWasDisabled": "Váš účet byl deaktivován."
},
"userEnabled": {
"name": "Uživatel povolen",
"title": "Váš účet byl znovu aktivován - Jellyfin",
"yourAccountWasEnabled": "Váš účet byl znovu aktivován."
},
"inviteEmail": {
"name": "Pozvací e-mail",
"title": "Pozvat - Jellyfin",
"hello": "Ahoj",
"youHaveBeenInvited": "Byli jste pozváni do Jellyfinu.",
"toJoin": "Chcete-li se připojit, postupujte podle níže uvedeného odkazu.",
"inviteExpiry": "Platnost této pozvánky vyprší {date} v {time}, což je za {expiresInMinutes}, proto jednejte rychle.",
"linkButton": "Nastavte si účet"
},
"welcomeEmail": {
"name": "Vítejte",
"title": "Vítejte v Jellyfin",
"welcome": "Vítejte v Jellyfin!",
"youCanLoginWith": "Přihlásit se můžete pomocí níže uvedených údajů",
"yourAccountWillExpire": "Platnost vašeho účtu vyprší dne {date}.",
"jellyfinURL": "URL"
},
"emailConfirmation": {
"name": "Potvrzující email",
"title": "Potvrďte svůj email - Jellyfin",
"clickBelow": "Kliknutím na odkaz níže potvrďte svou e-mailovou adresu a začněte používat Jellyfin.",
"confirmEmail": "Potvrdit email"
},
"userExpired": {
"name": "Vypršení platnosti uživatele",
"title": "Platnost vašeho účtu vypršela Jellyfin",
"yourAccountHasExpired": "Platnost vašeho účtu vypršela.",
"contactTheAdmin": "Pro více informací kontaktujte administrátora."
}
}

82
lang/form/cs-cz.json Normal file
View File

@ -0,0 +1,82 @@
{
"meta": {
"name": "Čeština (CZ)"
},
"strings": {
"pageTitle": "Vytvořte účet Jellyfin",
"createAccountHeader": "Vytvořit účet",
"accountDetails": "Podrobnosti",
"emailAddress": "Email",
"username": "Uživatelské jméno",
"oldPassword": "Staré heslo",
"newPassword": "Nové heslo",
"password": "Heslo",
"reEnterPassword": "Znovu zadejte heslo",
"reEnterPasswordInvalid": "Hesla nejsou stejná.",
"createAccountButton": "Vytvořit účet",
"passwordRequirementsHeader": "Požadavky na heslo",
"successHeader": "Hotovo!",
"confirmationRequired": "Vyžaduje se potvrzení e-mailem",
"confirmationRequiredMessage": "Zkontrolujte prosím svou e-mailovou schránku a ověřte svou adresu.",
"yourAccountIsValidUntil": "Váš účet bude platný do {date}.",
"sendPIN": "Odešlete robotovi níže uvedený PIN a poté se sem vraťte a propojte svůj účet.",
"sendPINDiscord": "Napište {command} do {server_channel} na Discordu a poté odešlete PIN níže.",
"matrixEnterUser": "Zadejte své uživatelské ID, stiskněte Odeslat a bude vám zaslán PIN. Chcete-li pokračovat, zadejte jej zde.",
"welcomeUser": "Vítejte, {user}!",
"addContactMethod": "Přidat metodu kontaktu",
"editContactMethod": "Upravit metodu kontaktu",
"joinTheServer": "Připojte se na server:",
"customMessagePlaceholderHeader": "Přizpůsobte si tuto kartu",
"customMessagePlaceholderContent": "Kliknutím na tlačítko upravit stránku uživatele v nastavení můžete přizpůsobit tuto kartu nebo ji zobrazit na přihlašovací obrazovce a nebojte se, uživatel to nevidí.",
"userPageSuccessMessage": "Podrobnosti o svém účtu můžete později zobrazit a změnit na stránce {myAccount}.",
"resetPassword": "Obnovit heslo",
"resetPasswordThroughJellyfin": "Chcete-li obnovit heslo, navštivte {jfLink} a stiskněte tlačítko \"Zapomenuté heslo\".",
"resetPasswordThroughLink": "Chcete-li obnovit heslo, zadejte své uživatelské jméno, e-mailovou adresu nebo uživatelské jméno propojené kontaktní metody a odešlete. Bude odeslán odkaz pro resetování hesla.",
"resetSent": "Resetování odesláno.",
"resetSentDescription": "Pokud existuje účet s daným uživatelským jménem/způsobem kontaktu, byl prostřednictvím všech dostupných způsobů kontaktu odeslán odkaz pro resetování hesla. Platnost kódu vyprší za 30 minut.",
"changePassword": "Změnit heslo",
"referralsDescription": "Pozvěte přátele a rodinu do Jellyfin pomocí tohoto odkazu. Vraťte se sem pro nový, pokud vyprší.",
"copyReferral": "Kopírovat odkaz",
"invitedBy": "Pozval vás uživatel {user}."
},
"notifications": {
"errorUserExists": "Uživatel již existuje.",
"errorInvalidCode": "Neplatný zvací kód.",
"errorAccountLinked": "Účet se již používá.",
"errorEmailLinked": "Email je již používán.",
"errorTelegramVerification": "Je vyžadováno ověření telegramem.",
"errorDiscordVerification": "Vyžaduje se ověření neshody.",
"errorMatrixVerification": "Vyžaduje se ověření matice.",
"errorInvalidPIN": "PIN je neplatný.",
"errorUnknown": "Neznámá chyba.",
"errorNoEmail": "Email je vyžadován.",
"errorCaptcha": "Captcha je nesprávná.",
"errorPassword": "Zkontrolujte požadavky na heslo.",
"errorNoMatch": "Hesla se neshodují.",
"errorOldPassword": "Staré heslo je nesprávné.",
"passwordChanged": "Heslo změněno.",
"verified": "Účet ověřen."
},
"validationStrings": {
"length": {
"singular": "Musí mít alespoň {n} znak",
"plural": "Musí mít nejméně {n} znaků"
},
"uppercase": {
"singular": "Musí mít alespoň {n} velkých písmen",
"plural": "Musí obsahovat alespoň {n} velkých písmen"
},
"lowercase": {
"singular": "Musí mít alespoň {n} malých písmen",
"plural": "Musí obsahovat alespoň {n} malých písmen"
},
"number": {
"singular": "Musí mít alespoň {n} číslo",
"plural": "Musí mít alespoň {n} čísel"
},
"special": {
"singular": "Musí mít alespoň {n} speciálních znaků",
"plural": "Musí obsahovat alespoň {n} speciálních znaků"
}
}
}

16
lang/pwreset/cs-cz.json Normal file
View File

@ -0,0 +1,16 @@
{
"meta": {
"name": "Čeština (CZ)"
},
"strings": {
"passwordReset": "Resetovat heslo",
"reset": "Resetovat",
"resetFailed": "Obnovení hesla se nezdařilo",
"tryAgain": "Prosím zkuste to znovu.",
"youCanLogin": "Nyní se můžete přihlásit pomocí níže uvedeného kódu jako svého hesla.",
"youCanLoginOmbi": "Nyní se můžete přihlásit do Jellyfin & Ombi pomocí níže uvedeného kódu jako hesla.",
"youCanLoginPassword": "Nyní se můžete přihlásit pomocí svého nového hesla. Stisknutím níže pokračujte na Jellyfin.",
"changeYourPassword": "Po přihlášení nezapomeňte změnit heslo.",
"enterYourPassword": "Níže zadejte své nové heslo."
}
}

160
lang/setup/cs-cz.json Normal file
View File

@ -0,0 +1,160 @@
{
"meta": {
"name": "Čeština (CZ)"
},
"strings": {
"pageTitle": "Nastavení - jfa-go",
"next": "Další",
"back": "Zpět",
"optional": "Volitelný",
"serverType": "Typ serveru",
"disabled": "Zakázáno",
"enabled": "Povoleno",
"port": "Port",
"message": "Zpráva",
"serverAddress": "Adresa serveru",
"emailSubject": "Předmět emailu",
"URL": "URL",
"apiKey": "Klíč API",
"error": "Chyba",
"errorInvalidUserPass": "Neplatné uživatelské jméno či heslo.",
"errorNotAdmin": "Uživatel nemá oprávnění spravovat server.",
"errorUserDisabled": "Uživatel může být zakázán.",
"error404": "404, zkontrolujte interní URL.",
"errorConnectionRefused": "Spojení odmítnuto.",
"errorUnknown": "Neznámá chyba, zkontrolujte protokoly aplikace."
},
"startPage": {
"welcome": "Vítejte!",
"pressStart": "Chcete-li nastavit jfa-go, budete muset udělat několik věcí. Pokračujte stisknutím tlačítka start.",
"httpsNotice": "Ujistěte se, že na tuto stránku přistupujete přes HTTPS nebo v privátní síti.",
"start": "Start"
},
"endPage": {
"finished": "Ukončeno!",
"restartMessage": "Funkce jako roboti Discord/Telegram/Matrix, vlastní zprávy Markdown a uživatelsky přístupná stránka \"Můj účet\" najdete v Nastavení, takže si ji nezapomeňte prohlédnout. Kliknutím níže restartujte a poté obnovte stránku.",
"refreshPage": "Obnovit"
},
"language": {
"title": "Jazyk",
"description": "Komunitní překlady jsou k dispozici pro většinu částí jfa-go. Níže si můžete vybrat výchozí jazyky, ale uživatelé je stále mohou změnit, pokud si to přejí. Pokud chcete pomoci s překladem, přihlaste se k {n} a začněte přispívat!",
"defaultAdminLang": "Výchozí jazyk správce",
"defaultFormLang": "Výchozí jazyk vytváření účtu",
"defaultEmailLang": "Výchozí jazyk e-mailu"
},
"general": {
"title": "Všeobecné",
"listenAddress": "Posloucha adresu",
"urlBase": "URL Base",
"urlBaseNotice": "Je potřeba pouze při použití reverzního proxy na subdoméně (např. 'jellyf.in/accounts').",
"lightTheme": "Světlý",
"darkTheme": "Tmavý",
"useHTTPS": "Použijte HTTPS",
"httpsPort": "HTTPS Port",
"useHTTPSNotice": "Doporučeno pouze v případě, že nepoužíváte reverzní proxy.",
"pathToCertificate": "Cesta k certifikátu",
"pathToKeyFile": "Cesta k souboru klíče"
},
"updates": {
"title": "Aktualizace",
"description": "Povolte, abyste byli informováni, když jsou k dispozici nové aktualizace. jfa-go bude kontrolovat {n} každých 30 minut. Nejsou shromažďovány žádné IP adresy ani osobní údaje.",
"updateChannel": "Aktualizovat kanál",
"stable": "Stabilní",
"unstable": "Nestabilní"
},
"login": {
"title": "Přihlásit se",
"description": "Pro přístup na stránku správce se musíte přihlásit níže uvedeným způsobem:",
"authorizeWithJellyfin": "Autorizovat pomocí Jellyfin/Emby: Přihlašovací údaje jsou sdíleny s Jellyfinem, což umožňuje více uživatelů.",
"authorizeManual": "Uživatelské jméno a heslo: Ručně nastavte uživatelské jméno a heslo.",
"adminOnly": "Pouze správci (doporučeno)",
"allowAll": "Povolit všem uživatelům Jellyfin přihlášení",
"allowAllDescription": "Nedoporučuje se, měli byste povolit přihlášení jednotlivých uživatelů po nastavení.",
"authorizeManualUserPageNotice": "Pomocí tohoto deaktivujete funkci \"Uživatelská stránka\".",
"emailNotice": "Vaši e-mailovou adresu lze použít k přijímání upozornění."
},
"jellyfinEmby": {
"title": "Jellyfin/Emby",
"description": "Účet správce je nutný, protože rozhraní API neumožňuje vytváření uživatelů pomocí klíče API. Měli byste si vytvořit samostatný účet a zaškrtnout 'Povolit tomuto uživateli spravovat server'. Vše ostatní můžete zakázat. Až budete hotovi, zadejte zde přihlašovací údaje.",
"embyNotice": "Podpora Emby je omezená a nepodporuje resetování hesla.",
"internal": "Vnitřní",
"external": "Externí",
"replaceJellyfin": "Název serveru",
"replaceJellyfinNotice": "Pokud je uveden, nahradí to jakýkoli výskyt 'Jellyfin' v aplikaci.",
"addressExternalNotice": "Chcete-li použít stejnou adresu, ponechte prázdné.",
"testConnection": "Test připojení"
},
"ombi": {
"title": "Ombi",
"description": "Připojením k Ombi se vytvoří účet Jellyfin i Ombi, když se uživatel připojí přes jfa-go. Po dokončení nastavení přejděte do Nastavení a nastavte výchozí profil pro nové uživatele ombi.",
"apiKeyNotice": "Najdete to na první kartě nastavení Ombi."
},
"messages": {
"title": "Zprávy",
"description": "jfa-go může odesílat resetování hesla a různé zprávy prostřednictvím e-mailu, Discordu, telegramu a/nebo Matrixu. Níže si můžete nastavit e-mail a ostatní můžete nakonfigurovat v Nastavení později. Pokyny naleznete na {n}. Pokud to nepotřebujete, můžete zde tyto funkce zakázat."
},
"email": {
"title": "Email",
"description": "jfa-go může posílat PINy pro resetování hesla a různá upozornění prostřednictvím e-mailu. Můžete se připojit k serveru SMTP nebo použít {n} API.",
"method": "Způsob odeslání",
"useEmailAsUsername": "Jako uživatelské jméno použijte e-mailové adresy",
"useEmailAsUsernameNotice": "Pokud je povoleno, noví uživatelé se budou přihlašovat do Jellyfin/Emby pomocí své e-mailové adresy namísto uživatelského jména.",
"fromAddress": "Z adresy",
"senderName": "Jméno odesílatele",
"dateFormat": "Datový formát",
"dateFormatNotice": "Datum má formát strftime. Pro více informací navštivte {n}.",
"encryption": "Šifrování",
"mailgunApiURL": "API URL"
},
"notifications": {
"title": "Upozornění pro správce",
"description": "Je-li povoleno, můžete si vybrat (na pozvánku), že chcete dostávat zprávu, když pozvánka vyprší nebo když je vytvořen uživatel. Pokud jste nezvolili způsob přihlášení Jellyfin, ujistěte se, že jste uvedli svou e-mailovou adresu, nebo později přidejte jiný způsob kontaktu."
},
"userPage": {
"title": "Uživatelská stránka",
"description": "Uživatelská stránka (zobrazená jako \"Můj účet\") umožňuje uživatelům přístup k informacím o jejich účtu, jako jsou jejich způsoby kontaktu a vypršení platnosti účtu. Mohou si také změnit heslo, zahájit resetování hesla a propojit/změnit způsoby kontaktu, aniž by se vás museli ptát. Kromě toho mohou být uživatelům před a po přihlášení zobrazeny přizpůsobené zprávy Markdown.",
"customizeMessages": "Chcete-li je nastavit později, klikněte v nastavení na tlačítko Upravit vedle položky \"Stránka uživatele\".",
"requiredSettings": "Musí být nastaveno přihlášení do jfa-go přes Jellyfin. Zajistěte, aby bylo později pro samoobslužné resetování hesla vybráno \"resetovat heslo přes odkaz\"."
},
"welcomeEmails": {
"title": "Uvítací zprávy",
"description": "Pokud je povoleno, bude novým uživatelům odeslána zpráva s adresou URL Jellyfin/Emby a jejich uživatelským jménem."
},
"inviteEmails": {
"title": "Pozvací zprávy",
"description": "Pokud je povoleno, můžete posílat pozvánky přímo na e-mailovou adresu uživatele, uživatele Discordu nebo Matrixu. Protože možná používáte reverzní proxy, musíte zadat adresy URL, ze kterých se přistupuje k pozvánkám. Napište základ URL a připojte '/invite'."
},
"passwordResets": {
"title": "Obnovení hesla",
"description": "Když se uživatel pokusí resetovat své heslo, Jellyfin vytvoří soubor s názvem 'passwordreset-*.json', který obsahuje PIN. jfa-go přečte soubor a odešle PIN uživateli. Pokud jste povolili funkci \"Uživatelská stránka\", lze reset provést také tam, zadáte-li uživatelské jméno, e-mail nebo způsob kontaktu.",
"pathToJellyfin": "Cesta ke konfiguračnímu adresáři Jellyfin",
"pathToJellyfinNotice": "Pokud nevíte, kde to je, zkuste resetovat heslo v Jellyfin. Objeví se vyskakovací okno s '<cesta k jellyfin>/passwordreset-*.json'. Toto není nutné, pokud chcete používat pouze samoobslužné resetování hesla prostřednictvím \"Uživatelské stránky\".",
"resetLinks": "Místo PINu pošlete odkaz",
"resetLinksRequiredForUserPage": "Vyžadováno pro samoobslužné resetování hesla na Uživatelské stránce.",
"resetLinksNotice": "Pokud je povolena integrace Ombi, použijte tuto možnost k synchronizaci resetování hesla Jellyfin s Ombi.",
"resetLinksLanguage": "Výchozí odkaz k resetování jazyku",
"setPassword": "Nastavit heslo přes odkaz",
"setPasswordNotice": "Povolení znamená, že uživatel nemusí po resetování měnit své heslo z PIN. Bude také vynuceno ověření hesla."
},
"passwordValidation": {
"title": "Ověření hesla",
"description": "Pokud je povoleno, na stránce vytvoření účtu se zobrazí sada požadavků na heslo, jako je minimální délka, velká/malá písmena atd.",
"length": "Délka",
"uppercase": "Velká písmena",
"lowercase": "Malá písmena",
"numbers": "Čísla",
"special": "Speciální znaky (%, * atd.)"
},
"helpMessages": {
"title": "Zprávy nápovědy",
"description": "Tyto zprávy se zobrazí na stránce vytvoření účtu a v některých e-mailech.",
"contactMessage": "Kontaktní zpráva",
"contactMessageNotice": "Zobrazí se v dolní části všech stránek kromě admin.",
"helpMessage": "Zpráva nápovědy",
"helpMessageNotice": "Zobrazí se na stránce vytvoření účtu.",
"successMessage": "Zpráva o úspěchu",
"successMessageNotice": "Zobrazí se, když si uživatel vytvoří svůj účet.",
"emailMessage": "Emailová zpráva",
"emailMessageNotice": "Zobrazuje se ve spodní části e-mailů."
}
}

View File

@ -18,11 +18,12 @@
"apiKey": "API Key", "apiKey": "API Key",
"error": "Error", "error": "Error",
"errorInvalidUserPass": "Invalid username/password.", "errorInvalidUserPass": "Invalid username/password.",
"errorNotAdmin": "User is not allowed to manage server.", "errorNotAdmin": "User is not aEnabledllowed to manage server.",
"errorUserDisabled": "User may be disabled.", "errorUserDisabled": "User may be disabled.",
"error404": "404, check the internal URL.", "error404": "404, check the internal URL.",
"errorConnectionRefused": "Connection refused.", "errorConnectionRefused": "Connection refused.",
"errorUnknown": "Unknown error, check app logs." "errorUnknown": "Unknown error, check app logs.",
"errorProxy": "Proxy configuration invalid."
}, },
"startPage": { "startPage": {
"welcome": "Welcome!", "welcome": "Welcome!",
@ -62,6 +63,12 @@
"stable": "Stable", "stable": "Stable",
"unstable": "Unstable" "unstable": "Unstable"
}, },
"proxy": {
"title": "Proxy",
"description": "Have jfa-go make all connections through a HTTP/SOCKS5 proxy. Connection to Jellyfin will be tested through this.",
"protocol": "Protocol",
"address": "Address (Including Port)"
},
"login": { "login": {
"title": "Login", "title": "Login",
"description": "To access the admin page, you need to login with a method below:", "description": "To access the admin page, you need to login with a method below:",

16
lang/telegram/cs-cz.json Normal file
View File

@ -0,0 +1,16 @@
{
"meta": {
"name": "Čeština (CZ)"
},
"strings": {
"startMessage": "Ahoj!\nZde zadejte svůj PIN kód Jellyfin pro ověření svého účtu.",
"discordStartMessage": "Ahoj!\n Zadejte svůj PIN pomocí `/pin <PIN>` pro ověření svého účtu.",
"matrixStartMessage": "Ahoj\nZadejte níže uvedený PIN na přihlašovací stránce Jellyfin a ověřte svůj účet.",
"invalidPIN": "Tento PIN byl neplatný, zkuste to znovu.",
"pinSuccess": "Hotovo! Nyní se můžete vrátit na stránku registrace.",
"languageMessage": "Poznámka: Dostupné jazyky zobrazíte pomocí příkazu {command} a jazyk nastavíte pomocí příkazu {command} <kód jazyka>.",
"languageMessageDiscord": "Poznámka: nastavte svůj jazyk pomocí /lang <název jazyka>.",
"languageSet": "Jazyk nastaven na {language}.",
"discordDMs": "Zkontrolujte prosím své DM pro odpověď."
}
}

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/easyproxy"
"github.com/hrfee/mediabrowser" "github.com/hrfee/mediabrowser"
) )
@ -47,10 +48,15 @@ func (app *appContext) ServeSetup(gc *gin.Context) {
} }
type testReq struct { type testReq struct {
ServerType string `json:"type"` ServerType string `json:"type"`
Server string `json:"server"` Server string `json:"server"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
Proxy bool `json:"proxy"`
ProxyProtocol string `json:"proxy_protocol,omitempty"`
ProxyAddress string `json:"proxy_address,omitempty"`
ProxyUsername string `json:"proxy_user,omitempty"`
ProxyPassword string `json:"proxy_password,omitempty"`
} }
func (app *appContext) TestJF(gc *gin.Context) { func (app *appContext) TestJF(gc *gin.Context) {
@ -64,6 +70,26 @@ func (app *appContext) TestJF(gc *gin.Context) {
serverType = mediabrowser.EmbyServer serverType = mediabrowser.EmbyServer
} }
tempjf, _ := mediabrowser.NewServer(serverType, req.Server, "jfa-go-setup", app.version, "auth", "auth", mediabrowser.NewNamedTimeoutHandler("authJF", req.Server, true), 30) tempjf, _ := mediabrowser.NewServer(serverType, req.Server, "jfa-go-setup", app.version, "auth", "auth", mediabrowser.NewNamedTimeoutHandler("authJF", req.Server, true), 30)
if req.Proxy {
conf := easyproxy.ProxyConfig{
Protocol: easyproxy.HTTP,
Addr: req.ProxyAddress,
User: req.ProxyUsername,
Password: req.ProxyPassword,
}
if strings.Contains(req.ProxyProtocol, "socks") {
conf.Protocol = easyproxy.SOCKS5
}
transport, err := easyproxy.NewTransport(conf)
if err != nil {
respond(400, "errorProxy", gc)
return
}
tempjf.SetTransport(transport)
}
user, status, err := tempjf.Authenticate(req.Username, req.Password) user, status, err := tempjf.Authenticate(req.Username, req.Password)
if !(status == 200 || status == 204) || err != nil { if !(status == 200 || status == 204) || err != nil {
msg := "" msg := ""
@ -126,6 +152,7 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
patchLang(&lang.Strings, &fallback.Strings, &english.Strings) patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
patchLang(&lang.StartPage, &fallback.StartPage, &english.StartPage) patchLang(&lang.StartPage, &fallback.StartPage, &english.StartPage)
patchLang(&lang.Updates, &fallback.Updates, &english.Updates) patchLang(&lang.Updates, &fallback.Updates, &english.Updates)
patchLang(&lang.Proxy, &fallback.Proxy, &english.Proxy)
patchLang(&lang.EndPage, &fallback.EndPage, &english.EndPage) patchLang(&lang.EndPage, &fallback.EndPage, &english.EndPage)
patchLang(&lang.Language, &fallback.Language, &english.Language) patchLang(&lang.Language, &fallback.Language, &english.Language)
patchLang(&lang.Login, &fallback.Login, &english.Login) patchLang(&lang.Login, &fallback.Login, &english.Login)
@ -144,6 +171,7 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
patchLang(&lang.Strings, &english.Strings) patchLang(&lang.Strings, &english.Strings)
patchLang(&lang.StartPage, &english.StartPage) patchLang(&lang.StartPage, &english.StartPage)
patchLang(&lang.Updates, &english.Updates) patchLang(&lang.Updates, &english.Updates)
patchLang(&lang.Proxy, &english.Proxy)
patchLang(&lang.EndPage, &english.EndPage) patchLang(&lang.EndPage, &english.EndPage)
patchLang(&lang.Language, &english.Language) patchLang(&lang.Language, &english.Language)
patchLang(&lang.Login, &english.Login) patchLang(&lang.Login, &english.Login)

View File

@ -946,6 +946,8 @@ export class accountsList {
} }
} }
private _notFoundPanel: HTMLElement = document.getElementById("accounts-not-found");
search = (query: String): string[] => { search = (query: String): string[] => {
console.log(this._queries); console.log(this._queries);
this._filterArea.textContent = ""; this._filterArea.textContent = "";
@ -2021,17 +2023,25 @@ export class accountsList {
this._inSearch = true; this._inSearch = true;
// this.setVisibility(this.search(query), true); // this.setVisibility(this.search(query), true);
} }
this.setVisibility(this.search(query), true); const results = this.search(query);
this.setVisibility(results, true);
this._checkCheckCount(); this._checkCheckCount();
this.showHideSearchOptionsHeader(); this.showHideSearchOptionsHeader();
if (results.length == 0) {
this._notFoundPanel.classList.remove("unfocused");
} else {
this._notFoundPanel.classList.add("unfocused");
}
}; };
this._search.oninput = onchange; this._search.oninput = onchange;
const clearSearchButton = document.getElementById("accounts-search-clear") as HTMLSpanElement; const clearSearchButtons = Array.from(document.getElementsByClassName("accounts-search-clear")) as Array<HTMLSpanElement>;
clearSearchButton.addEventListener("click", () => { for (let b of clearSearchButtons) {
this._search.value = ""; b.addEventListener("click", () => {
onchange(); this._search.value = "";
}); onchange();
});
}
this._announceTextarea.onkeyup = this.loadPreview; this._announceTextarea.onkeyup = this.loadPreview;
addDiscord = newDiscordSearch(window.lang.strings("linkDiscord"), window.lang.strings("searchDiscordUser"), window.lang.strings("add"), (user: DiscordUser, id: string) => { addDiscord = newDiscordSearch(window.lang.strings("linkDiscord"), window.lang.strings("searchDiscordUser"), window.lang.strings("add"), (user: DiscordUser, id: string) => {
@ -2084,8 +2094,15 @@ export class accountsList {
// console.log("ordering by", event.detail, ": ", this._ordering); // console.log("ordering by", event.detail, ": ", this._ordering);
if (!(this._inSearch)) { if (!(this._inSearch)) {
this.setVisibility(this._ordering, true); this.setVisibility(this._ordering, true);
this._notFoundPanel.classList.add("unfocused");
} else { } else {
this.setVisibility(this.search(this._search.value), true); const results = this.search(this._search.value);
this.setVisibility(results, true);
if (results.length == 0) {
this._notFoundPanel.classList.remove("unfocused");
} else {
this._notFoundPanel.classList.add("unfocused");
}
} }
this.showHideSearchOptionsHeader(); this.showHideSearchOptionsHeader();
}); });
@ -2195,8 +2212,15 @@ export class accountsList {
this._ordering = this._columns[this._activeSortColumn].sort(this._users); this._ordering = this._columns[this._activeSortColumn].sort(this._users);
if (!(this._inSearch)) { if (!(this._inSearch)) {
this.setVisibility(this._ordering, true); this.setVisibility(this._ordering, true);
this._notFoundPanel.classList.add("unfocused");
} else { } else {
this.setVisibility(this.search(this._search.value), true); const results = this.search(this._search.value);
if (results.length == 0) {
this._notFoundPanel.classList.remove("unfocused");
} else {
this._notFoundPanel.classList.add("unfocused");
}
this.setVisibility(results, true);
} }
this._checkCheckCount(); this._checkCheckCount();
} }

View File

@ -102,6 +102,7 @@ class DOMInput {
constructor(inputType: string, setting: Setting, section: string, name: string) { constructor(inputType: string, setting: Setting, section: string, name: string) {
this._container = document.createElement("div"); this._container = document.createElement("div");
this._container.classList.add("setting"); this._container.classList.add("setting");
this._container.setAttribute("data-name", name)
this._container.innerHTML = ` this._container.innerHTML = `
<label class="label"> <label class="label">
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span> <span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span>
@ -262,6 +263,7 @@ class DOMBool implements SBool {
constructor(setting: SBool, section: string, name: string) { constructor(setting: SBool, section: string, name: string) {
this._container = document.createElement("div"); this._container = document.createElement("div");
this._container.classList.add("setting"); this._container.classList.add("setting");
this._container.setAttribute("data-name", name)
this._container.innerHTML = ` this._container.innerHTML = `
<label class="switch mb-2"> <label class="switch mb-2">
<input type="checkbox"> <input type="checkbox">
@ -396,6 +398,7 @@ class DOMSelect implements SSelect {
this._options = []; this._options = [];
this._container = document.createElement("div"); this._container = document.createElement("div");
this._container.classList.add("setting"); this._container.classList.add("setting");
this._container.setAttribute("data-name", name)
this._container.innerHTML = ` this._container.innerHTML = `
<label class="label"> <label class="label">
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span> <span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span>
@ -544,9 +547,10 @@ class sectionPanel {
this._settings = {}; this._settings = {};
this._section = document.createElement("div") as HTMLDivElement; this._section = document.createElement("div") as HTMLDivElement;
this._section.classList.add("settings-section", "unfocused"); this._section.classList.add("settings-section", "unfocused");
this._section.setAttribute("data-section", sectionName);
this._section.innerHTML = ` this._section.innerHTML = `
<span class="heading">${s.meta.name}</span> <span class="heading">${s.meta.name}</span>
<p class="support lg my-2">${s.meta.description}</p> <p class="support lg my-2 settings-section-description">${s.meta.description}</p>
`; `;
this.update(s); this.update(s);
@ -618,10 +622,19 @@ export class settingsList {
private _panel = document.getElementById("settings-panel") as HTMLDivElement; private _panel = document.getElementById("settings-panel") as HTMLDivElement;
private _sidebar = document.getElementById("settings-sidebar") as HTMLDivElement; private _sidebar = document.getElementById("settings-sidebar") as HTMLDivElement;
private _visibleSection: string;
private _sections: { [name: string]: sectionPanel } private _sections: { [name: string]: sectionPanel }
private _buttons: { [name: string]: HTMLSpanElement } private _buttons: { [name: string]: HTMLSpanElement }
private _needsRestart: boolean = false; private _needsRestart: boolean = false;
private _messageEditor = new MessageEditor(); private _messageEditor = new MessageEditor();
private _settings: Settings;
private _advanced: boolean = false;
private _searchbox: HTMLInputElement = document.getElementById("settings-search") as HTMLInputElement;
private _clearSearchboxButtons: Array<HTMLButtonElement> = Array.from(document.getElementsByClassName("settings-search-clear")) as Array<HTMLButtonElement>;
private _noResultsPanel: HTMLElement = document.getElementById("settings-not-found");
addSection = (name: string, s: Section, subButton?: HTMLElement) => { addSection = (name: string, s: Section, subButton?: HTMLElement) => {
const section = new sectionPanel(s, name); const section = new sectionPanel(s, name);
@ -637,6 +650,7 @@ export class settingsList {
let state = true; let state = true;
if (s.meta.depends_false) { state = false; } if (s.meta.depends_false) { state = false; }
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => { document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
if (Boolean(event.detail) !== state) { if (Boolean(event.detail) !== state) {
button.classList.add("unfocused"); button.classList.add("unfocused");
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false })); document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: false }));
@ -659,6 +673,7 @@ export class settingsList {
} else { } else {
button.classList.remove("unfocused"); button.classList.remove("unfocused");
} }
this._searchbox.oninput(null);
}); });
} }
this._buttons[name] = button; this._buttons[name] = button;
@ -669,6 +684,7 @@ export class settingsList {
for (let n in this._sections) { for (let n in this._sections) {
if (n == name) { if (n == name) {
this._sections[name].visible = true; this._sections[name].visible = true;
this._visibleSection = name;
this._buttons[name].classList.add("selected"); this._buttons[name].classList.add("selected");
} else { } else {
this._sections[n].visible = false; this._sections[n].visible = false;
@ -736,15 +752,27 @@ export class settingsList {
advancedEnableToggle.onchange = () => { advancedEnableToggle.onchange = () => {
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: advancedEnableToggle.checked })); document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: advancedEnableToggle.checked }));
const parent = advancedEnableToggle.parentElement; const parent = advancedEnableToggle.parentElement;
if (advancedEnableToggle.checked) { this._advanced = advancedEnableToggle.checked;
if (this._advanced) {
parent.classList.add("~urge"); parent.classList.add("~urge");
parent.classList.remove("~neutral"); parent.classList.remove("~neutral");
} else { } else {
parent.classList.add("~neutral"); parent.classList.add("~neutral");
parent.classList.remove("~urge"); parent.classList.remove("~urge");
} }
this._searchbox.oninput(null);
}; };
advancedEnableToggle.checked = false; advancedEnableToggle.checked = false;
this._searchbox.oninput = () => {
this.search(this._searchbox.value);
};
for (let b of this._clearSearchboxButtons) {
b.onclick = () => {
this._searchbox.value = "";
this._searchbox.oninput(null);
};
};
} }
private _addMatrix = () => { private _addMatrix = () => {
@ -787,10 +815,10 @@ export class settingsList {
window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings")); window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings"));
return; return;
} }
let settings = req.response as Settings; this._settings = req.response as Settings;
for (let name of settings.order) { for (let name of this._settings.order) {
if (name in this._sections) { if (name in this._sections) {
this._sections[name].update(settings.sections[name]); this._sections[name].update(this._settings.sections[name]);
} else { } else {
if (name == "messages" || name == "user_page") { if (name == "messages" || name == "user_page") {
const editButton = document.createElement("div"); const editButton = document.createElement("div");
@ -806,7 +834,7 @@ export class settingsList {
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => { (editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => {
this._messageEditor.showList(name == "messages" ? "email" : "user"); this._messageEditor.showList(name == "messages" ? "email" : "user");
}; };
this.addSection(name, settings.sections[name], editButton); this.addSection(name, this._settings.sections[name], editButton);
} else if (name == "updates") { } else if (name == "updates") {
const icon = document.createElement("span") as HTMLSpanElement; const icon = document.createElement("span") as HTMLSpanElement;
if (window.updater.updateAvailable) { if (window.updater.updateAvailable) {
@ -814,7 +842,7 @@ export class settingsList {
icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`; icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show); icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
} }
this.addSection(name, settings.sections[name], icon); this.addSection(name, this._settings.sections[name], icon);
} else if (name == "matrix" && !window.matrixEnabled) { } else if (name == "matrix" && !window.matrixEnabled) {
const addButton = document.createElement("div"); const addButton = document.createElement("div");
addButton.classList.add("tooltip", "left"); addButton.classList.add("tooltip", "left");
@ -825,19 +853,126 @@ export class settingsList {
</span> </span>
`; `;
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix; (addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
this.addSection(name, settings.sections[name], addButton); this.addSection(name, this._settings.sections[name], addButton);
} else { } else {
this.addSection(name, settings.sections[name]); this.addSection(name, this._settings.sections[name]);
} }
} }
} }
this._showPanel(settings.order[0]); this._showPanel(this._settings.order[0]);
document.dispatchEvent(new CustomEvent("settings-loaded")); document.dispatchEvent(new CustomEvent("settings-loaded"));
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false })); document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false }));
this._saveButton.classList.add("unfocused"); this._saveButton.classList.add("unfocused");
this._needsRestart = false; this._needsRestart = false;
} }
}) })
// FIXME: Search "About" & "User profiles", pseudo-search "User profiles" for things like "Ombi", "Referrals", etc.
search = (query: string) => {
query = query.toLowerCase().trim();
// Make sure a blank search is detected when there's just whitespace.
if (query.replace(/\s+/g, "") == "") query = "";
let firstVisibleSection = "";
for (let section of this._settings.order) {
let dependencyCard = this._sections[section].asElement().querySelector(".settings-dependency-message");
if (dependencyCard) dependencyCard.remove();
dependencyCard = null;
let dependencyList = null;
// hide button, unhide if matched
this._buttons[section].classList.add("unfocused");
let matchedSection = false;
if (section.toLowerCase().includes(query) ||
this._settings.sections[section].meta.name.toLowerCase().includes(query) ||
this._settings.sections[section].meta.description.toLowerCase().includes(query)) {
if ((this._settings.sections[section].meta.advanced && this._advanced) || !(this._settings.sections[section].meta.advanced)) {
this._buttons[section].classList.remove("unfocused");
firstVisibleSection = firstVisibleSection || section;
matchedSection = true;
}
}
const sectionElement = this._sections[section].asElement();
for (let setting of this._settings.sections[section].order) {
if (this._settings.sections[section].settings[setting].type == "note") continue;
const element = sectionElement.querySelector(`div[data-name="${setting}"]`) as HTMLElement;
// If we match the whole section, don't bother searching settings.
if (matchedSection) {
element.classList.remove("opacity-50", "pointer-events-none");
element.setAttribute("aria-disabled", "false");
continue;
}
// element.classList.remove("-mx-2", "my-2", "p-2", "aside", "~neutral", "@low");
element.classList.add("opacity-50", "pointer-events-none");
element.setAttribute("aria-disabled", "true");
if (setting.toLowerCase().includes(query) ||
this._settings.sections[section].settings[setting].name.toLowerCase().includes(query) ||
this._settings.sections[section].settings[setting].description.toLowerCase().includes(query) ||
String(this._settings.sections[section].settings[setting].value).toLowerCase().includes(query)) {
if ((this._settings.sections[section].meta.advanced && this._advanced) || !(this._settings.sections[section].meta.advanced)) {
this._buttons[section].classList.remove("unfocused");
firstVisibleSection = firstVisibleSection || section;
}
const shouldShow = (query != "" &&
((this._settings.sections[section].settings[setting].advanced && this._advanced) ||
!(this._settings.sections[section].settings[setting].advanced)));
if (shouldShow || query == "") {
// element.classList.add("-mx-2", "my-2", "p-2", "aside", "~neutral", "@low");
element.classList.remove("opacity-50", "pointer-events-none");
element.setAttribute("aria-disabled", "false");
}
if (query != "" && ((shouldShow && element.querySelector("label").classList.contains("unfocused")) || (!shouldShow))) {
// Add a note explaining why the setting is hidden
if (!dependencyCard) {
dependencyCard = document.createElement("aside");
dependencyCard.classList.add("aside", "my-2", "~warning", "settings-dependency-message");
dependencyCard.innerHTML = `
<div class="content text-sm">
<span class="font-bold">${window.lang.strings("settingsHiddenDependency")}</span>
<ul class="settings-dependency-list"></ul>
</div>
`;
dependencyList = dependencyCard.querySelector(".settings-dependency-list") as HTMLUListElement;
// Insert it right after the description
this._sections[section].asElement().insertBefore(dependencyCard, this._sections[section].asElement().querySelector(".settings-section-description").nextElementSibling);
}
const li = document.createElement("li");
if (shouldShow) {
const depCode = this._settings.sections[section].settings[setting].depends_true || this._settings.sections[section].settings[setting].depends_false;
const dep = splitDependant(section, depCode);
let depName = this._settings.sections[dep[0]].settings[dep[1]].name;
if (dep[0] != section) {
depName = this._settings.sections[dep[0]].meta.name + " > " + depName;
}
li.textContent = window.lang.strings("settingsDependsOn").replace("{setting}", `"`+this._settings.sections[section].settings[setting].name+`"`).replace("{dependency}", `"`+depName+`"`);
} else {
li.textContent = window.lang.strings("settingsAdvancedMode").replace("{setting}", `"`+this._settings.sections[section].settings[setting].name+`"`);
}
dependencyList.appendChild(li);
}
}
}
}
if (firstVisibleSection && (query != "" || this._visibleSection == "")) {
this._buttons[firstVisibleSection].onclick(null);
this._noResultsPanel.classList.add("unfocused");
} else if (query != "") {
this._noResultsPanel.classList.remove("unfocused");
if (this._visibleSection) {
this._sections[this._visibleSection].visible = false;
this._buttons[this._visibleSection].classList.remove("selected");
this._visibleSection = "";
}
}
}
} }
export interface templateEmail { export interface templateEmail {

View File

@ -85,6 +85,13 @@ class Checkbox {
} }
}); });
} }
if (this._el.hasAttribute("checked")) {
this._el.checked = true;
} else {
this._el.checked = false;
}
this.broadcast();
} }
} }
@ -315,10 +322,14 @@ const settings = {
"tls": new Checkbox(get("advanced-tls"), "", false, "advanced", "tls"), "tls": new Checkbox(get("advanced-tls"), "", false, "advanced", "tls"),
"tls_port": new Input(get("advanced-tls_port"), "", "", "tls", true, "advanced"), "tls_port": new Input(get("advanced-tls_port"), "", "", "tls", true, "advanced"),
"tls_cert": new Input(get("advanced-tls_cert"), "", "", "tls", true, "advanced"), "tls_cert": new Input(get("advanced-tls_cert"), "", "", "tls", true, "advanced"),
"tls_key": new Input(get("advanced-tls_key"), "", "", "tls", true, "advanced") "tls_key": new Input(get("advanced-tls_key"), "", "", "tls", true, "advanced"),
"proxy": new Checkbox(get("advanced-proxy"), "", false, "advanced", "proxy"),
"proxy_protocol": new Select(get("advanced-proxy_protocol"), "proxy", true, "advanced"),
"proxy_address": new Input(get("advanced-proxy_address"), "", "", "proxy", true, "advanced"),
"proxy_user": new Input(get("advanced-proxy_user"), "", "", "proxy", true, "advanced"),
"proxy_password": new Input(get("advanced-proxy_password"), "", "", "proxy", true, "advanced")
} }
}; };
const checkTheme = () => { const checkTheme = () => {
if (settings["ui"]["theme"].value.includes("Dark")) { if (settings["ui"]["theme"].value.includes("Dark")) {
document.documentElement.classList.add("dark-theme"); document.documentElement.classList.add("dark-theme");
@ -553,7 +564,12 @@ window.onpopstate = (event: PopStateEvent) => {
"type": settings["jellyfin"]["type"].value, "type": settings["jellyfin"]["type"].value,
"server": settings["jellyfin"]["server"].value, "server": settings["jellyfin"]["server"].value,
"username": settings["jellyfin"]["username"].value, "username": settings["jellyfin"]["username"].value,
"password": settings["jellyfin"]["password"].value "password": settings["jellyfin"]["password"].value,
"proxy": settings["advanced"]["proxy"].value == "true",
"proxy_protocol": settings["advanced"]["proxy_protocol"].value,
"proxy_address": settings["advanced"]["proxy_address"].value,
"proxy_user": settings["advanced"]["proxy_user"].value,
"proxy_password": settings["advanced"]["proxy_password"].value
}; };
_post("/jellyfin/test", send, (req: XMLHttpRequest) => { _post("/jellyfin/test", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {