mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-03-18 21:50:33 +01:00
profiles: add ability to directly edit profile JSON
allows for customizing small things, like changing admin status.
This commit is contained in:
14
Makefile
14
Makefile
@@ -160,18 +160,24 @@ $(VARIANTS_TARGET): $(VARIANTS_SRC)
|
|||||||
|
|
||||||
ICON_SRC = node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2
|
ICON_SRC = node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2
|
||||||
ICON_TARGET = $(ICON_SRC:node_modules/remixicon/fonts/%=$(DATA)/web/css/%)
|
ICON_TARGET = $(ICON_SRC:node_modules/remixicon/fonts/%=$(DATA)/web/css/%)
|
||||||
SYNTAX_SRC = node_modules/highlight.js/styles/default.min.css
|
SYNTAX_LIGHT_SRC = node_modules/highlight.js/styles/base16/atelier-sulphurpool-light.min.css
|
||||||
SYNTAX_TARGET = $(DATA)/web/css/$(CSSVERSION)highlightjs.css
|
SYNTAX_LIGHT_TARGET = $(DATA)/web/css/$(CSSVERSION)highlightjs-light.css
|
||||||
|
SYNTAX_DARK_SRC = node_modules/highlight.js/styles/base16/circus.min.css
|
||||||
|
SYNTAX_DARK_TARGET = $(DATA)/web/css/$(CSSVERSION)highlightjs-dark.css
|
||||||
|
CODEINPUT_SRC = node_modules/@webcoder49/code-input/code-input.min.css
|
||||||
|
CODEINPUT_TARGET = $(DATA)/web/css/$(CSSVERSION)code-input.css
|
||||||
CSS_SRC = $(wildcard css/*.css)
|
CSS_SRC = $(wildcard css/*.css)
|
||||||
CSS_TARGET = $(DATA)/web/css/part-bundle.css
|
CSS_TARGET = $(DATA)/web/css/part-bundle.css
|
||||||
CSS_FULLTARGET = $(CSS_BUNDLE)
|
CSS_FULLTARGET = $(CSS_BUNDLE)
|
||||||
ALL_CSS_SRC = $(ICON_SRC) $(CSS_SRC) $(SYNTAX_SRC)
|
ALL_CSS_SRC = $(ICON_SRC) $(CSS_SRC) $(SYNTAX_LIGHT_SRC) $(SYNTAX_DARK_SRC)
|
||||||
ALL_CSS_TARGET = $(ICON_TARGET)
|
ALL_CSS_TARGET = $(ICON_TARGET)
|
||||||
|
|
||||||
$(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wildcard html/*.html)
|
$(CSS_FULLTARGET): $(TYPESCRIPT_TARGET) $(VARIANTS_TARGET) $(ALL_CSS_SRC) $(wildcard html/*.html)
|
||||||
$(info copying fonts)
|
$(info copying fonts)
|
||||||
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
|
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
|
||||||
cp -r $(SYNTAX_SRC) $(SYNTAX_TARGET)
|
cp -r $(SYNTAX_LIGHT_SRC) $(SYNTAX_LIGHT_TARGET)
|
||||||
|
cp -r $(SYNTAX_DARK_SRC) $(SYNTAX_DARK_TARGET)
|
||||||
|
cp -r $(CODEINPUT_SRC) $(CODEINPUT_TARGET)
|
||||||
$(info bundling css)
|
$(info bundling css)
|
||||||
rm -f $(CSS_TARGET) $(CSS_FULLTARGET)
|
rm -f $(CSS_TARGET) $(CSS_FULLTARGET)
|
||||||
$(ESBUILD) --bundle css/base.css --outfile=$(CSS_TARGET) --external:remixicon.css --external:../fonts/hanken* --minify
|
$(ESBUILD) --bundle css/base.css --outfile=$(CSS_TARGET) --external:remixicon.css --external:../fonts/hanken* --minify
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ func (app *appContext) GetProfiles(gc *gin.Context) {
|
|||||||
// @Param name path string true "name of profile (url encoded if necessary)"
|
// @Param name path string true "name of profile (url encoded if necessary)"
|
||||||
// @Router /profiles/raw/{name} [get]
|
// @Router /profiles/raw/{name} [get]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Users
|
// @tags Profiles & Settings
|
||||||
func (app *appContext) GetRawProfile(gc *gin.Context) {
|
func (app *appContext) GetRawProfile(gc *gin.Context) {
|
||||||
escapedName := gc.Param("name")
|
escapedName := gc.Param("name")
|
||||||
name, err := url.QueryUnescape(escapedName)
|
name, err := url.QueryUnescape(escapedName)
|
||||||
@@ -95,7 +96,8 @@ func (app *appContext) GetRawProfile(gc *gin.Context) {
|
|||||||
// @Summary Update the raw data of a profile (Configuration, Policy, Jellyseerr/Ombi if applicable, etc.).
|
// @Summary Update the raw data of a profile (Configuration, Policy, Jellyseerr/Ombi if applicable, etc.).
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param ProfileDTO body ProfileDTO true "Raw profile data (all of it, do not omit anything)"
|
// @Param ProfileDTO body ProfileDTO true "Raw profile data (all of it, do not omit anything)"
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 204 {object} boolResponse
|
||||||
|
// @Success 201 {object} boolResponse
|
||||||
// @Failure 400 {object} boolResponse
|
// @Failure 400 {object} boolResponse
|
||||||
// @Router /profiles/raw/{name} [put]
|
// @Router /profiles/raw/{name} [put]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
@@ -118,6 +120,7 @@ func (app *appContext) ReplaceRawProfile(gc *gin.Context) {
|
|||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
req.Name = name
|
req.Name = name
|
||||||
}
|
}
|
||||||
|
status := http.StatusNoContent
|
||||||
app.storage.SetProfileKey(req.Name, existingProfile)
|
app.storage.SetProfileKey(req.Name, existingProfile)
|
||||||
if req.Name != name {
|
if req.Name != name {
|
||||||
// Name change
|
// Name change
|
||||||
@@ -125,8 +128,9 @@ func (app *appContext) ReplaceRawProfile(gc *gin.Context) {
|
|||||||
if discordEnabled {
|
if discordEnabled {
|
||||||
app.discord.UpdateCommands()
|
app.discord.UpdateCommands()
|
||||||
}
|
}
|
||||||
|
status = http.StatusCreated
|
||||||
}
|
}
|
||||||
respondBool(200, true, gc)
|
respondBool(status, true, gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Set the default profile to use.
|
// @Summary Set the default profile to use.
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ sup.\~critical, .text-critical {
|
|||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textarea {
|
.textarea:not(code-input *) {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ sup.\~critical, .text-critical {
|
|||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
select, textarea {
|
select, textarea:not(code-input *) {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border: 0 solid var(--color-neutral-300);
|
border: 0 solid var(--color-neutral-300);
|
||||||
appearance: none;
|
appearance: none;
|
||||||
@@ -255,7 +255,7 @@ select, textarea {
|
|||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark textarea {
|
html.dark textarea:not(code-input *) {
|
||||||
background-color: #202020
|
background-color: #202020
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@ p.top {
|
|||||||
bottom: 115%;
|
bottom: 115%;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre:not(code-input *) {
|
||||||
white-space: pre-wrap; /* css-3 */
|
white-space: pre-wrap; /* css-3 */
|
||||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||||
white-space: -pre-wrap; /* Opera 4-6 */
|
white-space: -pre-wrap; /* Opera 4-6 */
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="{{ .cssClass }}">
|
<html lang="en" class="{{ .cssClass }}">
|
||||||
<head>
|
<head>
|
||||||
|
{{ template "syntaxhighlighting.html" . }}
|
||||||
<script>
|
<script>
|
||||||
window.usernameEnabled = {{ .username }};
|
window.usernameEnabled = {{ .username }};
|
||||||
window.langFile = JSON.parse({{ .language }});
|
window.langFile = JSON.parse({{ .language }});
|
||||||
@@ -445,6 +446,7 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
<th>{{ .strings.from }}</th>
|
<th>{{ .strings.from }}</th>
|
||||||
<th>{{ .strings.userProfilesLibraries }}</th>
|
<th>{{ .strings.userProfilesLibraries }}</th>
|
||||||
|
<th></th>
|
||||||
<th><span class="button ~neutral @high" id="button-profile-create">{{ .strings.create }}</span></th>
|
<th><span class="button ~neutral @high" id="button-profile-create">{{ .strings.create }}</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -453,6 +455,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="modal-edit-profile" class="modal">
|
||||||
|
<form class="relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-2/3 card flex flex-col gap-2" id="form-edit-profile">
|
||||||
|
<span class="heading">{{ .strings.editProfile }} <span class="modal-close">×</span></span>
|
||||||
|
<p class="content">{{ .strings.editProfileDescription }}</p>
|
||||||
|
<div id="modal-edit-profile-editor"></div>
|
||||||
|
<label>
|
||||||
|
<input type="submit" class="unfocused">
|
||||||
|
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<div id="modal-add-profile" class="modal">
|
<div id="modal-add-profile" class="modal">
|
||||||
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-add-profile" href="">
|
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-add-profile" href="">
|
||||||
<span class="heading">{{ .strings.addProfile }} <span class="modal-close">×</span></span>
|
<span class="heading">{{ .strings.addProfile }} <span class="modal-close">×</span></span>
|
||||||
|
|||||||
3
html/syntaxhighlighting.html
Normal file
3
html/syntaxhighlighting.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<link rel="stylesheet" type="text/css" href="{{ .pages.Base }}/css/{{ .cssVersion }}highlightjs-light.css" data-theme="light">
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ .pages.Base }}/css/{{ .cssVersion }}highlightjs-dark.css" data-theme="dark">
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ .pages.Base }}/css/{{ .cssVersion }}code-input.css">
|
||||||
@@ -39,6 +39,8 @@
|
|||||||
"commitNoun": "Commit",
|
"commitNoun": "Commit",
|
||||||
"newUser": "New User",
|
"newUser": "New User",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
|
"editProfile": "Edit profile",
|
||||||
|
"editProfileDescription": "For large changes, it is recommended you modify settings in Jellyfin/Jellyseerr/Ombi and re-generate the profile, but you can also make direct changes here. Please use caution when editing.",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
"userLabel": "User Label",
|
"userLabel": "User Label",
|
||||||
@@ -241,6 +243,7 @@
|
|||||||
"errorBlankFields": "Fields were left blank",
|
"errorBlankFields": "Fields were left blank",
|
||||||
"errorDeleteProfile": "Failed to delete profile {n}",
|
"errorDeleteProfile": "Failed to delete profile {n}",
|
||||||
"errorLoadProfiles": "Failed to load profiles.",
|
"errorLoadProfiles": "Failed to load profiles.",
|
||||||
|
"errorLoadProfile": "Failed to load profile.",
|
||||||
"errorCreateProfile": "Failed to create profile {n}",
|
"errorCreateProfile": "Failed to create profile {n}",
|
||||||
"errorSavedProfile": "Failed to save profile {n}",
|
"errorSavedProfile": "Failed to save profile {n}",
|
||||||
"errorSetDefaultProfile": "Failed to set default profile.",
|
"errorSetDefaultProfile": "Failed to set default profile.",
|
||||||
@@ -258,6 +261,7 @@
|
|||||||
"errorNoReferralTemplate": "Profile doesn't contain referral template, add one in settings.",
|
"errorNoReferralTemplate": "Profile doesn't contain referral template, add one in settings.",
|
||||||
"errorLoadActivities": "Failed to load activities.",
|
"errorLoadActivities": "Failed to load activities.",
|
||||||
"errorInvalidDate": "Date is invalid.",
|
"errorInvalidDate": "Date is invalid.",
|
||||||
|
"errorInvalidJSON": "Invalid JSON.",
|
||||||
"updateAvailable": "A new update is available, check settings.",
|
"updateAvailable": "A new update is available, check settings.",
|
||||||
"noUpdatesAvailable": "No new updates available."
|
"noUpdatesAvailable": "No new updates available."
|
||||||
},
|
},
|
||||||
|
|||||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -12,11 +12,13 @@
|
|||||||
"@af-utils/scrollend-polyfill": "^0.0.14",
|
"@af-utils/scrollend-polyfill": "^0.0.14",
|
||||||
"@ts-stack/markdown": "^1.4.0",
|
"@ts-stack/markdown": "^1.4.0",
|
||||||
"@types/node": "^20.3.0",
|
"@types/node": "^20.3.0",
|
||||||
|
"@webcoder49/code-input": "^2.7.2",
|
||||||
"a17t": "^0.10.1",
|
"a17t": "^0.10.1",
|
||||||
"any-date-parser": "^1.5.4",
|
"any-date-parser": "^1.5.4",
|
||||||
"browserslist": "^4.21.7",
|
"browserslist": "^4.21.7",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"fs-cheerio": "^3.0.0",
|
"fs-cheerio": "^3.0.0",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"inline-source": "^8.0.2",
|
"inline-source": "^8.0.2",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@@ -629,6 +631,12 @@
|
|||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@webcoder49/code-input": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@webcoder49/code-input/-/code-input-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-rW+bDCDhXJuUUS1+NMazYfHuaxgClcayEPbTXWsitqb0gRpyBaY2T83RM//YhRHzHPUYL4bX8hWU+zkj2u4LiA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/a17t": {
|
"node_modules/a17t": {
|
||||||
"version": "0.10.1",
|
"version": "0.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/a17t/-/a17t-0.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/a17t/-/a17t-0.10.1.tgz",
|
||||||
@@ -1125,6 +1133,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001726",
|
"caniuse-lite": "^1.0.30001726",
|
||||||
"electron-to-chromium": "^1.5.173",
|
"electron-to-chromium": "^1.5.173",
|
||||||
@@ -2928,6 +2937,15 @@
|
|||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/highlight.js": {
|
||||||
|
"version": "11.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
|
||||||
|
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-encoding-sniffer": {
|
"node_modules/html-encoding-sniffer": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||||
@@ -5180,6 +5198,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -5536,6 +5555,7 @@
|
|||||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||||
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
|
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws-sign2": "~0.7.0",
|
"aws-sign2": "~0.7.0",
|
||||||
"aws4": "^1.8.0",
|
"aws4": "^1.8.0",
|
||||||
@@ -6526,6 +6546,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
|
|||||||
@@ -20,11 +20,13 @@
|
|||||||
"@af-utils/scrollend-polyfill": "^0.0.14",
|
"@af-utils/scrollend-polyfill": "^0.0.14",
|
||||||
"@ts-stack/markdown": "^1.4.0",
|
"@ts-stack/markdown": "^1.4.0",
|
||||||
"@types/node": "^20.3.0",
|
"@types/node": "^20.3.0",
|
||||||
|
"@webcoder49/code-input": "^2.7.2",
|
||||||
"a17t": "^0.10.1",
|
"a17t": "^0.10.1",
|
||||||
"any-date-parser": "^1.5.4",
|
"any-date-parser": "^1.5.4",
|
||||||
"browserslist": "^4.21.7",
|
"browserslist": "^4.21.7",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"fs-cheerio": "^3.0.0",
|
"fs-cheerio": "^3.0.0",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"inline-source": "^8.0.2",
|
"inline-source": "^8.0.2",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ window.availableProfiles = window.availableProfiles || [];
|
|||||||
window.modals.profiles = new Modal(document.getElementById("modal-user-profiles"));
|
window.modals.profiles = new Modal(document.getElementById("modal-user-profiles"));
|
||||||
|
|
||||||
window.modals.addProfile = new Modal(document.getElementById("modal-add-profile"));
|
window.modals.addProfile = new Modal(document.getElementById("modal-add-profile"));
|
||||||
|
|
||||||
|
window.modals.editProfile = new Modal(document.getElementById("modal-edit-profile"));
|
||||||
|
|
||||||
window.modals.announce = new Modal(document.getElementById("modal-announce"));
|
window.modals.announce = new Modal(document.getElementById("modal-announce"));
|
||||||
|
|
||||||
|
|||||||
@@ -85,10 +85,10 @@ export const _upload = (url: string, formData: FormData): void => {
|
|||||||
req.send(formData);
|
req.send(formData);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
export const _req = (method: string, url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
if (window.pages) { url = window.pages.Base + url; }
|
if (window.pages) { url = window.pages.Base + url; }
|
||||||
req.open("POST", url, true);
|
req.open(method, url, true);
|
||||||
if (response) {
|
if (response) {
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
}
|
}
|
||||||
@@ -107,6 +107,10 @@ export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHt
|
|||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("POST", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||||
|
|
||||||
|
export const _put = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("PUT", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||||
|
|
||||||
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
|
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
if (window.pages) { url = window.pages.Base + url; }
|
if (window.pages) { url = window.pages.Base + url; }
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
import { _get, _post, _delete, toggleLoader } from "../modules/common.js";
|
import { _get, _post, _delete, toggleLoader, _put } from "../modules/common.js";
|
||||||
|
import hljs from "highlight.js/lib/core";
|
||||||
|
import json from 'highlight.js/lib/languages/json';
|
||||||
|
import codeInput, { CodeInput } from "@webcoder49/code-input/code-input.mjs";
|
||||||
|
import Template from "@webcoder49/code-input/templates/hljs.mjs";
|
||||||
|
import Indent from "@webcoder49/code-input/plugins/indent.mjs";
|
||||||
|
|
||||||
|
hljs.registerLanguage("json", json);
|
||||||
|
codeInput.registerTemplate("json-highlighted",
|
||||||
|
new Template(hljs, [new Indent()])
|
||||||
|
);
|
||||||
|
|
||||||
declare var window: GlobalWindow;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
@@ -32,6 +42,7 @@ class profile implements Profile {
|
|||||||
private _defaultRadio: HTMLInputElement;
|
private _defaultRadio: HTMLInputElement;
|
||||||
private _referralsButton: HTMLSpanElement;
|
private _referralsButton: HTMLSpanElement;
|
||||||
private _referralsEnabled: boolean;
|
private _referralsEnabled: boolean;
|
||||||
|
private _editButton: HTMLButtonElement;
|
||||||
|
|
||||||
get name(): string { return this._name.textContent; }
|
get name(): string { return this._name.textContent; }
|
||||||
set name(v: string) { this._name.textContent = v; }
|
set name(v: string) { this._name.textContent = v; }
|
||||||
@@ -119,6 +130,7 @@ class profile implements Profile {
|
|||||||
innerHTML += `
|
innerHTML += `
|
||||||
<td class="profile-from truncate"></td>
|
<td class="profile-from truncate"></td>
|
||||||
<td class="profile-libraries"></td>
|
<td class="profile-libraries"></td>
|
||||||
|
<td><button class="button ~neutral @low flex flex-row gap-2 profile-edit"><i class="ri-edit-line"></i>${window.lang.strings("edit")}</button></td>
|
||||||
<td><span class="button ~critical @low">${window.lang.strings("delete")}</span></td>
|
<td><span class="button ~critical @low">${window.lang.strings("delete")}</span></td>
|
||||||
`;
|
`;
|
||||||
this._row.innerHTML = innerHTML;
|
this._row.innerHTML = innerHTML;
|
||||||
@@ -132,6 +144,7 @@ class profile implements Profile {
|
|||||||
if (window.referralsEnabled)
|
if (window.referralsEnabled)
|
||||||
this._referralsButton = this._row.querySelector("span.profile-referrals") as HTMLSpanElement;
|
this._referralsButton = this._row.querySelector("span.profile-referrals") as HTMLSpanElement;
|
||||||
this._fromUser = this._row.querySelector("td.profile-from") as HTMLTableDataCellElement;
|
this._fromUser = this._row.querySelector("td.profile-from") as HTMLTableDataCellElement;
|
||||||
|
this._editButton = this._row.querySelector(".profile-edit") as HTMLButtonElement;
|
||||||
this._defaultRadio = this._row.querySelector("input[type=radio]") as HTMLInputElement;
|
this._defaultRadio = this._row.querySelector("input[type=radio]") as HTMLInputElement;
|
||||||
this._defaultRadio.onclick = () => document.dispatchEvent(new CustomEvent("profiles-default", { detail: this.name }));
|
this._defaultRadio.onclick = () => document.dispatchEvent(new CustomEvent("profiles-default", { detail: this.name }));
|
||||||
(this._row.querySelector("span.\\~critical") as HTMLSpanElement).onclick = this.delete;
|
(this._row.querySelector("span.\\~critical") as HTMLSpanElement).onclick = this.delete;
|
||||||
@@ -152,6 +165,7 @@ class profile implements Profile {
|
|||||||
setOmbiFunc = (ombiFunc: (ombi: boolean) => void) => { this._ombiButton.onclick = () => ombiFunc(this._ombi); }
|
setOmbiFunc = (ombiFunc: (ombi: boolean) => void) => { this._ombiButton.onclick = () => ombiFunc(this._ombi); }
|
||||||
setJellyseerrFunc = (jellyseerrFunc: (jellyseerr: boolean) => void) => { this._jellyseerrButton.onclick = () => jellyseerrFunc(this._jellyseerr); }
|
setJellyseerrFunc = (jellyseerrFunc: (jellyseerr: boolean) => void) => { this._jellyseerrButton.onclick = () => jellyseerrFunc(this._jellyseerr); }
|
||||||
setReferralFunc = (referralFunc: (enabled: boolean) => void) => { this._referralsButton.onclick = () => referralFunc(this._referralsEnabled); }
|
setReferralFunc = (referralFunc: (enabled: boolean) => void) => { this._referralsButton.onclick = () => referralFunc(this._referralsEnabled); }
|
||||||
|
setEditFunc = (editFunc: (name: string) => void) => { this._editButton.onclick = () => editFunc(this.name); }
|
||||||
|
|
||||||
remove = () => { document.dispatchEvent(new CustomEvent("profiles-delete", { detail: this._name })); this._row.remove(); }
|
remove = () => { document.dispatchEvent(new CustomEvent("profiles-delete", { detail: this._name })); this._row.remove(); }
|
||||||
|
|
||||||
@@ -262,6 +276,7 @@ export class ProfileEditor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this._profiles[name].setEditFunc(this._loadProfileEditor);
|
||||||
this._table.appendChild(this._profiles[name].asElement());
|
this._table.appendChild(this._profiles[name].asElement());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,6 +346,64 @@ export class ProfileEditor {
|
|||||||
window.modals.enableReferralsProfile.show();
|
window.modals.enableReferralsProfile.show();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _loadProfileEditor = (name: string) => {
|
||||||
|
const urlSafeName = encodeURIComponent(encodeURIComponent(name));
|
||||||
|
_get("/profiles/raw/" + urlSafeName, null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState != 4) return;
|
||||||
|
if (req.status != 200) {
|
||||||
|
window.notifications.customError("errorLoadProfile", window.lang.notif("errorLoadProfile"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const editorContainer = document.getElementById("modal-edit-profile-editor");
|
||||||
|
const editor = document.createElement("code-input") as CodeInput;
|
||||||
|
editor.setAttribute("template", "json-highlighted");
|
||||||
|
editor.setAttribute("language", "json");
|
||||||
|
editor.classList.add("rounded-sm");
|
||||||
|
editor.value = JSON.stringify(req.response, null, 2);
|
||||||
|
editorContainer.replaceChildren(editor);
|
||||||
|
|
||||||
|
const form = document.getElementById("form-edit-profile") as HTMLFormElement;
|
||||||
|
const submit = form.querySelector("input[type=submit]").nextElementSibling;
|
||||||
|
form.onsubmit = (event: SubmitEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
let send: any;
|
||||||
|
try {
|
||||||
|
send = JSON.parse(editor.value);
|
||||||
|
} catch(e: any) {
|
||||||
|
submit.classList.add("~critical");
|
||||||
|
submit.classList.remove("~urge");
|
||||||
|
window.notifications.customError("errorInvalidJSON", window.lang.notif("errorInvalidJSON"));
|
||||||
|
setTimeout(() => {
|
||||||
|
submit.classList.add("~urge");
|
||||||
|
submit.classList.remove("~critical");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
if (!send) return;
|
||||||
|
_put("/profiles/raw/" + urlSafeName, send, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState != 4) return;
|
||||||
|
if (req.status == 200 || req.status == 201 || req.status == 204) {
|
||||||
|
window.notifications.customSuccess("savedProfile", window.lang.notif("savedProfile"));
|
||||||
|
// a 201 implies the profile was renamed. Since reloading profiles doesn't delete missing ones,
|
||||||
|
// we should delete the old one ourselves.
|
||||||
|
if (req.status == 201) {
|
||||||
|
this._profiles[name].remove()
|
||||||
|
delete this._profiles[name];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.notifications.customError("errorSavedProfile", window.lang.notif("errorSavedProfile"));
|
||||||
|
}
|
||||||
|
window.modals.editProfile.close();
|
||||||
|
// Reload with new info from edits
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.modals.profiles.close();
|
||||||
|
window.modals.editProfile.show();
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
(document.getElementById('setting-profiles') as HTMLSpanElement).onclick = this.load;
|
(document.getElementById('setting-profiles') as HTMLSpanElement).onclick = this.load;
|
||||||
document.addEventListener("profiles-default", (event: CustomEvent) => {
|
document.addEventListener("profiles-default", (event: CustomEvent) => {
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ export class ThemeManager {
|
|||||||
|
|
||||||
private _themeButton: HTMLElement = null;
|
private _themeButton: HTMLElement = null;
|
||||||
private _metaTag: HTMLMetaElement;
|
private _metaTag: HTMLMetaElement;
|
||||||
|
|
||||||
|
private _cssLightFiles: HTMLLinkElement[];
|
||||||
|
private _cssDarkFiles: HTMLLinkElement[];
|
||||||
|
|
||||||
|
|
||||||
private _beforeTransition = () => {
|
private _beforeTransition = () => {
|
||||||
const doc = document.documentElement;
|
const doc = document.documentElement;
|
||||||
@@ -47,6 +51,11 @@ export class ThemeManager {
|
|||||||
|
|
||||||
constructor(button?: HTMLElement) {
|
constructor(button?: HTMLElement) {
|
||||||
this._metaTag = document.querySelector("meta[name=color-scheme]") as HTMLMetaElement;
|
this._metaTag = document.querySelector("meta[name=color-scheme]") as HTMLMetaElement;
|
||||||
|
|
||||||
|
this._cssLightFiles = Array.from(document.head.querySelectorAll("link[data-theme=light]")) as Array<HTMLLinkElement>;
|
||||||
|
this._cssDarkFiles = Array.from(document.head.querySelectorAll("link[data-theme=dark]")) as Array<HTMLLinkElement>;
|
||||||
|
this._cssLightFiles.forEach((el) => el.remove());
|
||||||
|
this._cssDarkFiles.forEach((el) => el.remove());
|
||||||
const theme = localStorage.getItem("theme");
|
const theme = localStorage.getItem("theme");
|
||||||
if (theme == "dark") {
|
if (theme == "dark") {
|
||||||
this._enable(true);
|
this._enable(true);
|
||||||
@@ -63,11 +72,16 @@ export class ThemeManager {
|
|||||||
private _toggle = () => {
|
private _toggle = () => {
|
||||||
let metaValue = "light dark";
|
let metaValue = "light dark";
|
||||||
this._beforeTransition();
|
this._beforeTransition();
|
||||||
if (!document.documentElement.classList.contains('dark')) {
|
const dark = !document.documentElement.classList.contains("dark");
|
||||||
|
if (dark) {
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add('dark');
|
||||||
metaValue = "dark light";
|
metaValue = "dark light";
|
||||||
|
this._cssLightFiles.forEach((el) => el.remove());
|
||||||
|
this._cssDarkFiles.forEach((el) => document.head.appendChild(el));
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('dark');
|
document.documentElement.classList.remove('dark');
|
||||||
|
this._cssDarkFiles.forEach((el) => el.remove());
|
||||||
|
this._cssLightFiles.forEach((el) => document.head.appendChild(el));
|
||||||
}
|
}
|
||||||
localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? "dark" : "light");
|
localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? "dark" : "light");
|
||||||
|
|
||||||
@@ -86,7 +100,14 @@ export class ThemeManager {
|
|||||||
document.documentElement.classList.remove(opposite);
|
document.documentElement.classList.remove(opposite);
|
||||||
}
|
}
|
||||||
document.documentElement.classList.add(mode);
|
document.documentElement.classList.add(mode);
|
||||||
|
|
||||||
|
if (dark) {
|
||||||
|
this._cssLightFiles.forEach((el) => el.remove());
|
||||||
|
this._cssDarkFiles.forEach((el) => document.head.appendChild(el));
|
||||||
|
} else {
|
||||||
|
this._cssDarkFiles.forEach((el) => el.remove());
|
||||||
|
this._cssLightFiles.forEach((el) => document.head.appendChild(el));
|
||||||
|
}
|
||||||
// this._metaTag.setAttribute("content", `${mode} ${opposite}`);
|
// this._metaTag.setAttribute("content", `${mode} ${opposite}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"typeRoots": ["./typings", "../node_modules/@types"],
|
"typeRoots": ["./typings", "../node_modules/@types"],
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ declare interface Modals {
|
|||||||
jellyseerrProfile?: Modal;
|
jellyseerrProfile?: Modal;
|
||||||
profiles: Modal;
|
profiles: Modal;
|
||||||
addProfile: Modal;
|
addProfile: Modal;
|
||||||
|
editProfile: Modal;
|
||||||
announce: Modal;
|
announce: Modal;
|
||||||
editor: Modal;
|
editor: Modal;
|
||||||
customizeEmails: Modal;
|
customizeEmails: Modal;
|
||||||
|
|||||||
Reference in New Issue
Block a user