1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-28 03:50:10 +00:00

Compare commits

..

No commits in common. "a11ac1b1a30d112704977bd3fda3ef26d63b0918" and "fcdd4e4518822e6a9e98bcab364cd57bebf07ea8" have entirely different histories.

34 changed files with 2695 additions and 2268 deletions

View File

@ -116,7 +116,7 @@ archives:
checksum:
name_template: 'checksums.txt'
snapshot:
version_template: "0.0.0-{{ .Env.JFA_GO_NFPM_EPOCH }}"
name_template: "0.0.0-{{ .Env.JFA_GO_NFPM_EPOCH }}"
changelog:
sort: asc
filters:

View File

@ -1,4 +1,4 @@
.PHONY: configuration email typescript swagger copy compile compress inline-css variants-html install clean npm config-description config-default precompile
.PHONY: configuration email typescript swagger copy compile compress tailwind bundle-css inline-css variants-html install clean npm config-description config-default precompile
all: compile
@ -101,22 +101,20 @@ else
SWAGINSTALL :=
endif
CONFIG_BASE = config/config-base.yaml
CONFIG_BASE = config/config-base.json
# CONFIG_DESCRIPTION = $(DATA)/config-base.json
CONFIG_DESCRIPTION = $(DATA)/config-base.json
CONFIG_DEFAULT = $(DATA)/config-default.ini
# $(CONFIG_DESCRIPTION) &: $(CONFIG_BASE)
# $(info Fixing config-base)
# -mkdir -p $(DATA)
$(CONFIG_DESCRIPTION) &: $(CONFIG_BASE)
$(info Fixing config-base)
-mkdir -p $(DATA)
python3 scripts/enumerate_config.py -i config/config-base.json -o $(DATA)/config-base.json
$(DATA):
mkdir -p $(DATA)
$(CONFIG_DEFAULT): $(DATA) $(CONFIG_BASE)
$(CONFIG_DEFAULT) &: $(CONFIG_BASE)
$(info Generating config-default.ini)
go run scripts/ini/main.go -in $(CONFIG_BASE) -out $(DATA)/config-default.ini
python3 scripts/generate_ini.py -i config/config-base.json -o $(DATA)/config-default.ini
configuration: $(CONFIG_DEFAULT)
configuration: $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT)
EMAIL_SRC_MJML = $(wildcard mail/*.mjml)
EMAIL_SRC_TXT = $(wildcard mail/*.txt)
@ -127,9 +125,7 @@ EMAIL_ALL = $(EMAIL_HTML) $(EMAIL_TXT)
EMAIL_TARGET = mail/confirmation.html
$(EMAIL_TARGET): $(EMAIL_SRC_MJML) $(EMAIL_SRC_TXT)
$(info Generating email html)
npx mjml mail/*.mjml -o $(DATA)/
$(info Copying plaintext mail)
cp mail/*.txt $(DATA)/
python3 scripts/compile_mjml.py -o $(DATA)/
TYPESCRIPT_FULLSRC = $(shell find ts/ -type f -name "*.ts")
TYPESCRIPT_SRC = $(wildcard ts/*.ts)
@ -183,6 +179,8 @@ $(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wild
# mv $(CSS_BUNDLE) $(DATA)/web/css/$(CSSVERSION)bundle.css
# npx postcss -o $(CSS_TARGET) $(CSS_TARGET)
bundle-css: tailwind
INLINE_SRC = html/crash.html
INLINE_TARGET = $(DATA)/crash.html
$(INLINE_TARGET): $(CSS_FULLTARGET) $(INLINE_SRC)
@ -199,8 +197,6 @@ COPY_SRC = images/banner.svg jfa-go.service LICENSE $(LANG_SRC) $(STATIC_SRC)
COPY_TARGET = $(DATA)/jfa-go.service
# $(DATA)/LICENSE $(LANG_TARGET) $(STATIC_TARGET) $(DATA)/web/css/$(CSSVERSION)bundle.css
$(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC)
$(info copying $(CONFIG_BASE))
cp $(CONFIG_BASE) $(DATA)/
$(info copying crash page)
cp $(DATA)/crash.html $(DATA)/html/
$(info copying static data)
@ -213,11 +209,11 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC)
cp -r lang $(DATA)/
cp LICENSE $(DATA)/
precompile: $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET)
precompile: $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET)
GO_SRC = $(shell find ./ -name "*.go")
GO_TARGET = build/jfa-go
$(GO_TARGET): $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
$(GO_TARGET): $(CONFIG_DESCRIPTION) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
$(info Downloading deps)
$(GOBINARY) mod download
$(info Building)
@ -229,6 +225,15 @@ compile: $(GO_TARGET)
compress:
upx --lzma build/jfa-go
# internal-files:
# python3 scripts/embed.py internal
#
# external-files:
# python3 scripts/embed.py external
# -mkdir -p build
# $(info copying internal data into build/)
# cp -r data build/
install:
cp -r build $(DESTDIR)/jfa-go

157
api.go
View File

@ -6,7 +6,6 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/itchyny/timefmt-go"
@ -230,15 +229,102 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
// @Summary Get jfa-go configuration.
// @Produce json
// @Success 200 {object} common.Config "Uses the same format as config-base.json"
// @Success 200 {object} settings "Uses the same format as config-base.json"
// @Router /config [get]
// @Security Bearer
// @tags Configuration
func (app *appContext) GetConfig(gc *gin.Context) {
if discordEnabled {
app.PatchConfigDiscordRoles()
resp := app.configBase
// Load language options
formOptions := app.storage.lang.User.getOptions()
fl := resp.Sections["ui"].Settings["language-form"]
fl.Options = formOptions
fl.Value = app.config.Section("ui").Key("language-form").MustString("en-us")
pwrOptions := app.storage.lang.PasswordReset.getOptions()
pl := resp.Sections["password_resets"].Settings["language"]
pl.Options = pwrOptions
pl.Value = app.config.Section("password_resets").Key("language").MustString("en-us")
adminOptions := app.storage.lang.Admin.getOptions()
al := resp.Sections["ui"].Settings["language-admin"]
al.Options = adminOptions
al.Value = app.config.Section("ui").Key("language-admin").MustString("en-us")
emailOptions := app.storage.lang.Email.getOptions()
el := resp.Sections["email"].Settings["language"]
el.Options = emailOptions
el.Value = app.config.Section("email").Key("language").MustString("en-us")
telegramOptions := app.storage.lang.Email.getOptions()
tl := resp.Sections["telegram"].Settings["language"]
tl.Options = telegramOptions
tl.Value = app.config.Section("telegram").Key("language").MustString("en-us")
if updater == "" {
delete(resp.Sections, "updates")
for i, v := range resp.Order {
if v == "updates" {
resp.Order = append(resp.Order[:i], resp.Order[i+1:]...)
break
}
}
}
gc.JSON(200, app.patchedConfig)
if PLATFORM == "windows" {
delete(resp.Sections["smtp"].Settings, "ssl_cert")
for i, v := range resp.Sections["smtp"].Order {
if v == "ssl_cert" {
sect := resp.Sections["smtp"]
sect.Order = append(sect.Order[:i], sect.Order[i+1:]...)
resp.Sections["smtp"] = sect
}
}
}
if !MatrixE2EE() {
delete(resp.Sections["matrix"].Settings, "encryption")
for i, v := range resp.Sections["matrix"].Order {
if v == "encryption" {
sect := resp.Sections["matrix"]
sect.Order = append(sect.Order[:i], sect.Order[i+1:]...)
resp.Sections["matrix"] = sect
}
}
}
for sectName, section := range resp.Sections {
for settingName, setting := range section.Settings {
val := app.config.Section(sectName).Key(settingName)
s := resp.Sections[sectName].Settings[settingName]
switch setting.Type {
case "list":
s.Value = val.StringsWithShadows("|")
case "text", "email", "select", "password", "note":
s.Value = val.MustString("")
case "number":
s.Value = val.MustInt(0)
case "bool":
s.Value = val.MustBool(false)
}
resp.Sections[sectName].Settings[settingName] = s
}
}
if discordEnabled {
r, err := app.discord.ListRoles()
if err == nil {
roles := make([][2]string, len(r)+1)
roles[0] = [2]string{"", "None"}
for i, role := range r {
roles[i+1] = role
}
s := resp.Sections["discord"].Settings["apply_role"]
s.Options = roles
resp.Sections["discord"].Settings["apply_role"] = s
}
}
resp.Sections["ui"].Settings["language-form"] = fl
resp.Sections["ui"].Settings["language-admin"] = al
resp.Sections["email"].Settings["language"] = el
resp.Sections["password_resets"].Settings["language"] = pl
resp.Sections["telegram"].Settings["language"] = tl
resp.Sections["discord"].Settings["language"] = tl
resp.Sections["matrix"].Settings["language"] = tl
gc.JSON(200, resp)
}
// @Summary Modify app config.
@ -254,46 +340,35 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
gc.BindJSON(&req)
// Load a new config, as we set various default values in app.config that shouldn't be stored.
tempConfig, _ := ini.ShadowLoad(app.configPath)
for _, section := range app.configBase.Sections {
ns, ok := req[section.Section]
if !ok {
continue
}
newSection := ns.(map[string]any)
iniSection, err := tempConfig.GetSection(section.Section)
if err != nil {
iniSection, _ = tempConfig.NewSection(section.Section)
}
for _, setting := range section.Settings {
newValue, ok := newSection[setting.Setting]
if !ok {
continue
for section, settings := range req {
if section != "restart-program" {
_, err := tempConfig.GetSection(section)
if err != nil {
tempConfig.NewSection(section)
}
// Patch disabled to actually be an empty string
if section.Section == "email" && setting.Setting == "method" && newValue == "disabled" {
newValue = ""
}
// Copy language preference for chatbots to root one in "telegram"
if (section.Section == "discord" || section.Section == "matrix") && setting.Setting == "language" {
iniSection.Key("language").SetValue(newValue.(string))
} else if setting.Type == common.ListType {
splitValues := strings.Split(newValue.(string), "|")
// Delete the key first to get rid of any shadow values
iniSection.DeleteKey(setting.Setting)
for i, v := range splitValues {
if i == 0 {
iniSection.Key(setting.Setting).SetValue(v)
} else {
iniSection.Key(setting.Setting).AddShadow(v)
}
for setting, value := range settings.(map[string]interface{}) {
if section == "email" && setting == "method" && value == "disabled" {
value = ""
}
if (section == "discord" || section == "matrix") && setting == "language" {
tempConfig.Section("telegram").Key("language").SetValue(value.(string))
} else if app.configBase.Sections[section].Settings[setting].Type == "list" {
splitValues := strings.Split(value.(string), "|")
// Delete the key first to get rid of any shadow values
tempConfig.Section(section).DeleteKey(setting)
for i, v := range splitValues {
if i == 0 {
tempConfig.Section(section).Key(setting).SetValue(v)
} else {
tempConfig.Section(section).Key(setting).AddShadow(v)
}
}
} else if value.(string) != app.config.Section(section).Key(setting).MustString("") {
tempConfig.Section(section).Key(setting).SetValue(value.(string))
}
} else if newValue.(string) != iniSection.Key(setting.Setting).MustString("") {
iniSection.Key(setting.Setting).SetValue(newValue.(string))
}
}
}
tempConfig.Section("").Key("first_run").SetValue("false")
if err := tempConfig.SaveTo(app.configPath); err != nil {
app.err.Printf(lm.FailedWriting, app.configPath, err)
@ -306,8 +381,6 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
app.Restart()
}
app.loadConfig()
// Patch new settings for next GetConfig
app.PatchConfigBase()
// Reinitialize password validator on config change, as opposed to every applicable request like in python.
if _, ok := req["password_validation"]; ok {
validatorConf := ValidatorConf{

View File

@ -251,7 +251,6 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
// host := gc.Request.URL.Hostname()
host := app.ExternalDomain
// Before you think this is broken: the first "true" arg is for "secure", i.e. only HTTPS!
gc.SetCookie("refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/", host, true, true)
gc.JSON(200, getTokenDTO{token})
}

View File

@ -1,62 +0,0 @@
package common
type SectionMeta struct {
Name string `json:"name" yaml:"name" example:"My Section"` // friendly name of the section
Description string `json:"description" yaml:"description"`
Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"`
Disabled bool `json:"disabled,omitempty" yaml:"disabled,omitempty"`
DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"`
DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"`
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
}
type Option [2]string
type SettingType string
var (
BoolType SettingType = "bool"
SelectType SettingType = "select"
TextType SettingType = "text"
PasswordType SettingType = "password"
NumberType SettingType = "number"
NoteType SettingType = "note"
EmailType SettingType = "email"
ListType SettingType = "list"
)
type Setting struct {
Setting string `json:"setting" yaml:"setting" example:"my_setting"`
Name string `json:"name" yaml:"name" example:"My Setting"`
Description string `json:"description" yaml:"description"`
Required bool `json:"required" yaml:"required"`
RequiresRestart bool `json:"requires_restart" yaml:"requires_restart"`
Advanced bool `json:"advanced,omitempty" yaml:"advanced,omitempty"`
Type SettingType `json:"type" yaml:"type"` // Type (string, number, bool, etc.)
Value any `json:"value" yaml:"value"`
Options []Option `json:"options,omitempty" yaml:"options,omitempty"`
DependsTrue string `json:"depends_true,omitempty" yaml:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled.
DependsFalse string `json:"depends_false,omitempty" yaml:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue.
Style string `json:"style,omitempty" yaml:"style,omitempty"`
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
WikiLink string `json:"wiki_link,omitempty" yaml:"wiki_link,omitempty"`
}
type Section struct {
Section string `json:"section" yaml:"section" example:"my_section"`
Meta SectionMeta `json:"meta" yaml:"meta"`
Settings []Setting `json:"settings" yaml:"settings"`
}
type Config struct {
Sections []Section `json:"sections" yaml:"sections"`
}
func (c *Config) removeSection(section string) {
for i, v := range c.Sections {
if v.Section == section {
c.Sections = append(c.Sections[:i], c.Sections[i+1:]...)
break
}
}
}

View File

@ -2,6 +2,6 @@ module github.com/hrfee/jfa-go/common
replace github.com/hrfee/jfa-go/logmessages => ../logmessages
go 1.18
go 1.22.4
require github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a

View File

@ -10,7 +10,6 @@ import (
"strings"
"time"
"github.com/hrfee/jfa-go/common"
"github.com/hrfee/jfa-go/easyproxy"
lm "github.com/hrfee/jfa-go/logmessages"
"gopkg.in/ini.v1"
@ -251,98 +250,3 @@ func (app *appContext) loadConfig() error {
return nil
}
func (app *appContext) PatchConfigBase() {
conf := app.configBase
// Load language options
formOptions := app.storage.lang.User.getOptions()
pwrOptions := app.storage.lang.PasswordReset.getOptions()
adminOptions := app.storage.lang.Admin.getOptions()
emailOptions := app.storage.lang.Email.getOptions()
telegramOptions := app.storage.lang.Email.getOptions()
for i, section := range app.configBase.Sections {
if section.Section == "updates" && updater == "" {
section.Meta.Disabled = true
}
for j, setting := range section.Settings {
if section.Section == "ui" {
if setting.Setting == "language-form" {
setting.Options = formOptions
setting.Value = "en-us"
} else if setting.Setting == "language-admin" {
setting.Options = adminOptions
setting.Value = "en-us"
}
} else if section.Section == "password_resets" {
if setting.Setting == "language" {
setting.Options = pwrOptions
setting.Value = "en-us"
}
} else if section.Section == "email" {
if setting.Setting == "language" {
setting.Options = emailOptions
setting.Value = "en-us"
}
} else if section.Section == "telegram" {
if setting.Setting == "language" {
setting.Options = telegramOptions
setting.Value = "en-us"
}
} else if section.Section == "smtp" {
if setting.Setting == "ssl_cert" && PLATFORM == "windows" {
// Not accurate but the effect is hiding the option, which we want.
setting.Deprecated = true
}
} else if section.Section == "matrix" {
if setting.Setting == "encryption" && !MatrixE2EE() {
// Not accurate but the effect is hiding the option, which we want.
setting.Deprecated = true
}
}
val := app.config.Section(section.Section).Key(setting.Setting)
switch setting.Type {
case "list":
setting.Value = val.StringsWithShadows("|")
case "text", "email", "select", "password", "note":
setting.Value = val.MustString("")
case "number":
setting.Value = val.MustInt(0)
case "bool":
setting.Value = val.MustBool(false)
}
section.Settings[j] = setting
}
conf.Sections[i] = section
}
app.patchedConfig = conf
}
func (app *appContext) PatchConfigDiscordRoles() {
if !discordEnabled {
return
}
r, err := app.discord.ListRoles()
if err != nil {
return
}
roles := make([]common.Option, len(r)+1)
roles[0] = common.Option{"", "None"}
for i, role := range r {
roles[i+1] = role
}
for i, section := range app.patchedConfig.Sections {
if section.Section != "discord" {
continue
}
for j, setting := range section.Settings {
if setting.Setting != "apply_role" {
continue
}
setting.Options = roles
section.Settings[j] = setting
}
app.patchedConfig.Sections[i] = section
}
}

4
config/README.md Normal file
View File

@ -0,0 +1,4 @@
### fixconfig
Python's `json` library retains the order of data in a JSON file, which meant settings sent to the web page would be in the right order. Go's `encoding/json` and maps do not retain order, so `enumerate/enumerate_config.py` opens the json file, and for each section, adds an "order" array which tells the web page in which order to display settings.
Specify the input and output files with `-i` and `-o` respectively.

View File

@ -1 +0,0 @@
The two python scripts here, `config-json-to-new-yaml.py` and `gen-rough-schema.py` were used to convert the old format, which was stored in a JSON file, to the new format in YAML. The latter script is used to get the possible values for settings and sections, so they could be properly defined in common/config.go.

2179
config/config-base.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
from ruamel.yaml import YAML
import json
from pathlib import Path
import sys
yaml = YAML()
# c = yaml.load(Path(sys.argv[len(sys.argv)-1]))
with open(sys.argv[len(sys.argv)-1], 'r') as f:
c = json.load(f)
c.pop("order")
c1 = c.copy()
c1["sections"] = []
for section in c["sections"]:
codeSection = { "section": section }
s = codeSection | c["sections"][section]
s.pop("order")
c1["sections"].append(s)
c2 = c.copy()
c2["sections"] = []
for section in c1["sections"]:
sArray = []
for setting in section["settings"]:
codeSetting = { "setting": setting }
s = codeSetting | section["settings"][setting]
sArray.append(s)
section["settings"] = sArray
c2["sections"].append(section)
yaml.dump(c2, sys.stdout)

View File

@ -1,40 +0,0 @@
import json
import sys
sectionSchema = {}
metaSchema = {}
settingSchema = {}
typeValues = {}
# c = yaml.load(Path(sys.argv[len(sys.argv)-1]))
with open(sys.argv[len(sys.argv)-1], 'r') as f:
c = json.load(f)
for section in c["sections"]:
for key in c["sections"][section]:
sectionSchema[key] = True
for key in c["sections"][section]["meta"]:
metaSchema[key] = c["sections"][section]["meta"][key]
for setting in c["sections"][section]["settings"]:
for field in c["sections"][section]["settings"][setting]:
settingSchema[field] = c["sections"][section]["settings"][setting][field]
typeValues[c["sections"][section]["settings"][setting]["type"]] = True
print("Section Content:")
for v in sectionSchema:
print(v)
print("---")
print("Meta Schema")
for v in metaSchema:
print(v, "=", type(metaSchema[v]))
print("---")
print("Setting Schema")
for v in settingSchema:
print(v, "=", type(settingSchema[v]))
print("---")
print("Possible Types")
for v in typeValues:
print(v)

View File

@ -1,7 +1,7 @@
module github.com/hrfee/jfa-go/easyproxy
go 1.18
go 1.20
require golang.org/x/net v0.23.0
require golang.org/x/net v0.15.0
require github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b
require github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b // indirect

View File

@ -1,4 +1,4 @@
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n7Frzh8CwyfAapUZLSg+gXH5m63YEaFCMpDHhpI=
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=

39
go.mod
View File

@ -1,8 +1,6 @@
module github.com/hrfee/jfa-go
go 1.22
toolchain go1.22.4
go 1.22.4
replace github.com/hrfee/jfa-go/docs => ./docs
@ -35,18 +33,18 @@ require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/gomarkdown/markdown v0.0.0-20240730141124-034f12af3bf6
github.com/hrfee/jfa-go/common v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/docs v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/easyproxy v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/linecache v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/logger v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/logmessages v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/ombi v0.0.0-20240824141650-fcdd4e451882
github.com/hrfee/jfa-go/common v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/docs v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/easyproxy v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/jellyseerr v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/linecache v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/logger v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/jfa-go/ombi v0.0.0-20240806200606-6308db495a0a
github.com/hrfee/mediabrowser v0.3.18
github.com/itchyny/timefmt-go v0.1.6
github.com/lithammer/shortuuid/v3 v3.0.7
github.com/mailgun/mailgun-go/v4 v4.15.0
github.com/mailgun/mailgun-go/v4 v4.14.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/robert-nix/ansihtml v1.0.1
github.com/steambap/captcha v1.4.1
@ -56,8 +54,7 @@ require (
github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/xhit/go-simple-mail/v2 v2.16.0
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
maunium.net/go/mautrix v0.20.0
maunium.net/go/mautrix v0.19.0
)
require (
@ -113,8 +110,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/swaggo/swag v1.16.3 // indirect
@ -126,20 +122,21 @@ require (
github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.mau.fi/util v0.7.0 // indirect
go.mau.fi/util v0.6.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/arch v0.9.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/image v0.19.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

18
go.sum
View File

@ -258,8 +258,6 @@ github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n
github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg=
github.com/mailgun/mailgun-go/v4 v4.14.0 h1:N9GMNs0XUq5nn3v2TlZHCknF9khSh88MkA1hdRm1o7I=
github.com/mailgun/mailgun-go/v4 v4.14.0/go.mod h1:L9s941Lgk7iB3TgywTPz074pK2Ekkg4kgbnAaAyJ2z8=
github.com/mailgun/mailgun-go/v4 v4.15.0 h1:3NQU0r2XItJbyIZ21iBI9ps0+vPIMoGyI2XK7ZTN/DQ=
github.com/mailgun/mailgun-go/v4 v4.15.0/go.mod h1:L9s941Lgk7iB3TgywTPz074pK2Ekkg4kgbnAaAyJ2z8=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -292,10 +290,6 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwU
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -390,25 +384,17 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mau.fi/util v0.6.0 h1:W6SyB3Bm/GjenQ5iq8Z8WWdN85Gy2xS6L0wmnR7SVjg=
go.mau.fi/util v0.6.0/go.mod h1:ljYdq3sPfpICc3zMU+/mHV/sa4z0nKxc67hSBwnrk8U=
go.mau.fi/util v0.7.0 h1:l31z+ivrSQw+cv/9eFebEqtQW2zhxivGypn+JT0h/ws=
go.mau.fi/util v0.7.0/go.mod h1:bWYreIoTULL/UiRbZdfddPh7uWDFW5yX4YCv5FB0eE0=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -433,8 +419,6 @@ golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn5
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
@ -593,6 +577,4 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
maunium.net/go/mautrix v0.19.0 h1:67eSJWam93mw44Q0/1SiOG7zQzXMUknUv5UaWkrODDU=
maunium.net/go/mautrix v0.19.0/go.mod h1:UE+mSQ4sDUuJMbjN0aB9EjQSGgXd48AzMvZ6+QJV1k8=
maunium.net/go/mautrix v0.20.0 h1:bzQnVQR+LvQxV1YlAr7BSWCS8AWa0Ov0lyPhbbChM0o=
maunium.net/go/mautrix v0.20.0/go.mod h1:V725r8w7oddsS7CxnmTAp634A4nwJCFY7J3jiTMUz2c=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@ -2,7 +2,7 @@ module github.com/hrfee/jfa-go/jellyseerr
replace github.com/hrfee/jfa-go/common => ../common
go 1.18
go 1.22.4
require github.com/hrfee/jfa-go/common v0.0.0-20240728190513-dabef831d769

38
lang.go
View File

@ -1,7 +1,5 @@
package main
import "github.com/hrfee/jfa-go/common"
type langMeta struct {
Name string `json:"name"`
// Language to fall back on if strings are missing. Defaults to en-us.
@ -15,11 +13,11 @@ type quantityString struct {
type adminLangs map[string]adminLang
func (ls *adminLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
func (ls *adminLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = common.Option{key, lang.Meta.Name}
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts
@ -44,11 +42,11 @@ type adminLang struct {
type userLangs map[string]userLang
func (ls *userLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
func (ls *userLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = common.Option{key, lang.Meta.Name}
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts
@ -67,11 +65,11 @@ type userLang struct {
type pwrLangs map[string]pwrLang
func (ls *pwrLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
func (ls *pwrLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = common.Option{key, lang.Meta.Name}
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts
@ -84,11 +82,11 @@ type pwrLang struct {
type emailLangs map[string]emailLang
func (ls *emailLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
func (ls *emailLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = common.Option{key, lang.Meta.Name}
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts
@ -137,11 +135,11 @@ type setupLang struct {
JSON string
}
func (ls *setupLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ls))
func (ls *setupLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = common.Option{key, lang.Meta.Name}
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts
@ -154,11 +152,11 @@ type telegramLang struct {
Strings langSection `json:"strings"`
}
func (ts *telegramLangs) getOptions() []common.Option {
opts := make([]common.Option, len(*ts))
func (ts *telegramLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ts))
i := 0
for key, lang := range *ts {
opts[i] = common.Option{key, lang.Meta.Name}
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts

View File

@ -1,3 +1,3 @@
module github.com/hrfee/jfa-go/logmessages
go 1.18
go 1.22.4

10
main.go
View File

@ -32,7 +32,6 @@ import (
"github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v3"
)
var (
@ -94,8 +93,7 @@ type appContext struct {
config *ini.File
configPath string
configBasePath string
configBase common.Config
patchedConfig common.Config
configBase settings
dataPath string
webFS httpFS
cssClass string // Default theme, "light"|"dark".
@ -390,11 +388,9 @@ func start(asDaemon, firstCall bool) {
defer app.storage.db.Close()
// Read config-base for settings on web.
app.configBasePath = "config-base.yaml"
app.configBasePath = "config-base.json"
configBase, _ := fs.ReadFile(localFS, app.configBasePath)
yaml.Unmarshal(configBase, &app.configBase)
// copy it to app.patchedConfig, and patch in settings from app.config, and language stuff.
app.PatchConfigBase()
json.Unmarshal(configBase, &app.configBase)
secret, err := generateSecret(16)
if err != nil {

View File

@ -212,6 +212,43 @@ type errorListDTO map[string]map[string]string
type configDTO map[string]interface{}
// Below are for sending config
type meta struct {
Name string `json:"name"`
Description string `json:"description"`
Advanced bool `json:"advanced,omitempty"`
DependsTrue string `json:"depends_true,omitempty"`
DependsFalse string `json:"depends_false,omitempty"`
WikiLink string `json:"wiki_link,omitempty"`
}
type setting struct {
Name string `json:"name"`
Description string `json:"description"`
Required bool `json:"required"`
Advanced bool `json:"advanced,omitempty"`
RequiresRestart bool `json:"requires_restart"`
Type string `json:"type"` // Type (string, number, bool, etc.)
Value interface{} `json:"value"`
Options [][2]string `json:"options,omitempty"`
DependsTrue string `json:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled.
DependsFalse string `json:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue.
Style string `json:"style,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
}
type section struct {
Meta meta `json:"meta"`
Order []string `json:"order"`
Settings map[string]setting `json:"settings"`
}
type settings struct {
Order []string `json:"order"`
Sections map[string]section `json:"sections"`
}
type langDTO map[string]string
type emailListDTO map[string]emailListEl

View File

@ -2,7 +2,7 @@ module github.com/hrfee/jfa-go/ombi
replace github.com/hrfee/jfa-go/common => ../common
go 1.18
go 1.22.4
require github.com/hrfee/jfa-go/common v0.0.0-20240806200606-6308db495a0a

50
package-lock.json generated
View File

@ -22,7 +22,7 @@
"mjml": "^4.15.3",
"nightwind": "^1.1.13",
"perl-regex": "^1.0.4",
"postcss": "^8.4.31",
"postcss": "^8.4.24",
"remixicon": "^4.3.0",
"remove-markdown": "^0.5.0",
"tailwindcss": "^3.3.2",
@ -4669,9 +4669,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"funding": [
{
"type": "opencollective",
@ -6653,9 +6653,9 @@
}
},
"node_modules/uncss/node_modules/ws": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"dependencies": {
"async-limiter": "~1.0.0"
}
@ -7167,9 +7167,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"engines": {
"node": ">=0.10.0"
}
@ -7215,9 +7215,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"engines": {
"node": ">=10.0.0"
},
@ -10690,9 +10690,9 @@
"dev": true
},
"postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"requires": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
@ -12180,9 +12180,9 @@
}
},
"ws": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"requires": {
"async-limiter": "~1.0.0"
}
@ -12546,9 +12546,9 @@
}
},
"word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
},
"wrap-ansi": {
"version": "7.0.0",
@ -12576,9 +12576,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"requires": {}
},
"xml-name-validator": {

View File

@ -30,7 +30,7 @@
"mjml": "^4.15.3",
"nightwind": "^1.1.13",
"perl-regex": "^1.0.4",
"postcss": "^8.4.31",
"postcss": "^8.4.24",
"remixicon": "^4.3.0",
"remove-markdown": "^0.5.0",
"tailwindcss": "^3.3.2",

View File

@ -1,5 +1,5 @@
module github.com/hrfee/jfa-go/scripts/account-gen
go 1.18
go 1.20
require github.com/hrfee/mediabrowser v0.3.8 // indirect

52
scripts/compile_mjml.py Executable file
View File

@ -0,0 +1,52 @@
import subprocess
import shutil
import os
import argparse
from pathlib import Path
from multiprocessing import Process
parser = argparse.ArgumentParser()
parser.add_argument("-o", "--output", help="output directory for .html and .txt files")
args = parser.parse_args()
def runcmd(cmd):
if os.name == "nt":
return subprocess.check_output(cmd, shell=True)
with subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) as proc:
return proc.communicate()
def compile(mjml: Path):
fname = mjml.with_suffix(".html")
runcmd(f"npx mjml {str(mjml)} -o {str(fname)}")
if fname.is_file():
print(f"Compiled {mjml.name}")
local_path = Path("mail")
threads = []
for mjml in [f for f in local_path.iterdir() if f.is_file() and "mjml" in f.suffix]:
p = Process(target=compile, args=(mjml,))
p.start()
threads.append(p)
for thread in threads:
thread.join()
html = [f for f in local_path.iterdir() if f.is_file() and "html" in f.suffix]
output = Path(args.output) # local_path.parent / "build" / "data"
output.mkdir(parents=True, exist_ok=True)
for f in html:
shutil.copy(str(f), str(output / f.name))
print(f"Copied {f.name} to {str(output / f.name)}")
txtfile = f.with_suffix(".txt")
if txtfile.is_file():
shutil.copy(str(txtfile), str(output / txtfile.name))
print(f"Copied {txtfile.name} to {str(output / txtfile.name)}")
else:
print(
f"Warning: {txtfile.name} does not exist. Text versions of emails should be supplied."
)

View File

@ -0,0 +1,29 @@
# Since go doesn't order its json, this script adds ordered lists
# of section/setting names for the settings tab to use.
import json, argparse
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", help="input config base from jf-accounts")
parser.add_argument("-o", "--output", help="output config base for jfa-go")
args = parser.parse_args()
with open(args.input, 'r') as f:
config = json.load(f)
newconfig = {"sections": {}, "order": []}
for sect in config["sections"]:
newconfig["order"].append(sect)
newconfig["sections"][sect] = {}
newconfig["sections"][sect]["order"] = []
newconfig["sections"][sect]["meta"] = config["sections"][sect]["meta"]
newconfig["sections"][sect]["settings"] = {}
for setting in config["sections"][sect]["settings"]:
newconfig["sections"][sect]["order"].append(setting)
newconfig["sections"][sect]["settings"][setting] = config["sections"][sect]["settings"][setting]
with open(args.output, 'w') as f:
f.write(json.dumps(newconfig, indent=4))

47
scripts/generate_ini.py Normal file
View File

@ -0,0 +1,47 @@
# Generates config file
import configparser
import json
import argparse
from pathlib import Path
def fix_description(desc):
return "; " + desc.replace("\n", "\n; ")
def generate_ini(base_file, ini_file):
"""
Generates .ini file from config-base file.
"""
with open(Path(base_file), "r") as f:
config_base = json.load(f)
ini = configparser.RawConfigParser(allow_no_value=True)
for section in config_base["sections"]:
ini.add_section(section)
if "meta" in config_base["sections"][section]:
ini.set(section, fix_description(config_base["sections"][section]["meta"]["description"]))
for entry in config_base["sections"][section]["settings"]:
if config_base["sections"][section]["settings"][entry]["type"] == "note":
continue
if "description" in config_base["sections"][section]["settings"][entry]:
ini.set(section, fix_description(config_base["sections"][section]["settings"][entry]["description"]))
value = config_base["sections"][section]["settings"][entry]["value"]
if isinstance(value, bool):
value = str(value).lower()
else:
value = str(value)
ini.set(section, entry, value)
with open(Path(ini_file), "w") as config_file:
ini.write(config_file)
return True
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", help="input config base from jf-accounts")
parser.add_argument("-o", "--output", help="output ini")
args = parser.parse_args()
print(generate_ini(base_file=args.input, ini_file=args.output))

View File

@ -1,12 +0,0 @@
module github.com/hrfee/jfa-go/scripts/ini
replace github.com/hrfee/jfa-go/common => ../../common
go 1.18
require (
github.com/hrfee/jfa-go/common v0.0.0-20240824141650-fcdd4e451882 // indirect
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -1,7 +0,0 @@
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a h1:qbXZgCqb9eaPSJfLEXczQD2lxTv6jb6silMPIWW9j6o=
github.com/hrfee/jfa-go/logmessages v0.0.0-20240806200606-6308db495a0a/go.mod h1:c5HKkLayo0GrEUDlJwT12b67BL9cdPjP271Xlv/KDRQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,130 +0,0 @@
package main
import (
"errors"
"flag"
"fmt"
"os"
"strings"
"github.com/hrfee/jfa-go/common"
"gopkg.in/ini.v1"
"gopkg.in/yaml.v3"
)
func fixDescription(desc string) string {
return "; " + strings.ReplaceAll(desc, "\n", "\n; ")
}
func generateIni(yamlPath string, iniPath string) {
yamlFile, err := os.ReadFile(yamlPath)
if err != nil {
panic(err)
}
configBase := common.Config{}
err = yaml.Unmarshal(yamlFile, &configBase)
if err != nil {
panic(err)
}
conf := ini.Empty()
for _, section := range configBase.Sections {
cSection, err := conf.NewSection(section.Section)
if err != nil {
panic(err)
}
if section.Meta.Description != "" {
cSection.Comment = fixDescription(section.Meta.Description)
}
for _, setting := range section.Settings {
if setting.Type == common.NoteType {
continue
}
val := ""
if setting.Value != nil {
// Easy way to convert bools and numbers to strings,
// Instead of checking setting.Type
val = fmt.Sprintf("%v", setting.Value)
}
cKey, err := cSection.NewKey(setting.Setting, val)
if err != nil {
panic(err)
}
if setting.Description != "" {
cKey.Comment = fixDescription(setting.Description)
}
// Explain how to use list type
if setting.Type == common.ListType {
if cKey.Comment != "" {
cKey.Comment += "\n"
}
cKey.Comment += `List type: duplicate and edit the line to add more entries.`
}
}
}
err = conf.SaveTo(iniPath)
if err != nil {
panic(err)
}
}
// Compares two inis, used to check this script does the equivalent of the old generate_ini.py.
func compareInis(p1, p2 string) {
cA, err := ini.ShadowLoad(p1)
if err != nil {
panic(err)
}
cB, err := ini.ShadowLoad(p2)
if err != nil {
panic(err)
}
for _, pair := range [][2]*ini.File{{cA, cB}, {cB, cA}} {
s1 := pair[0].Sections()
s2 := pair[1].Sections()
for i := range s1 {
if s1[i].Name() != s2[i].Name() {
panic(fmt.Errorf("mismatching section order: s0[i]=%s, s1[i]=%s", s1[i].Name(), s2[i].Name()))
}
// fmt.Println("Section order matches")
st1 := s1[i].Keys()
st2 := s2[i].Keys()
for i := range st1 {
if st1[i].Name() != st2[i].Name() {
panic(fmt.Errorf("mismatching setting order: st1[i]=%s, st2[i]=%s", st1[i].Name(), st2[i].Name()))
}
if st1[i].Value() != st2[i].Value() {
panic(fmt.Errorf("mismatching setting values: st1[i]=%s, st2[i]=%s", st1[i].Value(), st2[i].Value()))
}
// fmt.Println("Setting matches")
}
}
}
}
func main() {
var yamlPath string
var iniPath string
var comparePath string
flag.StringVar(&yamlPath, "in", "", "Input of the config base in yaml.")
flag.StringVar(&iniPath, "out", "", "Output path of an ini file.")
flag.StringVar(&comparePath, "comp", "", "Path to ini file to compare against.")
flag.Parse()
if yamlPath == "" {
panic(errors.New("invalid yaml path"))
}
if iniPath == "" {
panic(errors.New("invalid ini path"))
}
generateIni(yamlPath, iniPath)
if comparePath != "" {
compareInis(iniPath, comparePath)
fmt.Println("Passed.")
}
}

12
site/package-lock.json generated
View File

@ -4462,9 +4462,9 @@
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"engines": {
"node": ">=0.10.0"
}
@ -7828,9 +7828,9 @@
}
},
"word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
},
"wrappy": {
"version": "1.0.2",

View File

@ -19,33 +19,20 @@ interface settingsChangedEvent extends Event {
detail: string;
}
type SettingType = string;
const BoolType: SettingType = "bool";
const SelectType: SettingType = "select";
const TextType: SettingType = "text";
const PasswordType: SettingType = "password";
const NumberType: SettingType = "number";
const NoteType: SettingType = "note";
const EmailType: SettingType = "email";
const ListType: SettingType = "list";
interface Meta {
name: string;
description: string;
advanced?: boolean;
disabled?: boolean;
depends_true?: string;
depends_false?: string;
wiki_link?: string;
}
interface Setting {
setting: string;
name: string;
description: string;
required?: boolean;
requires_restart?: boolean;
required: boolean;
requires_restart: boolean;
advanced?: boolean;
type: string;
value: string | boolean | number | string[];
@ -80,17 +67,17 @@ class DOMSetting {
protected _restart: HTMLSpanElement;
protected _advanced: boolean;
protected _section: string;
setting: string;
protected _name: string;
hide = () => {
this._hideEl.classList.add("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": false })
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": false })
document.dispatchEvent(event);
};
show = () => {
this._hideEl.classList.remove("unfocused");
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": this.valueAsString() })
const event = new CustomEvent(`settings-${this._section}-${this._name}`, { "detail": this.valueAsString() })
document.dispatchEvent(event);
};
@ -155,8 +142,8 @@ class DOMSetting {
valueAsString = (): string => { return ""+this.value; };
onValueChange = () => {
const event = new CustomEvent(`settings-${this._section}-${this.setting}`, { "detail": this.valueAsString() })
const setEvent = new CustomEvent(`settings-set-${this._section}-${this.setting}`, { "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(setEvent);
if (this.requires_restart) { document.dispatchEvent(new CustomEvent("settings-requires-restart")); }
@ -164,7 +151,7 @@ class DOMSetting {
constructor(input: string, setting: Setting, section: string, name: string, inputOnTop: boolean = false) {
this._section = section;
this.setting = name;
this._name = name;
this._container = document.createElement("div");
this._container.classList.add("setting");
this._container.setAttribute("data-name", name);
@ -236,7 +223,7 @@ interface SText extends Setting {
}
class DOMText extends DOMInput implements SText {
constructor(setting: Setting, section: string, name: string) { super("text", setting, section, name); }
type: SettingType = TextType;
type: string = "text";
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
@ -246,7 +233,7 @@ interface SPassword extends Setting {
}
class DOMPassword extends DOMInput implements SPassword {
constructor(setting: Setting, section: string, name: string) { super("password", setting, section, name); }
type: SettingType = PasswordType;
type: string = "password";
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
@ -256,7 +243,7 @@ interface SEmail extends Setting {
}
class DOMEmail extends DOMInput implements SEmail {
constructor(setting: Setting, section: string, name: string) { super("email", setting, section, name); }
type: SettingType = EmailType;
type: string = "email";
get value(): string { return this._input.value }
set value(v: string) { this._input.value = v; }
}
@ -266,7 +253,7 @@ interface SNumber extends Setting {
}
class DOMNumber extends DOMInput implements SNumber {
constructor(setting: Setting, section: string, name: string) { super("number", setting, section, name); }
type: SettingType = NumberType;
type: string = "number";
get value(): number { return +this._input.value; }
set value(v: number) { this._input.value = ""+v; }
}
@ -276,7 +263,7 @@ interface SList extends Setting {
}
class DOMList extends DOMSetting implements SList {
protected _inputs: HTMLDivElement;
type: SettingType = ListType;
type: string = "list";
valueAsString = (): string => { return this.value.join("|"); };
@ -347,7 +334,7 @@ interface SBool extends Setting {
value: boolean;
}
class DOMBool extends DOMSetting implements SBool {
type: SettingType = BoolType;
type: string = "bool";
get value(): boolean { return this._input.checked; }
set value(state: boolean) { this._input.checked = state; }
@ -370,7 +357,7 @@ interface SSelect extends Setting {
value: string;
}
class DOMSelect extends DOMSetting implements SSelect {
type: SettingType = SelectType;
type: string = "bool";
private _options: string[][];
get options(): string[][] { return this._options; }
@ -408,7 +395,7 @@ interface SNote extends Setting {
class DOMNote extends DOMSetting implements SNote {
private _nameEl: HTMLElement;
private _description: HTMLElement;
type: SettingType = NoteType;
type: string = "note";
private _style: string;
// We're a note, no one depends on us so we don't need to broadcast a state change.
@ -470,9 +457,9 @@ class DOMNote extends DOMSetting implements SNote {
}
interface Section {
section: string;
meta: Meta;
settings: Setting[];
order: string[];
settings: { [settingName: string]: Setting };
}
class sectionPanel {
@ -504,49 +491,50 @@ class sectionPanel {
this.update(s);
}
update = (s: Section) => {
for (let setting of s.settings) {
if (setting.setting in this._settings) {
this._settings[setting.setting].update(setting);
for (let name of s.order) {
let setting: Setting = s.settings[name];
if (name in this._settings) {
this._settings[name].update(setting);
} else {
if (setting.deprecated) continue;
switch (setting.type) {
case TextType:
setting = new DOMText(setting, this._sectionName, setting.setting);
case "text":
setting = new DOMText(setting, this._sectionName, name);
break;
case PasswordType:
setting = new DOMPassword(setting, this._sectionName, setting.setting);
case "password":
setting = new DOMPassword(setting, this._sectionName, name);
break;
case EmailType:
setting = new DOMEmail(setting, this._sectionName, setting.setting);
case "email":
setting = new DOMEmail(setting, this._sectionName, name);
break;
case NumberType:
setting = new DOMNumber(setting, this._sectionName, setting.setting);
case "number":
setting = new DOMNumber(setting, this._sectionName, name);
break;
case BoolType:
setting = new DOMBool(setting as SBool, this._sectionName, setting.setting);
case "bool":
setting = new DOMBool(setting as SBool, this._sectionName, name);
break;
case SelectType:
setting = new DOMSelect(setting as SSelect, this._sectionName, setting.setting);
case "select":
setting = new DOMSelect(setting as SSelect, this._sectionName, name);
break;
case NoteType:
case "note":
setting = new DOMNote(setting as SNote, this._sectionName);
break;
case ListType:
setting = new DOMList(setting as SList, this._sectionName, setting.setting);
case "list":
setting = new DOMList(setting as SList, this._sectionName, name);
break;
}
if (setting.type != "note") {
this.values[setting.setting] = ""+setting.value;
this.values[name] = ""+setting.value;
// 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}-${setting.setting}`, (event: CustomEvent) => {
document.addEventListener(`settings-set-${this._sectionName}-${name}`, (event: CustomEvent) => {
// const oldValue = this.values[name];
this.values[setting.setting] = event.detail;
this.values[name] = event.detail;
document.dispatchEvent(new CustomEvent("settings-section-changed"));
});
}
this._section.appendChild(setting.asElement());
this._settings[setting.setting] = setting;
this._settings[name] = setting;
}
}
}
@ -564,7 +552,8 @@ class sectionPanel {
}
interface Settings {
sections: Section[];
order: string[];
sections: { [sectionName: string]: Section };
}
export class settingsList {
@ -865,65 +854,65 @@ export class settingsList {
}
addLoader(this._loader, false, true);
_get("/config", null, (req: XMLHttpRequest) => {
if (req.readyState != 4) return;
if (req.status != 200) {
window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings"));
return;
}
this._settings = req.response as Settings;
for (let section of this._settings.sections) {
if (section.meta.disabled) continue;
if (section.section in this._sections) {
this._sections[section.section].update(section);
} else {
if (section.section == "messages" || section.section == "user_page") {
const editButton = document.createElement("div");
editButton.classList.add("tooltip", "left");
editButton.innerHTML = `
<span class="button ~neutral @low">
<i class="icon ri-edit-line"></i>
</span>
<span class="content sm">
${window.lang.get("strings", "customizeMessages")}
</span>
`;
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => {
this._messageEditor.showList(section.section == "messages" ? "email" : "user");
};
this.addSection(section.section, section, editButton);
} else if (section.section == "updates") {
const icon = document.createElement("span") as HTMLSpanElement;
if (window.updater.updateAvailable) {
icon.classList.add("button", "~urge");
icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
}
this.addSection(section.section, section, icon);
} else if (section.section == "matrix" && !window.matrixEnabled) {
const addButton = document.createElement("div");
addButton.classList.add("tooltip", "left");
addButton.innerHTML = `
<span class="button ~neutral @low">+</span>
<span class="content sm">
${window.lang.strings("linkMatrix")}
</span>
`;
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
this.addSection(section.section, section, addButton);
if (req.readyState == 4) {
if (req.status != 200) {
window.notifications.customError("settingsLoadError", window.lang.notif("errorLoadSettings"));
return;
}
this._settings = req.response as Settings;
for (let name of this._settings.order) {
if (name in this._sections) {
this._sections[name].update(this._settings.sections[name]);
} else {
this.addSection(section.section, section);
if (name == "messages" || name == "user_page") {
const editButton = document.createElement("div");
editButton.classList.add("tooltip", "left");
editButton.innerHTML = `
<span class="button ~neutral @low">
<i class="icon ri-edit-line"></i>
</span>
<span class="content sm">
${window.lang.get("strings", "customizeMessages")}
</span>
`;
(editButton.querySelector("span.button") as HTMLSpanElement).onclick = () => {
this._messageEditor.showList(name == "messages" ? "email" : "user");
};
this.addSection(name, this._settings.sections[name], editButton);
} else if (name == "updates") {
const icon = document.createElement("span") as HTMLSpanElement;
if (window.updater.updateAvailable) {
icon.classList.add("button", "~urge");
icon.innerHTML = `<i class="ri-download-line" title="${window.lang.strings("update")}"></i>`;
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
}
this.addSection(name, this._settings.sections[name], icon);
} else if (name == "matrix" && !window.matrixEnabled) {
const addButton = document.createElement("div");
addButton.classList.add("tooltip", "left");
addButton.innerHTML = `
<span class="button ~neutral @low">+</span>
<span class="content sm">
${window.lang.strings("linkMatrix")}
</span>
`;
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
this.addSection(name, this._settings.sections[name], addButton);
} else {
this.addSection(name, this._settings.sections[name]);
}
}
}
removeLoader(this._loader);
for (let i = 0; i < this._loader.children.length; i++) {
this._loader.children[i].classList.remove("invisible");
}
this._showPanel(this._settings.order[0]);
document.dispatchEvent(new CustomEvent("settings-loaded"));
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false }));
this._saveButton.classList.add("unfocused");
this._needsRestart = false;
}
removeLoader(this._loader);
for (let i = 0; i < this._loader.children.length; i++) {
this._loader.children[i].classList.remove("invisible");
}
this._showPanel(this._settings.sections[0].section);
document.dispatchEvent(new CustomEvent("settings-loaded"));
document.dispatchEvent(new CustomEvent("settings-advancedState", { detail: false }));
this._saveButton.classList.add("unfocused");
this._needsRestart = false;
})
};
@ -934,31 +923,31 @@ export class settingsList {
if (query.replace(/\s+/g, "") == "") query = "";
let firstVisibleSection = "";
for (let section of this._settings.sections) {
for (let section of this._settings.order) {
let dependencyCard = this._sections[section.section].asElement().querySelector(".settings-dependency-message");
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.section].classList.add("unfocused");
this._buttons[section].classList.add("unfocused");
let matchedSection = false;
if (section.section.toLowerCase().includes(query) ||
section.meta.name.toLowerCase().includes(query) ||
section.meta.description.toLowerCase().includes(query)) {
if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) {
this._buttons[section.section].classList.remove("unfocused");
firstVisibleSection = firstVisibleSection || section.section;
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.section].asElement();
for (let setting of section.settings) {
if (setting.type == "note") continue;
const element = sectionElement.querySelector(`div[data-name="${setting.setting}"]`) as HTMLElement;
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) {
@ -970,17 +959,17 @@ export class settingsList {
// 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.setting.toLowerCase().includes(query) ||
setting.name.toLowerCase().includes(query) ||
setting.description.toLowerCase().includes(query) ||
String(setting.value).toLowerCase().includes(query)) {
if ((section.meta.advanced && this._advanced) || !(section.meta.advanced)) {
this._buttons[section.section].classList.remove("unfocused");
firstVisibleSection = firstVisibleSection || section.section;
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 != "" &&
((setting.advanced && this._advanced) ||
!(setting.advanced)));
((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");
@ -1000,21 +989,21 @@ export class settingsList {
`;
dependencyList = dependencyCard.querySelector(".settings-dependency-list") as HTMLUListElement;
// Insert it right after the description
this._sections[section.section].asElement().insertBefore(dependencyCard, this._sections[section.section].asElement().querySelector(".settings-section-description").nextElementSibling);
this._sections[section].asElement().insertBefore(dependencyCard, this._sections[section].asElement().querySelector(".settings-section-description").nextElementSibling);
}
const li = document.createElement("li");
if (shouldShow) {
const depCode = setting.depends_true || setting.depends_false;
const dep = splitDependant(section.section, depCode);
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.section) {
if (dep[0] != section) {
depName = this._settings.sections[dep[0]].meta.name + " > " + depName;
}
li.textContent = window.lang.strings("settingsDependsOn").replace("{setting}", `"`+setting.name+`"`).replace("{dependency}", `"`+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}", `"`+setting.name+`"`);
li.textContent = window.lang.strings("settingsAdvancedMode").replace("{setting}", `"`+this._settings.sections[section].settings[setting].name+`"`);
}
dependencyList.appendChild(li);
}