add scrolling
This commit is contained in:
@@ -22,6 +22,7 @@ const scriptsLink = document.getElementById("scripts-link");
|
|||||||
const pluginsLink = document.getElementById("plugins-link");
|
const pluginsLink = document.getElementById("plugins-link");
|
||||||
const rawServerListCheckbox = document.getElementById("raw-server-list");
|
const rawServerListCheckbox = document.getElementById("raw-server-list");
|
||||||
const serverUrlInput = document.getElementById("server-url");
|
const serverUrlInput = document.getElementById("server-url");
|
||||||
|
const serverUrlCurrentBtn = document.getElementById("server-url-current-btn");
|
||||||
const solMemoCheckbox = document.getElementById("sol-memo");
|
const solMemoCheckbox = document.getElementById("sol-memo");
|
||||||
const solSettings = document.getElementById("sol-settings");
|
const solSettings = document.getElementById("sol-settings");
|
||||||
const profileSelect = document.getElementById("build-profile-select");
|
const profileSelect = document.getElementById("build-profile-select");
|
||||||
@@ -70,13 +71,25 @@ async function loadServerVersion() {
|
|||||||
|
|
||||||
function getDefaultServerUrlPlaceholder(isRawList) {
|
function getDefaultServerUrlPlaceholder(isRawList) {
|
||||||
if (isRawList) {
|
if (isRawList) {
|
||||||
const isHttps = window.location.protocol === "https:";
|
return getCurrentRawServerListUrl();
|
||||||
const host = window.location.host;
|
|
||||||
return `${isHttps ? "https" : "http"}://${host}/list.txt`;
|
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCurrentServerHost() {
|
||||||
|
return window.location.host || window.location.hostname || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentRawServerListUrl() {
|
||||||
|
const protocol = window.location.protocol === "https:" ? "https" : "http";
|
||||||
|
const host = getCurrentServerHost();
|
||||||
|
return host ? `${protocol}://${host}/list.txt` : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentServerUrlForMode() {
|
||||||
|
return getCurrentServerHost();
|
||||||
|
}
|
||||||
|
|
||||||
function stripServerUrlPrefix(value) {
|
function stripServerUrlPrefix(value) {
|
||||||
return String(value || "").replace(/^\s*(wss?|https?):\/\//i, "").trimStart();
|
return String(value || "").replace(/^\s*(wss?|https?):\/\//i, "").trimStart();
|
||||||
}
|
}
|
||||||
@@ -93,9 +106,17 @@ function updateServerUrlPlaceholder() {
|
|||||||
if (!serverUrlInput) return;
|
if (!serverUrlInput) return;
|
||||||
const isRaw = rawServerListCheckbox?.checked ?? false;
|
const isRaw = rawServerListCheckbox?.checked ?? false;
|
||||||
serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(isRaw);
|
serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(isRaw);
|
||||||
|
updateServerUrlCurrentButton();
|
||||||
updateServerUrlHintMode();
|
updateServerUrlHintMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateServerUrlCurrentButton() {
|
||||||
|
if (!serverUrlCurrentBtn) return;
|
||||||
|
const isRaw = rawServerListCheckbox?.checked ?? false;
|
||||||
|
serverUrlCurrentBtn.classList.toggle("hidden", isRaw);
|
||||||
|
serverUrlCurrentBtn.disabled = isRaw;
|
||||||
|
}
|
||||||
|
|
||||||
let isBuilding = false;
|
let isBuilding = false;
|
||||||
|
|
||||||
function initAccordions() {
|
function initAccordions() {
|
||||||
@@ -563,8 +584,7 @@ function applyFormSettings(settings) {
|
|||||||
solSettings.classList.toggle("hidden", !solMemoCheckbox.checked);
|
solSettings.classList.toggle("hidden", !solMemoCheckbox.checked);
|
||||||
}
|
}
|
||||||
if (serverUrlInput && rawServerListCheckbox) {
|
if (serverUrlInput && rawServerListCheckbox) {
|
||||||
serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(rawServerListCheckbox.checked);
|
updateServerUrlPlaceholder();
|
||||||
updateServerUrlHintMode();
|
|
||||||
}
|
}
|
||||||
applyCryptableMode(document.getElementById("cryptable-mode")?.checked || false);
|
applyCryptableMode(document.getElementById("cryptable-mode")?.checked || false);
|
||||||
}
|
}
|
||||||
@@ -836,10 +856,23 @@ if (rawServerListCheckbox && serverUrlInput) {
|
|||||||
// Non-raw mode: bare domain only. The agent prepends wss:// itself.
|
// Non-raw mode: bare domain only. The agent prepends wss:// itself.
|
||||||
serverUrlInput.value = stripServerUrlPrefix(current);
|
serverUrlInput.value = stripServerUrlPrefix(current);
|
||||||
}
|
}
|
||||||
serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(isRaw);
|
updateServerUrlPlaceholder();
|
||||||
updateServerUrlHintMode();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (serverUrlCurrentBtn) {
|
||||||
|
serverUrlCurrentBtn.addEventListener("click", () => {
|
||||||
|
if (solMemoCheckbox?.checked) {
|
||||||
|
solMemoCheckbox.checked = false;
|
||||||
|
if (solSettings) solSettings.classList.add("hidden");
|
||||||
|
}
|
||||||
|
serverUrlInput.value = getCurrentServerUrlForMode();
|
||||||
|
serverUrlInput.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
serverUrlInput.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
saveFormSettings();
|
||||||
|
serverUrlInput.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Live-strip protocol prefixes in non-raw mode so the field stays clean and
|
// Live-strip protocol prefixes in non-raw mode so the field stays clean and
|
||||||
// the user sees their typo corrected immediately.
|
// the user sees their typo corrected immediately.
|
||||||
serverUrlInput.addEventListener("input", () => {
|
serverUrlInput.addEventListener("input", () => {
|
||||||
@@ -891,9 +924,7 @@ if (solMemoCheckbox && solSettings) {
|
|||||||
|
|
||||||
if (isSol && rawServerListCheckbox) {
|
if (isSol && rawServerListCheckbox) {
|
||||||
rawServerListCheckbox.checked = false;
|
rawServerListCheckbox.checked = false;
|
||||||
if (serverUrlInput) {
|
updateServerUrlPlaceholder();
|
||||||
serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSol) {
|
if (isSol) {
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ menuStyle.textContent = `
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
min-width: 196px;
|
min-width: 196px;
|
||||||
|
max-height: calc(100vh - 16px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overscroll-behavior: contain;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
@@ -92,6 +96,27 @@ menuStyle.textContent = `
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
max-height: calc(100vh - 16px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
#ctx-main, #ctx-sub {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(148,163,184,0.35) transparent;
|
||||||
|
}
|
||||||
|
#ctx-main::-webkit-scrollbar,
|
||||||
|
#ctx-sub::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
#ctx-main::-webkit-scrollbar-thumb,
|
||||||
|
#ctx-sub::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(148,163,184,0.28);
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
#ctx-main::-webkit-scrollbar-thumb:hover,
|
||||||
|
#ctx-sub::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(148,163,184,0.45);
|
||||||
}
|
}
|
||||||
.ctx-row {
|
.ctx-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -221,6 +246,8 @@ menuStyle.textContent = `
|
|||||||
}
|
}
|
||||||
#ctx-main, #ctx-sub {
|
#ctx-main, #ctx-sub {
|
||||||
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.7);
|
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.7);
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
#ctx-sub { position: static; margin-top: 0; min-width: 0; width: 100%; border-radius: 0 0 8px 8px; border-top: 1px solid rgba(148,163,184,0.08); left: auto; right: auto; }
|
#ctx-sub { position: static; margin-top: 0; min-width: 0; width: 100%; border-radius: 0 0 8px 8px; border-top: 1px solid rgba(148,163,184,0.08); left: auto; right: auto; }
|
||||||
#ctx-main { border-radius: 8px 8px 0 0; }
|
#ctx-main { border-radius: 8px 8px 0 0; }
|
||||||
@@ -290,12 +317,16 @@ function showSubmenu(groupId, rowEl) {
|
|||||||
const mainRect = ctxMain.getBoundingClientRect();
|
const mainRect = ctxMain.getBoundingClientRect();
|
||||||
let offsetY = rowRect.top - mainRect.top;
|
let offsetY = rowRect.top - mainRect.top;
|
||||||
|
|
||||||
|
ctxSub.style.maxHeight = "calc(100vh - 16px)";
|
||||||
const subH = ctxSub.offsetHeight;
|
const subH = ctxSub.offsetHeight;
|
||||||
const menuTop = parseFloat(menu.style.top) || 0;
|
const menuTop = parseFloat(menu.style.top) || 0;
|
||||||
if (menuTop + offsetY + subH > window.innerHeight - 8) {
|
if (menuTop + offsetY + subH > window.innerHeight - 8) {
|
||||||
offsetY = Math.max(0, window.innerHeight - 8 - subH - menuTop);
|
offsetY = Math.max(0, window.innerHeight - 8 - subH - menuTop);
|
||||||
}
|
}
|
||||||
ctxSub.style.top = offsetY + "px";
|
ctxSub.style.top = offsetY + "px";
|
||||||
|
const subTop = menuTop + offsetY;
|
||||||
|
ctxSub.style.maxHeight = Math.max(120, window.innerHeight - subTop - 8) + "px";
|
||||||
|
ctxSub.scrollTop = 0;
|
||||||
|
|
||||||
// Flip to left if submenu overflows right edge
|
// Flip to left if submenu overflows right edge
|
||||||
const subW = ctxSub.offsetWidth;
|
const subW = ctxSub.offsetWidth;
|
||||||
@@ -396,6 +427,8 @@ export function openMenu(clientId, x, y, setContext, options = {}) {
|
|||||||
menu.style.top = "-9999px";
|
menu.style.top = "-9999px";
|
||||||
menu.style.display = "flex";
|
menu.style.display = "flex";
|
||||||
menu.setAttribute("aria-hidden", "false");
|
menu.setAttribute("aria-hidden", "false");
|
||||||
|
ctxMain.scrollTop = 0;
|
||||||
|
ctxSub.scrollTop = 0;
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const mw = menu.offsetWidth;
|
const mw = menu.offsetWidth;
|
||||||
|
|||||||
@@ -350,8 +350,15 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2" for="server-url">Server URL</label>
|
<label class="block text-sm font-medium text-slate-300 mb-2" for="server-url">Server URL</label>
|
||||||
<input type="text" id="server-url" name="server-url"
|
<div class="flex gap-2">
|
||||||
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors" />
|
<input type="text" id="server-url" name="server-url"
|
||||||
|
class="min-w-0 flex-1 px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors" />
|
||||||
|
<button type="button" id="server-url-current-btn" aria-label="Use current website host"
|
||||||
|
title="Use current website host"
|
||||||
|
class="shrink-0 w-10 h-10 inline-flex items-center justify-center rounded-lg bg-slate-800/70 border border-slate-700 text-slate-300 hover:text-blue-300 hover:border-blue-500 transition-colors">
|
||||||
|
<i class="fa-solid fa-globe"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<p id="server-url-hint" class="text-xs text-slate-500 mt-1">
|
<p id="server-url-hint" class="text-xs text-slate-500 mt-1">
|
||||||
Just enter your domain or IP:PORT — no <code class="text-slate-400">wss://</code>, <code class="text-slate-400">https://</code>, or any other prefix.
|
Just enter your domain or IP:PORT — no <code class="text-slate-400">wss://</code>, <code class="text-slate-400">https://</code>, or any other prefix.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user