{{ template "lang-select.html" . }}
@@ -718,13 +718,13 @@
-
+
{{ .strings.searchAllRecords }}
-
+
@@ -828,13 +828,13 @@
-
+
{{ .strings.searchAllRecords }}
-
+
diff --git a/ts/admin.ts b/ts/admin.ts
index 202e216..9008441 100644
--- a/ts/admin.ts
+++ b/ts/admin.ts
@@ -10,9 +10,12 @@ import { ProfileEditor, reloadProfileNames } from "./modules/profiles.js";
import { _get, _post, notificationBox, whichAnimationEvent, bindManualDropdowns } from "./modules/common.js";
import { Updater } from "./modules/update.js";
import { Login } from "./modules/login.js";
+import { setupTooltips } from "./modules/ui.js";
declare var window: GlobalWindow;
+setupTooltips();
+
const theme = new ThemeManager(document.getElementById("button-theme"));
window.lang = new lang(window.langFile as LangFile);
diff --git a/ts/form.ts b/ts/form.ts
index 6743a7f..229c155 100644
--- a/ts/form.ts
+++ b/ts/form.ts
@@ -5,6 +5,7 @@ import { loadLangSelector } from "./modules/lang.js";
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
+import { setupTooltips } from "./modules/ui.js";
interface formWindow extends GlobalWindow {
invalidPassword: string;
@@ -42,6 +43,8 @@ interface formWindow extends GlobalWindow {
collectEmail: boolean;
}
+setupTooltips();
+
loadLangSelector("form");
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement);
diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts
index b6fa83f..2235937 100644
--- a/ts/modules/accounts.ts
+++ b/ts/modules/accounts.ts
@@ -1106,7 +1106,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
itemsPerPage: 40,
maxItemsLoadedForSearch: 200,
appendNewItems: (resp: PaginatedDTO) => {
- console.log("append");
+ // console.log("append");
for (let u of (resp as UsersDTO).users || []) {
if (this.users.has(u.id)) {
this.users.get(u.id).update(u);
@@ -1122,7 +1122,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
);
},
replaceWithNewItems: (resp: PaginatedDTO) => {
- console.log("replace");
+ // console.log("replace");
let accountsOnDOM = new Map
();
for (let id of this.users.keys()) {
diff --git a/ts/modules/invites.ts b/ts/modules/invites.ts
index 017c95a..3c4e6df 100644
--- a/ts/modules/invites.ts
+++ b/ts/modules/invites.ts
@@ -13,7 +13,7 @@ import {
} from "../modules/common.js";
import { DiscordSearch, DiscordUser, newDiscordSearch } from "../modules/discord.js";
import { reloadProfileNames } from "../modules/profiles.js";
-import { HiddenInputField, RadioBasedTabSelector } from "./ui.js";
+import { HiddenInputField, RadioBasedTabSelector, Tooltip } from "./ui.js";
declare var window: GlobalWindow;
@@ -205,10 +205,9 @@ class DOMInvite implements Invite {
}
set send_to(address: string | null) {
this._send_to = address;
- const container = this._infoArea.querySelector(".tooltip") as HTMLDivElement;
+ const container = this._infoArea.querySelector("tool-tip") as Tooltip;
const icon = container.querySelector("i");
const chip = container.querySelector("span.inv-email-chip");
- const tooltip = container.querySelector("span.content") as HTMLSpanElement;
if (!address) {
icon.classList.remove("ri-mail-line");
icon.classList.remove("ri-mail-close-line");
@@ -232,7 +231,7 @@ class DOMInvite implements Invite {
}
}
// innerHTML as the newer sent_to re-uses this with HTML.
- tooltip.innerHTML = address;
+ container.content.innerHTML = address;
}
private _sendToDialog: SendToDialog;
private _sent_to: SentToList;
@@ -603,10 +602,10 @@ class DOMInvite implements Invite {
this._header.appendChild(this._infoArea);
this._infoArea.classList.add("inv-infoarea", "flex", "flex-row", "items-center", "gap-2");
this._infoArea.innerHTML = `
-
+
-
+
${window.lang.strings("delete")}
`;
- this._tooltip = this._container.querySelector("div.setting-tooltip") as HTMLDivElement;
+ this._tooltip = this._container.querySelector("tool-tip.setting-tooltip") as Tooltip;
this._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
this._restart = this._container.querySelector("span.setting-restart") as HTMLSpanElement;
// "input" variable should supply the HTML of an element with class "setting-input"
@@ -1406,8 +1406,8 @@ export class settingsList implements AsTab {
// Create (restart)required badges (can't do on load as window.lang is unset)
RestartRequiredBadge = (() => {
- const rr = document.createElement("span");
- rr.classList.add("tooltip", "below", "force-ltr");
+ const rr = document.createElement("tool-tip") as Tooltip;
+ rr.classList.add("below", "force-ltr");
rr.innerHTML = `
${window.lang.strings("restartRequired")}
@@ -1416,8 +1416,8 @@ export class settingsList implements AsTab {
return rr;
})();
RequiredBadge = (() => {
- const r = document.createElement("span");
- r.classList.add("tooltip", "below", "force-ltr");
+ const r = document.createElement("tool-tip");
+ r.classList.add("below", "force-ltr");
r.innerHTML = `
${window.lang.strings("required")}
@@ -1490,8 +1490,8 @@ export class settingsList implements AsTab {
this._sections[section.section].update(section);
} else {
if (section.section == "messages" || section.section == "user_page") {
- const editButton = document.createElement("div");
- editButton.classList.add("tooltip", "left", "h-full", "force-ltr");
+ const editButton = document.createElement("tool-tip");
+ editButton.classList.add("left", "h-full", "force-ltr");
editButton.innerHTML = `
@@ -1528,8 +1528,8 @@ export class settingsList implements AsTab {
}
this.addSection(section.section, section, icon);
} else if (section.section == "matrix" && !window.matrixEnabled) {
- const addButton = document.createElement("div");
- addButton.classList.add("tooltip", "left", "h-full", "force-ltr");
+ const addButton = document.createElement("tool-tip");
+ addButton.classList.add("left", "h-full", "force-ltr");
addButton.innerHTML = `
@@ -1912,10 +1912,10 @@ class MessageEditor {
`;
if (this._names[id].description != "")
innerHTML += `
-
+
${this._names[id].description}
-
+
`;
innerHTML += `
diff --git a/ts/modules/ui.ts b/ts/modules/ui.ts
index 2c6f1f0..c572956 100644
--- a/ts/modules/ui.ts
+++ b/ts/modules/ui.ts
@@ -209,3 +209,130 @@ export class RadioBasedTabSelector {
}
}
}
+
+type TooltipPosition = "above" | "below" | "below-center" | "left" | "right";
+
+export class Tooltip extends HTMLElement {
+ private _content: HTMLElement;
+ get content(): HTMLElement {
+ if (!this._content) return this.getElementsByClassName("content")[0] as HTMLElement;
+ return this._content;
+ }
+
+ connectedCallback() {
+ this.setup();
+ }
+
+ get visible(): boolean {
+ return this.classList.contains("shown");
+ }
+
+ get position(): TooltipPosition {
+ return window.getComputedStyle(this).getPropertyValue("--tooltip-position").trim() as TooltipPosition;
+ }
+
+ toggle() {
+ console.log("toggle!");
+ this.visible ? this.close() : this.open();
+ }
+
+ clicked: boolean = false;
+
+ private _listener = (event: MouseEvent | TouchEvent) => {
+ if (event.target !== this && !this.contains(event.target as HTMLElement)) {
+ this.close();
+ document.removeEventListener("mousedown", this._listener);
+ // document.removeEventListener("touchstart", this._listener);
+ }
+ };
+
+ open() {
+ this.fixWidth(() => {
+ this.classList.add("shown");
+ if (this.clicked) {
+ document.addEventListener("mousedown", this._listener);
+ // document.addEventListener("touchstart", this._listener);
+ }
+ });
+ }
+
+ close() {
+ this.clicked = false;
+ this.classList.remove("shown");
+ }
+
+ setup() {
+ this._content = this.getElementsByClassName("content")[0] as HTMLElement;
+ const clickEvent = () => {
+ if (this.clicked) {
+ console.log("clicked again!");
+ this.toggle();
+ } else {
+ console.log("clicked!");
+ this.clicked = true;
+ this.open();
+ }
+ };
+ /// this.addEventListener("touchstart", clickEvent);
+ this.addEventListener("click", clickEvent);
+ this.addEventListener("mouseover", () => {
+ this.open();
+ });
+ this.addEventListener("mouseleave", () => {
+ if (this.clicked) return;
+ console.log("mouseleave");
+ this.close();
+ });
+ }
+
+ fixWidth(after?: () => void) {
+ this._content.style.left = "";
+ this._content.style.right = "";
+ if (this.position == "below-center") {
+ const offset = this.offsetLeft;
+ const pw = (this.offsetParent as HTMLElement).offsetWidth;
+ const cw = this._content.offsetWidth;
+ const pos = -1 * offset + (pw - cw) / 2.0;
+ this._content.style.left = pos + "px";
+ }
+ const [leftObscured, rightObscured] = wherePartiallyObscuredX(this._content);
+ if (rightObscured) {
+ const rect = this._content.getBoundingClientRect();
+ this._content.style.left =
+ "calc(-1rem + " + ((window.innerWidth || document.documentElement.clientHeight) - rect.right) + "px)";
+ }
+ if (leftObscured) {
+ const rect = this._content.getBoundingClientRect();
+ this._content.style.right = "calc(-1rem + " + rect.left + "px)";
+ "calc(-1rem + " + ((window.innerWidth || document.documentElement.clientHeight) - rect.right) + "px)";
+ }
+ if (after) after();
+ }
+}
+
+export function setupTooltips() {
+ customElements.define("tool-tip", Tooltip);
+}
+
+export function isPartiallyObscuredX(el: HTMLElement): boolean {
+ const rect = el.getBoundingClientRect();
+ return rect.left < 0 || rect.right > (window.innerWidth || document.documentElement.clientWidth);
+}
+
+export function wherePartiallyObscuredX(el: HTMLElement): [boolean, boolean] {
+ const rect = el.getBoundingClientRect();
+ return [Boolean(rect.left < 0), Boolean(rect.right > (window.innerWidth || document.documentElement.clientWidth))];
+}
+
+export function isPartiallyObscuredY(el: HTMLElement): boolean {
+ const rect = el.getBoundingClientRect();
+ return rect.top < 0 || rect.bottom > (window.innerHeight || document.documentElement.clientHeight);
+}
+
+export function wherePartiallyObscuredY(el: HTMLElement): [boolean, boolean] {
+ const rect = el.getBoundingClientRect();
+ return [
+ Boolean(rect.top < 0),
+ Boolean(rect.bottom > (window.innerHeight || document.documentElement.clientHeight)),
+ ];
+}
diff --git a/ts/pwr-pin.ts b/ts/pwr-pin.ts
index a274a58..fa6a4e3 100644
--- a/ts/pwr-pin.ts
+++ b/ts/pwr-pin.ts
@@ -1,7 +1,10 @@
import { toClipboard, notificationBox } from "./modules/common.js";
+import { setupTooltips } from "./modules/ui.js";
declare var window: GlobalWindow;
+setupTooltips();
+
const pin = document.getElementById("pin") as HTMLSpanElement;
if (pin) {
diff --git a/ts/pwr.ts b/ts/pwr.ts
index daec4d2..2f3646b 100644
--- a/ts/pwr.ts
+++ b/ts/pwr.ts
@@ -3,6 +3,7 @@ import { Validator, ValidatorConf } from "./modules/validator.js";
import { _post, addLoader, removeLoader } from "./modules/common.js";
import { loadLangSelector } from "./modules/lang.js";
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
+import { setupTooltips } from "./modules/ui.js";
interface formWindow extends Window {
invalidPassword: string;
@@ -39,6 +40,8 @@ loadLangSelector("pwr");
declare var window: formWindow;
+setupTooltips();
+
const form = document.getElementById("form-create") as HTMLFormElement;
const submitInput = form.querySelector("input[type=submit]") as HTMLInputElement;
const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
diff --git a/ts/setup.ts b/ts/setup.ts
index a07eeae..07ba847 100644
--- a/ts/setup.ts
+++ b/ts/setup.ts
@@ -2,6 +2,7 @@ import { _get, _post, toggleLoader, notificationBox } from "./modules/common.js"
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
import { ThemeManager } from "./modules/theme.js";
import { PageManager } from "./modules/pages.js";
+import { setupTooltips } from "./modules/ui.js";
interface sWindow extends GlobalWindow {
messages: {};
@@ -9,6 +10,8 @@ interface sWindow extends GlobalWindow {
declare var window: sWindow;
+setupTooltips();
+
const theme = new ThemeManager(document.getElementById("button-theme"));
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement, 5);
diff --git a/ts/user.ts b/ts/user.ts
index 91a6e48..7c0339c 100644
--- a/ts/user.ts
+++ b/ts/user.ts
@@ -17,6 +17,7 @@ import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration }
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
import { PageManager } from "./modules/pages.js";
import { generateCodeLink } from "./modules/invites.js";
+import { setupTooltips } from "./modules/ui.js";
interface userWindow extends GlobalWindow {
jellyfinID: string;
@@ -34,6 +35,8 @@ interface userWindow extends GlobalWindow {
declare var window: userWindow;
+setupTooltips();
+
// const basePath = window.location.pathname.replace("/password/reset", "");
const basePath = window.pages.Base + window.pages.MyAccount;
@@ -757,7 +760,7 @@ document.addEventListener("details-reload", () => {
}
if (!messageCard.textContent) {
messageCard.innerHTML = `
- ${window.lang.strings("customMessagePlaceholderHeader")} ✏️
+ ${window.lang.strings("customMessagePlaceholderHeader")} ✏
${window.lang.strings("customMessagePlaceholderContent")}
`;
}