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 = {};