1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2025-04-03 17:52:52 +00:00

Compare commits

...

5 Commits

Author SHA1 Message Date
a1612949bf
accounts: css adjustments
there is now a border between rows, on light mode a dashed line, on dark
a dotted (looks almost solid). Row height has been changed slightly,
too. Label and edit icon are back to being first after the username, and
the edit button is on the left now. Contact dropdowns now overflow out
of the table properly.
2024-08-28 20:55:52 +01:00
ae808c5109
accounts: standardise "text with edit button" component
The sort of thing used for the user's label and their email address is
now implemented by ui.ts/HiddenInputField, and used by the two.
2024-08-28 20:22:25 +01:00
418f3c4566
build: fix crash css/js inlining
when re-doing makefile, I removed the part where CSS is written to
bundle.css, then later moved to v3bundle.css. To solve, crash.html now
just directly requests web/css/v3bundle.css (and web/js/crash.js,
removing the `mv` line in the makefile too).
2024-08-28 15:55:30 +01:00
399ce3b044
activity: Just use window.URLBase
instead of figuring out the full URL. URLs are definitely the most
fragmented and annoying thing about this software.
2024-08-28 15:42:54 +01:00
1aa100dc7d
build: dont go build when INTERNAL=off and no changes
all build steps (apart from swagger, which generates go code) are stored
in BUILDDEPS, which is now a dependency of all. if INTERNAL=on,
BUILDDEPS is added to GO_TARGET, so the executable is rebuilt with new
content. Used the .DEFAULT_GOAL feature so I could move all: to the
bottom, where I think it belongs.
2024-08-28 15:24:43 +01:00
7 changed files with 169 additions and 120 deletions

View File

@ -1,6 +1,5 @@
.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 inline-css variants-html install clean npm config-description config-default precompile
.DEFAULT_GOAL := all
all: compile
GOESBUILD ?= off GOESBUILD ?= off
ifeq ($(GOESBUILD), on) ifeq ($(GOESBUILD), on)
@ -35,9 +34,11 @@ TAGS := -tags "
ifeq ($(INTERNAL), on) ifeq ($(INTERNAL), on)
DATA := data DATA := data
COMPDEPS := $(BUILDDEPS)
else else
DATA := build/data DATA := build/data
TAGS := $(TAGS) external TAGS := $(TAGS) external
COMPDEPS :=
endif endif
ifeq ($(TRAY), on) ifeq ($(TRAY), on)
@ -141,7 +142,6 @@ $(TYPESCRIPT_TARGET): $(TYPESCRIPT_FULLSRC) ts/tsconfig.json
scripts/dark-variant.sh tempts/modules scripts/dark-variant.sh tempts/modules
$(info compiling typescript) $(info compiling typescript)
$(foreach tempsrc,$(TYPESCRIPT_TEMPSRC),$(ESBUILD) --target=es6 --bundle $(tempsrc) $(SOURCEMAP) --outfile=$(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(tempsrc))) $(MINIFY);) $(foreach tempsrc,$(TYPESCRIPT_TEMPSRC),$(ESBUILD) --target=es6 --bundle $(tempsrc) $(SOURCEMAP) --outfile=$(patsubst %.ts,%.js,$(subst tempts/,./$(DATA)/web/js/,$(tempsrc))) $(MINIFY);)
mv $(DATA)/web/js/crash.js $(DATA)/
$(COPYTS) $(COPYTS)
SWAGGER_SRC = $(wildcard api*.go) $(wildcard *auth.go) views.go SWAGGER_SRC = $(wildcard api*.go) $(wildcard *auth.go) views.go
@ -206,21 +206,27 @@ $(COPY_TARGET): $(INLINE_TARGET) $(STATIC_SRC) $(LANG_SRC)
cp -r lang $(DATA)/ cp -r lang $(DATA)/
cp LICENSE $(DATA)/ cp LICENSE $(DATA)/
precompile: $(DATA) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) BUILDDEPS := $(DATA) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET)
precompile: $(BUILDDEPS)
COMPDEPS =
ifeq ($(INTERNAL), on)
COMPDEPS = $(BUILDDEPS)
endif
GO_SRC = $(shell find ./ -name "*.go") GO_SRC = $(shell find ./ -name "*.go")
GO_TARGET = build/jfa-go GO_TARGET = build/jfa-go
$(GO_TARGET): $(DATA) $(CONFIG_DEFAULT) $(EMAIL_TARGET) $(COPY_TARGET) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum $(GO_TARGET): $(COMPDEPS) $(SWAGGER_TARGET) $(GO_SRC) go.mod go.sum
$(info Downloading deps) $(info Downloading deps)
$(GOBINARY) mod download $(GOBINARY) mod download
$(info Building) $(info Building)
mkdir -p build mkdir -p build
$(GOBINARY) build $(RACEDETECTOR) -ldflags="$(LDFLAGS)" $(TAGS) -o $(GO_TARGET) $(GOBINARY) build $(RACEDETECTOR) -ldflags="$(LDFLAGS)" $(TAGS) -o $(GO_TARGET)
compile: $(GO_TARGET) all: $(BUILDDEPS) $(GO_TARGET)
compress: compress:
upx --lzma build/jfa-go upx --lzma $(GO_TARGET)
install: install:
cp -r build $(DESTDIR)/jfa-go cp -r build $(DESTDIR)/jfa-go

