mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-03-18 21:50:33 +01:00
tooltip: rework as pseudo-component, fix overflow
uses the <tool-tip> tag now, and the setupTooltips() function in ui.ts must be called at any point in the pages load. added "below-center" position, showing below and centered horizontally. breakpoint tailwind lingo also now works (e.g. "below-center sm:right"). If a left or right-aligned tooltip clips off the screen, it will be shifted the appropriate amount left/right to avoid that automatically.
This commit is contained in:
@@ -1,13 +1,15 @@
|
|||||||
.tooltip {
|
@layer components {
|
||||||
|
tool-tip {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip .content {
|
tool-tip .content {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
width: max-content;
|
||||||
max-width: 16rem;
|
max-width: 16rem;
|
||||||
min-width: 6rem;
|
/*min-width: 6rem;*/
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
@@ -21,14 +23,35 @@
|
|||||||
top: -1rem;
|
top: -1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.below .content {
|
tool-tip.above {
|
||||||
|
--tooltip-position: above;
|
||||||
|
}
|
||||||
|
tool-tip.below {
|
||||||
|
--tooltip-position: below;
|
||||||
|
}
|
||||||
|
tool-tip.below-center {
|
||||||
|
--tooltip-position: below-center;
|
||||||
|
}
|
||||||
|
tool-tip.left {
|
||||||
|
--tooltip-position: left;
|
||||||
|
}
|
||||||
|
tool-tip.right {
|
||||||
|
--tooltip-position: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
tool-tip.below .content {
|
||||||
top: calc(100% + 0.125rem);
|
top: calc(100% + 0.125rem);
|
||||||
left: 50%;
|
left: 50%;
|
||||||
right: 0;
|
right: 0;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.above .content {
|
tool-tip.below-center .content {
|
||||||
|
top: calc(100% + 0.125rem);
|
||||||
|
max-width: calc(100vw - 4rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
tool-tip.above .content {
|
||||||
top: unset;
|
top: unset;
|
||||||
bottom: calc(100% + 0.125rem);
|
bottom: calc(100% + 0.125rem);
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -36,36 +59,37 @@
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.darker .content {
|
tool-tip.darker .content {
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.right .content {
|
tool-tip.right .content {
|
||||||
left: 120%;
|
left: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.right:dir(rtl):not(.force-ltr) .content {
|
tool-tip.right:dir(rtl):not(.force-ltr) .content {
|
||||||
right: 120%;
|
right: 0%;
|
||||||
left: unset;
|
left: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.left .content {
|
tool-tip.left .content {
|
||||||
right: 120%;
|
right: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.left:dir(rtl):not(.force-ltr) .content {
|
tool-tip.left:dir(rtl):not(.force-ltr) .content {
|
||||||
left: 120%;
|
left: 0%;
|
||||||
right: unset;
|
right: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip .content.sm {
|
tool-tip .content.sm {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip:hover .content,
|
/*tool-tip:hover .content,
|
||||||
.tooltip:focus .content,
|
tool-tip:focus .content,
|
||||||
.tooltip:focus-within .content
|
tool-tip:focus-within .content*/
|
||||||
{
|
tool-tip.shown .content {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -229,10 +229,10 @@
|
|||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input type="checkbox" id="expiry-use-previous">
|
<input type="checkbox" id="expiry-use-previous">
|
||||||
<span>{{ .strings.extendFromPreviousExpiry }}</span>
|
<span>{{ .strings.extendFromPreviousExpiry }}</span>
|
||||||
<div class="tooltip left">
|
<tool-tip class="left">
|
||||||
<i class="icon ri-information-line align-middle"></i>
|
<i class="icon ri-information-line align-middle"></i>
|
||||||
<div class="content sm w-max">{{ .strings.extendFromPreviousExpiryDescription }}</div>
|
<div class="content sm w-max">{{ .strings.extendFromPreviousExpiryDescription }}</div>
|
||||||
</div>
|
</tool-tip>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
@@ -547,7 +547,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="notification-box"></div>
|
<div id="notification-box"></div>
|
||||||
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4 overflow-x-hidden">
|
<div class="page-container m-2 lg:my-20 lg:mx-64 flex flex-col gap-4">
|
||||||
<div class="top-2 inset-x-2 lg:absolute flex flex-row justify-between">
|
<div class="top-2 inset-x-2 lg:absolute flex flex-row justify-between">
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
{{ template "lang-select.html" . }}
|
{{ template "lang-select.html" . }}
|
||||||
@@ -718,13 +718,13 @@
|
|||||||
<div class="flex flex-row align-middle w-full gap-2">
|
<div class="flex flex-row align-middle w-full gap-2">
|
||||||
<input type="search" class="field ~neutral @low input search" id="accounts-search" placeholder="{{ .strings.search }}">
|
<input type="search" class="field ~neutral @low input search" id="accounts-search" placeholder="{{ .strings.search }}">
|
||||||
<span class="button ~neutral @low center inside-input rounded-s-none accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
<span class="button ~neutral @low center inside-input rounded-s-none accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||||
<div class="tooltip left">
|
<tool-tip class="below">
|
||||||
<button class="button ~info @low center h-full accounts-search-server gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
<button class="button ~info @low center h-full accounts-search-server gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||||
<i class="ri-search-line"></i>
|
<i class="ri-search-line"></i>
|
||||||
<span>{{ .strings.searchAll }}</span>
|
<span>{{ .strings.searchAll }}</span>
|
||||||
</button>
|
</button>
|
||||||
<span class="content sm">{{ .strings.searchAllRecords }}</span>
|
<span class="content sm">{{ .strings.searchAllRecords }}</span>
|
||||||
</div>
|
</tool-tip>
|
||||||
<button class="button ~info @low" id="accounts-refresh" aria-label="{{ .strings.refresh }}" disabled><i class="ri-refresh-line"></i></button>
|
<button class="button ~info @low" id="accounts-refresh" aria-label="{{ .strings.refresh }}" disabled><i class="ri-refresh-line"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -828,13 +828,13 @@
|
|||||||
<div class="flex flex-row align-middle w-full gap-2">
|
<div class="flex flex-row align-middle w-full gap-2">
|
||||||
<input type="search" class="field ~neutral @low input search" id="activity-search" placeholder="{{ .strings.search }}">
|
<input type="search" class="field ~neutral @low input search" id="activity-search" placeholder="{{ .strings.search }}">
|
||||||
<span class="button ~neutral @low center inside-input rounded-s-none activity-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
<span class="button ~neutral @low center inside-input rounded-s-none activity-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||||
<div class="tooltip left">
|
<tool-tip class="below">
|
||||||
<button class="button ~info @low center h-full activity-search-server gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
<button class="button ~info @low center h-full activity-search-server gap-1" aria-label="{{ .strings.searchAllRecords }}" text="{{ .strings.searchAllRecords }}">
|
||||||
<i class="ri-search-line"></i>
|
<i class="ri-search-line"></i>
|
||||||
<span>{{ .strings.searchAll }}</span>
|
<span>{{ .strings.searchAll }}</span>
|
||||||
</button>
|
</button>
|
||||||
<span class="content sm">{{ .strings.searchAllRecords }}</span>
|
<span class="content sm">{{ .strings.searchAllRecords }}</span>
|
||||||
</div>
|
</tool-tip>
|
||||||
<button class="button ~info @low" id="activity-refresh" aria-label="{{ .strings.refresh }}" disabled><i class="ri-refresh-line"></i></button>
|
<button class="button ~info @low" id="activity-refresh" aria-label="{{ .strings.refresh }}" disabled><i class="ri-refresh-line"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ import { ProfileEditor, reloadProfileNames } from "./modules/profiles.js";
|
|||||||
import { _get, _post, notificationBox, whichAnimationEvent, bindManualDropdowns } from "./modules/common.js";
|
import { _get, _post, notificationBox, whichAnimationEvent, bindManualDropdowns } from "./modules/common.js";
|
||||||
import { Updater } from "./modules/update.js";
|
import { Updater } from "./modules/update.js";
|
||||||
import { Login } from "./modules/login.js";
|
import { Login } from "./modules/login.js";
|
||||||
|
import { setupTooltips } from "./modules/ui.js";
|
||||||
|
|
||||||
declare var window: GlobalWindow;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
|
setupTooltips();
|
||||||
|
|
||||||
const theme = new ThemeManager(document.getElementById("button-theme"));
|
const theme = new ThemeManager(document.getElementById("button-theme"));
|
||||||
|
|
||||||
window.lang = new lang(window.langFile as LangFile);
|
window.lang = new lang(window.langFile as LangFile);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { loadLangSelector } from "./modules/lang.js";
|
|||||||
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
||||||
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
|
||||||
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
|
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
|
||||||
|
import { setupTooltips } from "./modules/ui.js";
|
||||||
|
|
||||||
interface formWindow extends GlobalWindow {
|
interface formWindow extends GlobalWindow {
|
||||||
invalidPassword: string;
|
invalidPassword: string;
|
||||||
@@ -42,6 +43,8 @@ interface formWindow extends GlobalWindow {
|
|||||||
collectEmail: boolean;
|
collectEmail: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupTooltips();
|
||||||
|
|
||||||
loadLangSelector("form");
|
loadLangSelector("form");
|
||||||
|
|
||||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement);
|
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement);
|
||||||
|
|||||||
@@ -1106,7 +1106,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
itemsPerPage: 40,
|
itemsPerPage: 40,
|
||||||
maxItemsLoadedForSearch: 200,
|
maxItemsLoadedForSearch: 200,
|
||||||
appendNewItems: (resp: PaginatedDTO) => {
|
appendNewItems: (resp: PaginatedDTO) => {
|
||||||
console.log("append");
|
// console.log("append");
|
||||||
for (let u of (resp as UsersDTO).users || []) {
|
for (let u of (resp as UsersDTO).users || []) {
|
||||||
if (this.users.has(u.id)) {
|
if (this.users.has(u.id)) {
|
||||||
this.users.get(u.id).update(u);
|
this.users.get(u.id).update(u);
|
||||||
@@ -1122,7 +1122,7 @@ export class accountsList extends PaginatedList implements Navigatable, AsTab {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
replaceWithNewItems: (resp: PaginatedDTO) => {
|
replaceWithNewItems: (resp: PaginatedDTO) => {
|
||||||
console.log("replace");
|
// console.log("replace");
|
||||||
let accountsOnDOM = new Map<string, boolean>();
|
let accountsOnDOM = new Map<string, boolean>();
|
||||||
|
|
||||||
for (let id of this.users.keys()) {
|
for (let id of this.users.keys()) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from "../modules/common.js";
|
} from "../modules/common.js";
|
||||||
import { DiscordSearch, DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
import { DiscordSearch, DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
||||||
import { reloadProfileNames } from "../modules/profiles.js";
|
import { reloadProfileNames } from "../modules/profiles.js";
|
||||||
import { HiddenInputField, RadioBasedTabSelector } from "./ui.js";
|
import { HiddenInputField, RadioBasedTabSelector, Tooltip } from "./ui.js";
|
||||||
|
|
||||||
declare var window: GlobalWindow;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
@@ -205,10 +205,9 @@ class DOMInvite implements Invite {
|
|||||||
}
|
}
|
||||||
set send_to(address: string | null) {
|
set send_to(address: string | null) {
|
||||||
this._send_to = address;
|
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 icon = container.querySelector("i");
|
||||||
const chip = container.querySelector("span.inv-email-chip");
|
const chip = container.querySelector("span.inv-email-chip");
|
||||||
const tooltip = container.querySelector("span.content") as HTMLSpanElement;
|
|
||||||
if (!address) {
|
if (!address) {
|
||||||
icon.classList.remove("ri-mail-line");
|
icon.classList.remove("ri-mail-line");
|
||||||
icon.classList.remove("ri-mail-close-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.
|
// innerHTML as the newer sent_to re-uses this with HTML.
|
||||||
tooltip.innerHTML = address;
|
container.content.innerHTML = address;
|
||||||
}
|
}
|
||||||
private _sendToDialog: SendToDialog;
|
private _sendToDialog: SendToDialog;
|
||||||
private _sent_to: SentToList;
|
private _sent_to: SentToList;
|
||||||
@@ -603,10 +602,10 @@ class DOMInvite implements Invite {
|
|||||||
this._header.appendChild(this._infoArea);
|
this._header.appendChild(this._infoArea);
|
||||||
this._infoArea.classList.add("inv-infoarea", "flex", "flex-row", "items-center", "gap-2");
|
this._infoArea.classList.add("inv-infoarea", "flex", "flex-row", "items-center", "gap-2");
|
||||||
this._infoArea.innerHTML = `
|
this._infoArea.innerHTML = `
|
||||||
<div class="tooltip below darker" tabindex="0">
|
<tool-tip class="below darker" tabindex="0">
|
||||||
<span class="inv-email-chip h-full"><i></i></span>
|
<span class="inv-email-chip h-full"><i></i></span>
|
||||||
<span class="content sm p-1"></span>
|
<span class="content sm p-1"></span>
|
||||||
</div>
|
</tool-tip>
|
||||||
<span class="button ~critical @low inv-delete h-full">${window.lang.strings("delete")}</span>
|
<span class="button ~critical @low inv-delete h-full">${window.lang.strings("delete")}</span>
|
||||||
<label>
|
<label>
|
||||||
<i class="icon px-2.5 py-2 ri-arrow-down-s-line text-xl not-rotated"></i>
|
<i class="icon px-2.5 py-2 ri-arrow-down-s-line text-xl not-rotated"></i>
|
||||||
|
|||||||
@@ -751,7 +751,7 @@ export class Search implements Navigatable {
|
|||||||
triggerManually = !url.searchParams.has("search");
|
triggerManually = !url.searchParams.has("search");
|
||||||
url.searchParams.delete("search");
|
url.searchParams.delete("search");
|
||||||
}
|
}
|
||||||
console.log("pushing", url.toString());
|
// console.log("pushing", url.toString());
|
||||||
window.history.pushState(null, "", url.toString());
|
window.history.pushState(null, "", url.toString());
|
||||||
if (triggerManually) this.navigate();
|
if (triggerManually) this.navigate();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "../modules/common.js";
|
} from "../modules/common.js";
|
||||||
import { Marked } from "@ts-stack/markdown";
|
import { Marked } from "@ts-stack/markdown";
|
||||||
import { stripMarkdown } from "../modules/stripmd.js";
|
import { stripMarkdown } from "../modules/stripmd.js";
|
||||||
|
import { Tooltip } from "./ui.js";
|
||||||
|
|
||||||
declare var window: GlobalWindow;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ class DOMSetting {
|
|||||||
protected _hideEl: HTMLElement;
|
protected _hideEl: HTMLElement;
|
||||||
protected _input: HTMLInputElement;
|
protected _input: HTMLInputElement;
|
||||||
protected _container: HTMLDivElement;
|
protected _container: HTMLDivElement;
|
||||||
protected _tooltip: HTMLDivElement;
|
protected _tooltip: Tooltip;
|
||||||
protected _required: HTMLSpanElement;
|
protected _required: HTMLSpanElement;
|
||||||
protected _restart: HTMLSpanElement;
|
protected _restart: HTMLSpanElement;
|
||||||
protected _advanced: boolean;
|
protected _advanced: boolean;
|
||||||
@@ -154,11 +155,10 @@ class DOMSetting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get description(): string {
|
get description(): string {
|
||||||
return this._tooltip.querySelector("span.content").textContent;
|
return this._tooltip.content.textContent;
|
||||||
}
|
}
|
||||||
set description(d: string) {
|
set description(d: string) {
|
||||||
const content = this._tooltip.querySelector("span.content") as HTMLSpanElement;
|
this._tooltip.content.textContent = d;
|
||||||
content.textContent = d;
|
|
||||||
if (d == "") {
|
if (d == "") {
|
||||||
this._tooltip.classList.add("unfocused");
|
this._tooltip.classList.add("unfocused");
|
||||||
} else {
|
} else {
|
||||||
@@ -248,17 +248,17 @@ class DOMSetting {
|
|||||||
${inputOnTop ? input : ""}
|
${inputOnTop ? input : ""}
|
||||||
<div class="flex flex-row gap-2 items-baseline">
|
<div class="flex flex-row gap-2 items-baseline">
|
||||||
<span class="setting-label"></span>
|
<span class="setting-label"></span>
|
||||||
<div class="setting-tooltip tooltip right unfocused">
|
<tool-tip class="setting-tooltip below-center sm:right unfocused">
|
||||||
<i class="icon ri-information-line align-[-0.05rem]"></i>
|
<i class="icon ri-information-line align-[-0.05rem]"></i>
|
||||||
<span class="content sm"></span>
|
<span class="content sm"></span>
|
||||||
</div>
|
</tool-tip>
|
||||||
<span class="setting-required unfocused"></span>
|
<span class="setting-required unfocused"></span>
|
||||||
<span class="setting-restart unfocused"></span>
|
<span class="setting-restart unfocused"></span>
|
||||||
</div>
|
</div>
|
||||||
${inputOnTop ? "" : input}
|
${inputOnTop ? "" : input}
|
||||||
</label>
|
</label>
|
||||||
`;
|
`;
|
||||||
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._required = this._container.querySelector("span.setting-required") as HTMLSpanElement;
|
||||||
this._restart = this._container.querySelector("span.setting-restart") 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"
|
// "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)
|
// Create (restart)required badges (can't do on load as window.lang is unset)
|
||||||
RestartRequiredBadge = (() => {
|
RestartRequiredBadge = (() => {
|
||||||
const rr = document.createElement("span");
|
const rr = document.createElement("tool-tip") as Tooltip;
|
||||||
rr.classList.add("tooltip", "below", "force-ltr");
|
rr.classList.add("below", "force-ltr");
|
||||||
rr.innerHTML = `
|
rr.innerHTML = `
|
||||||
<span class="badge ~info dark:~d_warning align-[0.08rem]"><i class="icon ri-refresh-line h-full"></i></span>
|
<span class="badge ~info dark:~d_warning align-[0.08rem]"><i class="icon ri-refresh-line h-full"></i></span>
|
||||||
<span class="content sm">${window.lang.strings("restartRequired")}</span>
|
<span class="content sm">${window.lang.strings("restartRequired")}</span>
|
||||||
@@ -1416,8 +1416,8 @@ export class settingsList implements AsTab {
|
|||||||
return rr;
|
return rr;
|
||||||
})();
|
})();
|
||||||
RequiredBadge = (() => {
|
RequiredBadge = (() => {
|
||||||
const r = document.createElement("span");
|
const r = document.createElement("tool-tip");
|
||||||
r.classList.add("tooltip", "below", "force-ltr");
|
r.classList.add("below", "force-ltr");
|
||||||
r.innerHTML = `
|
r.innerHTML = `
|
||||||
<span class="badge ~critical align-[0.08rem]"><i class="icon ri-asterisk h-full"></i></span>
|
<span class="badge ~critical align-[0.08rem]"><i class="icon ri-asterisk h-full"></i></span>
|
||||||
<span class="content sm">${window.lang.strings("required")}</span>
|
<span class="content sm">${window.lang.strings("required")}</span>
|
||||||
@@ -1490,8 +1490,8 @@ export class settingsList implements AsTab {
|
|||||||
this._sections[section.section].update(section);
|
this._sections[section.section].update(section);
|
||||||
} else {
|
} else {
|
||||||
if (section.section == "messages" || section.section == "user_page") {
|
if (section.section == "messages" || section.section == "user_page") {
|
||||||
const editButton = document.createElement("div");
|
const editButton = document.createElement("tool-tip");
|
||||||
editButton.classList.add("tooltip", "left", "h-full", "force-ltr");
|
editButton.classList.add("left", "h-full", "force-ltr");
|
||||||
editButton.innerHTML = `
|
editButton.innerHTML = `
|
||||||
<span class="button ~neutral @low h-full">
|
<span class="button ~neutral @low h-full">
|
||||||
<i class="icon ri-edit-line"></i>
|
<i class="icon ri-edit-line"></i>
|
||||||
@@ -1528,8 +1528,8 @@ export class settingsList implements AsTab {
|
|||||||
}
|
}
|
||||||
this.addSection(section.section, section, icon);
|
this.addSection(section.section, section, icon);
|
||||||
} else if (section.section == "matrix" && !window.matrixEnabled) {
|
} else if (section.section == "matrix" && !window.matrixEnabled) {
|
||||||
const addButton = document.createElement("div");
|
const addButton = document.createElement("tool-tip");
|
||||||
addButton.classList.add("tooltip", "left", "h-full", "force-ltr");
|
addButton.classList.add("left", "h-full", "force-ltr");
|
||||||
addButton.innerHTML = `
|
addButton.innerHTML = `
|
||||||
<span class="button ~neutral h-full"><i class="icon ri-links-line"></i></span>
|
<span class="button ~neutral h-full"><i class="icon ri-links-line"></i></span>
|
||||||
<span class="content sm">
|
<span class="content sm">
|
||||||
@@ -1912,10 +1912,10 @@ class MessageEditor {
|
|||||||
`;
|
`;
|
||||||
if (this._names[id].description != "")
|
if (this._names[id].description != "")
|
||||||
innerHTML += `
|
innerHTML += `
|
||||||
<div class="tooltip right">
|
<tool-tip class="right">
|
||||||
<i class="icon ri-information-line"></i>
|
<i class="icon ri-information-line"></i>
|
||||||
<span class="content sm">${this._names[id].description}</span>
|
<span class="content sm">${this._names[id].description}</span>
|
||||||
</div>
|
</tool-tip>
|
||||||
`;
|
`;
|
||||||
innerHTML += `
|
innerHTML += `
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
127
ts/modules/ui.ts
127
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)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { toClipboard, notificationBox } from "./modules/common.js";
|
import { toClipboard, notificationBox } from "./modules/common.js";
|
||||||
|
import { setupTooltips } from "./modules/ui.js";
|
||||||
|
|
||||||
declare var window: GlobalWindow;
|
declare var window: GlobalWindow;
|
||||||
|
|
||||||
|
setupTooltips();
|
||||||
|
|
||||||
const pin = document.getElementById("pin") as HTMLSpanElement;
|
const pin = document.getElementById("pin") as HTMLSpanElement;
|
||||||
|
|
||||||
if (pin) {
|
if (pin) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Validator, ValidatorConf } from "./modules/validator.js";
|
|||||||
import { _post, addLoader, removeLoader } from "./modules/common.js";
|
import { _post, addLoader, removeLoader } from "./modules/common.js";
|
||||||
import { loadLangSelector } from "./modules/lang.js";
|
import { loadLangSelector } from "./modules/lang.js";
|
||||||
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
|
import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
|
||||||
|
import { setupTooltips } from "./modules/ui.js";
|
||||||
|
|
||||||
interface formWindow extends Window {
|
interface formWindow extends Window {
|
||||||
invalidPassword: string;
|
invalidPassword: string;
|
||||||
@@ -39,6 +40,8 @@ loadLangSelector("pwr");
|
|||||||
|
|
||||||
declare var window: formWindow;
|
declare var window: formWindow;
|
||||||
|
|
||||||
|
setupTooltips();
|
||||||
|
|
||||||
const form = document.getElementById("form-create") as HTMLFormElement;
|
const form = document.getElementById("form-create") as HTMLFormElement;
|
||||||
const submitInput = form.querySelector("input[type=submit]") as HTMLInputElement;
|
const submitInput = form.querySelector("input[type=submit]") as HTMLInputElement;
|
||||||
const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
|
const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { _get, _post, toggleLoader, notificationBox } from "./modules/common.js"
|
|||||||
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
||||||
import { ThemeManager } from "./modules/theme.js";
|
import { ThemeManager } from "./modules/theme.js";
|
||||||
import { PageManager } from "./modules/pages.js";
|
import { PageManager } from "./modules/pages.js";
|
||||||
|
import { setupTooltips } from "./modules/ui.js";
|
||||||
|
|
||||||
interface sWindow extends GlobalWindow {
|
interface sWindow extends GlobalWindow {
|
||||||
messages: {};
|
messages: {};
|
||||||
@@ -9,6 +10,8 @@ interface sWindow extends GlobalWindow {
|
|||||||
|
|
||||||
declare var window: sWindow;
|
declare var window: sWindow;
|
||||||
|
|
||||||
|
setupTooltips();
|
||||||
|
|
||||||
const theme = new ThemeManager(document.getElementById("button-theme"));
|
const theme = new ThemeManager(document.getElementById("button-theme"));
|
||||||
|
|
||||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement, 5);
|
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement, 5);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration }
|
|||||||
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
|
||||||
import { PageManager } from "./modules/pages.js";
|
import { PageManager } from "./modules/pages.js";
|
||||||
import { generateCodeLink } from "./modules/invites.js";
|
import { generateCodeLink } from "./modules/invites.js";
|
||||||
|
import { setupTooltips } from "./modules/ui.js";
|
||||||
|
|
||||||
interface userWindow extends GlobalWindow {
|
interface userWindow extends GlobalWindow {
|
||||||
jellyfinID: string;
|
jellyfinID: string;
|
||||||
@@ -34,6 +35,8 @@ interface userWindow extends GlobalWindow {
|
|||||||
|
|
||||||
declare var window: userWindow;
|
declare var window: userWindow;
|
||||||
|
|
||||||
|
setupTooltips();
|
||||||
|
|
||||||
// const basePath = window.location.pathname.replace("/password/reset", "");
|
// const basePath = window.location.pathname.replace("/password/reset", "");
|
||||||
const basePath = window.pages.Base + window.pages.MyAccount;
|
const basePath = window.pages.Base + window.pages.MyAccount;
|
||||||
|
|
||||||
@@ -757,7 +760,7 @@ document.addEventListener("details-reload", () => {
|
|||||||
}
|
}
|
||||||
if (!messageCard.textContent) {
|
if (!messageCard.textContent) {
|
||||||
messageCard.innerHTML = `
|
messageCard.innerHTML = `
|
||||||
<span class="heading mb-2">${window.lang.strings("customMessagePlaceholderHeader")} ✏️ </span>
|
<span class="heading mb-2">${window.lang.strings("customMessagePlaceholderHeader")} ✏ </span>
|
||||||
<span class="block">${window.lang.strings("customMessagePlaceholderContent")}</span>
|
<span class="block">${window.lang.strings("customMessagePlaceholderContent")}</span>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user