diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json
index 795c343..20b00ea 100644
--- a/lang/admin/en-us.json
+++ b/lang/admin/en-us.json
@@ -186,6 +186,7 @@
"accountConnected": "Account connected.",
"referralsEnabled": "Referrals enabled.",
"activityDeleted": "Activity Deleted.",
+ "errorInviteDoesntExist": "Invite no longer exists.",
"errorSettingsAppliedNoHomescreenLayout": "Settings were applied, but applying homescreen layout may have failed.",
"errorHomescreenAppliedNoSettings": "Homescreen layout was applied, but applying settings may have failed.",
"errorSettingsFailed": "Application failed.",
diff --git a/router.go b/router.go
index 016c060..a50acab 100644
--- a/router.go
+++ b/router.go
@@ -119,6 +119,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
router.GET(p+"/accounts", 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+"/token/login", app.getTokenLogin)
router.GET(p+"/token/refresh", app.getTokenRefresh)
diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts
index 3fa73fa..f2850b3 100644
--- a/ts/modules/accounts.ts
+++ b/ts/modules/accounts.ts
@@ -74,6 +74,8 @@ class user implements User, SearchableItem {
private _referralsEnabled: boolean;
private _referralsEnabledCheck: HTMLElement;
+ focus = () => this._row.scrollIntoView({ behavior: "smooth", block: "center" });
+
lastNotifyMethod = (): string => {
// Telegram, Matrix, Discord
const telegram = window.telegramEnabled && this._telegramUsername && this._telegramUsername != "";
@@ -1678,6 +1680,14 @@ export class accountsList {
this._addUserProfile.innerHTML = innerHTML;
}
+ public static readonly _accountURLEvent = "account-url";
+ registerURLListener = () => document.addEventListener(accountsList._accountURLEvent, (event: CustomEvent) => {
+ const userID = event.detail;
+ this._searchBox.value = `id:"${userID}"`;
+ this._search.onSearchBoxChange();
+ this._users[userID].focus();
+ });
+
constructor() {
this._populateNumbers();
this._users = {};
@@ -1885,6 +1895,8 @@ export class accountsList {
this.showHideSearchOptionsHeader();
this._search.generateFilterList();
+
+ this.registerURLListener();
}
reload = () => {
@@ -1927,6 +1939,8 @@ export class accountsList {
}
}
+export const accountURLEvent = (id: string) => { return new CustomEvent(accountsList._accountURLEvent, {"detail": id}) };
+
type GetterReturnType = Boolean | boolean | String | Number | number;
type Getter = () => GetterReturnType;
diff --git a/ts/modules/activity.ts b/ts/modules/activity.ts
index e6c6e58..392d005 100644
--- a/ts/modules/activity.ts
+++ b/ts/modules/activity.ts
@@ -1,5 +1,7 @@
import { _post, _delete, toDateString, addLoader, removeLoader } from "../modules/common.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 {
id: string;
@@ -42,6 +44,14 @@ export class Activity implements activity, SearchableItem {
private _expiryTypeBadge: HTMLElement;
private _delete: HTMLElement;
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 => {
return `${this._act.username || this._act.user_id.substring(0, 5)}`;
@@ -52,17 +62,17 @@ export class Activity implements activity, SearchableItem {
}
_genUserLink = (): string => {
- return `${this._genUserText()}`;
+ return `${this._genUserText()}`;
}
_genSrcUserLink = (): string => {
- return `${this._genSrcUserText()}`;
+ return `${this._genSrcUserText()}`;
}
private _renderInvText = (): string => { return `${this.value || this.invite_code || "???"}`; }
private _genInvLink = (): string => {
- return `${this._renderInvText()}`;
+ return `${this._renderInvText()}`;
}
@@ -278,6 +288,30 @@ export class Activity implements activity, SearchableItem {
this._delete.addEventListener("click", this.delete);
this.update(act);
+
+ const pseudoUsers = this._card.getElementsByClassName("activity-pseudo-link-user") as HTMLCollectionOf;
+ const pseudoInvites = this._card.getElementsByClassName("activity-pseudo-link-invite") as HTMLCollectionOf;
+
+ 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) => {
diff --git a/ts/modules/invites.ts b/ts/modules/invites.ts
index c7dc04c..fb52be4 100644
--- a/ts/modules/invites.ts
+++ b/ts/modules/invites.ts
@@ -261,6 +261,8 @@ class DOMInvite implements Invite {
}
}
+ focus = () => this._container.scrollIntoView({ behavior: "smooth", block: "center" });
+
constructor(invite: Invite) {
// first create the invite structure, then use our setter methods to fill in the data.
this._container = document.createElement('div') as HTMLDivElement;
@@ -423,6 +425,16 @@ export class inviteList implements inviteList {
invites: { [code: string]: DOMInvite };
+ public static readonly _inviteURLEvent = "invite-url";
+ registerURLListener = () => document.addEventListener(inviteList._inviteURLEvent, (event: CustomEvent) => {
+ const inviteCode = event.detail;
+ 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", window.lang.notif("errorInviteDoesntExist"));
+ })
+
constructor() {
this._list = document.getElementById('invites') as HTMLDivElement;
this.empty = true;
@@ -436,6 +448,8 @@ export class inviteList implements inviteList {
this.empty = true;
}
}, false);
+
+ this.registerURLListener();
}
get empty(): boolean { return this._empty; }
@@ -500,7 +514,8 @@ export class inviteList implements inviteList {
}
})
}
-
+
+export const inviteURLEvent = (id: string) => { return new CustomEvent(inviteList._inviteURLEvent, {"detail": id}) };
function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean }): Invite {
let parsed: Invite = {};