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

Compare commits

...

4 Commits

11 changed files with 203 additions and 29 deletions

View File

@ -748,13 +748,18 @@
<div class="unfocused h-[100%] my-3" id="activity-not-found"> <div class="unfocused h-[100%] my-3" id="activity-not-found">
<div class="flex flex-col h-[100%] justify-center items-center"> <div class="flex flex-col h-[100%] justify-center items-center">
<span class="text-2xl font-medium italic mb-3">{{ .strings.noResultsFound }}</span> <span class="text-2xl font-medium italic mb-3">{{ .strings.noResultsFound }}</span>
<button class="button ~neutral @low activity-search-clear"> <span class="text-xl font-medium italic mb-3 unfocused" id="activity-keep-searching-description">{{ .strings.keepSearchingDescription }}</span>
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i> <div class="flex flex-row">
</button> <button class="button ~neutral @low activity-search-clear">
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
</button>
<button class="button ~neutral @low unfocused" id="activity-keep-searching">{{ .strings.keepSearching }}</button>
</div>
</div> </div>
</div> </div>
<div class="flex justify-center"> <div class="flex justify-center">
<button class="button my-2 ~neutral @low" id="activity-load-more">{{ .strings.loadMore }}</button> <button class="button m-2 ~neutral @low" id="activity-load-more">{{ .strings.loadMore }}</button>
<button class="button m-2 ~neutral @low" id="activity-load-all">{{ .strings.loadAll }}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -59,6 +59,8 @@
"disabled": "Disabled", "disabled": "Disabled",
"sendPWR": "Send Password Reset", "sendPWR": "Send Password Reset",
"noResultsFound": "No Results Found", "noResultsFound": "No Results Found",
"keepSearching": "Keep Searching",
"keepSearchingDescription": "Only the current loaded activities were searched. Click below if you wish to search all activities.",
"contactThrough": "Contact through:", "contactThrough": "Contact through:",
"extendExpiry": "Extend expiry", "extendExpiry": "Extend expiry",
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.", "sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
@ -169,6 +171,7 @@
"inviteCreatedFilter": "Invite Created", "inviteCreatedFilter": "Invite Created",
"inviteDeletedFilter": "Invite Deleted/Expired", "inviteDeletedFilter": "Invite Deleted/Expired",
"loadMore": "Load More", "loadMore": "Load More",
"loadAll": "Load All",
"noMoreResults": "No more results." "noMoreResults": "No more results."
}, },
"notifications": { "notifications": {
@ -186,6 +189,8 @@
"accountConnected": "Account connected.", "accountConnected": "Account connected.",
"referralsEnabled": "Referrals enabled.", "referralsEnabled": "Referrals enabled.",
"activityDeleted": "Activity Deleted.", "activityDeleted": "Activity Deleted.",
"errorInviteNoLongerExists": "Invite no longer exists.",
"errorInviteNotFound": "Invite not found.",
"errorSettingsAppliedNoHomescreenLayout": "Settings were applied, but applying homescreen layout may have failed.", "errorSettingsAppliedNoHomescreenLayout": "Settings were applied, but applying homescreen layout may have failed.",
"errorHomescreenAppliedNoSettings": "Homescreen layout was applied, but applying settings may have failed.", "errorHomescreenAppliedNoSettings": "Homescreen layout was applied, but applying settings may have failed.",
"errorSettingsFailed": "Application failed.", "errorSettingsFailed": "Application failed.",

View File

@ -118,6 +118,9 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
router.GET(p+"/accounts", app.AdminPage) router.GET(p+"/accounts", app.AdminPage)
router.GET(p+"/settings", app.AdminPage) router.GET(p+"/settings", app.AdminPage)
router.GET(p+"/activity", app.AdminPage)
router.GET(p+"/accounts/user/:userID", app.AdminPage)
router.GET(p+"/invites/:code", app.AdminPage)
router.GET(p+"/lang/:page/:file", app.ServeLang) router.GET(p+"/lang/:page/:file", app.ServeLang)
router.GET(p+"/token/login", app.getTokenLogin) router.GET(p+"/token/login", app.getTokenLogin)
router.GET(p+"/token/refresh", app.getTokenRefresh) router.GET(p+"/token/refresh", app.getTokenRefresh)

View File

@ -144,6 +144,9 @@ for (let tab of tabs) {
} }
} }
let isInviteURL = window.invites.isInviteURL();
let isAccountURL = accounts.isAccountURL();
// Default tab // Default tab
if ((window.URLBase + "/").includes(window.location.pathname)) { if ((window.URLBase + "/").includes(window.location.pathname)) {
window.tabs.switch(defaultTab.url, true); window.tabs.switch(defaultTab.url, true);
@ -153,7 +156,9 @@ document.addEventListener("tab-change", (event: CustomEvent) => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const lang = urlParams.get('lang'); const lang = urlParams.get('lang');
let tab = window.URLBase + "/" + event.detail; let tab = window.URLBase + "/" + event.detail;
if (tab == window.URLBase + "/invites") { if (event.detail == "") {
tab = window.location.pathname;
} else if (tab == window.URLBase + "/invites") {
if (window.location.pathname == window.URLBase + "/") { if (window.location.pathname == window.URLBase + "/") {
tab = window.URLBase + "/"; tab = window.URLBase + "/";
} else if (window.URLBase) { tab = window.URLBase; } } else if (window.URLBase) { tab = window.URLBase; }
@ -190,6 +195,20 @@ login.onLogin = () => {
case "activity": // FIXME: fix URL clash with route case "activity": // FIXME: fix URL clash with route
activity.reload(); activity.reload();
break; break;
default:
console.log(isAccountURL, isInviteURL);
if (isInviteURL) {
window.invites.reload(() => {
window.invites.loadInviteURL();
window.tabs.switch("invites", false, true);
});
} else if (isAccountURL) {
accounts.reload(() => {
accounts.loadAccountURL();
window.tabs.switch("accounts", false, true);
});
}
break;
} }
} }

View File

@ -74,6 +74,8 @@ class user implements User, SearchableItem {
private _referralsEnabled: boolean; private _referralsEnabled: boolean;
private _referralsEnabledCheck: HTMLElement; private _referralsEnabledCheck: HTMLElement;
focus = () => this._row.scrollIntoView({ behavior: "smooth", block: "center" });
lastNotifyMethod = (): string => { lastNotifyMethod = (): string => {
// Telegram, Matrix, Discord // Telegram, Matrix, Discord
const telegram = window.telegramEnabled && this._telegramUsername && this._telegramUsername != ""; const telegram = window.telegramEnabled && this._telegramUsername && this._telegramUsername != "";
@ -1678,6 +1680,25 @@ export class accountsList {
this._addUserProfile.innerHTML = innerHTML; this._addUserProfile.innerHTML = innerHTML;
} }
focusAccount = (userID: string) => {
console.log("focusing user", userID);
this._searchBox.value = `id:"${userID}"`;
this._search.onSearchBoxChange();
if (userID in this._users) this._users[userID].focus();
}
public static readonly _accountURLEvent = "account-url";
registerURLListener = () => document.addEventListener(accountsList._accountURLEvent, (event: CustomEvent) => {
this.focusAccount(event.detail);
});
isAccountURL = () => { return window.location.pathname.startsWith(window.URLBase + "/accounts/user/"); }
loadAccountURL = () => {
let userID = window.location.pathname.split(window.URLBase + "/accounts/user/")[1].split("?lang")[0];
this.focusAccount(userID);
}
constructor() { constructor() {
this._populateNumbers(); this._populateNumbers();
this._users = {}; this._users = {};
@ -1685,7 +1706,7 @@ export class accountsList {
this._selectAll.onchange = () => { this._selectAll.onchange = () => {
this.selectAll = this._selectAll.checked; this.selectAll = this._selectAll.checked;
}; };
document.addEventListener("accounts-reload", this.reload); document.addEventListener("accounts-reload", () => this.reload());
document.addEventListener("accountCheckEvent", () => { this._checkCount++; this._checkCheckCount(); }); document.addEventListener("accountCheckEvent", () => { this._checkCount++; this._checkCheckCount(); });
document.addEventListener("accountUncheckEvent", () => { this._checkCount--; this._checkCheckCount(); }); document.addEventListener("accountUncheckEvent", () => { this._checkCount--; this._checkCheckCount(); });
this._addUserButton.onclick = () => { this._addUserButton.onclick = () => {
@ -1808,7 +1829,7 @@ export class accountsList {
queries: this._queries, queries: this._queries,
setVisibility: this.setVisibility, setVisibility: this.setVisibility,
clearSearchButtonSelector: ".accounts-search-clear", clearSearchButtonSelector: ".accounts-search-clear",
onSearchCallback: (_0: number, _1: boolean) => { onSearchCallback: (_0: number, _1: boolean, _2: boolean) => {
this._checkCheckCount(); this._checkCheckCount();
} }
}; };
@ -1885,9 +1906,11 @@ export class accountsList {
this.showHideSearchOptionsHeader(); this.showHideSearchOptionsHeader();
this._search.generateFilterList(); this._search.generateFilterList();
this.registerURLListener();
} }
reload = () => { reload = (callback?: () => void) => {
_get("/users", null, (req: XMLHttpRequest) => { _get("/users", null, (req: XMLHttpRequest) => {
if (req.readyState == 4 && req.status == 200) { if (req.readyState == 4 && req.status == 200) {
// same method as inviteList.reload() // same method as inviteList.reload()
@ -1921,12 +1944,16 @@ export class accountsList {
this.setVisibility(results, true); this.setVisibility(results, true);
} }
this._checkCheckCount(); this._checkCheckCount();
if (callback) callback();
} }
}); });
this.loadTemplates(); this.loadTemplates();
} }
} }
export const accountURLEvent = (id: string) => { return new CustomEvent(accountsList._accountURLEvent, {"detail": id}) };
type GetterReturnType = Boolean | boolean | String | Number | number; type GetterReturnType = Boolean | boolean | String | Number | number;
type Getter = () => GetterReturnType; type Getter = () => GetterReturnType;

View File

@ -1,5 +1,7 @@
import { _post, _delete, toDateString, addLoader, removeLoader } from "../modules/common.js"; import { _post, _delete, toDateString, addLoader, removeLoader } from "../modules/common.js";
import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js"; import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
import { accountURLEvent } from "../modules/accounts.js";
import { inviteURLEvent } from "../modules/invites.js";
export interface activity { export interface activity {
id: string; id: string;
@ -42,6 +44,14 @@ export class Activity implements activity, SearchableItem {
private _expiryTypeBadge: HTMLElement; private _expiryTypeBadge: HTMLElement;
private _delete: HTMLElement; private _delete: HTMLElement;
private _act: activity; private _act: activity;
private _urlBase: string = ((): string => {
let link = window.location.href;
for (let split of ["#", "?", "/activity"]) {
link = link.split(split)[0];
}
if (link.slice(-1) != "/") { 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>`;
@ -52,17 +62,17 @@ export class Activity implements activity, SearchableItem {
} }
_genUserLink = (): string => { _genUserLink = (): string => {
return `<a class="hover:underline" href="/accounts/user/${this._act.user_id}">${this._genUserText()}</a>`; return `<span role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" data-href="${this._urlBase}accounts/user/${this._act.user_id}">${this._genUserText()}</span>`;
} }
_genSrcUserLink = (): string => { _genSrcUserLink = (): string => {
return `<a class="hover:underline" href="/accounts/user/${this._act.source}">${this._genSrcUserText()}</a>`; return `<span role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-user" data-id="${this._act.user_id}" data-href="${this._urlBase}accounts/user/${this._act.source}">${this._genSrcUserText()}</span>`;
} }
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 class="hover:underline" href="/accounts/invites/${this.invite_code}">${this._renderInvText()}</a>`; return `<span role="link" tabindex="0" class="hover:underline cursor-pointer activity-pseudo-link-invite" data-id="${this.invite_code}" data-href="${this._urlBase}invites/${this.invite_code}">${this._renderInvText()}</span>`;
} }
@ -278,6 +288,30 @@ export class Activity implements activity, SearchableItem {
this._delete.addEventListener("click", this.delete); this._delete.addEventListener("click", this.delete);
this.update(act); this.update(act);
const pseudoUsers = this._card.getElementsByClassName("activity-pseudo-link-user") as HTMLCollectionOf<HTMLAnchorElement>;
const pseudoInvites = this._card.getElementsByClassName("activity-pseudo-link-invite") as HTMLCollectionOf<HTMLAnchorElement>;
for (let i = 0; i < pseudoUsers.length; i++) {
const navigate = (event: Event) => {
event.preventDefault()
window.tabs.switch("accounts");
document.dispatchEvent(accountURLEvent(pseudoUsers[i].getAttribute("data-id")));
window.history.pushState(null, document.title, pseudoUsers[i].getAttribute("data-href"));
}
pseudoUsers[i].onclick = navigate;
pseudoUsers[i].onkeydown = navigate;
}
for (let i = 0; i < pseudoInvites.length; i++) {
const navigate = (event: Event) => {
event.preventDefault();
window.tabs.switch("invites");
document.dispatchEvent(inviteURLEvent(pseudoInvites[i].getAttribute("data-id")));
window.history.pushState(null, document.title, pseudoInvites[i].getAttribute("data-href"));
}
pseudoInvites[i].onclick = navigate;
pseudoInvites[i].onkeydown = navigate;
}
} }
update = (act: activity) => { update = (act: activity) => {
@ -318,7 +352,11 @@ export class activityList {
private _sortDirection = document.getElementById("activity-sort-direction") as HTMLButtonElement; private _sortDirection = document.getElementById("activity-sort-direction") as HTMLButtonElement;
private _loader = document.getElementById("activity-loader"); private _loader = document.getElementById("activity-loader");
private _loadMoreButton = document.getElementById("activity-load-more") as HTMLButtonElement; private _loadMoreButton = document.getElementById("activity-load-more") as HTMLButtonElement;
private _loadAllButton = document.getElementById("activity-load-all") as HTMLButtonElement;
private _refreshButton = document.getElementById("activity-refresh") as HTMLButtonElement; private _refreshButton = document.getElementById("activity-refresh") as HTMLButtonElement;
private _keepSearchingDescription = document.getElementById("activity-keep-searching-description");
private _keepSearchingButton = document.getElementById("activity-keep-searching");
private _search: Search; private _search: Search;
private _ascending: boolean; private _ascending: boolean;
private _hasLoaded: boolean; private _hasLoaded: boolean;
@ -341,6 +379,10 @@ export class activityList {
reload = () => { reload = () => {
this._lastLoad = Date.now(); this._lastLoad = Date.now();
this._lastPage = false; this._lastPage = false;
this._loadMoreButton.textContent = window.lang.strings("loadMore");
this._loadMoreButton.disabled = false;
this._loadAllButton.classList.remove("unfocused");
this._loadAllButton.disabled = false;
// this._page = 0; // this._page = 0;
let limit = 10; let limit = 10;
if (this._page != 0) { if (this._page != 0) {
@ -380,17 +422,23 @@ export class activityList {
if (this._search.inSearch) { if (this._search.inSearch) {
this._search.onSearchBoxChange(true); this._search.onSearchBoxChange(true);
this._loadAllButton.classList.remove("unfocused");
} else { } else {
this.setVisibility(this._ordering, true); this.setVisibility(this._ordering, true);
this._loadAllButton.classList.add("unfocused");
this._notFoundPanel.classList.add("unfocused"); this._notFoundPanel.classList.add("unfocused");
} }
}, true); }, true);
} }
loadMore = () => { loadMore = (callback?: () => void, loadAll: boolean = false) => {
this._lastLoad = Date.now(); this._lastLoad = Date.now();
this._loadMoreButton.disabled = true; this._loadMoreButton.disabled = true;
const timeout = setTimeout(() => this._loadMoreButton.disabled = false, 1000); // this._loadAllButton.disabled = true;
const timeout = setTimeout(() => {
this._loadMoreButton.disabled = false;
// this._loadAllButton.disabled = false;
}, 1000);
this._page += 1; this._page += 1;
let send = { let send = {
@ -416,6 +464,8 @@ export class activityList {
if (this._lastPage) { if (this._lastPage) {
clearTimeout(timeout); clearTimeout(timeout);
this._loadMoreButton.disabled = true; this._loadMoreButton.disabled = true;
removeLoader(this._loadAllButton);
this._loadAllButton.classList.add("unfocused");
this._loadMoreButton.textContent = window.lang.strings("noMoreResults"); this._loadMoreButton.textContent = window.lang.strings("noMoreResults");
} }
@ -426,12 +476,17 @@ export class activityList {
// this._search.items = this._activities; // this._search.items = this._activities;
// this._search.ordering = this._ordering; // this._search.ordering = this._ordering;
if (this._search.inSearch) { if (this._search.inSearch || loadAll) {
this._search.onSearchBoxChange(true); if (this._lastPage) {
loadAll = false;
}
this._search.onSearchBoxChange(true, loadAll);
} else { } else {
this.setVisibility(this._ordering, true); this.setVisibility(this._ordering, true);
this._notFoundPanel.classList.add("unfocused"); this._notFoundPanel.classList.add("unfocused");
} }
if (callback) callback();
// removeLoader(this._loader); // removeLoader(this._loader);
// this._activityList.classList.remove("unfocused"); // this._activityList.classList.remove("unfocused");
}, true); }, true);
@ -563,17 +618,31 @@ export class activityList {
} }
detectScroll = () => { detectScroll = () => {
if (!this._hasLoaded) return;
// console.log(window.innerHeight + document.documentElement.scrollTop, document.scrollingElement.scrollHeight); // console.log(window.innerHeight + document.documentElement.scrollTop, document.scrollingElement.scrollHeight);
if (Math.abs(window.innerHeight + document.documentElement.scrollTop - document.scrollingElement.scrollHeight) < 50) { if (Math.abs(window.innerHeight + document.documentElement.scrollTop - document.scrollingElement.scrollHeight) < 50) {
// window.notifications.customSuccess("scroll", "Reached bottom."); // window.notifications.customSuccess("scroll", "Reached bottom.");
// Wait 1s between loads // Wait .5s between loads
if (this._lastLoad + 1000 > Date.now()) return; if (this._lastLoad + 500 > Date.now()) return;
this.loadMore(); this.loadMore();
} }
} }
private _prevResultCount = 0; private _prevResultCount = 0;
private _notFoundCallback = (notFound: boolean) => {
if (notFound) this._loadMoreButton.classList.add("unfocused");
else this._loadMoreButton.classList.remove("unfocused");
if (notFound && !this._lastPage) {
this._keepSearchingButton.classList.remove("unfocused");
this._keepSearchingDescription.classList.remove("unfocused");
} else {
this._keepSearchingButton.classList.add("unfocused");
this._keepSearchingDescription.classList.add("unfocused");
}
};
constructor() { constructor() {
this._activityList = document.getElementById("activity-card-list"); this._activityList = document.getElementById("activity-card-list");
document.addEventListener("activity-reload", this.reload); document.addEventListener("activity-reload", this.reload);
@ -588,10 +657,13 @@ export class activityList {
queries: this._queries, queries: this._queries,
setVisibility: this.setVisibility, setVisibility: this.setVisibility,
filterList: document.getElementById("activity-filter-list"), filterList: document.getElementById("activity-filter-list"),
onSearchCallback: (visibleCount: number, newItems: boolean) => { // notFoundCallback: this._notFoundCallback,
onSearchCallback: (visibleCount: number, newItems: boolean, loadAll: boolean) => {
if (this._search.inSearch && !this._lastPage) this._loadAllButton.classList.remove("unfocused");
else this._loadAllButton.classList.add("unfocused");
if (visibleCount < 10) { if (visibleCount < 10) {
if (!newItems || this._prevResultCount != visibleCount || (visibleCount == 0 && !this._lastPage)) this.loadMore(); if (!newItems || this._prevResultCount != visibleCount || (visibleCount == 0 && !this._lastPage) || loadAll) this.loadMore(() => {}, loadAll);
} }
this._prevResultCount = visibleCount; this._prevResultCount = visibleCount;
} }
@ -603,7 +675,15 @@ export class activityList {
this.ascending = false; this.ascending = false;
this._sortDirection.addEventListener("click", () => this.ascending = !this.ascending); this._sortDirection.addEventListener("click", () => this.ascending = !this.ascending);
this._loadMoreButton.onclick = this.loadMore; this._loadMoreButton.onclick = () => this.loadMore();
this._loadAllButton.onclick = () => {
addLoader(this._loadAllButton, true);
this.loadMore(() => {}, true);
};
/* this._keepSearchingButton.onclick = () => {
addLoader(this._keepSearchingButton, true);
this.loadMore(() => removeLoader(this._keepSearchingButton, true));
}; */
this._refreshButton.onclick = this.reload; this._refreshButton.onclick = this.reload;
window.onscroll = this.detectScroll; window.onscroll = this.detectScroll;

View File

@ -261,6 +261,8 @@ class DOMInvite implements Invite {
} }
} }
focus = () => this._container.scrollIntoView({ behavior: "smooth", block: "center" });
constructor(invite: Invite) { constructor(invite: Invite) {
// first create the invite structure, then use our setter methods to fill in the data. // first create the invite structure, then use our setter methods to fill in the data.
this._container = document.createElement('div') as HTMLDivElement; this._container = document.createElement('div') as HTMLDivElement;
@ -423,6 +425,26 @@ export class inviteList implements inviteList {
invites: { [code: string]: DOMInvite }; invites: { [code: string]: DOMInvite };
focusInvite = (inviteCode: string, errorMsg: string = window.lang.notif("errorInviteNoLongerExists")) => {
for (let code of Object.keys(this.invites)) {
this.invites[code].expanded = code == inviteCode;
}
if (inviteCode in this.invites) this.invites[inviteCode].focus();
else window.notifications.customError("inviteDoesntExistError", errorMsg);
};
public static readonly _inviteURLEvent = "invite-url";
registerURLListener = () => document.addEventListener(inviteList._inviteURLEvent, (event: CustomEvent) => {
this.focusInvite(event.detail);
})
isInviteURL = () => { return window.location.pathname.startsWith(window.URLBase + "/invites/"); }
loadInviteURL = () => {
let inviteCode = window.location.pathname.split(window.URLBase + "/invites/")[1].split("?lang")[0];
this.focusInvite(inviteCode, window.lang.notif("errorInviteNotFound"));
}
constructor() { constructor() {
this._list = document.getElementById('invites') as HTMLDivElement; this._list = document.getElementById('invites') as HTMLDivElement;
this.empty = true; this.empty = true;
@ -436,6 +458,8 @@ export class inviteList implements inviteList {
this.empty = true; this.empty = true;
} }
}, false); }, false);
this.registerURLListener();
} }
get empty(): boolean { return this._empty; } get empty(): boolean { return this._empty; }
@ -468,7 +492,7 @@ export class inviteList implements inviteList {
this._list.appendChild(domInv.asElement()); this._list.appendChild(domInv.asElement());
} }
reload = () => _get("/invites", null, (req: XMLHttpRequest) => { reload = (callback?: () => void) => _get("/invites", null, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
let data = req.response; let data = req.response;
if (req.status == 200) { if (req.status == 200) {
@ -497,10 +521,13 @@ export class inviteList implements inviteList {
this.invites[code].remove(); this.invites[code].remove();
delete this.invites[code]; delete this.invites[code];
} }
if (callback) callback();
} }
}) })
} }
export const inviteURLEvent = (id: string) => { return new CustomEvent(inviteList._inviteURLEvent, {"detail": id}) };
function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean }): Invite { function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean }): Invite {
let parsed: Invite = {}; let parsed: Invite = {};

View File

@ -16,12 +16,13 @@ export interface SearchConfiguration {
sortingByButton: HTMLButtonElement; sortingByButton: HTMLButtonElement;
searchOptionsHeader: HTMLElement; searchOptionsHeader: HTMLElement;
notFoundPanel: HTMLElement; notFoundPanel: HTMLElement;
notFoundCallback?: (notFound: boolean) => void;
filterList: HTMLElement; filterList: HTMLElement;
clearSearchButtonSelector: string; clearSearchButtonSelector: string;
search: HTMLInputElement; search: HTMLInputElement;
queries: { [field: string]: QueryType }; queries: { [field: string]: QueryType };
setVisibility: (items: string[], visible: boolean) => void; setVisibility: (items: string[], visible: boolean) => void;
onSearchCallback: (visibleCount: number, newItems: boolean) => void; onSearchCallback: (visibleCount: number, newItems: boolean, loadAll: boolean) => void;
loadMore?: () => void; loadMore?: () => void;
} }
@ -268,7 +269,7 @@ export class Search {
get ordering(): string[] { return this._ordering; } get ordering(): string[] { return this._ordering; }
set ordering(v: string[]) { this._ordering = v; } set ordering(v: string[]) { this._ordering = v; }
onSearchBoxChange = (newItems: boolean = false) => { onSearchBoxChange = (newItems: boolean = false, loadAll: boolean = false) => {
const query = this._c.search.value; const query = this._c.search.value;
if (!query) { if (!query) {
this.inSearch = false; this.inSearch = false;
@ -277,13 +278,14 @@ export class Search {
} }
const results = this.search(query); const results = this.search(query);
this._c.setVisibility(results, true); this._c.setVisibility(results, true);
this._c.onSearchCallback(results.length, newItems); this._c.onSearchCallback(results.length, newItems, loadAll);
this.showHideSearchOptionsHeader(); this.showHideSearchOptionsHeader();
if (results.length == 0) { if (results.length == 0) {
this._c.notFoundPanel.classList.remove("unfocused"); this._c.notFoundPanel.classList.remove("unfocused");
} else { } else {
this._c.notFoundPanel.classList.add("unfocused"); this._c.notFoundPanel.classList.add("unfocused");
} }
if (this._c.notFoundCallback) this._c.notFoundCallback(results.length == 0);
} }
fillInFilter = (name: string, value: string, offset?: number) => { fillInFilter = (name: string, value: string, offset?: number) => {

View File

@ -20,7 +20,7 @@ export class Tabs implements Tabs {
get current(): string { return this._current; } get current(): string { return this._current; }
set current(tabID: string) { this.switch(tabID); } set current(tabID: string) { this.switch(tabID); }
switch = (tabID: string, noRun: boolean = false) => { switch = (tabID: string, noRun: boolean = false, keepURL: boolean = false) => {
this._current = tabID; this._current = tabID;
for (let t of this.tabs) { for (let t of this.tabs) {
if (t.tabID == tabID) { if (t.tabID == tabID) {
@ -28,7 +28,7 @@ export class Tabs implements Tabs {
if (t.preFunc && !noRun) { t.preFunc(); } if (t.preFunc && !noRun) { t.preFunc(); }
t.tabEl.classList.remove("unfocused"); t.tabEl.classList.remove("unfocused");
if (t.postFunc && !noRun) { t.postFunc(); } if (t.postFunc && !noRun) { t.postFunc(); }
document.dispatchEvent(new CustomEvent("tab-change", { detail: tabID })); document.dispatchEvent(new CustomEvent("tab-change", { detail: keepURL ? "" : tabID }));
} else { } else {
t.buttonEl.classList.remove("active"); t.buttonEl.classList.remove("active");
t.buttonEl.classList.remove("~urge"); t.buttonEl.classList.remove("~urge");

View File

@ -80,7 +80,7 @@ declare interface Tabs {
current: string; current: string;
tabs: Array<Tab>; tabs: Array<Tab>;
addTab: (tabID: string, preFunc?: () => void, postFunc?: () => void) => void; addTab: (tabID: string, preFunc?: () => void, postFunc?: () => void) => void;
switch: (tabID: string, noRun?: boolean) => void; switch: (tabID: string, noRun?: boolean, keepURL?: boolean) => void;
} }
declare interface Tab { declare interface Tab {
@ -139,7 +139,9 @@ interface inviteList {
empty: boolean; empty: boolean;
invites: { [code: string]: Invite } invites: { [code: string]: Invite }
add: (invite: Invite) => void; add: (invite: Invite) => void;
reload: () => void; reload: (callback?: () => void) => void;
isInviteURL: () => boolean;
loadInviteURL: () => void;
} }
// Finally added to typescript, dont need this anymore. // Finally added to typescript, dont need this anymore.

View File

@ -39,6 +39,10 @@ func (app *appContext) loadCSSHeader() string {
func (app *appContext) getURLBase(gc *gin.Context) string { func (app *appContext) getURLBase(gc *gin.Context) string {
if strings.HasPrefix(gc.Request.URL.String(), app.URLBase) { if strings.HasPrefix(gc.Request.URL.String(), app.URLBase) {
// Hack to fix the common URL base /accounts
if app.URLBase == "/accounts" && strings.HasPrefix(gc.Request.URL.String(), "/accounts/user/") {
return ""
}
return app.URLBase return app.URLBase
} }
return "" return ""