mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-28 03:50:10 +00:00
Compare commits
3 Commits
7c808b56f7
...
6c30a1ff40
Author | SHA1 | Date | |
---|---|---|---|
6c30a1ff40 | |||
a7aa3fd53e | |||
32161139b2 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -26,3 +26,4 @@ scripts/langmover/lang2
|
|||||||
scripts/langmover/out
|
scripts/langmover/out
|
||||||
tinyproxy.conf
|
tinyproxy.conf
|
||||||
static/banner.svg
|
static/banner.svg
|
||||||
|
start.sh
|
||||||
|
2
Makefile
2
Makefile
@ -196,7 +196,7 @@ STATIC_TARGET = $(STATIC_SRC:static/%=$(DATA)/web/%)
|
|||||||
COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC)
|
COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC)
|
||||||
COPY_TARGET = $(DATA)/jfa-go.service
|
COPY_TARGET = $(DATA)/jfa-go.service
|
||||||
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
|
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
|
||||||
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC)
|
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC)
|
||||||
$(info copying crash page)
|
$(info copying crash page)
|
||||||
cp $(DATA)/crash.html $(DATA)/html/
|
cp $(DATA)/crash.html $(DATA)/html/
|
||||||
$(info copying static data)
|
$(info copying static data)
|
||||||
|
@ -127,6 +127,8 @@ func (app *appContext) loadConfig() error {
|
|||||||
app.MustSetValue("user_expiry", "adjustment_email_html", "jfa-go:"+"expiry-adjusted.html")
|
app.MustSetValue("user_expiry", "adjustment_email_html", "jfa-go:"+"expiry-adjusted.html")
|
||||||
app.MustSetValue("user_expiry", "adjustment_email_text", "jfa-go:"+"expiry-adjusted.txt")
|
app.MustSetValue("user_expiry", "adjustment_email_text", "jfa-go:"+"expiry-adjusted.txt")
|
||||||
|
|
||||||
|
app.MustSetValue("email", "collect", "true")
|
||||||
|
|
||||||
app.MustSetValue("matrix", "topic", "Jellyfin notifications")
|
app.MustSetValue("matrix", "topic", "Jellyfin notifications")
|
||||||
app.MustSetValue("matrix", "show_on_reg", "true")
|
app.MustSetValue("matrix", "show_on_reg", "true")
|
||||||
|
|
||||||
|
@ -896,11 +896,20 @@
|
|||||||
"value": false,
|
"value": false,
|
||||||
"description": "Send emails as plain text instead of HTML."
|
"description": "Send emails as plain text instead of HTML."
|
||||||
},
|
},
|
||||||
|
"collect": {
|
||||||
|
"name": "Collect on sign-up",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "method",
|
||||||
|
"type": "bool",
|
||||||
|
"value": true,
|
||||||
|
"description": "Ask for an email address on the sign-up form."
|
||||||
|
},
|
||||||
"required": {
|
"required": {
|
||||||
"name": "Require on sign-up",
|
"name": "Require on sign-up",
|
||||||
"required": false,
|
"required": false,
|
||||||
"requires_restart": false,
|
"requires_restart": false,
|
||||||
"depends_true": "method",
|
"depends_true": "collect",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"value": false,
|
"value": false,
|
||||||
"description": "Require an email address on sign-up."
|
"description": "Require an email address on sign-up."
|
||||||
@ -909,6 +918,7 @@
|
|||||||
"name": "Require unique address",
|
"name": "Require unique address",
|
||||||
"required": false,
|
"required": false,
|
||||||
"requires_restart": true,
|
"requires_restart": true,
|
||||||
|
"depends_true": "method",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"value": false,
|
"value": false,
|
||||||
"description": "Disables using the same address on multiple accounts."
|
"description": "Disables using the same address on multiple accounts."
|
||||||
|
@ -312,25 +312,27 @@
|
|||||||
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-editor" href="">
|
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-editor" href="">
|
||||||
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
<span class="heading"><span id="header-editor"></span> <span class="modal-close">×</span></span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col card ~neutral @low">
|
<div class="col card ~neutral @low flex flex-col gap-2 justify-between">
|
||||||
<aside class="aside sm ~urge dark:~d_info mb-2 @low" id="aside-editor"></aside>
|
<div class="flex flex-col gap-2">
|
||||||
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
|
<aside class="aside sm ~urge dark:~d_info @low" id="aside-editor"></aside>
|
||||||
<div id="editor-variables" class="mt-4"></div>
|
<label class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</label>
|
||||||
|
<div id="editor-variables" class="flex flex-row gap-2 flex-wrap"></div>
|
||||||
<span class="label supra" for="editor-conditionals" id="label-editor-conditionals">{{ .strings.conditionals }}</span>
|
<span class="label supra" for="editor-conditionals" id="label-editor-conditionals">{{ .strings.conditionals }}</span>
|
||||||
<div id="editor-conditionals"></div>
|
<div id="editor-conditionals"></div>
|
||||||
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
|
||||||
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low mt-4 font-mono"></textarea>
|
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low font-mono"></textarea>
|
||||||
<p class="support mt-4 mb-2">{{ .strings.markdownSupported }}</p>
|
</div>
|
||||||
<div class="flex-row">
|
<div class="flex flex-col gap-2">
|
||||||
<label class="full-width ml-2">
|
<p class="support">{{ .strings.markdownSupported }}</p>
|
||||||
|
<label class="w-full">
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
|
<span class="button ~urge @low w-full supra submit">{{ .strings.submit }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col card ~neutral @low">
|
<div class="col card ~neutral @low flex flex-col gap-2">
|
||||||
<span class="subheading supra">{{ .strings.preview }}</span>
|
<span class="subheading supra">{{ .strings.preview }}</span>
|
||||||
<div class="mt-8" id="editor-preview"></div>
|
<div id="editor-preview"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -48,7 +48,8 @@
|
|||||||
"errorConnection": "Couldn't connect to jfa-go.",
|
"errorConnection": "Couldn't connect to jfa-go.",
|
||||||
"errorUnknown": "Unknown error.",
|
"errorUnknown": "Unknown error.",
|
||||||
"error401Unauthorized": "Unauthorized. Try refreshing the page.",
|
"error401Unauthorized": "Unauthorized. Try refreshing the page.",
|
||||||
"errorSaveSettings": "Couldn't save settings."
|
"errorSaveSettings": "Couldn't save settings.",
|
||||||
|
"errorSpecialSymbols": "Field cannot contain special symbols."
|
||||||
},
|
},
|
||||||
"quantityStrings": {
|
"quantityStrings": {
|
||||||
"year": {
|
"year": {
|
||||||
|
@ -52,6 +52,14 @@ func lshortfile() string {
|
|||||||
return Lshortfile(3)
|
return Lshortfile(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LshortfileTree() string {
|
||||||
|
out := ""
|
||||||
|
for i := 6; i >= 0; i-- {
|
||||||
|
out += strconv.Itoa(i) + ":" + Lshortfile(i) + " "
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func NewLogger(out io.Writer, prefix string, flag int, color c.Attribute) (l *Logger) {
|
func NewLogger(out io.Writer, prefix string, flag int, color c.Attribute) (l *Logger) {
|
||||||
l = &Logger{}
|
l = &Logger{}
|
||||||
// Use reimplemented Lshortfile since wrapping the log functions messes them up
|
// Use reimplemented Lshortfile since wrapping the log functions messes them up
|
||||||
@ -96,6 +104,13 @@ func (l *Logger) PrintfCustomLevel(level int, format string, v ...interface{}) {
|
|||||||
l.logger.Print(out)
|
l.logger.Print(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Logger) PrintfNoFile(format string, v ...interface{}) {
|
||||||
|
if l.empty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.logger.Print(l.printer.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Logger) Print(v ...interface{}) {
|
func (l *Logger) Print(v ...interface{}) {
|
||||||
if l.empty {
|
if l.empty {
|
||||||
return
|
return
|
||||||
|
@ -175,6 +175,7 @@ const (
|
|||||||
AccountLinked = "account already linked and require_unique enabled"
|
AccountLinked = "account already linked and require_unique enabled"
|
||||||
AccountUnverified = "unverified"
|
AccountUnverified = "unverified"
|
||||||
FailedSetDiscordMemberRole = "Failed to apply/remove " + Discord + " member role: %v"
|
FailedSetDiscordMemberRole = "Failed to apply/remove " + Discord + " member role: %v"
|
||||||
|
InvalidChar = "Invalid character '%c'"
|
||||||
|
|
||||||
FailedSetEmailAddress = "Failed to set email address for %s user \"%s\": %v"
|
FailedSetEmailAddress = "Failed to set email address for %s user \"%s\": %v"
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ import (
|
|||||||
|
|
||||||
type stringResponse struct {
|
type stringResponse struct {
|
||||||
Response string `json:"response" example:"message"`
|
Response string `json:"response" example:"message"`
|
||||||
Error string `json:"error" example:"errorDescription"`
|
ErrorText string `json:"error" example:"No special symbols allowed."`
|
||||||
|
ErrorCode string `json:"error_code" example:"errorSpecialSymbols"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type boolResponse struct {
|
type boolResponse struct {
|
||||||
|
19
ts/form.ts
19
ts/form.ts
@ -176,13 +176,32 @@ const rePasswordField = document.getElementById("create-reenter-password") as HT
|
|||||||
|
|
||||||
let captcha = new Captcha(window.code, window.captcha, window.reCAPTCHA, false);
|
let captcha = new Captcha(window.code, window.captcha, window.reCAPTCHA, false);
|
||||||
|
|
||||||
|
const clearSubmitButton = () => {
|
||||||
|
submitInput.setCustomValidity("");
|
||||||
|
submitSpan.title = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const invalidMessage = (el: HTMLInputElement, msg: string) => {
|
||||||
|
el.setCustomValidity(msg);
|
||||||
|
submitInput.setCustomValidity(msg);
|
||||||
|
submitSpan.title = msg;
|
||||||
|
};
|
||||||
|
|
||||||
function _baseValidator(oncomplete: (valid: boolean) => void, captchaValid: boolean): void {
|
function _baseValidator(oncomplete: (valid: boolean) => void, captchaValid: boolean): void {
|
||||||
|
clearSubmitButton();
|
||||||
if (window.emailRequired) {
|
if (window.emailRequired) {
|
||||||
if (!emailField.value.includes("@")) {
|
if (!emailField.value.includes("@")) {
|
||||||
oncomplete(false);
|
oncomplete(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
usernameField.setCustomValidity("");
|
||||||
|
// Jellyfin doesn't like having "+" in the username field
|
||||||
|
if (usernameField.value.includes("+")) {
|
||||||
|
invalidMessage(usernameField, window.messages["errorSpecialSymbols"]);
|
||||||
|
oncomplete(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (window.discordEnabled && window.discordRequired && !discordVerified) {
|
if (window.discordEnabled && window.discordRequired && !discordVerified) {
|
||||||
oncomplete(false);
|
oncomplete(false);
|
||||||
return;
|
return;
|
||||||
|
@ -1138,7 +1138,12 @@ export class accountsList {
|
|||||||
console.log("User created, but welcome email failed");
|
console.log("User created, but welcome email failed");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.notifications.customError("addUser", window.lang.var("notifications", "errorUserCreated", `"${send['username']}"`));
|
let msg = window.lang.var("notifications", "errorUserCreated", `"${send['username']}"`);
|
||||||
|
if ("error" in req.response) {
|
||||||
|
let realError = window.lang.notif(req.response["error"]);
|
||||||
|
if (realError) msg = realError;
|
||||||
|
}
|
||||||
|
window.notifications.customError("addUser", msg);
|
||||||
}
|
}
|
||||||
if (req.response["error"] as String) {
|
if (req.response["error"] as String) {
|
||||||
console.log(req.response["error"]);
|
console.log(req.response["error"]);
|
||||||
|
@ -2,6 +2,12 @@ import { _get, _post, _delete, _download, _upload, toggleLoader, addLoader, remo
|
|||||||
import { Marked } from "@ts-stack/markdown";
|
import { Marked } from "@ts-stack/markdown";
|
||||||
import { stripMarkdown } from "../modules/stripmd.js";
|
import { stripMarkdown } from "../modules/stripmd.js";
|
||||||
|
|
||||||
|
const toBool = (s: string): boolean => {
|
||||||
|
let b = Boolean(s);
|
||||||
|
if (s == "false") b = false;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
interface BackupDTO {
|
interface BackupDTO {
|
||||||
size: string;
|
size: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -9,8 +15,8 @@ interface BackupDTO {
|
|||||||
date: number;
|
date: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface settingsBoolEvent extends Event {
|
interface settingsChangedEvent extends Event {
|
||||||
detail: boolean;
|
detail: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Meta {
|
interface Meta {
|
||||||
@ -52,7 +58,8 @@ const splitDependant = (section: string, dep: string): string[] => {
|
|||||||
return parts
|
return parts
|
||||||
};
|
};
|
||||||
|
|
||||||
class DOMInput {
|
class DOMSetting {
|
||||||
|
protected _hideEl: HTMLElement;
|
||||||
protected _input: HTMLInputElement;
|
protected _input: HTMLInputElement;
|
||||||
protected _container: HTMLDivElement;
|
protected _container: HTMLDivElement;
|
||||||
protected _tooltip: HTMLDivElement;
|
protected _tooltip: HTMLDivElement;
|
||||||
@ -62,11 +69,20 @@ class DOMInput {
|
|||||||
protected _section: string;
|
protected _section: string;
|
||||||
protected _name: string;
|
protected _name: string;
|
||||||
|
|
||||||
hide = () => this._input.parentElement.classList.add("unfocused");
|
hide = () => {
|
||||||
show = () => this._input.parentElement.classList.remove("unfocused");
|
this._hideEl.classList.add("unfocused");
|
||||||
|
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": false })
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
private _advancedListener = (event: settingsBoolEvent) => {
|
};
|
||||||
if (!Boolean(event.detail)) {
|
show = () => {
|
||||||
|
this._hideEl.classList.remove("unfocused");
|
||||||
|
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _advancedListener = (event: settingsChangedEvent) => {
|
||||||
|
if (!toBool(event.detail)) {
|
||||||
this.hide();
|
this.hide();
|
||||||
} else {
|
} else {
|
||||||
this.show();
|
this.show();
|
||||||
@ -100,9 +116,11 @@ class DOMInput {
|
|||||||
get required(): boolean { return this._required.classList.contains("badge"); }
|
get required(): boolean { return this._required.classList.contains("badge"); }
|
||||||
set required(state: boolean) {
|
set required(state: boolean) {
|
||||||
if (state) {
|
if (state) {
|
||||||
|
this._required.classList.remove("unfocused");
|
||||||
this._required.classList.add("badge", "~critical");
|
this._required.classList.add("badge", "~critical");
|
||||||
this._required.textContent = "*";
|
this._required.textContent = "*";
|
||||||
} else {
|
} else {
|
||||||
|
this._required.classList.add("unfocused");
|
||||||
this._required.classList.remove("badge", "~critical");
|
this._required.classList.remove("badge", "~critical");
|
||||||
this._required.textContent = "";
|
this._required.textContent = "";
|
||||||
}
|
}
|
||||||
@ -111,9 +129,11 @@ class DOMInput {
|
|||||||
get requires_restart(): boolean { return this._restart.classList.contains("badge"); }
|
get requires_restart(): boolean { return this._restart.classList.contains("badge"); }
|
||||||
set requires_restart(state: boolean) {
|
set requires_restart(state: boolean) {
|
||||||
if (state) {
|
if (state) {
|
||||||
|
this._restart.classList.remove("unfocused");
|
||||||
this._restart.classList.add("badge", "~info", "dark:~d_warning");
|
this._restart.classList.add("badge", "~info", "dark:~d_warning");
|
||||||
this._restart.textContent = "R";
|
this._restart.textContent = "R";
|
||||||
} else {
|
} else {
|
||||||
|
this._restart.classList.add("unfocused");
|
||||||
this._restart.classList.remove("badge", "~info", "dark:~d_warning");
|
this._restart.classList.remove("badge", "~info", "dark:~d_warning");
|
||||||
this._restart.textContent = "";
|
this._restart.textContent = "";
|
||||||
}
|
}
|
||||||
@ -123,39 +143,44 @@ class DOMInput {
|
|||||||
|
|
||||||
onValueChange = () => {
|
onValueChange = () => {
|
||||||
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
|
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
|
||||||
|
const setEvent = new CustomEvent(`settings-set-${this._section}-${this._name}`, { "detail": this.valueAsString() })
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
|
document.dispatchEvent(setEvent);
|
||||||
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
|
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(inputType: string, setting: Setting, section: string, name: string, customInput?: string) {
|
constructor(input: string, setting: Setting, section: string, name: string, inputOnTop: boolean = false) {
|
||||||
this._section = section;
|
this._section = section;
|
||||||
this._name = name;
|
this._name = name;
|
||||||
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.setAttribute("data-name", name);
|
||||||
const defaultInput = `
|
|
||||||
<input type="${inputType}" class="input setting-input ~neutral @low mt-2 mb-2">
|
|
||||||
`;
|
|
||||||
this._container.innerHTML = `
|
this._container.innerHTML = `
|
||||||
<label class="label">
|
<label class="label flex flex-col gap-2">
|
||||||
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span>
|
${inputOnTop ? input : ""}
|
||||||
|
<div class="flex flex-row gap-2 items-baseline">
|
||||||
|
<span class="setting-label"></span>
|
||||||
<div class="setting-tooltip tooltip right unfocused">
|
<div class="setting-tooltip tooltip right unfocused">
|
||||||
<i class="icon ri-information-line"></i>
|
<i class="icon ri-information-line align-baseline"></i>
|
||||||
<span class="content sm"></span>
|
<span class="content sm"></span>
|
||||||
</div>
|
</div>
|
||||||
${customInput ? customInput : defaultInput}
|
<span class="setting-required unfocused"></span>
|
||||||
|
<span class="setting-restart unfocused"></span>
|
||||||
|
</div>
|
||||||
|
${inputOnTop ? "" : input}
|
||||||
</label>
|
</label>
|
||||||
`;
|
`;
|
||||||
this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
|
this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
|
||||||
this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
|
this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
|
||||||
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
|
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
|
||||||
|
// "input" variable should supply the HTML of an element with class "setting-input"
|
||||||
this._input = this._container.querySelector(".setting-input") as HTMLInputElement;
|
this._input = this._container.querySelector(".setting-input") as HTMLInputElement;
|
||||||
if (setting.depends_false || setting.depends_true) {
|
if (setting.depends_false || setting.depends_true) {
|
||||||
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
|
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
|
||||||
let state = true;
|
let state = true;
|
||||||
if (setting.depends_false) { state = false; }
|
if (setting.depends_false) { state = false; }
|
||||||
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
|
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsChangedEvent) => {
|
||||||
if (Boolean(event.detail) !== state) {
|
if (toBool(event.detail) !== state) {
|
||||||
this.hide();
|
this.hide();
|
||||||
} else {
|
} else {
|
||||||
this.show();
|
this.show();
|
||||||
@ -163,13 +188,14 @@ class DOMInput {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this._input.onchange = this.onValueChange;
|
this._input.onchange = this.onValueChange;
|
||||||
this.update(setting);
|
document.addEventListener(`settings-loaded`, this.onValueChange);
|
||||||
|
this._hideEl = this._container;
|
||||||
}
|
}
|
||||||
|
|
||||||
get value(): any { return this._input.value; }
|
get value(): any { return this._input.value; }
|
||||||
set value(v: any) { this._input.value = v; }
|
set value(v: any) { this._input.value = v; }
|
||||||
|
|
||||||
update = (s: Setting) => {
|
update(s: Setting) {
|
||||||
this.name = s.name;
|
this.name = s.name;
|
||||||
this.description = s.description;
|
this.description = s.description;
|
||||||
this.required = s.required;
|
this.required = s.required;
|
||||||
@ -181,6 +207,17 @@ class DOMInput {
|
|||||||
asElement = (): HTMLDivElement => { return this._container; }
|
asElement = (): HTMLDivElement => { return this._container; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DOMInput extends DOMSetting {
|
||||||
|
constructor(inputType: string, setting: Setting, section: string, name: string) {
|
||||||
|
super(
|
||||||
|
`<input type="${inputType}" class="input setting-input ~neutral @low">`,
|
||||||
|
setting, section, name,
|
||||||
|
);
|
||||||
|
// this._hideEl = this._input.parentElement;
|
||||||
|
this.update(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface SText extends Setting {
|
interface SText extends Setting {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
@ -191,85 +228,6 @@ class DOMText extends DOMInput implements SText {
|
|||||||
set value(v: string) { this._input.value = v; }
|
set value(v: string) { this._input.value = v; }
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SList extends Setting {
|
|
||||||
value: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
class DOMList extends DOMInput implements SList {
|
|
||||||
protected _inputs: HTMLDivElement;
|
|
||||||
type: string = "list";
|
|
||||||
|
|
||||||
valueAsString = (): string => { return this.value.join("|"); };
|
|
||||||
|
|
||||||
get value(): string[] {
|
|
||||||
let values = [];
|
|
||||||
const inputs = this._input.querySelectorAll("input") as NodeListOf<HTMLInputElement>;
|
|
||||||
for (let i in inputs) {
|
|
||||||
if (inputs[i].value) values.push(inputs[i].value);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
set value(v: string[]) {
|
|
||||||
this._input.textContent = ``;
|
|
||||||
for (let val of v) {
|
|
||||||
let input = this.inputRow(val);
|
|
||||||
this._input.appendChild(input);
|
|
||||||
}
|
|
||||||
const addDummy = () => {
|
|
||||||
const dummyRow = this.inputRow();
|
|
||||||
const input = dummyRow.querySelector("input") as HTMLInputElement;
|
|
||||||
input.placeholder = window.lang.strings("add");
|
|
||||||
const onDummyChange = () => {
|
|
||||||
if (!(input.value)) return;
|
|
||||||
addDummy();
|
|
||||||
input.removeEventListener("change", onDummyChange);
|
|
||||||
input.placeholder = ``;
|
|
||||||
}
|
|
||||||
input.addEventListener("change", onDummyChange);
|
|
||||||
this._input.appendChild(dummyRow);
|
|
||||||
};
|
|
||||||
addDummy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private inputRow(v: string = ""): HTMLDivElement {
|
|
||||||
let container = document.createElement("div") as HTMLDivElement;
|
|
||||||
container.classList.add("flex", "flex-row", "justify-between");
|
|
||||||
container.innerHTML = `
|
|
||||||
<input type="text" class="input ~neutral @low">
|
|
||||||
<button class="button ~neutral @low center -ml-10 rounded-s-none aria-label="${window.lang.strings("delete")}" title="${window.lang.strings("delete")}">
|
|
||||||
<i class="ri-close-line"></i>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
const input = container.querySelector("input") as HTMLInputElement;
|
|
||||||
input.value = v;
|
|
||||||
input.onchange = this.onValueChange;
|
|
||||||
const removeRow = container.querySelector("button") as HTMLButtonElement;
|
|
||||||
removeRow.onclick = () => {
|
|
||||||
if (!(container.nextElementSibling)) return;
|
|
||||||
container.remove();
|
|
||||||
this.onValueChange();
|
|
||||||
}
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
update = (s: Setting) => {
|
|
||||||
this.name = s.name;
|
|
||||||
this.description = s.description;
|
|
||||||
this.required = s.required;
|
|
||||||
this.requires_restart = s.requires_restart;
|
|
||||||
this.value = s.value as string[];
|
|
||||||
this.advanced = s.advanced;
|
|
||||||
}
|
|
||||||
|
|
||||||
asElement = (): HTMLDivElement => { return this._container; }
|
|
||||||
|
|
||||||
constructor(setting: Setting, section: string, name: string) {
|
|
||||||
super("list", setting, section, name,
|
|
||||||
`<div class="setting-input flex flex-col gap-2 mt-2 mb-2"></div>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SPassword extends Setting {
|
interface SPassword extends Setting {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
@ -300,209 +258,107 @@ class DOMNumber extends DOMInput implements SNumber {
|
|||||||
set value(v: number) { this._input.value = ""+v; }
|
set value(v: number) { this._input.value = ""+v; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SList extends Setting {
|
||||||
|
value: string[];
|
||||||
|
}
|
||||||
|
class DOMList extends DOMSetting implements SList {
|
||||||
|
protected _inputs: HTMLDivElement;
|
||||||
|
type: string = "list";
|
||||||
|
|
||||||
|
valueAsString = (): string => { return this.value.join("|"); };
|
||||||
|
|
||||||
|
get value(): string[] {
|
||||||
|
let values = [];
|
||||||
|
const inputs = this._input.querySelectorAll("input") as NodeListOf<HTMLInputElement>;
|
||||||
|
for (let i in inputs) {
|
||||||
|
if (inputs[i].value) values.push(inputs[i].value);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
set value(v: string[]) {
|
||||||
|
this._input.textContent = ``;
|
||||||
|
for (let val of v) {
|
||||||
|
let input = this.inputRow(val);
|
||||||
|
this._input.appendChild(input);
|
||||||
|
}
|
||||||
|
const addDummy = () => {
|
||||||
|
const dummyRow = this.inputRow();
|
||||||
|
const input = dummyRow.querySelector("input") as HTMLInputElement;
|
||||||
|
input.placeholder = window.lang.strings("add");
|
||||||
|
const onDummyChange = () => {
|
||||||
|
if (!(input.value)) return;
|
||||||
|
addDummy();
|
||||||
|
input.removeEventListener("change", onDummyChange);
|
||||||
|
input.removeEventListener("keyup", onDummyChange);
|
||||||
|
input.placeholder = ``;
|
||||||
|
}
|
||||||
|
input.addEventListener("change", onDummyChange);
|
||||||
|
input.addEventListener("keyup", onDummyChange);
|
||||||
|
this._input.appendChild(dummyRow);
|
||||||
|
};
|
||||||
|
addDummy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private inputRow(v: string = ""): HTMLDivElement {
|
||||||
|
let container = document.createElement("div") as HTMLDivElement;
|
||||||
|
container.classList.add("flex", "flex-row", "justify-between");
|
||||||
|
container.innerHTML = `
|
||||||
|
<input type="text" class="input ~neutral @low">
|
||||||
|
<button class="button ~neutral @low center -ml-10 rounded-s-none aria-label="${window.lang.strings("delete")}" title="${window.lang.strings("delete")}">
|
||||||
|
<i class="ri-close-line"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
const input = container.querySelector("input") as HTMLInputElement;
|
||||||
|
input.value = v;
|
||||||
|
input.onchange = this.onValueChange;
|
||||||
|
const removeRow = container.querySelector("button") as HTMLButtonElement;
|
||||||
|
removeRow.onclick = () => {
|
||||||
|
if (!(container.nextElementSibling)) return;
|
||||||
|
container.remove();
|
||||||
|
this.onValueChange();
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(setting: Setting, section: string, name: string) {
|
||||||
|
super(
|
||||||
|
`<div class="setting-input flex flex-col gap-2"></div>`,
|
||||||
|
setting, section, name,
|
||||||
|
);
|
||||||
|
// this._hideEl = this._input.parentElement;
|
||||||
|
this.update(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface SBool extends Setting {
|
interface SBool extends Setting {
|
||||||
value: boolean;
|
value: boolean;
|
||||||
}
|
}
|
||||||
class DOMBool implements SBool {
|
class DOMBool extends DOMSetting implements SBool {
|
||||||
protected _input: HTMLInputElement;
|
|
||||||
private _container: HTMLDivElement;
|
|
||||||
private _tooltip: HTMLDivElement;
|
|
||||||
private _required: HTMLSpanElement;
|
|
||||||
private _restart: HTMLSpanElement;
|
|
||||||
type: string = "bool";
|
type: string = "bool";
|
||||||
private _advanced: boolean;
|
|
||||||
|
|
||||||
hide = () => this._input.parentElement.classList.add("unfocused");
|
|
||||||
show = () => this._input.parentElement.classList.remove("unfocused");
|
|
||||||
|
|
||||||
private _advancedListener = (event: settingsBoolEvent) => {
|
|
||||||
if (!Boolean(event.detail)) {
|
|
||||||
this.hide();
|
|
||||||
} else {
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get advanced(): boolean { return this._advanced; }
|
|
||||||
set advanced(advanced: boolean) {
|
|
||||||
this._advanced = advanced;
|
|
||||||
if (advanced) {
|
|
||||||
document.addEventListener("settings-advancedState", this._advancedListener);
|
|
||||||
} else {
|
|
||||||
document.removeEventListener("settings-advancedState", this._advancedListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get name(): string { return this._container.querySelector("span.setting-label").textContent; }
|
|
||||||
set name(n: string) { this._container.querySelector("span.setting-label").textContent = n; }
|
|
||||||
|
|
||||||
get description(): string { return this._tooltip.querySelector("span.content").textContent; }
|
|
||||||
set description(d: string) {
|
|
||||||
const content = this._tooltip.querySelector("span.content") as HTMLSpanElement;
|
|
||||||
content.textContent = d;
|
|
||||||
if (d == "") {
|
|
||||||
this._tooltip.classList.add("unfocused");
|
|
||||||
} else {
|
|
||||||
this._tooltip.classList.remove("unfocused");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get required(): boolean { return this._required.classList.contains("badge"); }
|
|
||||||
set required(state: boolean) {
|
|
||||||
if (state) {
|
|
||||||
this._required.classList.add("badge", "~critical");
|
|
||||||
this._required.textContent = "*";
|
|
||||||
} else {
|
|
||||||
this._required.classList.remove("badge", "~critical");
|
|
||||||
this._required.textContent = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get requires_restart(): boolean { return this._restart.classList.contains("badge"); }
|
|
||||||
set requires_restart(state: boolean) {
|
|
||||||
if (state) {
|
|
||||||
this._restart.classList.add("badge", "~info", "dark:~d_warning");
|
|
||||||
this._restart.textContent = "R";
|
|
||||||
} else {
|
|
||||||
this._restart.classList.remove("badge", "~info", "dark:~d_warning");
|
|
||||||
this._restart.textContent = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueAsString = (): string => { return ""+this.value; };
|
|
||||||
|
|
||||||
get value(): boolean { return this._input.checked; }
|
get value(): boolean { return this._input.checked; }
|
||||||
set value(state: boolean) { this._input.checked = state; }
|
set value(state: boolean) { this._input.checked = state; }
|
||||||
|
|
||||||
constructor(setting: SBool, section: string, name: string) {
|
constructor(setting: SBool, section: string, name: string) {
|
||||||
this._container = document.createElement("div");
|
super(
|
||||||
this._container.classList.add("setting");
|
`<input type="checkbox" class="setting-input">`,
|
||||||
this._container.setAttribute("data-name", name)
|
setting, section, name, true,
|
||||||
this._container.innerHTML = `
|
);
|
||||||
<label class="switch mb-2">
|
const label = this._container.getElementsByTagName("LABEL")[0];
|
||||||
<input type="checkbox">
|
label.classList.remove("flex-col");
|
||||||
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span>
|
label.classList.add("flex-row");
|
||||||
<div class="setting-tooltip tooltip right unfocused">
|
// this._hideEl = this._input.parentElement;
|
||||||
<i class="icon ri-information-line"></i>
|
|
||||||
<span class="content sm"></span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
`;
|
|
||||||
this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
|
|
||||||
this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
|
|
||||||
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
|
|
||||||
this._input = this._container.querySelector("input[type=checkbox]") as HTMLInputElement;
|
|
||||||
const onValueChange = () => {
|
|
||||||
const event = new CustomEvent(`settings-${section}-${name}`, { "detail": this.valueAsString() })
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
};
|
|
||||||
this._input.onchange = () => {
|
|
||||||
onValueChange();
|
|
||||||
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
|
|
||||||
};
|
|
||||||
document.addEventListener(`settings-loaded`, onValueChange);
|
|
||||||
|
|
||||||
if (setting.depends_false || setting.depends_true) {
|
|
||||||
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
|
|
||||||
let state = true;
|
|
||||||
if (setting.depends_false) { state = false; }
|
|
||||||
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
|
|
||||||
if (Boolean(event.detail) !== state) {
|
|
||||||
this.hide();
|
|
||||||
} else {
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.update(setting);
|
this.update(setting);
|
||||||
}
|
}
|
||||||
update = (s: SBool) => {
|
|
||||||
this.name = s.name;
|
|
||||||
this.description = s.description;
|
|
||||||
this.required = s.required;
|
|
||||||
this.requires_restart = s.requires_restart;
|
|
||||||
this.value = s.value;
|
|
||||||
this.advanced = s.advanced;
|
|
||||||
}
|
|
||||||
|
|
||||||
asElement = (): HTMLDivElement => { return this._container; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SSelect extends Setting {
|
interface SSelect extends Setting {
|
||||||
options: string[][];
|
options: string[][];
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
class DOMSelect implements SSelect {
|
class DOMSelect extends DOMSetting implements SSelect {
|
||||||
protected _select: HTMLSelectElement;
|
|
||||||
private _container: HTMLDivElement;
|
|
||||||
private _tooltip: HTMLDivElement;
|
|
||||||
private _required: HTMLSpanElement;
|
|
||||||
private _restart: HTMLSpanElement;
|
|
||||||
private _options: string[][];
|
|
||||||
type: string = "bool";
|
type: string = "bool";
|
||||||
private _advanced: boolean;
|
private _options: string[][];
|
||||||
|
|
||||||
hide = () => this._container.classList.add("unfocused");
|
|
||||||
show = () => this._container.classList.remove("unfocused");
|
|
||||||
|
|
||||||
private _advancedListener = (event: settingsBoolEvent) => {
|
|
||||||
if (!Boolean(event.detail)) {
|
|
||||||
this.hide();
|
|
||||||
} else {
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get advanced(): boolean { return this._advanced; }
|
|
||||||
set advanced(advanced: boolean) {
|
|
||||||
this._advanced = advanced;
|
|
||||||
if (advanced) {
|
|
||||||
document.addEventListener("settings-advancedState", this._advancedListener);
|
|
||||||
} else {
|
|
||||||
document.removeEventListener("settings-advancedState", this._advancedListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get name(): string { return this._container.querySelector("span.setting-label").textContent; }
|
|
||||||
set name(n: string) { this._container.querySelector("span.setting-label").textContent = n; }
|
|
||||||
|
|
||||||
get description(): string { return this._tooltip.querySelector("span.content").textContent; }
|
|
||||||
set description(d: string) {
|
|
||||||
const content = this._tooltip.querySelector("span.content") as HTMLSpanElement;
|
|
||||||
content.textContent = d;
|
|
||||||
if (d == "") {
|
|
||||||
this._tooltip.classList.add("unfocused");
|
|
||||||
} else {
|
|
||||||
this._tooltip.classList.remove("unfocused");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get required(): boolean { return this._required.classList.contains("badge"); }
|
|
||||||
set required(state: boolean) {
|
|
||||||
if (state) {
|
|
||||||
this._required.classList.add("badge", "~critical");
|
|
||||||
this._required.textContent = "*";
|
|
||||||
} else {
|
|
||||||
this._required.classList.remove("badge", "~critical");
|
|
||||||
this._required.textContent = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get requires_restart(): boolean { return this._restart.classList.contains("badge"); }
|
|
||||||
set requires_restart(state: boolean) {
|
|
||||||
if (state) {
|
|
||||||
this._restart.classList.add("badge", "~info", "dark:~d_warning");
|
|
||||||
this._restart.textContent = "R";
|
|
||||||
} else {
|
|
||||||
this._restart.classList.remove("badge", "~info", "dark:~d_warning");
|
|
||||||
this._restart.textContent = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueAsString = (): string => { return ""+this.value; };
|
|
||||||
|
|
||||||
get value(): string { return this._select.value; }
|
|
||||||
set value(v: string) { this._select.value = v; }
|
|
||||||
|
|
||||||
get options(): string[][] { return this._options; }
|
get options(): string[][] { return this._options; }
|
||||||
set options(opt: string[][]) {
|
set options(opt: string[][]) {
|
||||||
@ -511,88 +367,47 @@ class DOMSelect implements SSelect {
|
|||||||
for (let option of this._options) {
|
for (let option of this._options) {
|
||||||
innerHTML += `<option value="${option[0]}">${option[1]}</option>`;
|
innerHTML += `<option value="${option[0]}">${option[1]}</option>`;
|
||||||
}
|
}
|
||||||
this._select.innerHTML = innerHTML;
|
this._input.innerHTML = innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update(s: SSelect) {
|
||||||
|
this.options = s.options;
|
||||||
|
super.update(s);
|
||||||
|
};
|
||||||
|
|
||||||
constructor(setting: SSelect, section: string, name: string) {
|
constructor(setting: SSelect, section: string, name: string) {
|
||||||
this._options = [];
|
super(
|
||||||
this._container = document.createElement("div");
|
`<div class="select ~neutral @low">
|
||||||
this._container.classList.add("setting");
|
<select class="setting-select setting-input"></select>
|
||||||
this._container.setAttribute("data-name", name)
|
</div>`,
|
||||||
this._container.innerHTML = `
|
setting, section, name,
|
||||||
<label class="label">
|
|
||||||
<span class="setting-label"></span> <span class="setting-required"></span> <span class="setting-restart"></span>
|
|
||||||
<div class="setting-tooltip tooltip right unfocused">
|
|
||||||
<i class="icon ri-information-line"></i>
|
|
||||||
<span class="content sm"></span>
|
|
||||||
</div>
|
|
||||||
<div class="select ~neutral @low mt-2 mb-2">
|
|
||||||
<select class="settings-select"></select>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
`;
|
|
||||||
this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
|
|
||||||
this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
|
|
||||||
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
|
|
||||||
this._select = this._container.querySelector("select.settings-select") as HTMLSelectElement;
|
|
||||||
if (setting.depends_false || setting.depends_true) {
|
|
||||||
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
|
|
||||||
let state = true;
|
|
||||||
if (setting.depends_false) { state = false; }
|
|
||||||
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
|
|
||||||
if (Boolean(event.detail) !== state) {
|
|
||||||
this.hide();
|
|
||||||
} else {
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const onValueChange = () => {
|
|
||||||
const event = new CustomEvent(`settings-${section}-${name}`, { "detail": this.valueAsString() })
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
|
|
||||||
};
|
|
||||||
this._select.onchange = onValueChange;
|
|
||||||
document.addEventListener(`settings-loaded`, onValueChange);
|
|
||||||
|
|
||||||
const message = document.getElementById("settings-message") as HTMLElement;
|
|
||||||
message.innerHTML = window.lang.var("strings",
|
|
||||||
"settingsRequiredOrRestartMessage",
|
|
||||||
`<span class="badge ~critical">*</span>`,
|
|
||||||
`<span class="badge ~info dark:~d_warning">R</span>`
|
|
||||||
);
|
);
|
||||||
|
this._options = [];
|
||||||
|
// this._hideEl = this._container;
|
||||||
this.update(setting);
|
this.update(setting);
|
||||||
}
|
}
|
||||||
update = (s: SSelect) => {
|
|
||||||
this.name = s.name;
|
|
||||||
this.description = s.description;
|
|
||||||
this.required = s.required;
|
|
||||||
this.requires_restart = s.requires_restart;
|
|
||||||
this.options = s.options;
|
|
||||||
this.value = s.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
asElement = (): HTMLDivElement => { return this._container; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SNote extends Setting {
|
interface SNote extends Setting {
|
||||||
value: string;
|
value: string;
|
||||||
style?: string;
|
style?: string;
|
||||||
}
|
}
|
||||||
class DOMNote implements SNote {
|
class DOMNote extends DOMSetting implements SNote {
|
||||||
private _container: HTMLDivElement;
|
private _nameEl: HTMLElement;
|
||||||
private _aside: HTMLElement;
|
|
||||||
private _name: HTMLElement;
|
|
||||||
private _description: HTMLElement;
|
private _description: HTMLElement;
|
||||||
type: string = "note";
|
type: string = "note";
|
||||||
private _style: string;
|
private _style: string;
|
||||||
|
|
||||||
hide = () => this._container.classList.add("unfocused");
|
// We're a note, no one depends on us so we don't need to broadcast a state change.
|
||||||
show = () => this._container.classList.remove("unfocused");
|
hide = () => {
|
||||||
|
this._container.classList.add("unfocused");
|
||||||
|
};
|
||||||
|
show = () => {
|
||||||
|
this._container.classList.remove("unfocused");
|
||||||
|
};
|
||||||
|
|
||||||
get name(): string { return this._name.textContent; }
|
get name(): string { return this._nameEl.textContent; }
|
||||||
set name(n: string) { this._name.textContent = n; }
|
set name(n: string) { this._nameEl.textContent = n; }
|
||||||
|
|
||||||
get description(): string { return this._description.textContent; }
|
get description(): string { return this._description.textContent; }
|
||||||
set description(d: string) {
|
set description(d: string) {
|
||||||
@ -602,51 +417,37 @@ class DOMNote implements SNote {
|
|||||||
valueAsString = (): string => { return ""; };
|
valueAsString = (): string => { return ""; };
|
||||||
|
|
||||||
get value(): string { return ""; }
|
get value(): string { return ""; }
|
||||||
set value(v: string) { return; }
|
set value(_: string) { return; }
|
||||||
|
|
||||||
get required(): boolean { return false; }
|
get required(): boolean { return false; }
|
||||||
set required(v: boolean) { return; }
|
set required(_: boolean) { return; }
|
||||||
|
|
||||||
get requires_restart(): boolean { return false; }
|
get requires_restart(): boolean { return false; }
|
||||||
set requires_restart(v: boolean) { return; }
|
set requires_restart(_: boolean) { return; }
|
||||||
|
|
||||||
get style(): string { return this._style; }
|
get style(): string { return this._style; }
|
||||||
set style(s: string) {
|
set style(s: string) {
|
||||||
this._aside.classList.remove("~" + this._style);
|
this._input.classList.remove("~" + this._style);
|
||||||
this._style = s;
|
this._style = s;
|
||||||
this._aside.classList.add("~" + this._style);
|
this._input.classList.add("~" + this._style);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(setting: SNote, section: string) {
|
constructor(setting: SNote, section: string) {
|
||||||
this._container = document.createElement("div");
|
super(
|
||||||
this._container.classList.add("setting");
|
`
|
||||||
this._container.innerHTML = `
|
<aside class="aside setting-input">
|
||||||
<aside class="aside my-2">
|
|
||||||
<span class="font-bold setting-name"></span>
|
<span class="font-bold setting-name"></span>
|
||||||
<span class="content setting-description">
|
<span class="content setting-description">
|
||||||
</aside>
|
</aside>
|
||||||
`;
|
`, setting, section, "",
|
||||||
this._name = this._container.querySelector(".setting-name");
|
);
|
||||||
|
// this._hideEl = this._container;
|
||||||
|
this._nameEl = this._container.querySelector(".setting-name");
|
||||||
this._description = this._container.querySelector(".setting-description");
|
this._description = this._container.querySelector(".setting-description");
|
||||||
this._aside = this._container.querySelector("aside");
|
|
||||||
|
|
||||||
if (setting.depends_false || setting.depends_true) {
|
|
||||||
let dependant = splitDependant(section, setting.depends_true || setting.depends_false);
|
|
||||||
let state = true;
|
|
||||||
if (setting.depends_false) { state = false; }
|
|
||||||
document.addEventListener(`settings-${dependant[0]}-${dependant[1]}`, (event: settingsBoolEvent) => {
|
|
||||||
if (Boolean(event.detail) !== state) {
|
|
||||||
this.hide();
|
|
||||||
} else {
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(setting);
|
this.update(setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
update = (s: SNote) => {
|
update(s: SNote) {
|
||||||
this.name = s.name;
|
this.name = s.name;
|
||||||
this.description = s.description;
|
this.description = s.description;
|
||||||
this.style = ("style" in s && s.style) ? s.style : "info";
|
this.style = ("style" in s && s.style) ? s.style : "info";
|
||||||
@ -671,7 +472,7 @@ class sectionPanel {
|
|||||||
this._sectionName = sectionName;
|
this._sectionName = sectionName;
|
||||||
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", "flex", "flex-col", "gap-2");
|
||||||
this._section.setAttribute("data-section", sectionName);
|
this._section.setAttribute("data-section", sectionName);
|
||||||
let innerHTML = `
|
let innerHTML = `
|
||||||
<div class="flex flex-row justify-between">
|
<div class="flex flex-row justify-between">
|
||||||
@ -683,7 +484,7 @@ class sectionPanel {
|
|||||||
|
|
||||||
innerHTML += `
|
innerHTML += `
|
||||||
</div>
|
</div>
|
||||||
<p class="support lg my-2 settings-section-description">${s.meta.description}</p>
|
<p class="support lg settings-section-description">${s.meta.description}</p>
|
||||||
`;
|
`;
|
||||||
this._section.innerHTML = innerHTML;
|
this._section.innerHTML = innerHTML;
|
||||||
|
|
||||||
@ -724,7 +525,9 @@ class sectionPanel {
|
|||||||
}
|
}
|
||||||
if (setting.type != "note") {
|
if (setting.type != "note") {
|
||||||
this.values[name] = ""+setting.value;
|
this.values[name] = ""+setting.value;
|
||||||
document.addEventListener(`settings-${this._sectionName}-${name}`, (event: CustomEvent) => {
|
// settings-section-name: Implies the setting changed or was shown/hidden.
|
||||||
|
// settings-set-section-name: Implies the setting changed.
|
||||||
|
document.addEventListener(`settings-set-${this._sectionName}-${name}`, (event: CustomEvent) => {
|
||||||
// const oldValue = this.values[name];
|
// const oldValue = this.values[name];
|
||||||
this.values[name] = event.detail;
|
this.values[name] = event.detail;
|
||||||
document.dispatchEvent(new CustomEvent("settings-section-changed"));
|
document.dispatchEvent(new CustomEvent("settings-section-changed"));
|
||||||
@ -791,9 +594,8 @@ export class settingsList {
|
|||||||
let dependant = splitDependant(name, s.meta.depends_true || s.meta.depends_false);
|
let dependant = splitDependant(name, s.meta.depends_true || s.meta.depends_false);
|
||||||
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: settingsChangedEvent) => {
|
||||||
|
if (toBool(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 }));
|
||||||
} else {
|
} else {
|
||||||
@ -801,16 +603,16 @@ export class settingsList {
|
|||||||
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: true }));
|
document.dispatchEvent(new CustomEvent(`settings-${name}`, { detail: true }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.addEventListener(`settings-${dependant[0]}`, (event: settingsBoolEvent) => {
|
document.addEventListener(`settings-${dependant[0]}`, (event: settingsChangedEvent) => {
|
||||||
if (Boolean(event.detail) !== state) {
|
if (toBool(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 }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (s.meta.advanced) {
|
if (s.meta.advanced) {
|
||||||
document.addEventListener("settings-advancedState", (event: settingsBoolEvent) => {
|
document.addEventListener("settings-advancedState", (event: settingsChangedEvent) => {
|
||||||
if (!Boolean(event.detail)) {
|
if (!toBool(event.detail)) {
|
||||||
button.classList.add("unfocused");
|
button.classList.add("unfocused");
|
||||||
} else {
|
} else {
|
||||||
button.classList.remove("unfocused");
|
button.classList.remove("unfocused");
|
||||||
@ -1002,6 +804,14 @@ export class settingsList {
|
|||||||
this._searchbox.oninput(null);
|
this._searchbox.oninput(null);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// What possessed me to put this in the DOMSelect constructor originally? like what????????
|
||||||
|
const message = document.getElementById("settings-message") as HTMLElement;
|
||||||
|
message.innerHTML = window.lang.var("strings",
|
||||||
|
"settingsRequiredOrRestartMessage",
|
||||||
|
`<span class="badge ~critical">*</span>`,
|
||||||
|
`<span class="badge ~info dark:~d_warning">R</span>`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addMatrix = () => {
|
private _addMatrix = () => {
|
||||||
@ -1278,7 +1088,7 @@ class MessageEditor {
|
|||||||
let innerHTML = '';
|
let innerHTML = '';
|
||||||
for (let i = 0; i < this._templ.variables.length; i++) {
|
for (let i = 0; i < this._templ.variables.length; i++) {
|
||||||
let ci = i % colors.length;
|
let ci = i % colors.length;
|
||||||
innerHTML += '<span class="button ~' + colors[ci] +' @low mb-4" style="margin-left: 0.25rem; margin-right: 0.25rem;"></span>'
|
innerHTML += '<span class="button ~' + colors[ci] +' @low"></span>'
|
||||||
}
|
}
|
||||||
if (this._templ.variables.length == 0) {
|
if (this._templ.variables.length == 0) {
|
||||||
this._variablesLabel.classList.add("unfocused");
|
this._variablesLabel.classList.add("unfocused");
|
||||||
|
17
users.go
17
users.go
@ -1,10 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/hrfee/jfa-go/logger"
|
||||||
lm "github.com/hrfee/jfa-go/logmessages"
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/lithammer/shortuuid/v3"
|
"github.com/lithammer/shortuuid/v3"
|
||||||
@ -53,9 +56,11 @@ type NewUserData struct {
|
|||||||
func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData, pendingTasks *sync.WaitGroup) {
|
func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData, pendingTasks *sync.WaitGroup) {
|
||||||
pendingTasks = &sync.WaitGroup{}
|
pendingTasks = &sync.WaitGroup{}
|
||||||
// Some helper functions which will behave as our app.info/error/debug
|
// Some helper functions which will behave as our app.info/error/debug
|
||||||
|
// And make sure we capture the correct caller location.
|
||||||
deferLogInfo := func(s string, args ...any) {
|
deferLogInfo := func(s string, args ...any) {
|
||||||
|
loc := logger.Lshortfile(2)
|
||||||
out.Log = func() {
|
out.Log = func() {
|
||||||
app.info.Printf(s, args)
|
app.info.PrintfNoFile(loc+" "+s, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* deferLogDebug := func(s string, args ...any) {
|
/* deferLogDebug := func(s string, args ...any) {
|
||||||
@ -64,11 +69,19 @@ func (app *appContext) NewUserPostVerification(p NewUserParams) (out NewUserData
|
|||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
deferLogError := func(s string, args ...any) {
|
deferLogError := func(s string, args ...any) {
|
||||||
|
loc := logger.Lshortfile(2)
|
||||||
out.Log = func() {
|
out.Log = func() {
|
||||||
app.err.Printf(s, args)
|
app.err.PrintfNoFile(loc+" "+s, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.ContainsRune(p.Req.Username, '+') {
|
||||||
|
deferLogError(lm.FailedCreateUser, lm.Jellyfin, p.Req.Username, fmt.Sprintf(lm.InvalidChar, '+'))
|
||||||
|
out.Status = 400
|
||||||
|
out.Message = "errorSpecialSymbols"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
existingUser, _ := app.jf.UserByName(p.Req.Username, false)
|
existingUser, _ := app.jf.UserByName(p.Req.Username, false)
|
||||||
if existingUser.Name != "" {
|
if existingUser.Name != "" {
|
||||||
out.Message = lm.UserExists
|
out.Message = lm.UserExists
|
||||||
|
Loading…
Reference in New Issue
Block a user