View File

@ -962,7 +962,7 @@ func (app *appContext) ModifyLabels(gc *gin.Context) {
emailStore = oldEmail emailStore = oldEmail
} }
emailStore.Label = label emailStore.Label = label
app.debug.Println(lm.UserLabelAdjusted, id, label) app.debug.Printf(lm.UserLabelAdjusted, id, label)
app.storage.SetEmailsKey(id, emailStore) app.storage.SetEmailsKey(id, emailStore)
} }
} }

View File

@ -27,16 +27,18 @@
<body class="max-w-full overflow-x-hidden section"> <body class="max-w-full overflow-x-hidden section">
{{ template "login-modal.html" . }} {{ template "login-modal.html" . }}
<div id="modal-add-user" class="modal"> <div id="modal-add-user" class="modal">
<form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3" id="form-add-user" href=""> <form class="card relative mx-auto my-[10%] w-11/12 sm:w-4/5 lg:w-1/3 flex flex-col gap-2" id="form-add-user" href="">
<span class="heading">{{ .strings.newUser }} <span class="modal-close">&times;</span></span> <span class="heading">{{ .strings.newUser }} <span class="modal-close">&times;</span></span>
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="add-user-user"> <input type="text" class="field input ~neutral @high" placeholder="{{ .strings.username }}" id="add-user-user">
<input type="email" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.emailAddress }}"> <input type="email" class="field input ~neutral @high" placeholder="{{ .strings.emailAddress }}">
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="add-user-password"> <input type="password" class="field input ~neutral @high" placeholder="{{ .strings.password }}" id="add-user-password">
<label class="label supra">{{ .strings.profile }}</label> <label class="label flex flex-col gap-2">
<div class="select ~neutral @low mb-2 mt-4"> <span class="supra">{{ .strings.profile }}</span>
<select id="add-user-profile"> <div class="select ~neutral @low">
</select> <select id="add-user-profile">
</div> </select>
</div>
</label>
<label> <label>
<input type="submit" class="unfocused"> <input type="submit" class="unfocused">
<span class="button ~urge @low full-width center supra submit">{{ .strings.create }}</span> <span class="button ~urge @low full-width center supra submit">{{ .strings.create }}</span>
@ -800,7 +802,7 @@
<span class="button ~critical @low center " id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span> <span class="button ~critical @low center " id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
</div> </div>
<div class="card @low accounts-header table-responsive mt-2"> <div class="card @low accounts-header table-responsive mt-2">
<table class="table text-base leading-4"> <table class="table text-base leading-5">
<thead> <thead>
<tr> <tr>
<th><input type="checkbox" value="" id="accounts-select-all"></th> <th><input type="checkbox" value="" id="accounts-select-all"></th>

View File

@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<link inline rel="stylesheet" type="text/css" href="bundle.css"> <link inline rel="stylesheet" type="text/css" href="web/css/v3bundle.css">
{{ template "header.html" . }} {{ template "header.html" . }}
<title>Crash report</title> <title>Crash report</title>
</head> </head>
@ -40,6 +40,6 @@
</section> </section>
</div> </div>
</div> </div>
<script inline src="crash.js"></script> <script inline src="web/js/crash.js"></script>
</body> </body>
</html> </html>

View File

@ -4,6 +4,8 @@ import { Marked } from "@ts-stack/markdown";
import { stripMarkdown } from "../modules/stripmd.js"; import { stripMarkdown } from "../modules/stripmd.js";
import { DiscordUser, newDiscordSearch } from "../modules/discord.js"; import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js"; import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
import { HiddenInputField } from "./ui.js";
const dateParser = require("any-date-parser"); const dateParser = require("any-date-parser");
interface User { interface User {
@ -48,9 +50,9 @@ class user implements User, SearchableItem {
private _admin: HTMLSpanElement; private _admin: HTMLSpanElement;
private _disabled: HTMLSpanElement; private _disabled: HTMLSpanElement;
private _email: HTMLInputElement; private _email: HTMLInputElement;
private _emailEditor: HiddenInputField;
private _notifyEmail: boolean; private _notifyEmail: boolean;
private _emailAddress: string; private _emailAddress: string;
private _emailEditButton: HTMLElement;
private _telegram: HTMLTableDataCellElement; private _telegram: HTMLTableDataCellElement;
private _telegramUsername: string; private _telegramUsername: string;
private _notifyTelegram: boolean; private _notifyTelegram: boolean;
@ -67,8 +69,8 @@ class user implements User, SearchableItem {
private _lastActiveUnix: number; private _lastActiveUnix: number;
private _notifyDropdown: HTMLDivElement; private _notifyDropdown: HTMLDivElement;
private _label: HTMLInputElement; private _label: HTMLInputElement;
private _labelEditor: HiddenInputField;
private _userLabel: string; private _userLabel: string;
private _labelEditButton: HTMLElement;
private _accounts_admin: HTMLInputElement private _accounts_admin: HTMLInputElement
private _selected: boolean; private _selected: boolean;
private _referralsEnabled: boolean; private _referralsEnabled: boolean;
@ -110,10 +112,10 @@ class user implements User, SearchableItem {
get admin(): boolean { return this._admin.classList.contains("chip"); } get admin(): boolean { return this._admin.classList.contains("chip"); }
set admin(state: boolean) { set admin(state: boolean) {
if (state) { if (state) {
this._admin.classList.add("chip", "~info", "ml-4"); this._admin.classList.remove("hidden")
this._admin.textContent = window.lang.strings("admin"); this._admin.textContent = window.lang.strings("admin");
} else { } else {
this._admin.classList.remove("chip", "~info", "ml-4"); this._admin.classList.add("hidden")
this._admin.textContent = ""; this._admin.textContent = "";
} }
} }
@ -133,10 +135,10 @@ class user implements User, SearchableItem {
get disabled(): boolean { return this._disabled.classList.contains("chip"); } get disabled(): boolean { return this._disabled.classList.contains("chip"); }
set disabled(state: boolean) { set disabled(state: boolean) {
if (state) { if (state) {
this._disabled.classList.add("chip", "~warning", "ml-4"); this._disabled.classList.remove("hidden")
this._disabled.textContent = window.lang.strings("disabled"); this._disabled.textContent = window.lang.strings("disabled");
} else { } else {
this._disabled.classList.remove("chip", "~warning", "ml-4"); this._disabled.classList.add("hidden")
this._disabled.textContent = ""; this._disabled.textContent = "";
} }
} }
@ -144,12 +146,7 @@ class user implements User, SearchableItem {
get email(): string { return this._emailAddress; } get email(): string { return this._emailAddress; }
set email(value: string) { set email(value: string) {
this._emailAddress = value; this._emailAddress = value;
const input = this._email.querySelector("input"); this._emailEditor.value = value;
if (input) {
input.value = value;
} else {
this._email.textContent = value;
}
const lastNotifyMethod = this.lastNotifyMethod() == "email"; const lastNotifyMethod = this.lastNotifyMethod() == "email";
if (!value) { if (!value) {
this._notifyDropdown.querySelector(".accounts-area-email").classList.add("unfocused"); this._notifyDropdown.querySelector(".accounts-area-email").classList.add("unfocused");
@ -188,7 +185,7 @@ class user implements User, SearchableItem {
if (!telegram && !discord && !matrix && !email) return; if (!telegram && !discord && !matrix && !email) return;
let innerHTML = ` let innerHTML = `
<i class="icon ri-settings-2-line ml-2 dropdown-button"></i> <i class="icon ri-settings-2-line ml-2 dropdown-button"></i>
<div class="dropdown manual"> <div class="dropdown manual over-top">
<div class="dropdown-display lg"> <div class="dropdown-display lg">
<div class="card ~neutral @low"> <div class="card ~neutral @low">
<div class="supra sm mb-2">${window.lang.strings("contactThrough")}</div> <div class="supra sm mb-2">${window.lang.strings("contactThrough")}</div>
@ -468,14 +465,7 @@ class user implements User, SearchableItem {
get label(): string { return this._userLabel; } get label(): string { return this._userLabel; }
set label(l: string) { set label(l: string) {
this._userLabel = l ? l : ""; this._userLabel = l ? l : "";
this._label.innerHTML = l ? l : ""; this._labelEditor.value = l ? l : "";
this._labelEditButton.classList.add("ri-edit-line");
this._labelEditButton.classList.remove("ri-check-line");
if (!l) {
this._label.classList.remove("chip", "~gray");
} else {
this._label.classList.add("chip", "~gray", "mr-2");
}
} }
matchesSearch = (query: string): boolean => { matchesSearch = (query: string): boolean => {
@ -496,9 +486,17 @@ class user implements User, SearchableItem {
constructor(user: User) { constructor(user: User) {
this._row = document.createElement("tr") as HTMLTableRowElement; this._row = document.createElement("tr") as HTMLTableRowElement;
this._row.classList.add("border-b", "border-dashed", "dark:border-dotted", "dark:border-stone-700");
let innerHTML = ` let innerHTML = `
<td><input type="checkbox" class="accounts-select-user" value=""></td> <td><input type="checkbox" class="accounts-select-user" value=""></td>
<td><div class="table-inline"><span class="accounts-username py-2 mr-2"></span><span class="accounts-label-container ml-2"></span> <i class="icon ri-edit-line accounts-label-edit"></i> <span class="accounts-admin"></span> <span class="accounts-disabled"></span></span></div></td> <td><div class="flex flex-row gap-2 items-center">
<span class="accounts-username"></span>
<div class="flex flex-row gap-2 items-baseline">
<span class="accounts-label-container" title="${window.lang.strings("label")}"></span>
<span class="accounts-admin chip ~info hidden"></span>
<span class="accounts-disabled chip ~warning hidden"></span></span>
</div>
</div></td>
`; `;
if (window.jellyfinLogin) { if (window.jellyfinLogin) {
innerHTML += ` innerHTML += `
@ -506,7 +504,9 @@ class user implements User, SearchableItem {
`; `;
} }
innerHTML += ` innerHTML += `
<td><div class="table-inline"><i class="icon ri-edit-line accounts-email-edit"></i><span class="accounts-email-container ml-2"></span></div></td> <td><div class="flex flex-row gap-2 items-baseline">
<span class="accounts-email-container" title="${window.lang.strings("emailAddress")}"></span>
</div></td>
`; `;
if (window.telegramEnabled) { if (window.telegramEnabled) {
innerHTML += ` innerHTML += `
@ -534,21 +534,33 @@ class user implements User, SearchableItem {
`; `;
this._row.innerHTML = innerHTML; this._row.innerHTML = innerHTML;
const emailEditor = `<input type="email" class="input ~neutral @low stealth-input">`; const emailEditor = `<input type="email" class="input ~neutral @low stealth-input">`;
const labelEditor = `<input type="text" class="field ~neutral @low stealth-input">`;
this._check = this._row.querySelector("input[type=checkbox].accounts-select-user") as HTMLInputElement; this._check = this._row.querySelector("input[type=checkbox].accounts-select-user") as HTMLInputElement;
this._accounts_admin = this._row.querySelector("input[type=checkbox].accounts-access-jfa") as HTMLInputElement; this._accounts_admin = this._row.querySelector("input[type=checkbox].accounts-access-jfa") as HTMLInputElement;
this._username = this._row.querySelector(".accounts-username") as HTMLSpanElement; this._username = this._row.querySelector(".accounts-username") as HTMLSpanElement;
this._admin = this._row.querySelector(".accounts-admin") as HTMLSpanElement; this._admin = this._row.querySelector(".accounts-admin") as HTMLSpanElement;
this._disabled = this._row.querySelector(".accounts-disabled") as HTMLSpanElement; this._disabled = this._row.querySelector(".accounts-disabled") as HTMLSpanElement;
this._email = this._row.querySelector(".accounts-email-container") as HTMLInputElement; this._email = this._row.querySelector(".accounts-email-container") as HTMLInputElement;
this._emailEditButton = this._row.querySelector(".accounts-email-edit") as HTMLElement; this._emailEditor = new HiddenInputField({
container: this._email,
onSet: this._updateEmail,
customContainerHTML: `<span class="hidden-input-content"></span>`,
buttonOnLeft: true,
clickAwayShouldSave: false,
});
this._telegram = this._row.querySelector(".accounts-telegram") as HTMLTableDataCellElement; this._telegram = this._row.querySelector(".accounts-telegram") as HTMLTableDataCellElement;
this._discord = this._row.querySelector(".accounts-discord") as HTMLTableDataCellElement; this._discord = this._row.querySelector(".accounts-discord") as HTMLTableDataCellElement;
this._matrix = this._row.querySelector(".accounts-matrix") as HTMLTableDataCellElement; this._matrix = this._row.querySelector(".accounts-matrix") as HTMLTableDataCellElement;
this._expiry = this._row.querySelector(".accounts-expiry") as HTMLTableDataCellElement; this._expiry = this._row.querySelector(".accounts-expiry") as HTMLTableDataCellElement;
this._lastActive = this._row.querySelector(".accounts-last-active") as HTMLTableDataCellElement; this._lastActive = this._row.querySelector(".accounts-last-active") as HTMLTableDataCellElement;
this._label = this._row.querySelector(".accounts-label-container") as HTMLInputElement; this._label = this._row.querySelector(".accounts-label-container") as HTMLInputElement;
this._labelEditButton = this._row.querySelector(".accounts-label-edit") as HTMLElement; this._labelEditor = new HiddenInputField({
container: this._label,
onSet: this._updateLabel,
customContainerHTML: `<span class="chip ~gray hidden-input-content"></span>`,
buttonOnLeft: true,
clickAwayShouldSave: false,
});
this._check.onchange = () => { this.selected = this._check.checked; } this._check.onchange = () => { this.selected = this._check.checked; }
if (window.jellyfinLogin) { if (window.jellyfinLogin) {
@ -573,66 +585,6 @@ class user implements User, SearchableItem {
this._notifyDropdown = this._constructDropdown(); this._notifyDropdown = this._constructDropdown();
const toggleEmailInput = () => {
if (this._emailEditButton.classList.contains("ri-edit-line")) {
this._email.innerHTML = emailEditor;
this._email.querySelector("input").value = this._emailAddress;
this._email.classList.remove("ml-2");
} else {
this._email.textContent = this._emailAddress;
this._email.classList.add("ml-2");
}
this._emailEditButton.classList.toggle("ri-check-line");
this._emailEditButton.classList.toggle("ri-edit-line");
};
const emailClickListener = (event: Event) => {
if (!(event.target instanceof HTMLElement && (this._email.contains(event.target) || this._emailEditButton.contains(event.target)))) {
toggleEmailInput();
this.email = this.email;
document.removeEventListener("click", emailClickListener);
}
};
this._emailEditButton.onclick = () => {
if (this._emailEditButton.classList.contains("ri-edit-line")) {
document.addEventListener('click', emailClickListener);
} else {
this._updateEmail();
document.removeEventListener('click', emailClickListener);
}
toggleEmailInput();
};
const toggleLabelInput = () => {
if (this._labelEditButton.classList.contains("ri-edit-line")) {
this._label.innerHTML = labelEditor;
const input = this._label.querySelector("input");
input.value = this._userLabel;
input.placeholder = window.lang.strings("label");
this._label.classList.remove("ml-2");
this._labelEditButton.classList.add("ri-check-line");
this._labelEditButton.classList.remove("ri-edit-line");
} else {
this._updateLabel();
this._email.classList.add("ml-2");
}
};
const labelClickListener = (event: Event) => {
if (!(event.target instanceof HTMLElement && (this._label.contains(event.target) || this._labelEditButton.contains(event.target)))) {
toggleLabelInput();
document.removeEventListener("click", labelClickListener);
}
};
this._labelEditButton.onclick = () => {
if (this._labelEditButton.classList.contains("ri-edit-line")) {
document.addEventListener('click', labelClickListener);
} else {
document.removeEventListener('click', labelClickListener);
}
toggleLabelInput();
};
this.update(user); this.update(user);
document.addEventListener("timefmt-change", () => { document.addEventListener("timefmt-change", () => {
@ -642,14 +594,12 @@ class user implements User, SearchableItem {
} }
private _updateLabel = () => { private _updateLabel = () => {
let oldLabel = this.label;
this.label = this._label.querySelector("input").value;
let send = {}; let send = {};
send[this.id] = this.label; send[this.id] = this._labelEditor.value;
_post("/users/labels", send, (req: XMLHttpRequest) => { _post("/users/labels", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status != 204) { if (req.status != 204) {
this.label = oldLabel; this.label = this._labelEditor.previous;
window.notifications.customError("labelChanged", window.lang.notif("errorUnknown")); window.notifications.customError("labelChanged", window.lang.notif("errorUnknown"));
} }
} }
@ -657,16 +607,14 @@ class user implements User, SearchableItem {
}; };
private _updateEmail = () => { private _updateEmail = () => {
let oldEmail = this.email;
this.email = this._email.querySelector("input").value;
let send = {}; let send = {};
send[this.id] = this.email; send[this.id] = this._emailEditor.value;
_post("/users/emails", send, (req: XMLHttpRequest) => { _post("/users/emails", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status == 200) { if (req.status == 200) {
window.notifications.customSuccess("emailChanged", window.lang.var("notifications", "changedEmailAddress", `"${this.name}"`)); window.notifications.customSuccess("emailChanged", window.lang.var("notifications", "changedEmailAddress", `"${this.name}"`));
} else { } else {
this.email = oldEmail; this.email = this._emailEditor.previous;
window.notifications.customError("emailChanged", window.lang.var("notifications", "errorChangedEmailAddress", `"${this.name}"`)); window.notifications.customError("emailChanged", window.lang.var("notifications", "errorChangedEmailAddress", `"${this.name}"`));
} }
} }

View File

@ -46,14 +46,16 @@ export class Activity implements activity, SearchableItem {
private _delete: HTMLElement; private _delete: HTMLElement;
private _ip: HTMLElement; private _ip: HTMLElement;
private _act: activity; private _act: activity;
private _urlBase: string = ((): string => { /* private _urlBase: string = ((): string => {
let link = window.location.href; let link = window.location.href;
for (let split of ["#", "?", "/activity"]) { for (let split of ["#", "?", "/activity"]) {
link = link.split(split)[0]; link = link.split(split)[0];
} }
if (link.slice(-1) != "/") { link += "/"; } if (link.slice(-1) != "/") { link += "/"; }
// FIXME: I should probably just be using window.URLBase, but incase thats not right, i'll put this warning here
if (link != window.URLBase) console.error(`URL Bases don't match: "${link}" != "${window.URLBase}"`);
return link; return link;
})(); })(); */
_genUserText = (): string => { _genUserText = (): string => {
return `<span class="font-medium">${this._act.username || this._act.user_id.substring(0, 5)}</span>`; return `<span class="font-medium">${this._act.username || this._act.user_id.substring(0, 5)}</span>`;
@ -64,17 +66,17 @@ export class Activity implements activity, SearchableItem {
} }
_genUserLink = (): string => { _genUserLink = (): string => {
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${this._urlBase}accounts?user=${this._act.user_id}">${this._genUserText()}</a>`; return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${window.URLBase}/accounts?user=${this._act.user_id}">${this._genUserText()}</a>`;
} }
_genSrcUserLink = (): string => { _genSrcUserLink = (): string => {
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${this._urlBase}accounts?user=${this._act.source}">${this._genSrcUserText()}</a>`; return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" href="${window.URLBase}/accounts?user=${this._act.source}">${this._genSrcUserText()}</a>`;
} }
private _renderInvText = (): string => { return `<span class="font-medium font-mono">${this.value || this.invite_code || "???"}</span>`; } private _renderInvText = (): string => { return `<span class="font-medium font-mono">${this.value || this.invite_code || "???"}</span>`; }
private _genInvLink = (): string => { private _genInvLink = (): string => {
return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" href="${this._urlBase}?invite=${this.invite_code}">${this._renderInvText()}</a>`; return `<a role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" href="${window.URLBase}/?invite=${this.invite_code}">${this._renderInvText()}</a>`;
} }

91
ts/modules/ui.ts Normal file
View File

@ -0,0 +1,91 @@
export interface HiddenInputConf {
container: HTMLElement;
onSet: () => void;
buttonOnLeft?: boolean;
customContainerHTML?: string;
input?: string;
clickAwayShouldSave?: boolean;
};
export class HiddenInputField {
public static editClass = "ri-edit-line";
public static saveClass = "ri-check-line";
private _c: HiddenInputConf;
private _input: HTMLInputElement;
private _content: HTMLElement
private _toggle: HTMLElement;
previous: string;
constructor(c: HiddenInputConf) {
this._c = c;
if (!(this._c.customContainerHTML)) {
this._c.customContainerHTML = `<span class="hidden-input-content"></span>`;
}
if (!(this._c.input)) {
this._c.input = `<input type="text" class="field ~neutral @low max-w-24 hidden-input-input">`;
}
this._c.container.innerHTML = `
<div class="flex flex-row gap-2 items-baseline">
${this._c.buttonOnLeft ? "" : this._c.input}
${this._c.buttonOnLeft ? "" : this._c.customContainerHTML}
<i class="hidden-input-toggle"></i>
${this._c.buttonOnLeft ? this._c.input : ""}
${this._c.buttonOnLeft ? this._c.customContainerHTML : ""}
</div>
`;
this._input = this._c.container.querySelector(".hidden-input-input") as HTMLInputElement;
this._input.classList.add("py-0.5", "px-1", "hidden");
this._toggle = this._c.container.querySelector(".hidden-input-toggle");
this._content = this._c.container.querySelector(".hidden-input-content");
this._toggle.onclick = () => {
this.editing = !this.editing;
};
this.setEditing(false, true);
}
// FIXME: not working
outerClickListener = ((event: Event) => {
if (!(event.target instanceof HTMLElement && (this._input.contains(event.target) || this._toggle.contains(event.target)))) {
this.toggle(!(this._c.clickAwayShouldSave));
}
}).bind(this);
get editing(): boolean { return this._toggle.classList.contains(HiddenInputField.saveClass); }
set editing(e: boolean) { this.setEditing(e); }
setEditing(e: boolean, noEvent: boolean = false, noSave: boolean = false) {
if (e) {
document.addEventListener("click", this.outerClickListener);
this.previous = this.value;
this._input.value = this.value;
this._toggle.classList.add(HiddenInputField.saveClass);
this._toggle.classList.remove(HiddenInputField.editClass);
this._input.classList.remove("hidden");
this._content.classList.add("hidden");
} else {
document.removeEventListener("click", this.outerClickListener);
this.value = noSave ? this.previous : this._input.value;
this._toggle.classList.add(HiddenInputField.editClass);
this._toggle.classList.remove(HiddenInputField.saveClass);
// done by set value()
// this._content.classList.remove("hidden");
this._input.classList.add("hidden");
if (this.value != this.previous && !noEvent && !noSave) this._c.onSet()
}
}
get value(): string { return this._content.textContent; };
set value(v: string) {
this._content.textContent = v;
this._input.value = v;
if (!v) this._content.classList.add("hidden");
else this._content.classList.remove("hidden");
}
toggle(noSave: boolean = false) { this.setEditing(!this.editing, false, noSave); }
}